#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
import scipy as sci

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

N = 3
s = np.zeros(N)
for i in range(N):
    s[i] = m.floor(2*np.random.rand())
    
inputs = np.zeros(2**N)
outputs = []
for o in range(2**N):
    inputs[o] = o
    outputs.append(o)

f = np.zeros(2**N)
for j in range(2**N):
    out = outputs[int(m.floor(len(outputs)*np.random.rand()))]
    f[j] = out
    f[int(oq.From_Binary(oq.Oplus(oq.Binary(j, 2**N),s)))] = out
    outputs.remove(out)
    
print('      s: ', s)
print(' ')
print(' inputs: ', inputs)
print('outputs: ', f)

q = QuantumRegister(2, name='q')
anc=QuantumRegister(2, name='anc')
S_qc=QuantumCircuit(q,anc,name='qc')

S_qc.h(q[0])
S_qc.h(q[1])

print('___________Initial state_______________')
oq.Wavefunction(S_qc, sustem=[2,2])

S_qc,s,f = oq.Blackbox_g_S(2, S_qc, q, anc)

print('\ns = ', s)
print('\n__________________After g__________')
oq.Wavefunction(S_qc, systems=[2,2,1], show_systems=[True,True,False])

S_qc.h(q[0])
S_qc.h(q[1])

print('\n___________Afeter H^2_______________')
oq.Wavefunction(S_qc, systems=[2,2,1], show_systems=[True,True,False]);

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

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

S_qc, s, f = oq.Blackbox_g_S(3, S_qc, q, anc)

for i in range(3):
    S_qc.h(q[i])
    
S_qc.measure(q,c)

run_quantum = True

while(run_quantum):
    M = oq.Measurement(S_qc, shots = 20, return_M = True, print_M = False)
    if len(list(M.keys())) >= 4:
        run_quantum = False
        print('Measurement Results: ', M)
        Equations = []
        for i in range(len(list(M.keys()))):
            if list(M.keys())[i] != '000':
                Equations.append([int(list(M.keys())[i][0]), int(list(M.keys())[i][1]), int(list(M.keys())[i][2])])
        s_primes = oq.Simons_Solver(Equations,3)
        print('\ncandidate: ', s_primes)
        print('\n   hidden: ', s)

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

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

S_qc, s, f = oq.Blackbox_g_S(3, S_qc, q, anc)

for i in range(3):
    S_qc.h(q[i])
    
S_qc.measure(q,c)

run_quantum = True
Equations = []
Results = []
quantum_runs = 0
    
while(run_quantum):
    quantum_runs += 1
    M = oq.Measurement(S_qc, shots = 20, return_M = True, print_M = False)
    new_result = True
        
    for r in range(len(Results)):
        if list(M.keys())[0] == Results[r]:
            new_result = False
            break
        
    if new_result:
        Results.append(list(M.keys())[0])
        Equations.append([int(list(M.keys())[0][0]), int(list(M.keys())[0][1]), int(list(M.keys())[0][2])])
        s_primes = oq.Simons_Solver(Equations, 3)
        if len(s_primes) == 1:
            run_quantum = False
            
print('\n          candidate: ', s_primes)
print('\n             hidden: ', s)
print('\nunique measurements: ', Results)
print('\n       quantum runs: ', quantum_runs)

Q = 4

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

S_qc, s = oq.Simons_Quantum(Q, S_qc, q, c, anc)
sp, r, qr = oq.Simons_Classical(Q, S_qc)

print('\n          candidate: ', sp)
print('\n             hidden: ', s)
print('\nunique measurements: ', r)
print('\n       quantum runs: ', qr)


