import qiskit
qiskit.__qiskit_version__

from qiskit import QuantumCircuit, BasicAer, execute
from qiskit.visualization import plot_histogram

qcSwap = QuantumCircuit(2, 2) #create a quantum circuit with 2 quantum bits and 2 classical bits.

qcSwap.x(0) #apply a pauli X gate to the first qubit to better demonstrate the swap.
qcSwap.swap(0, 1) #apply a swap gate on the first and second qubits. 

qcSwap.measure([0, 1], [0, 1]) #measure the qubits to see what their states end up being

qcSwap.draw(output = "mpl") #draw the circuit.

backend = BasicAer.get_backend("statevector_simulator")
state = execute(qcSwap, backend, shots=1024).result().get_counts()

#the initial state should've been '01' before the swap,
#go ahead and comment the swap gate out and run the code!

#the state is '01' before the swap because of qiskit's little endian encoding.
print(state)

n = 4 #number of data qubits.
ancilla = n #number of ancillary qubits is the same as number of qubits in this example.

#number of classical registers is equal to the number of data qubits, since we want the values of only those.
qcShift = QuantumCircuit(n + ancilla, n)

seed = "1011" #this is the initial value of our data qubits.
seed = list(map(int, list(seed)))[::-1] #reverse the list to follow qiskit's encoding scheme.

#to encode the seed, when we encounter a 1, we apply a pauli x gate. 
for bit in range(len(seed)):
    if seed[bit] == 1:
        qcShift.x(bit)
qcShift.barrier(range(n + ancilla)) #add a barrier to mark the end of the encoding phase.

#to apply the swap gates
for qubit in range(1, n + ancilla)[::-1]:
    qcShift.swap(qubit, qubit - 1)
qcShift.barrier(range(n + ancilla)) #add a barrier to mark the end of the swapping phase.

#measure the values of the data qubits.
qcShift.measure(range(n), range(n))
qcShift.barrier(range(n + ancilla)) #add a barrier to mark the end of the measuring phase.

qcShift.draw(output = "mpl")

def create_shift_gate(n:int, circular:bool = False):
    """Creates a gate out of a shift circuit.
    
    Args:
        n        : number of data qubits.
        seed     : initial state of the data qubits.
        circular : flag indicating whether the shift register is circular in nature or not.
    """
    
    if circular:
        name = "Circular Left Shift"
    else:
        name = "Left Shift"
        
    ancilla = n
    
    #n data qubits and the same number of ancillary qubits and one extra qubit to act as the control
    #bit for deciding if the operation should be a left shift or a circular left shift.
    shiftGate = QuantumCircuit(n + ancilla + 1, name=name)
        
    for qubit in range(1, n + ancilla)[::-1]:
        shiftGate.swap(qubit, qubit - 1)
    
    #implement the controlled swap.
    shiftGate.cx(0, n)
    shiftGate.toffoli(n + ancilla, n, 0)
    shiftGate.cx(0, n)
    
    return shiftGate.to_gate()

n = 4
seed = '1011'
ancilla = n

#n data qubits and the same number of ancillary qubits and one extra qubit to act as the control
#bit for deciding if the operation should be a left shift or a circular left shift.
shiftCirc = QuantumCircuit(n + ancilla + 1, n)

seed = list(map(int, list(seed)))[::-1]
circular = True

#encode the circuit
for bit in range(len(seed)):
    if seed[bit] == 1:
        shiftCirc.x(bit)
if circular:
    shiftCirc.x(n + ancilla) #encode the control qubit.
shiftCirc.barrier(range(n + ancilla + 1))

shiftGate = create_shift_gate(n, circular)

#perform 2 shift operations.
shiftCirc.append(shiftGate, range(n + ancilla + 1))
shiftCirc.append(shiftGate, range(n + ancilla + 1))
shiftCirc.barrier(range(n + ancilla + 1))

#get the state of the data qubits.
shiftCirc.measure(range(n), range(n))
shiftCirc.barrier(range(n + ancilla))

#draw the circuit.
shiftCirc.draw(output="mpl")

#get the state of the shift register
backend = BasicAer.get_backend("statevector_simulator")
register_state = execute(shiftCirc, backend, shots=1024).result().get_counts()

plot_histogram(register_state)

#as it can be seen, the state of the register is '1110' which is what
#you would get by performing two left shift operations circularly.

#to see what the shift gate is acutally made up of. 
seeShiftGate = QuantumCircuit(n + ancilla + 1)
seeShiftGate.append(shiftGate, range(n + ancilla + 1))

seeShiftGate.decompose().draw(output = "mpl")
#q8 is the control qubit. q0-q3 are the data qubits and q4-q7 are the ancillary qubits.