use std::{cell::RefCell, collections::BTreeSet, ops::Deref, rc::Rc};

use rand::Rng;

use crate::{
    code_block::{CodeBlock, CodeBlockHandler},
    error::{KetError, Result},
    instruction::{ClassicalOp, EndInstruction, Instruction, QuantumGate},
    ir::{Metrics, ResultData},
    object::{Dump, DumpData, Future, Label, Pid, Qubit},
    serialize::{DataType, SerializedData},
};

#[derive(Clone, Debug)]
pub struct Features {
    allow_dirty_qubits: bool,
    allow_free_qubits: bool,
    valid_after_measure: bool,
    plugins: BTreeSet<String>,
    classical_control_flow: bool,
    allow_dump: bool,
    allow_measure: bool,
    continue_after_dump: bool,
}

impl Features {
    pub fn new(
        allow_dirty_qubits: bool,
        allow_free_qubits: bool,
        valid_after_measure: bool,
        classical_control_flow: bool,
        allow_dump: bool,
        allow_measure: bool,
        continue_after_dump: bool,
    ) -> Features {
        Features {
            allow_dirty_qubits,
            allow_free_qubits,
            valid_after_measure,
            plugins: BTreeSet::new(),
            classical_control_flow,
            allow_dump,
            allow_measure,
            continue_after_dump,
        }
    }

    pub fn all() -> Features {
        Features {
            allow_dirty_qubits: true,
            allow_free_qubits: true,
            valid_after_measure: true,
            plugins: BTreeSet::new(),
            classical_control_flow: true,
            allow_dump: true,
            allow_measure: true,
            continue_after_dump: true,
        }
    }

    pub fn none() -> Features {
        Features {
            allow_dirty_qubits: false,
            allow_free_qubits: false,
            valid_after_measure: false,
            plugins: BTreeSet::new(),
            classical_control_flow: false,
            allow_dump: false,
            allow_measure: true,
            continue_after_dump: false,
        }
    }

    pub fn register_plugin(&mut self, plugin: String) {
        self.plugins.insert(plugin);
    }
}

#[derive(Debug)]

pub struct Process {
    pid: usize,

    metrics: Metrics,

    num_qubit: usize,
    blocks: Vec<CodeBlockHandler>,
    current_block: usize,

    ctrl_stack: Vec<Vec<usize>>,

    futures: Vec<Rc<RefCell<Option<i64>>>>,
    dumps: Vec<Rc<RefCell<Option<DumpData>>>>,

    quantum_code_serialized: Option<SerializedData>,
    metrics_serialized: Option<SerializedData>,

    exec_time: Option<f64>,

    features: Features,
}

impl Process {
    pub fn new(pid: usize) -> Self {
        Self {
            pid,
            metrics: Default::default(),
            num_qubit: Default::default(),
            blocks: vec![CodeBlockHandler::default()],
            current_block: Default::default(),
            ctrl_stack: Default::default(),
            futures: vec![Rc::new(RefCell::new(Some(0)))],
            dumps: Default::default(),
            quantum_code_serialized: Default::default(),
            metrics_serialized: Default::default(),
            exec_time: Default::default(),
            features: Features::all(),
        }
    }

    pub fn new_ptr() -> Rc<RefCell<Self>> {
        Rc::new(RefCell::new(Self::new(rand::thread_rng().gen())))
    }

    fn match_pid(&self, obj: &impl Pid) -> Result<()> {
        if obj.pid() != self.pid {
            Err(KetError::UnmatchedPid)
        } else {
            Ok(())
        }
    }

    fn get_control_qubits(&self) -> Vec<usize> {
        let mut tmp_vec = Vec::new();
        for inner_ctrl in self.ctrl_stack.iter() {
            tmp_vec.extend(inner_ctrl.iter());
        }
        tmp_vec
    }

    fn assert_target_not_in_control(&self, target: &Qubit) -> Result<()> {
        if self
            .ctrl_stack
            .iter()
            .any(|inner| inner.contains(&target.index()))
        {
            Err(KetError::TargetOnControl)
        } else {
            Ok(())
        }
    }

    pub fn set_features(&mut self, features: Features) {
        self.features = features;
    }

    pub fn allocate_qubit(&mut self, dirty: bool) -> Result<Qubit> {
        if !self.features.allow_dirty_qubits && dirty {
            return Err(KetError::DirtyNotAllowed);
        }

        let index = self.metrics.qubit_count;
        self.metrics.qubit_count += 1;
        self.num_qubit += 1;
        self.metrics.qubit_simultaneous = if self.num_qubit > self.metrics.qubit_simultaneous {
            self.num_qubit
        } else {
            self.metrics.qubit_simultaneous
        };

        self.blocks
            .get_mut(self.current_block)
            .unwrap()
            .add_instruction(Instruction::Alloc {
                dirty,
                target: index,
            })?;

        Ok(Qubit::new(index, self.pid))
    }

