from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
from qiskit import execute

# Choose the drawer you like best:
from qiskit.tools.visualization import matplotlib_circuit_drawer as draw
#from qiskit.tools.visualization import circuit_drawer as draw

from qiskit import IBMQ
IBMQ.load_accounts() # make sure you have setup your token locally to use this

%matplotlib inline

import matplotlib.pyplot as plt

def show_results(D):
    # D is a dictionary with classical bits as keys and count as value
    # example: D = {'000': 497, '001': 527}
    plt.bar(range(len(D)), list(D.values()), align='center')
    plt.xticks(range(len(D)), list(D.keys()))
    plt.show()

from qiskit import Aer
# See a list of available local simulators
print("Aer backends: ", Aer.backends())

# see a list of available remote backends (these are freely given by IBM)
print("IBMQ Backends: ", IBMQ.backends())

# execute circuit and either display a histogram of the results
def execute_locally(qc, draw_circuit=False, show_results=False):
    # Compile and run the Quantum circuit on a simulator backend
    backend_sim = Aer.get_backend('qasm_simulator')
    job_sim = execute(qc, backend_sim)
    result_sim = job_sim.result()
    result_counts = result_sim.get_counts(qc)
    
    if draw_circuit or show_results: # Print the results
        print("simulation: ", result_sim, result_counts)
    
    if draw_circuit: # draw the circuit
        draw(qc)
    elif show_results: # or show the results
        show_results(result_counts)
    return result_counts

from qiskit.backends.ibmq import least_busy
import time
# Compile and run on a real device backend
def execute_remotely(qc, draw_circuit=False, show_results=False):
    if draw_circuit: # draw the circuit
        draw(qc)
    try:
        # select least busy available device and execute.
        least_busy_device = least_busy(IBMQ.backends(simulator=False))
        print("Running on current least busy device: ", least_busy_device)

        # running the job
        job_exp = execute(qc, backend=least_busy_device, shots=1024, max_credits=10)

        lapse, interval = 0, 10
        while job_exp.status().name != 'DONE':
            print('Status @ {} seconds'.format(interval * lapse))
            print(job_exp.status())
            time.sleep(interval)
            lapse += 1
        print(job_exp.status())
        exp_result = job_exp.result()
        result_counts = exp_result.get_counts(qc)

        # Show the results
        print("experiment: ", exp_result, result_counts)
        if show_results: # show the results
            show_results(result_counts)
        return result_counts
    except:
        print("All devices are currently unavailable.")
        return {}

def new_circuit(size):
    # Create a Quantum Register with size qubits
    qr = QuantumRegister(size)

    # Create a Classical Register with size bits
    cr = ClassicalRegister(size)

    # Create a Quantum Circuit acting on the qr and cr register
    return qr, cr, QuantumCircuit(qr, cr)

ERROR_MESSAGE = "Looks like your Deutsch has a bug"

def quantum_oracle_1(qr, cr, circuit):
    pass

def quantum_oracle_2(qr, cr, circuit):
    circuit.cx(qr[0], qr[1])

def quantum_oracle_3(qr, cr, circuit):
    circuit.cx(qr[0], qr[1])
    circuit.cx(qr[1], qr[0])
    circuit.cx(qr[0], qr[1])

def quantum_oracle_4(qr, cr, circuit):
    circuit.z(qr[1])
    circuit.cx(qr[0], qr[1])

qr, cr, circuit = new_circuit(2)

# X gate on qubit 1 (bit flip)
circuit.x(qr[1]);

circuit.h(qr);

quantum_oracle_1(qr, cr, circuit)

circuit.h(qr[0]);

# measure the specific qubit
circuit.measure(qr[0], cr[0]);

# Try both commands:
# results = execute_locally(circuit, draw_circuit=False, show_results=False) # silent mode
results = execute_locally(circuit, draw_circuit=True, show_results=False)
# results = execute_locally(circuit, draw_circuit=False, show_results=True)
# results = execute_locally(circuit, draw_circuit=True, show_results=True) # this will be the same as True, False

if '00' in results:
    print("CONSTANT")
elif '10' in results:
    print("BALANCED")

def get_deutsch_verdict(res): # should be improved for error handling
    if '00' in res:
        return "CONSTANT"
    elif '01' in res:
        return "BALANCED"

print(get_deutsch_verdict(results))

def deutsch(black_box):
    qr, cr, circuit = new_circuit(2)
    circuit.x(qr[1]) # X gate on qubit 1 (bit flip)
    circuit.h(qr) # Hadamard on both qubits
    
    black_box(qr, cr, circuit)
    
    circuit.h(qr[0]) # Hadamard on interesting qubit
    circuit.measure(qr[0], cr[0]) # measure the specific qubit
    
    results = execute_locally(circuit, draw_circuit=False, show_results=False) # silent mode
    return get_deutsch_verdict(results)

deutsch(quantum_oracle_1)

assert deutsch(quantum_oracle_1) == 'CONSTANT', ERROR_MESSAGE

assert deutsch(quantum_oracle_2) == 'BALANCED', "Looks like your Deutsch has a bug"

assert deutsch(quantum_oracle_3) == 'BALANCED', "Looks like your Deutsch has a bug"

assert deutsch(quantum_oracle_4) == 'CONSTANT', "Looks like your Deutsch has a bug"