# useful additional packages 
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

# importing Qiskit
from qiskit import Aer, IBMQ
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
import qiskit as qk
#from qiskit.wrapper.jupyter import *

# import basic plot tools
from qiskit.tools.visualization import circuit_drawer, plot_histogram

#IBMQ.save_account('APItoken')
# Load our saved IBMQ accounts
IBMQ.load_account()

n = 2 # n is the length of the first register

# Let's choose ranomly the type of oracle. The probability that it is balanced is 50% and that is constant is 50%
# In order to do so we will use a function from numpy module

oracleType, oracleValue = np.random.randint(2), np.random.randint(2)

if oracleType == 0:
    print("The oracle is constant ", oracleValue)
else:
    print("The oracle is balanced")
    a = np.random.randint(1,2**n) # let's save an hidden value for the balanced function. 

# create quantum and classical registers all initialized at zero
# 2 qubits for oracle interrogation, and the other one is to save the result
qr = QuantumRegister(n+1) 
# we only need the classical register for the measurement of the first quantum circuit
cr = ClassicalRegister(n)

circuitName = "DeutschJozsa"
djCircuit = QuantumCircuit(qr, cr)

# Superpose the first register.
for i in range(n):
    djCircuit.h(qr[i])

# Invert the second register and apply H.
djCircuit.x(qr[n])
djCircuit.h(qr[n])
    
# Let's visualize a barrier of the oracle (this is only for stylistical purpose)
djCircuit.barrier()

if oracleType == 0:#The function is constant 
    if oracleValue == 1:
        djCircuit.x(qr[n])
    else:
        djCircuit.iden(qr[n])
else: # The function is balanced, in that case the oracle applies a CNOT on the qubit i controlling the qubit n
    for i in range(n):
        if (a & (1 << i)):
            djCircuit.cx(qr[i], qr[n])
        
# End of the oracle, let's close the barrier
djCircuit.barrier()

# Apply H to each qubit
for i in range(n):
    djCircuit.h(qr[i])
    
# Measure
djCircuit.barrier()
for i in range(n):
    djCircuit.measure(qr[i], cr[i])

#draw the circuit
circuit_drawer(djCircuit)

backend = Aer.get_backend('qasm_simulator')
shots = 1024
job = qk.execute(djCircuit, backend=backend, shots=shots)
results = job.result() 
answer = results.get_counts()

plot_histogram(answer)

from qiskit.tools.monitor import job_monitor
backend = IBMQ.get_backend('ibmq_16_melbourne')
shots = 1024 
job = qk.execute(djCircuit, backend=backend, shots=shots)
job_monitor(job, interval=5)

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

threshold = int(0.03 * shots) # set a threshold for significant measurements
filteredAnswer = {k: v for k,v in answer.items() if v >= threshold} # filter for an optimal visualization of the results

removedCounts = np.sum([ v for k,v in answer.items() if v < threshold ]) # 
filteredAnswer['other_bitstring'] = removedCounts  # 

plot_histogram(answer)

print(answer)