from qiskit import QuantumCircuit, QuantumRegister, Aer, execute, ClassicalRegister
from qiskit.ignis.verification.topological_codes import RepetitionCode
from qiskit.ignis.verification.topological_codes import lookuptable_decoding
from qiskit.ignis.verification.topological_codes import GraphDecoder
from qiskit.providers.aer.noise import NoiseModel
from qiskit.providers.aer.noise.errors import pauli_error, depolarizing_error
from qiskit.ignis.mitigation.measurement import (complete_meas_cal,CompleteMeasFitter)
from qiskit.visualization import plot_histogram
import random
backend = Aer.get_backend('qasm_simulator')
#These two functions were taken from https://stackoverflow.com/questions/10237926/convert-string-to-list-of-bits-and-viceversa

def tobits(s):
    result = []
    for c in s:
        bits = bin(ord(c))[2:]
        bits = '00000000'[len(bits):] + bits
        result.extend([int(b) for b in bits])
    return ''.join([str(x) for x in result])

def frombits(bits):
    chars = []
    for b in range(int(len(bits) / 8)):
        byte = bits[b*8:(b+1)*8]
        chars.append(chr(int(''.join([str(bit) for bit in byte]), 2)))
    return ''.join(chars)

def get_noise(circuit,probability,qubits):
    random_number = random.uniform(0, 1)
    if(random_number <= probability):
        qubit = random.randint(0,len(qubits)-1)
        circuit.x(qubit)
    return circuit

def codificate(bitString):
    qubits = list()
    for i in range(len(bitString)):
        mycircuit = QuantumCircuit(1,1)
        if(bitString[i] == "1"):
            mycircuit.x(0)
        qubits.append(mycircuit)
    return qubits

m0 = tobits("I like dogs") 
qubits = codificate(m0)
measurements = list()
for i in range(len(qubits)):
    qubit = qubits[i]
    qubit.measure(0,0)
    result = execute(qubit, backend, shots=1, memory=True).result()
    measurements.append(int(result.get_memory()[0]))
print(frombits(measurements))

m0 = tobits("I like dogs") 
qubits = codificate(m0)
measurements = list()
for i in range(len(qubits)):
    qubit = qubits[i]
    qubit = get_noise(qubit,0.2,range(qubit.num_qubits))
    qubit.measure(0,0)    
    result = execute(qubit, backend, shots=1, memory=True).result()
    measurements.append(int(result.get_memory()[0]))
print(frombits(measurements))

for i in range(len(qubits)):
    cb = QuantumRegister(1,'code_qubit')
    lq = QuantumRegister(4,'ancilla_qubit')
    sb = ClassicalRegister(2,'syndrome_bit')
    out = ClassicalRegister(1,'output_bit')
    mycircuit = QuantumCircuit(cb,lq,sb,out)
    if(m0[i] == "1"):
        mycircuit.x(0)
    mycircuit.cx(0,1)
    mycircuit.cx(1,2)
    mycircuit = get_noise(mycircuit,0.2,range(3))
    mycircuit.cx(0,3)
    mycircuit.cx(1,3)
    mycircuit.cx(0,4)
    mycircuit.cx(2,4)
    mycircuit.measure(3,0)
    mycircuit.measure(4,1)
    qubits[i] = mycircuit

measurements = list()
raw_bits = list()
for i in range(len(qubits)):
    qubit = qubits[i]
    qubit.measure(0,2)
    result = execute(qubit, backend, shots=1, memory=True).result()
    bits = result.get_memory()[0]
    raw_bits.append(int(bits[0]))
for i in range(len(qubits)):
    qubit = qubits[i]
    result = execute(qubit, backend, shots=1, memory=True).result()
    bits = result.get_memory()[0]
    if(bits[2] == '1' and bits[3] == '0'):
        qubit.x(2)
    if(bits[2] == '0' and bits[3] == '1'):
        qubit.x(1)
    if(bits[2] == '1' and bits[3] == '1'):
        qubit.x(0)
    qubit.measure(0,2)
    result = execute(qubit, backend, shots=1, memory=True).result()
    bits = result.get_memory()[0]
    measurements.append(int(bits[0]))
print("without error correction the string was: " + frombits(raw_bits))
print("with error correction the string was: " + frombits(measurements))