from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, Aer
from qiskit.visualization import plot_histogram, plot_bloch_multivector, array_to_latex
from qiskit.extensions import Initialize
from qiskit.quantum_info import random_statevector
from qiskit.quantum_info import Statevector

# Creamos 3 registros cuánticos
qr  = QuantumRegister(3, name="q")

# Creamos dos registros clásicos con nombres diferentes
crz = ClassicalRegister(1, name="crz") 
crx = ClassicalRegister(1, name="crx") 

# Creamos el circuito
qc  = QuantumCircuit(qr, crz, crx) # O QuantumCircuit(3, 2)

qc.draw('mpl')

# Creamos un circuito cuántico de dos cúbits
phi_plus = QuantumCircuit(2)
# Aplicamos la puerta H
phi_plus.h(0)
# Aplicamos la CNOT
phi_plus.cnot(0, 1)
phi_plus.draw('mpl')

Statevector(phi_plus).draw('latex')

# Primero fabricamos un estado aleatorio
psi = random_statevector(2)

#Esta función nos permite meter el estado en q_{0}
init_gate = Initialize(psi) 
init_gate.label = "psi"

# Lo representamos en la esfera de Bloch
plot_bloch_multivector(psi)

# Lo aplicamos qc
qc.append(init_gate, [0])

# Y creamos el estado de bell en los dos últimos cúbits
qc.h(1)
qc.cnot(1, 2)

# Representamos
qc.draw('mpl')

# Implementamos la acción de alice
qc.barrier()
qc.cnot(0, 1)
qc.h(0)

qc.draw('mpl')

# Medimos los cúbits de Alice
qc.barrier()
qc.measure(1,crx)
qc.measure(0,crz)

qc.draw('mpl')

# Aplicamos estas operaciones controladas por los bits clásicos

#c_if controla las puertas con un bit clásico en vez de con un cubit
qc.x(2).c_if(crx, 1) # Estas dos líneas verifican
qc.z(2).c_if(crz, 1) # las acciones vistas arriba.

qc.draw('mpl')

sim = Aer.get_backend('aer_simulator')
qc.save_statevector()
out_vector = sim.run(qc).result().get_statevector()
plot_bloch_multivector(out_vector)