    pub fn free_qubit(&mut self, qubit: &mut Qubit, dirty: bool) -> Result<()> {
        if !self.features.allow_dirty_qubits && dirty {
            return Err(KetError::DirtyNotAllowed);
        }

        if !self.features.allow_free_qubits {
            return Err(KetError::FreeNotAllowed);
        }

        self.match_pid(qubit)?;
        qubit.assert_allocated()?;

        self.blocks
            .get_mut(self.current_block)
            .unwrap()
            .add_instruction(Instruction::Free {
                dirty,
                target: qubit.index(),
            })?;

        qubit.set_deallocated();

        Ok(())
    }

    pub fn apply_gate(&mut self, gate: QuantumGate, target: &Qubit) -> Result<()> {
        target.assert_allocated()?;
        self.match_pid(target)?;
        let control = self.get_control_qubits();
        self.assert_target_not_in_control(target)?;

        let block = self.blocks.get_mut(self.current_block).unwrap();

        let gate = if block.in_adj() { gate.inverse() } else { gate };

        block.add_instruction(Instruction::Gate {
            gate,
            target: target.index(),
            control,
        })?;

        Ok(())
    }

    pub fn apply_plugin(&mut self, name: &str, target: &[&Qubit], args: &str) -> Result<()> {
        if !self.features.plugins.contains(name) {
            return Err(KetError::PluginNotRegistered);
        }

        if !self.ctrl_stack.is_empty() {
            return Err(KetError::PluginOnCtrl);
        }

        for target in target {
            self.match_pid(*target)?;
        }

        self.metrics.plugins.insert(String::from(name));

        self.blocks
            .get_mut(self.current_block)
            .unwrap()
            .add_instruction(Instruction::Plugin {
                name: String::from(name),
                target: target.iter().map(|q| q.index()).collect(),
                args: String::from(args),
            })?;

        Ok(())
    }

    pub fn measure(&mut self, qubits: &mut [&mut Qubit]) -> Result<Future> {
        if !self.features.allow_measure {
            return Err(KetError::MeasureNotAllowed);
        }

        for qubit in qubits.iter_mut() {
            self.match_pid(*qubit)?;
            qubit.assert_allocated()?;
            qubit.set_measured();
        }

        if !self.features.valid_after_measure {
            for qubit in qubits.iter_mut() {
                qubit.set_deallocated();
            }
        }

        let future_index = self.metrics.future_count;
        self.metrics.future_count += 1;

        let future_value = Rc::new(RefCell::new(None));

        self.futures.push(Rc::clone(&future_value));

        self.blocks
            .get_mut(self.current_block)
            .unwrap()
            .add_instruction(Instruction::Measure {
                qubits: qubits.iter().map(|qubit| qubit.index()).collect(),
                output: future_index,
            })?;

        Ok(Future::new(future_index, self.pid, future_value))
    }

    pub fn measure_ref(&mut self, qubits: &mut [Rc<RefCell<Qubit>>]) -> Result<Future> {
        if !self.features.allow_measure {
            return Err(KetError::MeasureNotAllowed);
        }

        for qubit in qubits.iter() {
            self.match_pid(qubit.as_ref().borrow().deref())?;
            qubit.as_ref().borrow().assert_allocated()?;
            qubit.borrow_mut().set_measured();
        }

        if !self.features.valid_after_measure {
            for qubit in qubits.iter() {
                qubit.borrow_mut().set_deallocated();
            }
        }

        let future_index = self.metrics.future_count;
        self.metrics.future_count += 1;

        let future_value = Rc::new(RefCell::new(None));

        self.futures.push(Rc::clone(&future_value));

        self.blocks
            .get_mut(self.current_block)
            .unwrap()
            .add_instruction(Instruction::Measure {
                qubits: qubits
                    .iter()
                    .map(|qubit| qubit.as_ref().borrow().index())
                    .collect(),
                output: future_index,
            })?;

        Ok(Future::new(future_index, self.pid, future_value))
    }

    pub fn ctrl_push(&mut self, qubits: &[&Qubit]) -> Result<()> {
        for ctrl_list in self.ctrl_stack.iter() {
            for qubit in qubits.iter() {
                qubit.assert_allocated()?;
                self.match_pid(*qubit)?;
                if ctrl_list.contains(&qubit.index()) {
                    return Err(KetError::ControlTwice);
                }
            }
        }

        self.ctrl_stack
            .push(qubits.iter().map(|qubit| qubit.index()).collect());

        Ok(())
    }

