# import qiskit
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit import Aer, execute
from qiskit.circuit.library import QFT

from qiskit_ionq import IonQProvider 

#Call provider and set token value
provider = IonQProvider(token='my token')

# numpy
import numpy as np

# plotting
from matplotlib import pyplot as plt
%matplotlib inline 

provider.backends()

# Quantum Fourier transform of |q>, of length n.
def qft(circ, q, n):
    # Loop through the target qubits.
    for i in range(n,0,-1):
        # Apply the H gate to the target.
        circ.h(q[i-1])

        # Loop through the control qubits.
        for j in range(i-1,0,-1):
            circ.cp(2*np.pi/2**(i-j+1), q[j-1], q[i-1])

# Inverse Fourier transform of |q>, of length n.
def iqft(circ, q, n):
    # Loop through the target qubits.
    for i in range(1,n+1):
        # Loop through the control qubits.
        for j in range(1,i):
            # The inverse Fourier transform just uses a negative phase.
            circ.cp(-2*np.pi/2**(i-j+1), q[j-1], q[i-1])

        # Apply the H gate to the target.
        circ.h(q[i-1])


# define the Add function
def Add(circ, a, b, n):
    
    # add 1 to n to account for overflow
    n += 1
    
    # take the QFT
    qft(circ, b, n)
    
    circ.barrier()
    
    # Compute the controlled phases 
    # Iterate over targets 
    for i in range(n, 0, -1):
        # Iterate over controls
        for j in range(i, 0, -1):
            # If the qubit a[j-1] exists run cp, if not assume the qubit is 0 and never existed
            if len(a) - 1 >= j - 1:
                circ.cp(2*np.pi/2**(i-j+1), a[j-1], b[i-1])
    
    circ.barrier()
    
    # take the inverse QFT
    iqft(circ, b, n)

# Registers and circuit.
a = QuantumRegister(2)
b = QuantumRegister(3)
ca = ClassicalRegister(2)
cb = ClassicalRegister(3)
qc = QuantumCircuit(a, b, ca, cb)

# Numbers to add.
qc.x(a[1]) # a = 01110 / a = 10
#qc.x(a[2])
#qc.x(a[3])
qc.x(b[0]) # b = 01011 / b = 001
#qc.x(b[1])
#qc.x(b[3])

qc.barrier()

# Add the numbers, so |a>|b> to |a>|a+b>.
Add(qc, a, b, 2)

qc.barrier()

# Measure the results.
qc.measure(a, ca)
qc.measure(b, cb)

qc.draw(output="mpl")

# simulate the circuit
simulator = Aer.get_backend('qasm_simulator')
job_sim   = execute(qc, simulator)
result_sim = job_sim.result()

print(result_sim.get_counts(qc))

# run the circuit on a real-device
qpu = provider.get_backend("ionq_qpu")
qpu_job = qpu.run(qc)


from qiskit.providers.jobstatus import JobStatus
import time
# Check if job is done
while qpu_job.status() is not JobStatus.DONE:
    print("Job status is", qpu_job.status() )
    time.sleep(60)


# grab a coffee! This can take up to a few minutes.
# once we break out of that while loop, we know our job is finished
print("Job status is", qpu_job.status() )
print(qpu_job.get_counts()) # these counts are the “true” counts from the actual QPU Run

result_exp = qpu_job.result()

from qiskit.visualization import plot_histogram

plot_histogram(qpu_job.get_counts())

