open Microsoft.Quantum.Intrinsic;
open Microsoft.Quantum.Canon;
open Microsoft.Quantum.Math;
open Microsoft.Quantum.Diagnostics;
open Microsoft.Quantum.Measurement;
open Microsoft.Quantum.Convert;
open Microsoft.Quantum.Preparation;
open Microsoft.Quantum.Arithmetic;


//If all entries of x are One, the Oracle returns |y> = |1>
operation andOracle (x : Qubit[], y : Qubit) : Unit {
    body {
        (Controlled X)(x, y);
    }
    adjoint auto;
}

//If all entries of x are Zero, the Oracle returns |y> = |0>
operation orOracle (x : Qubit[], y : Qubit) : Unit {
    body {
        ApplyToEachA(X, x);
        Controlled X (x, y);
        X(y);
        ApplyToEachA(X, x);
    }
    adjoint auto;
}

//Given two qubits, XOR gate if 01 or 10 |y> = |1>
operation xorOracle2 (x : Qubit[], y : Qubit) : Unit {
    X(y);
    Controlled X(x, y);
    X(x[0]);
    X(x[1]);
    Controlled X(x, y);
    X(x[0]);
    X(x[1]);
}

//Puts a state |000...0> into the GHZ state |000..0> + |111..1>/sqrt2
operation buildGHZ (qs : Qubit[]) : Unit{
    body {
        H(qs[0]);
        for (i in 1..Length(qs)-1){
            CNOT(qs[0], qs[i]);
        }
    }
    adjoint auto;
}

//Puts a state |000...0> into the GHZ state |100..0> + |010..0> + ... |000..1>/sqrtN
operation buildW (qs : Qubit[]) : Unit is Adj + Ctl {
    body {
        let N = Length(qs);
        Ry(2.0*ArcSin(Sqrt(1.0/(IntAsDouble(N)))), qs[0]);
        if (N > 1){
            ((ControlledOnInt)(0, buildW))([qs[0]], qs[1...]);
        }
    }
    adjoint auto;
}

operation isAllZeros (qs : Qubit[]) : Int {
    return MeasureInteger(LittleEndian(qs)) == 0 ? 0 | 1;
}

//Generate Bell State 0 : |00>, 1 : |01>, 2 : |10>, 3 |11>
operation buildBell (qs : Qubit[], index : Int) : Unit {
    H(qs[0]);
    CNOT(qs[0], qs[1]);
        
    if (index%2 == 1){
        Z(qs[1]);
    }
    if (index/2 == 1){
        X(qs[0]);
    }
}

//Distinguish Bell State 0 : |00>, 1 : |01>, 2 : |10>, 3 : |11>
operation distinguishBell (qs : Qubit[]) : Int {
    CNOT(qs[0], qs[1]);
    H(qs[0]);
    
    let m1 = M(qs[0]) == One ? 1 | 0;
    let m2 = M(qs[1]) == One ? 1 | 0;
    
    return 2*m2 + m1;
}

//Create a copy of a the given state
operation stateCopy (qs : Qubit[], copy : Qubit[]) : Unit {
    for (i in 0..Length(qs)-1){
        CNOT(qs[i], copy[i]);
    }
}

//takes a integer array of 0/1s and creates an equivalent qubit string 
operation arrToQbit (bits : Int[], qs : Qubit[]) : Unit {
    for (i in 0..Length(qs)-1){
        if (bits[i] == 1){
            X(qs[i]);
        }
    }
}

//Teleport 2 qubits + message
operation Teleport (qAlice : Qubit, qBob : Qubit, qMessage : Qubit) : Unit {
    H(qAlice);
    CNOT(qAlice, qBob);
    CNOT(qMessage, qAlice);
    H(qMessage);
    
    (Controlled Z)([qMessage], qBob);
    (Controlled X)([qAlice], qBob);
}

//Superdensecoding protocol
operation SuperdenseCodingProtocol (b1 : Bool, b2 : Bool) : (Bool, Bool) {
    using ((qAlice, qBob) = (Qubit(), Qubit())){
        H(qAlice);
        CNOT(qAlice, qBob);
        
        //MCMAHON BOOK P236
        if (b1){
            Z(qAlice);
        }
        if (b2){
            X(qAlice);
        }
        
        CNOT(qAlice, qBob);
        H(qAlice);
        
        //Measure the qubit at Z basis, and reset it
        return ((MResetZ(qAlice) == One, MResetZ(qBob) == One));
    }
}

//Destruct state
//Given a superposition state and a integer representing
//the state to be destroied we build a uniform superposition
//with the remaining ones.
operation Destruct(qs : Qubit[], state : Int) : Unit {
    using (a = Qubit()) {
      repeat {
        (ControlledOnInt(state, X)) (qs, a);
        let r = MResetZ(a);
      }
      until (r == Zero)
        fixup {
          ResetAll(qs);
        }
    }
}

//Increment, sum two qubits
operation Increment(register : LittleEndian) : Unit is Adj + Ctl {
    using (q = Qubit[Length(register!)]){
        X(q[0]);
        RippleCarryAdderNoCarryTTK(LittleEndian(q), register);
        X(q[0]);
    }
}

//Decrement, 
operation Decrement(register : LittleEndian) : Unit is Adj + Ctl {
    using (q = Qubit[Length(register!)]){
        ApplyToEachCA(X, q);
        RippleCarryAdderNoCarryTTK(LittleEndian(q), register);
        ApplyToEachCA(X, q);
    }
}

//QUANTUM FOURIER TRANSFORM
operation Reverse(x : Qubit[]) : Unit is Adj+Ctl {
    let N = Length(x);
    for (i in 0 .. N/2-1) {
        SWAP(x[i], x[N-1-i]);
    }
}

operation myQFT(x : Qubit[]) : Unit is Adj+Ctl {
    let n = Length(x);
    for (i in n-1..-1..0) {
        H(x[i]);
        for (j in i-1..-1..0) {
            let theta = (2.0*PI())/(IntAsDouble(1 <<< i-j+1));
            Controlled R1([x[j]], (theta, x[i]));
        }
    }
    Reverse (x);
}