import numpy as np

# Importing standard Qiskit libraries
from qiskit import QuantumCircuit, transpile, assemble, Aer, IBMQ, execute
from qiskit.quantum_info import Statevector
from qiskit.visualization import plot_bloch_multivector, plot_histogram
from qiskit_textbook.problems import dj_problem_oracle

from qiskit.providers.ibmq import least_busy
from qiskit.tools.jupyter import *
provider = IBMQ.load_account()

def lab1_ex1():
    qc = QuantumCircuit(1)
    qc.x(0)
    return qc

state = Statevector.from_instruction(lab1_ex1())
plot_bloch_multivector(state)

def lab1_ex2():
    qc = QuantumCircuit(1)
    qc.h(0)
    return qc

state = Statevector.from_instruction(lab1_ex2())
plot_bloch_multivector(state)

def lab1_ex3():
    qc = QuantumCircuit(1)
    qc.x(0)
    qc.h(0)
    return qc

state = Statevector.from_instruction(lab1_ex3())
plot_bloch_multivector(state)

def lab1_ex4():
    qc = QuantumCircuit(1)
    qc.x(0)
    qc.h(0)
    qc.s(0)
    return qc

state = Statevector.from_instruction(lab1_ex4())
plot_bloch_multivector(state)

def lab1_ex5():
    qc = QuantumCircuit(2,2) # this time, we not only want two qubits, but also two classical bits for the measurement
    qc.h(0)
    qc.x(1)
    qc.cx(0,1)
    return qc

qc = lab1_ex5()
qc.draw() # we draw the circuit

qc.measure_all() # we measure all the qubits
backend = Aer.get_backend('qasm_simulator') # we choose the simulator as our backend
counts = execute(qc, backend, shots = 1000).result().get_counts() # we run the simulation and get the counts
plot_histogram(counts) # let us plot a histogram to see the possible outcomes and corresponding probabilities

def lab1_ex6():
    qc = QuantumCircuit(3,3)
    qc.x(0)
    qc.h(0)
    qc.x(1)
    qc.x(2)
    qc.cx(0,1)
    qc.cx(1,2)
    return qc

qc = lab1_ex6()
qc.draw() # we draw the circuit

oraclenr = 2 # determines the oracle (can range from 1 to 5)
oracle = dj_problem_oracle(oraclenr) # gives one out of 5 oracles
oracle.name = "DJ-Oracle"

def dj_classical(n, input_str):
    
    # build a quantum circuit with n qubits and 1 classical readout bit
    dj_circuit = QuantumCircuit(n+1,1)
    
    # Prepare the initial state corresponding to your input bit string
    for i in range(n):
        if input_str[i] == '1':
            dj_circuit.x(i)
    
    # append oracle
    dj_circuit.append(oracle, range(n+1))
    
    # measure the fourth qubit 
    dj_circuit.measure(n,0)
    
    return dj_circuit
    
n = 4 # number of qubits
input_str = '1111'
dj_circuit = dj_classical(n, input_str)
dj_circuit.draw() # draw the circuit

input_str = '1011'
dj_circuit = dj_classical(n, input_str)
qasm_sim = Aer.get_backend('qasm_simulator')
transpiled_dj_circuit = transpile(dj_circuit, qasm_sim)
qobj = assemble(transpiled_dj_circuit, qasm_sim)
results = qasm_sim.run(qobj).result()
answer = results.get_counts()
plot_histogram(answer)

def lab1_ex7():
    min_nr_inputs = 2 # put your answer here, then uncomment
    max_nr_inputs = (2**n)/2 + 1 # put your answer here, then uncomment
    return [min_nr_inputs, max_nr_inputs]

n=4
def psi_0(n):
    qc = QuantumCircuit(n+1,n)
    
    # Build the state (|00000> - |10000>)/sqrt(2)
    qc.x(4)
    qc.h(4)
    qc.barrier()
    return qc

dj_circuit = psi_0(n)
dj_circuit.draw()

def psi_1(n):
    
    # obtain the |psi_0> = (|00000> - |10000>)/sqrt(2) state
    qc = psi_0(n)
    
    # create the superposition state |psi_1>
    for i in range(4):
        qc.h(i)
    return qc

dj_circuit = psi_1(n)
dj_circuit.draw()

def psi_2(oracle,n):
    
    # circuit to obtain psi_1
    qc = psi_1(n)
    
    # append the oracle
    qc.append(oracle, range(n+1))
    
    return qc

dj_circuit = psi_2(oracle, n)
dj_circuit.draw()

def lab1_ex8(oracle, n): # note that this exercise also depends on the code in the functions psi_0 (In [24]) and psi_1 (In [25])
    qc = psi_2(oracle, n)
    
    # apply n-fold hadamard gate
    for i in range(n):
        qc.h(i)
        
    qc.barrier()
       
    # add the measurement by connecting qubits to classical bits

    for j in range(n):
        qc.measure(j, j)
    
    return qc

dj_circuit = lab1_ex8(oracle, n)
dj_circuit.draw()

qasm_sim = Aer.get_backend('qasm_simulator')
transpiled_dj_circuit = transpile(dj_circuit, qasm_sim)
qobj = assemble(transpiled_dj_circuit)
results = qasm_sim.run(qobj).result()
answer = results.get_counts()
plot_histogram(answer)

backend = least_busy(provider.backends(filters=lambda x: x.configuration().n_qubits >= (n+1) and not 
                                       x.configuration().simulator and x.status().operational==True))
print('least busy backend: ', backend)
%qiskit_job_watcher
job = execute(dj_circuit, backend=backend, shots=1024, optimization_level=3)

# Get results of computation

results = job.result()
answer = results.get_counts()
plot_histogram(answer)

# Jobs I ran before

backend = provider.get_backend('ibmq_quito')
old_job = backend.retrieve_job('61cbfe7be867009014a0362e')
results = old_job.result()
answer = results.get_counts()
plot_histogram(answer)

