use std::mem;
use crate::{
    boolean_vector::BooleanVector,
    clifford_helper,
    collection::{Base, Full},
    pauli::PauliStack,
    tracker::{
        PauliString, Tracker,
        frames::{Frames, MoveError, OverwriteStack},
    },
};
macro_rules! single_doc_standard {
    ($gate:literal) => {
        concat!("Apply the ", $gate, " gate on the qu`bit`.")
    };
}
macro_rules! single_doc_equivalent {
    ($gate:literal, $equiv:literal) => {
        concat!(single_doc_standard!($gate), " Equivalent to the ", $equiv, " gate.")
    };
}
macro_rules! double_doc {
    ($gate:literal) => {
        double_doc!($gate, bit_a, bit_b)
    };
    ($gate:literal, $bit_a:ident, $bit_b:ident) => {
        concat!(
            "Apply the ",
            $gate,
            " on the `",
            stringify!($bit_a),
            "` and `",
            stringify!($bit_b),
            "` qubits."
        )
    };
}
macro_rules! coset {
    ($coset:ident, $coset_name:literal, $(($name:ident, $gate:literal),)*) => {$(
        #[doc = single_doc_equivalent!($gate, $coset_name)]
        fn $name(&mut self, bit: usize) {
            self.$coset(bit);
        }
    )*};
}
pub trait CliffordCircuit {
            type Outcome;
    clifford_helper::trait_gates!();
        fn measure(&mut self, bit: usize) -> Self::Outcome;
}
mod dummies;
pub use dummies::{DummyCircuit, RandomMeasurementCircuit};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TrackedCircuit<Circuit, Tracker, Storage> {
        pub circuit: Circuit,
        pub tracker: Tracker,
        pub storage: Storage,
}
macro_rules! track_paulis {
    ($(($name:ident, $gate:literal),)*) => {$(
        /// Append a tracked
        #[doc = $gate]
        /// gate to the tracker.
        pub fn $name(&mut self, bit: usize) {
            self.tracker.$name(bit)
        }
    )*};
}
macro_rules! apply_paulis {
    ($(($name:ident, $gate:literal),)*) => {$(
        /// Apply the
        #[doc = $gate]
        /// gate on the circuit (identity on the tracker).
        pub fn $name(&mut self, bit: usize) {
            self.circuit.$name(bit)
        }
    )*};
}
macro_rules! single_gate {
    ($(($name:ident, $gate:literal),)*) => {$(
        /// Apply the
        #[doc = $gate]
        /// gate on the circuit and update the Pauli tracker accordingly.
        pub fn $name(&mut self, bit: usize) {
            self.circuit.$name(bit);
            self.tracker.$name(bit)
        }
    )*};
}
macro_rules! movements {
    ($((
        $name:ident,
        $from_doc:literal,
        $to_doc:literal
    ),)*) => {$(
        /// On the tracker, "move" the
        #[doc=$from_doc]
        /// Pauli stack from the `origin` qubit to the `destination` qubit,
        /// transforming it to an
        #[doc=$to_doc]
        /// stack.
        pub fn $name(&mut self, source: usize, destination: usize) {
            self.tracker.$name(source, destination)
        }
    )*};
}
macro_rules! remove {
    ($((
        $name:ident,
        $correction:literal
    ),)*) => {$(
        /// On the tracker, "remove" the
        #[doc=$correction]
        /// Pauli stack from the qu`bit`.
        pub fn $name(&mut self, bit: usize) {
            self.tracker.$name(bit)
        }
    )*};
}
macro_rules! double_gate {
    ($name:ident, $gate:literal) => {
        double_gate!($name, $gate, bit_a, bit_b);
    };
    ($name:ident, $gate:literal, $bit_a:ident, $bit_b:ident) => {
                #[doc = $gate]
                pub fn $name(&mut self, $bit_a: usize, $bit_b: usize) {
            self.circuit.$name($bit_a, $bit_b);
            self.tracker.$name($bit_a, $bit_b)
        }
    };
}
impl<C, T, S> TrackedCircuit<C, T, S>
where
    T: Tracker,
{
        pub fn track_pauli(&mut self, bit: usize, pauli: T::Pauli) {
        self.tracker.track_pauli(bit, pauli)
    }
        pub fn track_pauli_string(&mut self, pauli: PauliString<T::Pauli>) {
        self.tracker.track_pauli_string(pauli)
    }
    track_paulis!((track_x, "X"), (track_y, "Y"), (track_z, "Z"),);
    movements!(
        (move_x_to_x, "X", "X"),
        (move_x_to_z, "X", "Z"),
        (move_z_to_x, "Z", "X"),
        (move_z_to_z, "Z", "Z"),
    );
    remove!((remove_z, "Z"), (remove_x, "X"),);
}
impl<C, T, S> TrackedCircuit<C, T, S>
where
    C: CliffordCircuit,
{
    apply_paulis!((id, "I"), (x, "X"), (y, "Y"), (z, "Z"),);
        pub fn measure(&mut self, bit: usize) -> C::Outcome {
        self.circuit.measure(bit)
    }
}
impl<C, T, S> TrackedCircuit<C, T, S>
where
    C: CliffordCircuit,
    T: Tracker,
{
    single_gate!(
        (s, "S"),
        (sdg, "SDG"),
        (sz, "SZ"),
        (szdg, "SZDG"),
        (hxy, "H_xy"),
        (h, "H"),
        (sy, "SY"),
        (sydg, "SYDG"),
        (sh, "SH"),
        (hs, "HS"),
        (shs, "SHS"),
        (sx, "SX"),
        (sxdg, "SXDG"),
        (hyz, "H_yz"),
    );
    double_gate!(cz, "Control Z");
    double_gate!(cx, "Control X (Control Not)", control, target);
    double_gate!(cy, "Control Y", control, target);
    double_gate!(swap, "SWAP");
    double_gate!(zcz, "Z-Control Z", control, target);
    double_gate!(zcx, "Z-Control X");
    double_gate!(zcy, "Z-Control Y", control, target);
    double_gate!(iswap, "iSWAP");
    double_gate!(iswapdg, "iSWAP^dagger");
}
impl<C, A, S, B> TrackedCircuit<C, Frames<A>, S>
where
    C: CliffordCircuit,
    A: Full<T = PauliStack<B>> + Default,
    S: Base<TB = PauliStack<B>>,
    B: BooleanVector,
{
                pub fn measure_and_store(
        &mut self,
        bit: usize,
    ) -> (C::Outcome, Result<(), MoveError<B>>) {
        let outcome = self.circuit.measure(bit);
        match self.tracker.measure_and_store(bit, &mut self.storage) {
            Ok(_) => (outcome, Ok(())),
            Err(e) => (outcome, Err(e)),
        }
    }
                    #[allow(clippy::type_complexity)]     pub fn measure_and_store_all(
        &mut self,
    ) -> (Vec<(usize, C::Outcome)>, Result<(), OverwriteStack<B>>) {
        let mut outcome = Vec::<(usize, C::Outcome)>::new();
        let num_frames = self.tracker.frames_num();
        let mut storage = mem::take(&mut self.tracker).into_storage().into_iter();
        while let Some((bit, pauli)) = storage.next() {
            outcome.push((bit, self.circuit.measure(bit)));
            if let Some(stack) = self.storage.insert(bit, pauli) {
                self.tracker = Frames::new_unchecked(storage.collect(), num_frames);
                return (outcome, Err(OverwriteStack { bit, stack }));
            }
        }
        (outcome, Ok(()))
    }
}
#[cfg(test)]
mod tests {
        use bit_vec::BitVec;
    use coverage_helper::test;
    use super::*;
    use crate::{
        boolean_vector::bitvec_simd::SimdBitVec,
        collection::{BufferedVector, Init, Map, MappedVector, NaiveVector},
        pauli::{PauliDense, PauliEnum},
        tracker::{MissingBit, frames::induced_order, live},
    };
    type PauliBitVec = PauliStack<BitVec>;
    type PauliSimdBitVec = PauliStack<SimdBitVec>;
        type Live<P> = live::Live<crate::collection::MappedVector<P>>;
    #[test]
    fn measure_and_store() {
        let mut circ = TrackedCircuit {
            circuit: DummyCircuit {},
            tracker: Frames::<MappedVector<PauliStack<BitVec>>>::init(3),
            storage: Map::<_>::default(),
        };
        circ.measure_and_store(0).1.unwrap();
        circ.track_z(2);
        circ.cx(1, 2);
        circ.h(1);
        circ.measure_and_store(2).1.unwrap();
        circ.tracker.new_qubit(2);
        match circ.measure_and_store(2).1.unwrap_err() {
            MoveError::OverwriteStack(e) => {
                assert_eq!(e, OverwriteStack {
                    bit: 2,
                    stack: PauliBitVec::try_from_str("1", "0").unwrap()
                });
            },
            MoveError::MissingBit(_) => panic!("wrong error"),
        }
        match circ.measure_and_store(2).1.unwrap_err() {
            MoveError::OverwriteStack(_) => panic!("wrong error"),
            MoveError::MissingBit(e) => {
                assert_eq!(e, MissingBit(2));
            },
        }
        circ.tracker.new_qubit(2);
        circ.tracker.new_qubit(3);
        circ.tracker.new_qubit(4);
                                let (outcome, r) = circ.measure_and_store_all();
        assert_eq!(outcome.len(), 2);
        assert_eq!(r.unwrap_err(), {
            OverwriteStack {
                bit: 2,
                stack: PauliBitVec::try_from_str("0", "0").unwrap(),
            }
        });
        let (outcome, r) = circ.measure_and_store_all();
        assert_eq!(outcome.len(), 2);
        r.unwrap()
    }
    #[test]
    fn move_and_remove() {
        let mut circ = TrackedCircuit {
            circuit: DummyCircuit {},
            tracker: Frames::<Map<PauliStack<Vec<bool>>>>::init(3),
            storage: (),
        };
        let mut live = TrackedCircuit {
            circuit: DummyCircuit {},
            tracker: live::Live::<NaiveVector<PauliEnum>>::init(3),
            storage: (),
        };
        circ.track_z(0);
        live.track_z(0);
        circ.track_z(1);
        live.track_z(1);
        circ.track_x(1);
        live.track_x(1);
        circ.track_x(2);
        live.track_x(2);
        assert_eq!(
            vec![
                (0, PauliStack::try_from_str("1000", "0000").unwrap()),
                (1, PauliStack::try_from_str("0100", "0010").unwrap()),
                (2, PauliStack::try_from_str("0000", "0001").unwrap())
            ],
            circ.tracker.as_storage().clone().into_sorted_by_key()
        );
        assert_eq!(live.tracker.as_storage().0, [
            PauliEnum::Z,
            PauliEnum::Y,
            PauliEnum::X
        ]);
        circ.move_z_to_x(0, 1);
        live.move_z_to_x(0, 1);
        assert_eq!(
            vec![
                (0, PauliStack::try_from_str("", "0000").unwrap()),
                (1, PauliStack::try_from_str("0100", "1010").unwrap()),
                (2, PauliStack::try_from_str("0000", "0001").unwrap())
            ],
            circ.tracker.as_storage().clone().into_sorted_by_key()
        );
        assert_eq!(live.tracker.as_storage().0, [
            PauliEnum::I,
            PauliEnum::Z,
            PauliEnum::X
        ]);
        circ.remove_x(2);
        live.remove_x(2);
        assert_eq!(
            vec![
                (0, PauliStack::try_from_str("", "0000").unwrap()),
                (1, PauliStack::try_from_str("0100", "1010").unwrap()),
                (2, PauliStack::try_from_str("0000", "").unwrap())
            ],
            circ.tracker.as_storage().clone().into_sorted_by_key()
        );
        assert_eq!(live.tracker.as_storage().0, [
            PauliEnum::I,
            PauliEnum::Z,
            PauliEnum::I
        ]);
        circ.move_x_to_z(1, 2);
        live.move_x_to_z(1, 2);
        assert_eq!(
            vec![
                (0, PauliStack::try_from_str("", "0000").unwrap()),
                (1, PauliStack::try_from_str("0100", "").unwrap()),
                (2, PauliStack::try_from_str("1010", "").unwrap())
            ],
            circ.tracker.as_storage().clone().into_sorted_by_key()
        );
        assert_eq!(live.tracker.as_storage().0, [
            PauliEnum::I,
            PauliEnum::Z,
            PauliEnum::I
        ]);
    }
    #[test]
    fn single_rotation_teleportation() {
        let mut circ = TrackedCircuit {
            circuit: DummyCircuit {},
            tracker: Frames::<MappedVector<PauliStack<BitVec>>>::init(2),
            storage: MappedVector::<_>::default(),
        };
        circ.cz(0, 1);
        circ.measure_and_store(0).1.unwrap();
                        circ.h(1);
        circ.track_z(1);
        assert_eq!(
            vec![(1, PauliBitVec::try_from_str("1", "0").unwrap())],
            circ.tracker.into_storage().into_sorted_by_key()
        );
        assert_eq!(vec![(0, PauliBitVec::new())], circ.storage.into_sorted_by_key());
    }
    #[test]
    fn control_v_dagger() {
        let mut circ = TrackedCircuit {
            circuit: DummyCircuit {},
            tracker: Frames::<MappedVector<PauliSimdBitVec>>::init(5),
            storage: MappedVector::<_>::default(),
        };
        circ.cx(0, 2);
                circ.measure_and_store(0).1.unwrap();
                circ.track_z(2);
                circ.h(1);
                circ.cx(1, 2);
                circ.cx(2, 3);
                circ.measure_and_store(2).1.unwrap();
                circ.track_z(3);
                        circ.cx(1, 4);
                        circ.measure_and_store(1).1.unwrap();
                        circ.track_z(4);
                                circ.cx(4, 3);
                                circ.h(4);
                        
        assert_eq!(
            vec![
                (3, PauliSimdBitVec::try_from_str("010", "000").unwrap()),
                (4, PauliSimdBitVec::try_from_str("000", "011").unwrap())
            ],
            circ.tracker.into_storage().into_sorted_by_key()
        );
        assert_eq!(
            vec![
                (0, PauliSimdBitVec::new()),
                (1, PauliSimdBitVec::try_from_str("10", "00").unwrap()),
                (2, PauliSimdBitVec::try_from_str("1", "0").unwrap())
            ],
            circ.storage.into_sorted_by_key()
        );
    }
    #[test]
    fn toffoli_live() {
        let mut circ = TrackedCircuit {
            circuit: RandomMeasurementCircuit {},
            tracker: Live::init(10),
            storage: (),
        };
        trait TTele {
            fn t_tele(&mut self, origin: usize, new: usize) -> bool;
        }
        impl TTele for TrackedCircuit<RandomMeasurementCircuit, Live<PauliDense>, ()> {
            #[cfg_attr(coverage_nightly, coverage(off))]
            fn t_tele(&mut self, origin: usize, new: usize) -> bool {
                self.cx(origin, new);
                self.move_z_to_z(origin, new);
                let result = self.circuit.measure(origin);
                if result {
                    self.track_z(new);
                };
                result
            }
        }
        let mut results = Vec::new();
        results.push(circ.t_tele(0, 3) as u8);
        results.push(circ.t_tele(1, 4) as u8);
        circ.h(2);
        circ.cx(3, 4);
        results.push(circ.t_tele(2, 5) as u8);
        circ.cx(4, 5);
        results.push(circ.t_tele(4, 6) as u8);
        results.push(circ.t_tele(5, 7) as u8);
        circ.cx(3, 6);
        circ.cx(6, 7);
        circ.cx(3, 6);
        results.push(circ.t_tele(7, 8) as u8);
        circ.cx(6, 8);
        circ.cx(3, 6);
        results.push(circ.t_tele(8, 9) as u8);
        circ.cx(6, 9);
        circ.h(9);
        let mut check = Live::<PauliDense>::init(10);
                                        check
            .get_mut(3)
            .unwrap()
            .set_storage((results[0] + results[3] + results[4] + results[5]) % 2);
        check
            .get_mut(6)
            .unwrap()
            .set_storage((results[1] + results[3] + results[4] + results[6]) % 2);
        check
            .get_mut(9)
            .unwrap()
            .set_storage(((results[2] + results[4] + results[5] + results[6]) % 2) * 2);
                assert_eq!(circ.tracker, check);
    }
    #[test]
    fn another_graph_test() {
        let mut circ = TrackedCircuit {
            circuit: DummyCircuit {},
            tracker: Frames::<BufferedVector<PauliStack<BitVec>>>::init(10),
            storage: (),
        };
                        trait TTele {
            fn t_tele(&mut self, origin: usize, new: usize);
        }
        impl TTele
            for TrackedCircuit<
                DummyCircuit,
                Frames<BufferedVector<PauliStack<BitVec>>>,
                (),
            >
        {
            #[cfg_attr(coverage_nightly, coverage(off))]
            fn t_tele(&mut self, origin: usize, new: usize) {
                self.cx(origin, new);
                self.move_z_to_z(origin, new);
                self.measure(origin);
                self.track_z(new);
            }
        }
        circ.t_tele(0, 3);
        circ.t_tele(1, 4);
        circ.h(4);
        circ.cz(4, 3);
        circ.cx(3, 4);
        circ.t_tele(2, 5);
        circ.cx(4, 5);
        circ.t_tele(4, 6);
        circ.h(6);
        circ.t_tele(5, 7);
        circ.cx(3, 6);
        circ.h(3);
        circ.cx(3, 7);
        circ.s(3);
        circ.cz(3, 6);
        circ.t_tele(7, 8);
        circ.cx(6, 8);
        circ.s(8);
        circ.cx(3, 6);
        circ.cx(8, 6);
        circ.t_tele(8, 9);
        circ.cx(6, 9);
        circ.h(9);
        let rest = circ.tracker.into_storage();
        
        let _graph = induced_order::get_order(&rest, &[0, 1, 2, 4, 5, 7, 8]);
                    }
}