#Libraries needed to implement and simulate quantum circuits
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, transpile, qasm2, qasm3
from qiskit_aer import Aer
from qiskit.primitives import BackendSampler
#Custem functions to simplify answers
import Our_Qiskit_Functions as oq #a part of the library presented in arXiv:1903.04359v1.

import numpy as np
import math as m

#Initialize backends simulators to visualize circuits
S_simulator = Aer.backends(name='statevector_simulator')[0]
M_simulator = Aer.backends(name='qasm_simulator')[0]

q = QuantumRegister(1, name='q')
qc = QuantumCircuit(1, name='qc')

qc.h(q[0])
oq.Wavefunction(qc);

q = QuantumRegister(1, name='q')
qc = QuantumCircuit(1, name='qc')

qc.x(q[0])
qc.h(q[0])
oq.Wavefunction(qc);

qc.x(q[0])
oq.Wavefunction(qc);

q = QuantumRegister(3, name='q')
anc = QuantumRegister(1, name='anc')
DJ_qc = QuantumCircuit(q, anc, name='qc')

DJ_qc.h(q[0])
DJ_qc.h(q[1])
DJ_qc.h(q[2])
DJ_qc.x(anc[0])

print('___________Before g___________________')
oq.Wavefunction(DJ_qc, systems=[3,1], show_systems=[True,False])

DJ_qc.h(anc[0])

f = oq.Blackbox_g_DJ(3, DJ_qc, q, anc)
if f[0] == 'constant':
    A = 1
else:
    A = 2

DJ_qc.h(anc[0])

print('\n______________After g___________')
oq.Wavefunction(DJ_qc, systems=[3,A], show_systems=[True,False])

print('\nf type: ', f[0])
if len(f) > 1:
    print('States mapped to 1: ', f[1:])

q = QuantumRegister(3, name='q')
anc = QuantumRegister(1, name='anc')
c = ClassicalRegister(3, name='c')
DJ_qc = QuantumCircuit(q, anc, c, name='qc')

DJ_qc.h(q[0])
DJ_qc.h(q[1])
DJ_qc.h(q[2])
DJ_qc.x(anc[0])

print('___________Before g___________________')
oq.Wavefunction(DJ_qc, systems=[3,1], show_systems=[True,False])

DJ_qc.h(anc[0])

f = oq.Blackbox_g_DJ(3, DJ_qc, q, anc)
if f[0] == 'constant':
    A = 1
else:
    A = 2

DJ_qc.h(anc[0])

print('\n______________After g___________')
oq.Wavefunction(DJ_qc, systems=[3,A], show_systems=[True,False])

print('\nf type: ', f[0])
if len(f) > 1:
    print('States mapped to 1: ', f[1:])
    
DJ_qc.h(q[0])
DJ_qc.h(q[1])
DJ_qc.h(q[2])

print('\n______________After H^3___________')
oq.Wavefunction(DJ_qc, systems=[3,A], show_systems=[True,False])

DJ_qc.measure(q, c)

print('\n______________Measured State____________')
oq.Measurement(DJ_qc, shots=1)

q = QuantumRegister(3, name='q')
anc = QuantumRegister(1, name='anc')
con1_qc = QuantumCircuit(q, anc, name='qc1')
con2_qc = QuantumCircuit(q, anc, name='qc2')

for i in range(2):
    con1_qc.h(q[i])
    con2_qc.h(q[i])
    
con1_qc.h(anc[0])
con1_qc.x(anc[0])
con2_qc.x(anc[0])
con2_qc.h(anc[0])

print('___________Before g___________________')
oq.Wavefunction(con1_qc)

con2_qc.x(q[0])
con2_qc.x(q[1])
con2_qc.x(anc[0])

print('\n______________After g, f type: balanced___________')
oq.Wavefunction(con1_qc)
print('    ');
oq.Wavefunction(con2_qc)

for i in range(2):
    con1_qc.h(q[i])
    con2_qc.h(q[i])
    
con1_qc.h(anc[0])
con2_qc.h(anc[0])

print('\n______________After H^3___________')

oq.Wavefunction(con1_qc)
print('    ');
oq.Wavefunction(con2_qc);

q = QuantumRegister(3, name='q')
anc = QuantumRegister(1, name='anc')
DJ_qc = QuantumCircuit(q, anc, name='qc')

