// SPDX-FileCopyrightText: 2020 Evandro Chagas Ribeiro da Rosa <evandro@quantuloop.com>
// SPDX-FileCopyrightText: 2020 Rafael de Santiago <r.santiago@ufsc.br>
//
// SPDX-License-Identifier: Apache-2.0

use crate::error::Result;
use crate::quantum_execution::QuantumExecution;
use crate::FloatOps;
use crate::{
    bitwise::{bit_flip_vec, ctrl_check_vec, is_one_at_vec},
    quantum_execution::ExecutionFeatures,
};
use itertools::Itertools;
use ket::execution::Capability;
use ket::process::DumpData;
use num::complex::Complex;
use num::One;
use rayon::prelude::*;
use serde::{Deserialize, Serialize};
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use twox_hash::xxhash64::RandomState;

type StateMap<F> = HashMap<Vec<u64>, Complex<F>, RandomState>;

#[derive(Serialize, Deserialize)]
pub struct Sparse<F: FloatOps> {
    state_0: StateMap<F>,
    state_1: StateMap<F>,
    state: bool,
    num_states: usize,
}

impl<F: FloatOps> Sparse<F> {
    const fn get_states(&mut self) -> (&mut StateMap<F>, &mut StateMap<F>) {
        self.state = !self.state;
        if self.state {
            (&mut self.state_1, &mut self.state_0)
        } else {
            (&mut self.state_0, &mut self.state_1)
        }
    }

    const fn get_current_state_mut(&mut self) -> &mut StateMap<F> {
        if self.state {
            &mut self.state_0
        } else {
            &mut self.state_1
        }
    }

    const fn get_current_state(&self) -> &StateMap<F> {
        if self.state {
            &self.state_0
        } else {
            &self.state_1
        }
    }
}

impl<F: FloatOps> QuantumExecution for Sparse<F> {
    fn new(num_qubits: usize) -> Result<Self> {
        let num_states = num_qubits.div_ceil(64);

        let mut state_0 = StateMap::<F>::default();

        let zero = vec![0; num_states];

        state_0.insert(zero, Complex::<F>::one());

        Ok(Self {
            state_0,
            state_1: StateMap::<F>::default(),
            state: true,
            num_states,
        })
    }

    fn pauli_x(&mut self, target: usize, control: &[usize]) {
        let (current_state, next_state) = self.get_states();
        let has_control = !control.is_empty();

        current_state.drain().for_each(|(state, amp)| {
            next_state.insert(
                if !has_control || ctrl_check_vec(&state, control) {
                    bit_flip_vec(state, target)
                } else {
                    state
                },
                amp,
            );
        });
    }

    fn pauli_y(&mut self, target: usize, control: &[usize]) {
        let (current_state, next_state) = self.get_states();
        let has_control = !control.is_empty();
        let i_complex = Complex::<F>::i();
        let neg_i_complex = -i_complex;

        current_state.drain().for_each(|(state, mut amp)| {
            if !has_control || ctrl_check_vec(&state, control) {
                amp *= if is_one_at_vec(&state, target) {
                    neg_i_complex
                } else {
                    i_complex
                };
                next_state.insert(bit_flip_vec(state, target), amp);
            } else {
                next_state.insert(state, amp);
            }
        });
    }

    fn pauli_z(&mut self, target: usize, control: &[usize]) {
        let current_state = self.get_current_state_mut();
        let has_control = !control.is_empty();

        current_state.par_iter_mut().for_each(|(state, amp)| {
            if is_one_at_vec(state, target) && (!has_control || ctrl_check_vec(state, control)) {
                *amp = -*amp;
            }
        });
    }

