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

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

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

from qc_grader import grade_lab1_ex1

# Note that the grading function is expecting a quantum circuit without measurements
grade_lab1_ex1(lab1_ex1())


def lab1_ex2():
    qc = QuantumCircuit(1)
    qc.h(0)
    #
    #
    # FILL YOUR CODE IN HERE
    #
    #
    return qc

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

from qc_grader import grade_lab1_ex2

# Note that the grading function is expecting a quantum circuit without measurements
grade_lab1_ex2(lab1_ex2())

def lab1_ex3():
    qc = QuantumCircuit(1)
    qc.h(0)
    qc.z(0)
    #
    #
    # FILL YOUR CODE IN HERE
    #
    #
    return qc

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

from qc_grader import grade_lab1_ex3

# Note that the grading function is expecting a quantum circuit without measurements
grade_lab1_ex3(lab1_ex3())

def lab1_ex4():
    qc = QuantumCircuit(1)
    qc.h(0)
    qc.y(0)
    qc.sdg(0)
    qc.sdg(0)
    qc.sdg(0)
    #
    #
    # FILL YOUR CODE IN HERE
    #
    #
    return qc

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

from qc_grader import grade_lab1_ex4

# Note that the grading function is expecting a quantum circuit without measurements
grade_lab1_ex4(lab1_ex4())

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.cx(0,1)
    qc.x(1)
    #
    #
    # FILL YOUR CODE IN HERE
    #
    #
    return qc

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

from qc_grader import grade_lab1_ex5

# Note that the grading function is expecting a quantum circuit without measurements
grade_lab1_ex5(lab1_ex5())

qc.measure(0, 0) # we perform a measurement on qubit q_0 and store the information on the classical bit c_0
qc.measure(1, 1) # we perform a measurement on qubit q_1 and store the information on the classical bit c_1
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) # this time, we not only want two qubits, but also two classical bits for the measurement
    qc.h(0)
    qc.cx(0,1)
    qc.x(1)

    qc.cx(1,2)
    qc.y(2)
    #
    #
    # FILL YOUR CODE IN HERE
    #
    #
    return qc


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

from qc_grader import grade_lab1_ex6

# Note that the grading function is expecting a quantum circuit without measurements
grade_lab1_ex6(lab1_ex6())

oraclenr = 4 # 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 = '1111'
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
    max_nr_inputs = 9# put your answer here
    return [min_nr_inputs, max_nr_inputs]

from qc_grader import grade_lab1_ex7

# Note that the grading function is expecting a list of two integers
grade_lab1_ex7(lab1_ex7())

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

dj_circuit = psi_0(n)
dj_circuit.draw()

def psi_1(n):
    
    # obtain the |psi_0> = |00001> state
    qc = psi_0(n)
    for qubit in range(n+1):
        qc.h(qubit)
    # create the superposition state |psi_1>
    #
    #
    # FILL YOUR CODE IN HERE
    #
    #
    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
    #
    #
    # FILL YOUR CODE IN HERE
    #
    #
    for qubit in range(n):
        qc.h(qubit)  
    # add the measurement by connecting qubits to classical bits
    #
    #
    # FILL YOUR CODE IN HERE
    #
    #
    for i in range(n):
        qc.measure(i, i)
    return qc

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

from qc_grader import grade_lab1_ex8

# Note that the grading function is expecting a quantum circuit with measurements
grade_lab1_ex8(lab1_ex8(dj_problem_oracle(4),n))

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)