    pub fn ctrl_push_ref(&mut self, qubits: &[Rc<RefCell<Qubit>>]) -> Result<()> {
        for ctrl_list in self.ctrl_stack.iter() {
            for qubit in qubits {
                let qubit = qubit.as_ref().borrow();
                qubit.assert_allocated()?;
                self.match_pid(qubit.deref())?;
                if ctrl_list.contains(&qubit.index()) {
                    return Err(KetError::ControlTwice);
                }
            }
        }

        self.ctrl_stack.push(
            qubits
                .iter()
                .map(|qubit| qubit.deref().borrow().index())
                .collect(),
        );

        Ok(())
    }

    pub fn ctrl_pop(&mut self) -> Result<()> {
        match self.ctrl_stack.pop() {
            Some(_) => Ok(()),
            None => Err(KetError::NoCtrl),
        }
    }

    pub fn adj_begin(&mut self) -> Result<()> {
        self.blocks.get_mut(self.current_block).unwrap().adj_begin()
    }

    pub fn adj_end(&mut self) -> Result<()> {
        self.blocks.get_mut(self.current_block).unwrap().adj_end()
    }

    pub fn get_label(&mut self) -> Result<Label> {
        if !self.features.classical_control_flow {
            return Err(KetError::ControlFlowNotAllowed);
        }

        let index = self.metrics.block_count;
        self.metrics.block_count += 1;
        self.blocks.push(Default::default());
        Ok(Label::new(index, self.pid))
    }

    pub fn open_block(&mut self, label: &Label) -> Result<()> {
        self.match_pid(label)?;
        self.current_block = label.index();
        Ok(())
    }

    pub fn jump(&mut self, label: &Label) -> Result<()> {
        self.match_pid(label)?;
        self.blocks
            .get_mut(self.current_block)
            .unwrap()
            .add_instruction(Instruction::End(EndInstruction::Jump {
                addr: label.index(),
            }))?;
        Ok(())
    }

    pub fn branch(&mut self, test: &Future, then: &Label, otherwise: &Label) -> Result<()> {
        self.match_pid(test)?;
        self.match_pid(then)?;
        self.match_pid(otherwise)?;

        self.blocks
            .get_mut(self.current_block)
            .unwrap()
            .add_instruction(Instruction::End(EndInstruction::Branch {
                test: test.index(),
                then: then.index(),
                otherwise: otherwise.index(),
            }))?;

        Ok(())
    }

    pub fn dump(&mut self, qubits: &[&Qubit]) -> Result<Dump> {
        if !self.features.allow_dump {
            return Err(KetError::DumpNotAllowed);
        }

        for qubit in qubits.iter() {
            self.match_pid(*qubit)?;
            qubit.assert_allocated()?;
        }

        let dump_index = self.metrics.dump_count;
        self.metrics.dump_count += 1;

        let dump_value = Rc::new(RefCell::new(None));
        self.dumps.push(Rc::clone(&dump_value));

        self.blocks
            .get_mut(self.current_block)
            .unwrap()
            .add_instruction(Instruction::Dump {
                qubits: qubits.iter().map(|qubit| qubit.index()).collect(),
                output: dump_index,
            })?;

        if !self.features.continue_after_dump {
            self.prepare_for_execution()?
        }

        Ok(Dump::new(dump_value))
    }

    pub fn dump_ref(&mut self, qubits: &[Rc<RefCell<Qubit>>]) -> Result<Dump> {
        if !self.features.allow_dump {
            return Err(KetError::DumpNotAllowed);
        }

        for qubit in qubits {
            self.match_pid(qubit.as_ref().borrow().deref())?;
            qubit.deref().borrow().assert_allocated()?;
        }

        let dump_index = self.metrics.dump_count;
        self.metrics.dump_count += 1;

        let dump_value = Rc::new(RefCell::new(None));
        self.dumps.push(Rc::clone(&dump_value));

        self.blocks
            .get_mut(self.current_block)
            .unwrap()
            .add_instruction(Instruction::Dump {
                qubits: qubits
                    .iter()
                    .map(|qubit| qubit.deref().borrow().index())
                    .collect(),
                output: dump_index,
            })?;

        if !self.features.continue_after_dump {
            self.prepare_for_execution()?
        }

        Ok(Dump::new(dump_value))
    }

    pub fn add_int_op(&mut self, op: ClassicalOp, lhs: &Future, rhs: &Future) -> Result<Future> {
        if !self.features.classical_control_flow {
            return Err(KetError::ControlFlowNotAllowed);
        }

        self.match_pid(lhs)?;
        self.match_pid(rhs)?;

        let result_index = self.metrics.future_count;
        self.metrics.future_count += 1;

        let result_value = Rc::new(RefCell::new(None));

        self.futures.push(Rc::clone(&result_value));

        self.blocks
            .get_mut(self.current_block)
            .unwrap()
            .add_instruction(Instruction::IntOp {
                op,
                result: result_index,
                lhs: lhs.index(),
                rhs: rhs.index(),
            })?;

        Ok(Future::new(result_index, self.pid, result_value))
    }