    fn hadamard(&mut self, target: usize, control: &[usize]) {
        let (current_state, next_state) = self.get_states();
        let has_control = !control.is_empty();

        let epsilon = F::from(F::small_epsilon()).unwrap();
        let epsilon_sqr = epsilon * epsilon;
        let inv_sqrt_2 = F::FRAC_1_SQRT_2();

        current_state.drain().for_each(|(state, mut amp)| {
            if !has_control || ctrl_check_vec(&state, control) {
                amp *= inv_sqrt_2;
                let state_flipped = bit_flip_vec(Vec::clone(&state), target);

                match next_state.entry(state_flipped) {
                    Entry::Occupied(mut entry) => {
                        let c_amp = entry.get_mut();
                        *c_amp += amp;
                        if c_amp.norm_sqr() < epsilon_sqr {
                            entry.remove();
                        }
                    }
                    Entry::Vacant(entry) => {
                        entry.insert(amp);
                    }
                }

                amp = if is_one_at_vec(&state, target) {
                    -amp
                } else {
                    amp
                };

                match next_state.entry(state) {
                    Entry::Occupied(mut entry) => {
                        let c_amp = entry.get_mut();
                        *c_amp += amp;
                        if c_amp.norm_sqr() < epsilon_sqr {
                            entry.remove();
                        }
                    }
                    Entry::Vacant(entry) => {
                        entry.insert(amp);
                    }
                }
            } else {
                next_state.insert(state, amp);
            }
        });
    }

    fn phase(&mut self, lambda: f64, target: usize, control: &[usize]) {
        let current_state = self.get_current_state_mut();
        let has_control = !control.is_empty();

        let phase = Complex::<F>::exp(Complex::<F>::i() * F::from(lambda).unwrap());

        current_state.par_iter_mut().for_each(|(state, amp)| {
            if is_one_at_vec(state, target) && (!has_control || ctrl_check_vec(state, control)) {
                *amp *= phase;
            }
        });
    }

    fn rx(&mut self, theta: f64, target: usize, control: &[usize]) {
        let (current_state, next_state) = self.get_states();
        let has_control = !control.is_empty();

        let half_theta = F::from(theta / 2.0).unwrap();
        let cons_theta_2 = Complex::<F>::from(F::cos(half_theta));
        let sin_theta_2 = -Complex::<F>::i() * F::sin(half_theta);

        let epsilon = F::from(F::small_epsilon()).unwrap();
        let epsilon_sqr = epsilon * epsilon;

        current_state.drain().for_each(|(state, amp)| {
            if !has_control || ctrl_check_vec(&state, control) {
                let state_flipped = bit_flip_vec(Vec::clone(&state), target);

                // Branch 1: Flip
                let flip_term = amp * sin_theta_2;
                match next_state.entry(state_flipped) {
                    Entry::Occupied(mut entry) => {
                        let c_amp = entry.get_mut();
                        *c_amp += flip_term;
                        if c_amp.norm_sqr() < epsilon_sqr {
                            entry.remove();
                        }
                    }
                    Entry::Vacant(entry) => {
                        entry.insert(flip_term);
                    }
                }

                // Branch 2: Stay
                let stay_term = amp * cons_theta_2;
                match next_state.entry(state) {
                    Entry::Occupied(mut entry) => {
                        let c_amp = entry.get_mut();
                        *c_amp += stay_term;
                        if c_amp.norm_sqr() < epsilon_sqr {
                            entry.remove();
                        }
                    }
                    Entry::Vacant(entry) => {
                        entry.insert(stay_term);
                    }
                }
            } else {
                next_state.insert(state, amp);
            }
        });
    }

    fn ry(&mut self, theta: f64, target: usize, control: &[usize]) {
        let (current_state, next_state) = self.get_states();
        let has_control = !control.is_empty();

        let half_theta = F::from(theta / 2.0).unwrap();
        let cons_theta_2 = Complex::<F>::from(F::cos(half_theta));
        let p_sin_theta_2 = Complex::<F>::from(F::sin(half_theta));
        let m_sin_theta_2 = -p_sin_theta_2;

        let epsilon = F::from(F::small_epsilon()).unwrap();
        let epsilon_sqr = epsilon * epsilon;

        current_state.drain().for_each(|(state, amp)| {
            if !has_control || ctrl_check_vec(&state, control) {
                let state_flipped = bit_flip_vec(Vec::clone(&state), target);

                let flipped_amp = amp
                    * if is_one_at_vec(&state, target) {
                        m_sin_theta_2
                    } else {
                        p_sin_theta_2
                    };

                // Branch 1: Flip
                match next_state.entry(state_flipped) {
                    Entry::Occupied(mut entry) => {
                        let c_amp = entry.get_mut();
                        *c_amp += flipped_amp;
                        if c_amp.norm_sqr() < epsilon_sqr {
                            entry.remove();
                        }
                    }
                    Entry::Vacant(entry) => {
                        entry.insert(flipped_amp);
                    }
                }

                // Branch 2: Stay
                let stay_amp = amp * cons_theta_2;
                match next_state.entry(state) {
                    Entry::Occupied(mut entry) => {
                        let c_amp = entry.get_mut();
                        *c_amp += stay_amp;
                        if c_amp.norm_sqr() < epsilon_sqr {
                            entry.remove();
                        }
                    }
                    Entry::Vacant(entry) => {
                        entry.insert(stay_amp);
                    }
                }
            } else {
                next_state.insert(state, amp);
            }
        });
    }

