# Do the necessary imports
import numpy as np
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, execute, BasicAer, IBMQ
from qiskit.visualization import plot_histogram, plot_bloch_multivector
### Creating 3 qubit and 2 classical bits in each separate register
qr = QuantumRegister(3)
crz = ClassicalRegister(1)
crx = ClassicalRegister(1)
teleportation_circuit = QuantumCircuit(qr, crz, crx)
### A third party eve helps to create an entangled state

def create_bell_pair(qc, a, b):
    qc.h(a)
    qc.cx(a, b)
    
create_bell_pair(teleportation_circuit, 1, 2)
teleportation_circuit.draw()

def alice_gates(qc, a, b):
    qc.cx(a, b)
    qc.h(a)

teleportation_circuit.barrier()
alice_gates(teleportation_circuit, 0, 1)
teleportation_circuit.draw()

def measure_and_send(qc, a, b):
    qc.barrier()
    qc.measure(a,0)
    qc.measure(b,1)
teleportation_circuit.barrier()
measure_and_send(teleportation_circuit, 0, 1)
teleportation_circuit.draw()

def bob_gates(qc, qubit, crz, crx):
    qc.x(qubit).c_if(crx, 1)
    qc.z(qubit).c_if(crz, 1)
    
teleportation_circuit.barrier()
bob_gates(teleportation_circuit, 2, crz, crx)
teleportation_circuit.draw()

from qiskit.extensions import Initialize
import math


qc = QuantumCircuit(1)
initial_state = [0,1]
init_gate = Initialize(initial_state)
# qc.append(initialize_qubit, [0])

qr = QuantumRegister(3)   # Protocol uses 3 qubits
crz = ClassicalRegister(1) # and 2 classical registers
crx = ClassicalRegister(1)
qc = QuantumCircuit(qr, crz, crx)

# First, let's initialise Alice's q0
qc.append(init_gate, [0])
qc.barrier()

# Now begins the teleportation protocol
create_bell_pair(qc, 1, 2)
qc.barrier()
# Send q1 to Alice and q2 to Bob
alice_gates(qc, 0, 1)

# Alice then sends her classical bits to Bob
measure_and_send(qc, 0, 1)

# Bob decodes qubits
bob_gates(qc, 2, crz, crx)

qc.draw()

backend = BasicAer.get_backend('statevector_simulator')
out_vector = execute(qc, backend).result().get_statevector()
plot_bloch_multivector(out_vector)

inverse_init_gate = init_gate.gates_to_uncompute()


qc.append(inverse_init_gate, [2])
qc.draw()

cr_result = ClassicalRegister(1)
qc.add_register(cr_result)
qc.measure(2,2)
qc.draw()

backend = BasicAer.get_backend('qasm_simulator')
counts = execute(qc, backend, shots=1024).result().get_counts()
plot_histogram(counts)

def bob_gates(qc, a, b, c):
    qc.cz(a, c)
    qc.cx(b, c)

qc = QuantumCircuit(3,1)

# First, let's initialise Alice's q0
qc.append(init_gate, [0])
qc.barrier()

# Now begins the teleportation protocol
create_bell_pair(qc, 1, 2)
qc.barrier()
# Send q1 to Alice and q2 to Bob
alice_gates(qc, 0, 1)
qc.barrier()
# Alice sends classical bits to Bob
bob_gates(qc, 0, 1, 2)

# We undo the initialisation process
qc.append(inverse_init_gate, [2])

# See the results, we only care about the state of qubit 2
qc.measure(2,0)

# View the results:
qc.draw()

from qiskit import IBMQ
IBMQ.save_account('### IMB TOKEN ')

IBMQ.load_account()
provider = IBMQ.get_provider(hub='ibm-q')
provider.backends()

from qiskit.providers.ibmq import least_busy
backend = least_busy(provider.backends(filters=lambda b: b.configuration().n_qubits >= 3 and
                                   not b.configuration().simulator and b.status().operational==True))
job_exp = execute(qc, backend=backend, shots=8192)

exp_result = job_exp.result()
exp_measurement_result = exp_result.get_counts(qc)
print(exp_measurement_result)
plot_histogram(exp_measurement_result)

error_rate_percent = sum([exp_measurement_result[result] for result in exp_measurement_result.keys() if result[0]=='1']) \
                    * 100./ sum(list(exp_measurement_result.values()))
print("The experimental error rate : ", error_rate_percent, "%")