DJ_qc.h(q[0])
DJ_qc.h(q[1])
DJ_qc.h(q[2])
DJ_qc.x(anc[0])

print('___________Before g___________________')
oq.Wavefunction(DJ_qc, systems=[3,1], show_systems=[True,False])

DJ_qc.h(anc[0])

f = oq.Blackbox_g_DJ(3, DJ_qc, q, anc)
if f[0] == 'constant':
    A = 1
else:
    A = 2

DJ_qc.h(anc[0])

print('\n______________After g___________')
oq.Wavefunction(DJ_qc, systems=[3,A], show_systems=[True,False])

DJ_qc.h(q[0])
DJ_qc.h(q[1])
DJ_qc.h(q[2])

print('\n______________After H^3___________')
oq.Wavefunction(DJ_qc, systems=[3,A], show_systems=[True,False])


print('\nf type: ', f[0])
if len(f) > 1:
    print('States mapped to 1: ', f[1:])
    print('Note that the state |000> is not in our final system!')

# example for |010>

q = QuantumRegister(3, name='q')
trgt = QuantumRegister(1, name='trgt')
anc = QuantumRegister(1, name='anc')
qc_010 = QuantumCircuit(q, trgt, anc, name='qc')

qc_010.h(q[1])

qc_010.x(trgt[0])
qc_010.h(trgt[0])

print('___________Initial state___________________')
oq.Wavefunction(qc_010, systems=[3,1,1], show_systems=[True,True,False])

qc_010.x(q[0])
qc_010.x(q[2])
oq.n_NOT(qc_010, q, trgt[0], anc)
qc_010.x(q[0])
qc_010.x(q[2])

print('\n______________After n_NOT____________')
oq.Wavefunction(qc_010, systems=[3,1,1], show_systems=[True,True,False]);


Q = 5

q = QuantumRegister(Q, name='q')
anc = QuantumRegister(1, name='anc')
c = ClassicalRegister(Q, name='c')
DJ_qc = QuantumCircuit(q, anc, c, name='qc')

DJ_qc.x(anc[0])

f = oq.Deutsch_Josza(Q, DJ_qc, q, anc)

DJ_qc.measure(q, c)

print('_______Measured state_______________')
M = oq.Measurement(DJ_qc, shots = 1, return_M=True)
M = list(list(M.keys())[0])


con = True
for i in range(len(M)):
    if (list(M)[i] == '1'):
        con = False
print('    ')
if con:
    print('Conclusion: f is a constant function')
else:
    print('Conclusion: f is a balanced function')

print('   ')
print('sneak peak: f is ', f[0])

q = QuantumRegister(3, name='q')
anc = QuantumRegister(1, name='anc')
BV_qc = QuantumCircuit(q, anc, name='qc')

for i in range(3):
    BV_qc.h(q[i])

print('___________Before g___________________')
oq.Wavefunction(BV_qc, systems=[3,1], show_systems=[True,False])

BV_qc.x(anc[0])
BV_qc.h(anc[0])

a = oq.Blackbox_g_BV(3, BV_qc, q, anc)
BV_qc.h(anc[0])

print('\n______________After g___________')
oq.Wavefunction(BV_qc, systems=[3,2], show_systems=[True,False])

for i in range(3):
    BV_qc.h(q[i])
    
print('\n______________After H^3___________')
oq.Wavefunction(BV_qc, systems=[3,2], show_systems=[True,False])

print('    ')
print('Hidden string a = ', a)

q = QuantumRegister(3, name='q')
H3_qc = QuantumCircuit(q, name='qc')

state=[1,0,1]
print('Quantum State: ',state)
print(' ');
for i in range(len(state)):
    if state[i] == 1:
       H3_qc.x(q[i])
    H3_qc.h(q[i])
    
print('_____________Corresponding H^3 state_______________')
oq.Wavefunction(H3_qc);

Q = 4

q = QuantumRegister(Q, name='q')
anc = QuantumRegister(1, name='anc')
c = ClassicalRegister(Q, name='c')
BV_qc = QuantumCircuit(q, anc, c, name='qc')

BV_qc.x(anc[0])

a = oq.Bernstein_Vazirani(Q, BV_qc, q, anc)
BV_qc.measure(q, c)

print('_______Measured state_______________')
oq.Measurement(BV_qc, shots = 1)

print('\nsneak peak: a = ', a)