    fn rz(&mut self, theta: f64, target: usize, control: &[usize]) {
        let current_state = self.get_current_state_mut();
        let has_control = !control.is_empty();

        let half_theta = F::from(theta / 2.0).unwrap();
        let i_complex = Complex::<F>::i();
        let phase_0 = Complex::<F>::exp(i_complex * -half_theta);
        let phase_1 = Complex::<F>::exp(i_complex * half_theta);

        current_state.par_iter_mut().for_each(|(state, amp)| {
            if !has_control || ctrl_check_vec(state, control) {
                if is_one_at_vec(state, target) {
                    *amp *= phase_1;
                } else {
                    *amp *= phase_0;
                }
            }
        });
    }

    fn measure_p1(&mut self, target: usize) -> f64 {
        let current_state = self.get_current_state();

        current_state
            .iter()
            .map(|(state, amp)| {
                if is_one_at_vec(state, target) {
                    amp.norm_sqr() // Optimization: norm_sqr is faster than norm().powi(2)
                } else {
                    F::zero()
                }
            })
            .sum::<F>()
            .to_f64()
            .unwrap()
    }

    fn measure_collapse(&mut self, target: usize, result: bool, p: f64) {
        let (current_state, next_state) = self.get_states();

        let norm_factor = F::from(p).unwrap();

        current_state.drain().for_each(|(state, amp)| {
            if is_one_at_vec(&state, target) == result {
                next_state.insert(state, amp * norm_factor);
            }
        });
    }

    fn dump(&mut self, qubits: &[usize]) -> DumpData {
        let state = self.get_current_state();

        let (basis_states, amplitudes_real, amplitudes_imag): (Vec<_>, Vec<_>, Vec<_>) = state
            .iter()
            .sorted_by_key(|x| x.0)
            .map(|(state, amp)| {
                let mut state: Vec<u64> = qubits
                    .iter()
                    .rev()
                    .chunks(64)
                    .into_iter()
                    .map(|qubits| {
                        qubits
                            .into_iter()
                            .enumerate()
                            .map(|(index, qubit)| {
                                usize::from(is_one_at_vec(state, *qubit)) << index
                            })
                            .reduce(|a, b| a | b)
                            .unwrap_or(0) as u64
                    })
                    .collect();
                state.reverse();

                (state, amp.re.to_f64().unwrap(), amp.im.to_f64().unwrap())
            })
            .multiunzip();

        DumpData {
            basis_states,
            amplitudes_real,
            amplitudes_imag,
        }
    }

    fn clear(&mut self) {
        self.state_0 = StateMap::<F>::default();
        self.state_1 = StateMap::<F>::default();

        let zero = vec![0; self.num_states];

        self.state_0.insert(zero, Complex::<F>::one());
        self.state = true;
    }

    fn save(&self) -> Vec<u8> {
        unimplemented!()
    }

    fn load(&mut self, _data: &[u8]) {
        unimplemented!()
    }
}

impl<F: FloatOps> ExecutionFeatures for Sparse<F> {
    fn feature_measure() -> Capability {
        Capability::Advanced
    }

    fn feature_sample() -> Capability {
        Capability::Advanced
    }

    fn feature_exp_value() -> Capability {
        Capability::Advanced
    }

    fn feature_dump() -> Capability {
        Capability::Advanced
    }

    fn feature_need_decomposition() -> bool {
        false
    }

    fn feature_allow_live() -> bool {
        true
    }

    fn supports_gradient() -> bool {
        false
    }
}