    pub fn int_set(&mut self, dst: &Future, src: &Future) -> Result<()> {
        if !self.features.classical_control_flow {
            return Err(KetError::ControlFlowNotAllowed);
        }

        self.match_pid(dst)?;
        self.match_pid(src)?;

        self.blocks
            .get_mut(self.current_block)
            .unwrap()
            .add_instruction(Instruction::IntOp {
                op: ClassicalOp::Add,
                result: dst.index(),
                lhs: 0,
                rhs: src.index(),
            })?;

        Ok(())
    }

    pub fn int_new(&mut self, value: i64) -> Result<Future> {
        if !self.features.classical_control_flow {
            return Err(KetError::ControlFlowNotAllowed);
        }

        let index = self.metrics.future_count;
        self.metrics.future_count += 1;

        self.blocks
            .get_mut(self.current_block)
            .unwrap()
            .add_instruction(Instruction::IntSet {
                result: index,
                value,
            })?;

        let value = Rc::new(RefCell::new(None));
        self.futures.push(Rc::clone(&value));

        Ok(Future::new(index, self.pid, value))
    }

    pub fn prepare_for_execution(&mut self) -> Result<()> {
        if !self.metrics.ready {
            self.metrics.ready = true;

            self.blocks
                .get_mut(self.current_block)
                .unwrap()
                .add_instruction(Instruction::End(EndInstruction::Halt))?;
        }
        Ok(())
    }

    pub fn exec_time(&self) -> Option<f64> {
        self.exec_time
    }

    pub fn set_timeout(&mut self, timeout: u64) {
        self.metrics.timeout = Some(timeout);
    }

    pub fn metrics(&self) -> &Metrics {
        &self.metrics
    }

    pub fn blocks(&self) -> Vec<&CodeBlock> {
        self.blocks.iter().map(|handler| handler.block()).collect()
    }

    pub fn serialize_metrics(&mut self, data_type: DataType) -> &SerializedData {
        match data_type {
            DataType::JSON => {
                self.metrics_serialized = Some(SerializedData::JSON(
                    serde_json::to_string(&self.metrics).unwrap(),
                ))
            }
            DataType::BIN => {
                self.metrics_serialized = Some(SerializedData::BIN(
                    bincode::serialize(&self.metrics).unwrap(),
                ))
            }
        }

        self.metrics_serialized.as_ref().unwrap()
    }

    pub fn serialize_quantum_code(&mut self, data_type: DataType) -> &SerializedData {
        match data_type {
            DataType::JSON => {
                self.quantum_code_serialized = Some(SerializedData::JSON(
                    serde_json::to_string(&self.blocks()).unwrap(),
                ));
            }
            DataType::BIN => {
                self.quantum_code_serialized = Some(SerializedData::BIN(
                    bincode::serialize(&self.blocks()).unwrap(),
                ));
            }
        }
        self.quantum_code_serialized.as_ref().unwrap()
    }

    pub fn get_serialized_metrics(&self) -> Option<&SerializedData> {
        self.metrics_serialized.as_ref()
    }

    pub fn get_serialized_quantum_code(&self) -> Option<&SerializedData> {
        self.quantum_code_serialized.as_ref()
    }

    pub fn set_result(&mut self, mut result: ResultData) -> Result<()> {
        if (self.futures.len() != result.future.len()) | (self.dumps.len() != result.dump.len()) {
            Err(KetError::UnexpectedResultData)
        } else {
            for (index, value) in result.future.iter().enumerate() {
                *(self.futures.get(index).unwrap().borrow_mut()) = Some(*value);
            }

            for dump in self.dumps.iter_mut().rev() {
                *dump.borrow_mut() = result.dump.pop();
            }

            self.exec_time = Some(result.exec_time);

            Ok(())
        }
    }

    pub fn set_serialized_result(&mut self, result: &SerializedData) -> Result<()> {
        match result {
            SerializedData::JSON(result) => self.set_result(match serde_json::from_str(result) {
                Ok(result) => result,
                Err(_) => return Err(KetError::FailToParseResult),
            }),
            SerializedData::BIN(result) => self.set_result(match bincode::deserialize(result) {
                Ok(result) => result,
                Err(_) => return Err(KetError::FailToParseResult),
            }),
        }
    }

    pub fn pid(&self) -> usize {
        self.pid
    }
}
