#Libraries needed to implement and simulate quantum circuits
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, transpile
from qiskit_aer import Aer
from qiskit.primitives import BackendSampler
#from qiskit.providers.aer import Aer, execute 
#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]
Q_simulator = Aer.backends(name='qasm_simulator')[0]

q = QuantumRegister(1)
hello_qubit = QuantumCircuit(q)

hello_qubit.id(q[0])

#job = execute(hello_qubit, S_simulator)
new_circuit = transpile(hello_qubit, S_simulator)
job = S_simulator.run(new_circuit)


print('job = AerJob class: ', type(job))
result = job.result()
print('result = Result class: ',type(result))
result.get_statevector()

print('simulator: ', S_simulator)
print('simulator type: ', type(S_simulator))
print('Aer.get_backend(name=statevector_simulator): ', Aer.get_backend(name='statevector_simulator'))
print('backend type: ', type(Aer.get_backend(name='statevector_simulator')))

q = QuantumRegister(3)
three_qubits = QuantumCircuit(q)

three_qubits.id(q[0])
three_qubits.id(q[1])
three_qubits.id(q[2])

job = oq.execute(three_qubits, S_simulator)
result = job.result()
result.get_statevector()

q = QuantumRegister(3)
three_qubits = QuantumCircuit(q)

three_qubits.x(q[0])
three_qubits.id(q[1])
three_qubits.id(q[2])

job = oq.execute(three_qubits, S_simulator)
result = job.result()
result.get_statevector()

oq.Wavefunction(three_qubits)

q = QuantumRegister(2)
H_circuit = QuantumCircuit(q)

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

oq.Wavefunction(H_circuit)

q = QuantumRegister(2)
H_circuit = QuantumCircuit(q)

H_circuit.h(q[0])
H_circuit.id(q[1])

oq.Wavefunction(H_circuit)

from qiskit import ClassicalRegister
M_simulator = Aer.backends(name='qasm_simulator')[0]

q = QuantumRegister(1)
c = ClassicalRegister(1)
qc = QuantumCircuit(q, c)

qc.h(q[0])
qc.measure(q, c)

job = oq.execute(qc, M_simulator)
result = job.result()
result.get_counts(qc)

q = QuantumRegister(1)
c = ClassicalRegister(1)
qc = QuantumCircuit(q, c)

qc.h(q[0])
qc.measure(q, c)

M = oq.execute(qc, M_simulator, shots = 100).result().get_counts(qc)

print('Dictionary entry 0: ', M['0'])
print('Dictionary entry 1: ', M['1'])

q = QuantumRegister(2)
c = ClassicalRegister(2)
qc = QuantumCircuit(q, c)

qc.h(q[0])
qc.h(q[1])
qc.measure(q, c)

M = oq.execute(qc, M_simulator).result().get_counts(qc)
print(M)

q = QuantumRegister(2)
c = ClassicalRegister(2)
qc = QuantumCircuit(q, c)

qc.h(q[0])
qc.h(q[1])
qc.measure(q[0], c[0])

M = oq.execute(qc, M_simulator).result().get_counts(qc)
print(M)

q = QuantumRegister(2)
c = ClassicalRegister(2)
qc = QuantumCircuit(q, c)

qc.id(q[0])
qc.x(q[1])
qc.measure(q, c)

M = oq.execute(qc, M_simulator).result().get_counts(qc)
print(M)

q = QuantumRegister(2)
qc = QuantumCircuit(q)

qc.id(q[0])
qc.x(q[1])

oq.Wavefunction(qc)

q = QuantumRegister(2)
c = ClassicalRegister(2)
qc = QuantumCircuit(q, c)

qc.h(q[0])
qc.h(q[1])
qc.measure(q[0], c[0])

oq.Measurement(qc, shots = 1024)

def Quantum_Coin_Flips(flips):
    '''
    Simulates a perfect coin, measuring heads or tails, using a qubit
    '''
    q = QuantumRegister(1)
    c = ClassicalRegister(1)
    perfect_coin = QuantumCircuit(q, c)
    
    perfect_coin.h(q[0])
    perfect_coin.measure(q,c)
    
    M = oq.execute(perfect_coin, M_simulator, shots=flips).result().get_counts(perfect_coin)
    heads = M['0']
    tails = M['1']
    
    return heads, tails

Heads, Tails = Quantum_Coin_Flips(100)

if (Heads > Tails):
    print('Alice wins!')
if (Heads < Tails):
    print('Bob wins!')
if (Heads == Tails):
    print('Draw!')
print(' ')
print('Score: Alice: ', Heads, ' Bob: ', Tails)

