use std::{cell::RefCell, ops::RangeBounds, rc::Rc, result};

use crate::{error::KetError, Dump, Future, Process, Qubit};

#[derive(Debug)]

pub struct Quant {
    pub(crate) qubits: Vec<Rc<RefCell<Qubit>>>,
    pub(crate) process: Rc<RefCell<Process>>,
}

pub type Result<T> = result::Result<T, KetError>;

impl Quant {
    pub fn new(process: &Rc<RefCell<Process>>, size: usize) -> Result<Quant> {
        let mut qubits = Vec::with_capacity(size);
        for _ in 0..size {
            qubits.push(Rc::new(RefCell::new(
                process.borrow_mut().allocate_qubit(false)?,
            )));
        }

        Ok(Quant {
            qubits,
            process: Rc::clone(process),
        })
    }

    pub fn slice<R: RangeBounds<usize>>(&self, range: R) -> Quant {
        let start = match range.start_bound() {
            std::ops::Bound::Included(start) => *start,
            std::ops::Bound::Excluded(_) => panic!(),
            std::ops::Bound::Unbounded => 0,
        };

        let end = match range.end_bound() {
            std::ops::Bound::Included(_) => panic!(),
            std::ops::Bound::Excluded(end) => *end,
            std::ops::Bound::Unbounded => self.qubits.len(),
        };

        Quant {
            process: Rc::clone(&self.process),
            qubits: Vec::from_iter(self.qubits[start..end].iter().map(Rc::clone)),
        }
    }

    pub fn cat(quants: &[&Quant]) -> Result<Quant> {
        let process = &quants
            .first()
            .expect("\"quants\" must have length >= 1")
            .process;

        let pid = process.borrow().pid();

        let mut qubits = Vec::new();
        for quant in quants {
            if quant.process.borrow().pid() != pid {
                return Err(KetError::UnmatchedPid);
            }
            qubits.extend(quant.qubits.iter().cloned());
        }

        Ok(Quant {
            qubits,
            process: Rc::clone(process),
        })
    }

    pub fn at(&self, index: usize) -> Quant {
        Quant {
            process: Rc::clone(&self.process),
            qubits: vec![self.qubits[index].clone()],
        }
    }
}

pub fn x(qubits: &Quant) -> Result<()> {
    for target in &qubits.qubits {
        qubits
            .process
            .borrow_mut()
            .apply_gate(crate::QuantumGate::PauliX, &target.borrow())?
    }

    Ok(())
}

pub fn y(qubits: &Quant) -> Result<()> {
    for target in &qubits.qubits {
        qubits
            .process
            .borrow_mut()
            .apply_gate(crate::QuantumGate::PauliY, &target.borrow())?
    }

    Ok(())
}

pub fn z(qubits: &Quant) -> Result<()> {
    for target in &qubits.qubits {
        qubits
            .process
            .borrow_mut()
            .apply_gate(crate::QuantumGate::PauliZ, &target.borrow())?
    }

    Ok(())
}

pub fn h(qubits: &Quant) -> Result<()> {
    for target in &qubits.qubits {
        qubits
            .process
            .borrow_mut()
            .apply_gate(crate::QuantumGate::Hadamard, &target.borrow())?
    }

    Ok(())
}

pub fn phase(lambda: f64, qubits: &Quant) -> Result<()> {
    for target in &qubits.qubits {
        qubits
            .process
            .borrow_mut()
            .apply_gate(crate::QuantumGate::Phase(lambda), &target.borrow())?
    }

    Ok(())
}

pub fn rx(theta: f64, qubits: &Quant) -> Result<()> {
    for target in &qubits.qubits {
        qubits
            .process
            .borrow_mut()
            .apply_gate(crate::QuantumGate::RX(theta), &target.borrow())?
    }

    Ok(())
}

pub fn ry(theta: f64, qubits: &Quant) -> Result<()> {
    for target in &qubits.qubits {
        qubits
            .process
            .borrow_mut()
            .apply_gate(crate::QuantumGate::RY(theta), &target.borrow())?
    }

    Ok(())
}

pub fn rz(theta: f64, qubits: &Quant) -> Result<()> {
    for target in &qubits.qubits {
        qubits
            .process
            .borrow_mut()
            .apply_gate(crate::QuantumGate::RZ(theta), &target.borrow())?
    }

    Ok(())
}

pub fn ctrl<F, T>(control: &Quant, gate: F) -> Result<T>
where
    F: FnOnce() -> T,
{
    control
        .process
        .borrow_mut()
        .ctrl_push_ref(&control.qubits)?;

    let result = gate();

    control.process.borrow_mut().ctrl_pop()?;

    Ok(result)
}

pub fn adj<F, T>(process: &Rc<RefCell<Process>>, gate: F) -> Result<T>
where
    F: FnOnce() -> T,
{
    process.borrow_mut().adj_begin()?;

    let result = gate();

    process.borrow_mut().adj_end()?;

    Ok(result)
}

pub fn around<Outer, Inner, T>(
    process: &Rc<RefCell<Process>>,
    outer: Outer,
    inner: Inner,
) -> Result<T>
where
    Outer: Fn(),
    Inner: FnOnce() -> T,
{
    outer();

    let result = inner();

    adj(process, outer)?;

    Ok(result)
}

pub fn measure(qubits: &mut Quant) -> Result<Future> {
    qubits.process.borrow_mut().measure_ref(&mut qubits.qubits)
}

pub fn dump(qubits: &Quant) -> Result<Dump> {
    qubits.process.borrow_mut().dump_ref(&qubits.qubits)
}

#[cfg(test)]
mod tests {

    use crate::{dump, Process};

    use super::{ctrl, h, x, Quant};

    #[test]
    fn test_slice() {
        let p = Process::new_ptr();

        let q = Quant::new(&p, 10).unwrap();

        let tail = q.slice(1..);
        let head = q.at(0);
        print!("{:#?}", Quant::cat(&[&tail, &head]).unwrap().qubits)
    }

    #[test]
    fn test_bell() {
        let p = Process::new_ptr();

        let q = Quant::new(&p, 2).unwrap();
        h(&q.at(0)).unwrap();
        ctrl(&q.at(0), || x(&q.at(1)).unwrap()).unwrap();

        let d = dump(&q).unwrap();

        p.borrow_mut().prepare_for_execution().unwrap();

        let result = "{\n\"future\": [\n0\n],\n\"dump\": [\n{\n\"Vector\": {\n\"basis_states\": [\n[\n0\n],\n[\n3\n]\n],\n\"amplitudes_real\": [\n0.7071067811865476,\n0.7071067811865476\n],\n\"amplitudes_imag\": [\n0.0,\n0.0\n]\n}\n}\n],\n\"exec_time\": 0.00103683\n}";

        p.borrow_mut()
            .set_result(serde_json::from_str(result).unwrap())
            .unwrap();

        println!("{:#?}", d);
    }
}
