use std::{cell::UnsafeCell, ptr};
use p3_field::AbstractField;
use sp1_primitives::types::RecursionProgramType;
use super::{
    Array, Config, DslIr, DslIrBlock, Ext, ExtHandle, ExtOperations, Felt, FeltHandle,
    FeltOperations, FromConstant, SymbolicExt, SymbolicFelt, SymbolicUsize, SymbolicVar, Usize,
    Var, VarHandle, VarOperations, Variable,
};
#[derive(Debug, Clone)]
pub struct InnerBuilder<C: Config> {
    pub(crate) variable_count: u32,
    pub operations: Vec<DslIr<C>>,
}
#[derive(Debug)]
pub struct Builder<C: Config> {
    pub(crate) inner: Box<UnsafeCell<InnerBuilder<C>>>,
    pub(crate) nb_public_values: Option<Var<C::N>>,
    pub(crate) witness_var_count: u32,
    pub(crate) witness_felt_count: u32,
    pub(crate) witness_ext_count: u32,
    pub(crate) var_handle: Box<VarHandle<C::N>>,
    pub(crate) felt_handle: Box<FeltHandle<C::F>>,
    pub(crate) ext_handle: Box<ExtHandle<C::F, C::EF>>,
    pub(crate) p2_hash_num: Var<C::N>,
    pub(crate) debug: bool,
    pub(crate) is_sub_builder: bool,
    pub program_type: RecursionProgramType,
}
impl<C: Config> Default for Builder<C> {
    fn default() -> Self {
        Self::new(RecursionProgramType::Core)
    }
}
impl<C: Config> Builder<C> {
    pub fn new(program_type: RecursionProgramType) -> Self {
        let placeholder_p2_hash_num = Var::new(0, ptr::null_mut());
        let mut inner = Box::new(UnsafeCell::new(InnerBuilder {
            variable_count: 0,
            operations: Default::default(),
        }));
        let var_handle = Box::new(VarOperations::var_handle(&mut inner));
        let mut ext_handle = Box::new(ExtOperations::ext_handle(&mut inner));
        let felt_handle = Box::new(FeltOperations::felt_handle(
            &mut inner,
            ext_handle.as_mut() as *mut _ as *mut (),
        ));
        let mut new_builder = Self {
            inner,
            witness_var_count: 0,
            witness_felt_count: 0,
            witness_ext_count: 0,
            nb_public_values: None,
            var_handle,
            felt_handle,
            ext_handle,
            p2_hash_num: placeholder_p2_hash_num,
            debug: false,
            is_sub_builder: false,
            program_type,
        };
        new_builder.p2_hash_num = new_builder.uninit();
        new_builder
    }
    pub fn new_sub_builder(
        variable_count: u32,
        nb_public_values: Option<Var<C::N>>,
        p2_hash_num: Var<C::N>,
        debug: bool,
        program_type: RecursionProgramType,
    ) -> Self {
        let mut builder = Self::new(program_type);
        builder.inner.get_mut().variable_count = variable_count;
        builder.nb_public_values = nb_public_values;
        builder.p2_hash_num = p2_hash_num;
        builder.debug = debug;
        builder
    }
    pub fn sub_builder(&self) -> Self {
        Builder::<C>::new_sub_builder(
            self.variable_count(),
            self.nb_public_values,
            self.p2_hash_num,
            self.debug,
            self.program_type,
        )
    }
    #[inline(always)]
    pub fn push_op(&mut self, op: DslIr<C>) {
        self.inner.get_mut().operations.push(op);
    }
    pub fn extend_ops(&mut self, ops: impl IntoIterator<Item = DslIr<C>>) {
        self.inner.get_mut().operations.extend(ops);
    }
    #[inline(always)]
    pub fn push_backtrace(&mut self) {
        #[cfg(feature = "debug")]
        self.push_op(DslIr::DebugBacktrace(backtrace::Backtrace::new_unresolved()));
    }
    #[inline(always)]
    pub fn push_traced_op(&mut self, op: DslIr<C>) {
        self.push_backtrace();
        self.push_op(op);
    }
    pub fn variable_count(&self) -> u32 {
        unsafe { (*self.inner.get()).variable_count }
    }
    pub fn set_variable_count(&mut self, variable_count: u32) {
        self.inner.get_mut().variable_count = variable_count;
    }
    pub fn into_operations(self) -> Vec<DslIr<C>> {
        self.inner.into_inner().operations
    }
    pub fn into_root_block(self) -> DslIrBlock<C> {
        let addrs_written = 0..self.variable_count();
        DslIrBlock { ops: self.inner.into_inner().operations, addrs_written }
    }
    pub fn get_mut_operations(&mut self) -> &mut Vec<DslIr<C>> {
        &mut self.inner.get_mut().operations
    }
    pub fn uninit<V: Variable<C>>(&mut self) -> V {
        V::uninit(self)
    }
    pub fn eval<V: Variable<C>, E: Into<V::Expression>>(&mut self, expr: E) -> V {
        let dst = V::uninit(self);
        dst.assign(expr.into(), self);
        dst
    }
    pub fn constant<V: FromConstant<C>>(&mut self, value: V::Constant) -> V {
        V::constant(value, self)
    }
    pub fn assign<V: Variable<C>, E: Into<V::Expression>>(&mut self, dst: V, expr: E) {
        dst.assign(expr.into(), self);
    }
    pub fn assert_eq<V: Variable<C>>(
        &mut self,
        lhs: impl Into<V::Expression>,
        rhs: impl Into<V::Expression>,
    ) {
        V::assert_eq(lhs, rhs, self);
    }
    pub fn assert_ne<V: Variable<C>>(
        &mut self,
        lhs: impl Into<V::Expression>,
        rhs: impl Into<V::Expression>,
    ) {
        V::assert_ne(lhs, rhs, self);
    }
    pub fn assert_var_eq<LhsExpr: Into<SymbolicVar<C::N>>, RhsExpr: Into<SymbolicVar<C::N>>>(
        &mut self,
        lhs: LhsExpr,
        rhs: RhsExpr,
    ) {
        self.assert_eq::<Var<C::N>>(lhs, rhs);
    }
    pub fn assert_var_ne<LhsExpr: Into<SymbolicVar<C::N>>, RhsExpr: Into<SymbolicVar<C::N>>>(
        &mut self,
        lhs: LhsExpr,
        rhs: RhsExpr,
    ) {
        self.assert_ne::<Var<C::N>>(lhs, rhs);
    }
    pub fn assert_felt_eq<LhsExpr: Into<SymbolicFelt<C::F>>, RhsExpr: Into<SymbolicFelt<C::F>>>(
        &mut self,
        lhs: LhsExpr,
        rhs: RhsExpr,
    ) {
        self.assert_eq::<Felt<C::F>>(lhs, rhs);
    }
    pub fn assert_felt_ne<LhsExpr: Into<SymbolicFelt<C::F>>, RhsExpr: Into<SymbolicFelt<C::F>>>(
        &mut self,
        lhs: LhsExpr,
        rhs: RhsExpr,
    ) {
        self.assert_ne::<Felt<C::F>>(lhs, rhs);
    }
    pub fn assert_usize_eq<
        LhsExpr: Into<SymbolicUsize<C::N>>,
        RhsExpr: Into<SymbolicUsize<C::N>>,
    >(
        &mut self,
        lhs: LhsExpr,
        rhs: RhsExpr,
    ) {
        self.assert_eq::<Usize<C::N>>(lhs, rhs);
    }
    pub fn assert_usize_ne(
        &mut self,
        lhs: impl Into<SymbolicUsize<C::N>>,
        rhs: impl Into<SymbolicUsize<C::N>>,
    ) {
        self.assert_ne::<Usize<C::N>>(lhs, rhs);
    }
    pub fn assert_ext_eq<
        LhsExpr: Into<SymbolicExt<C::F, C::EF>>,
        RhsExpr: Into<SymbolicExt<C::F, C::EF>>,
    >(
        &mut self,
        lhs: LhsExpr,
        rhs: RhsExpr,
    ) {
        self.assert_eq::<Ext<C::F, C::EF>>(lhs, rhs);
    }
    pub fn assert_ext_ne<
        LhsExpr: Into<SymbolicExt<C::F, C::EF>>,
        RhsExpr: Into<SymbolicExt<C::F, C::EF>>,
    >(
        &mut self,
        lhs: LhsExpr,
        rhs: RhsExpr,
    ) {
        self.assert_ne::<Ext<C::F, C::EF>>(lhs, rhs);
    }
    pub fn lt(&mut self, lhs: Var<C::N>, rhs: Var<C::N>) -> Var<C::N> {
        let result = self.uninit();
        self.push_op(DslIr::LessThan(result, lhs, rhs));
        result
    }
    pub fn if_eq<LhsExpr: Into<SymbolicVar<C::N>>, RhsExpr: Into<SymbolicVar<C::N>>>(
        &mut self,
        lhs: LhsExpr,
        rhs: RhsExpr,
    ) -> IfBuilder<C> {
        IfBuilder { lhs: lhs.into(), rhs: rhs.into(), is_eq: true, builder: self }
    }
    pub fn if_ne<LhsExpr: Into<SymbolicVar<C::N>>, RhsExpr: Into<SymbolicVar<C::N>>>(
        &mut self,
        lhs: LhsExpr,
        rhs: RhsExpr,
    ) -> IfBuilder<C> {
        IfBuilder { lhs: lhs.into(), rhs: rhs.into(), is_eq: false, builder: self }
    }
    pub fn range(
        &mut self,
        start: impl Into<Usize<C::N>>,
        end: impl Into<Usize<C::N>>,
    ) -> RangeBuilder<C> {
        RangeBuilder { start: start.into(), end: end.into(), builder: self, step_size: 1 }
    }
    pub fn break_loop(&mut self) {
        self.push_op(DslIr::Break);
    }
    pub fn print_debug(&mut self, val: usize) {
        let constant = self.eval(C::N::from_canonical_usize(val));
        self.print_v(constant);
    }
    pub fn print_v(&mut self, dst: Var<C::N>) {
        self.push_op(DslIr::PrintV(dst));
    }
    pub fn print_f(&mut self, dst: Felt<C::F>) {
        self.push_op(DslIr::PrintF(dst));
    }
    pub fn print_e(&mut self, dst: Ext<C::F, C::EF>) {
        self.push_op(DslIr::PrintE(dst));
    }
    pub fn hint_len(&mut self) -> Var<C::N> {
        let len = self.uninit();
        self.push_op(DslIr::HintLen(len));
        len
    }
    pub fn hint_var(&mut self) -> Var<C::N> {
        let len = self.hint_len();
        let arr = self.dyn_array(len);
        self.push_op(DslIr::HintVars(arr.clone()));
        self.get(&arr, 0)
    }
    pub fn hint_felt(&mut self) -> Felt<C::F> {
        let len = self.hint_len();
        let arr = self.dyn_array(len);
        self.push_op(DslIr::HintFelts(arr.clone()));
        self.get(&arr, 0)
    }
    pub fn hint_ext(&mut self) -> Ext<C::F, C::EF> {
        let len = self.hint_len();
        let arr = self.dyn_array(len);
        self.push_op(DslIr::HintExts(arr.clone()));
        self.get(&arr, 0)
    }
    pub fn hint_vars(&mut self) -> Array<C, Var<C::N>> {
        let len = self.hint_len();
        let arr = self.dyn_array(len);
        self.push_op(DslIr::HintVars(arr.clone()));
        arr
    }
    pub fn hint_felts(&mut self) -> Array<C, Felt<C::F>> {
        let len = self.hint_len();
        let arr = self.dyn_array(len);
        self.push_op(DslIr::HintFelts(arr.clone()));
        arr
    }
    pub fn hint_exts(&mut self) -> Array<C, Ext<C::F, C::EF>> {
        let len = self.hint_len();
        let arr = self.dyn_array(len);
        self.push_op(DslIr::HintExts(arr.clone()));
        arr
    }
    pub fn witness_var(&mut self) -> Var<C::N> {
        assert!(!self.is_sub_builder, "Cannot create a witness var with a sub builder");
        let witness = self.uninit();
        self.push_op(DslIr::WitnessVar(witness, self.witness_var_count));
        self.witness_var_count += 1;
        witness
    }
    pub fn witness_felt(&mut self) -> Felt<C::F> {
        assert!(!self.is_sub_builder, "Cannot create a witness felt with a sub builder");
        let witness = self.uninit();
        self.push_op(DslIr::WitnessFelt(witness, self.witness_felt_count));
        self.witness_felt_count += 1;
        witness
    }
    pub fn witness_ext(&mut self) -> Ext<C::F, C::EF> {
        assert!(!self.is_sub_builder, "Cannot create a witness ext with a sub builder");
        let witness = self.uninit();
        self.push_op(DslIr::WitnessExt(witness, self.witness_ext_count));
        self.witness_ext_count += 1;
        witness
    }
    pub fn error(&mut self) {
        self.push_traced_op(DslIr::Error());
    }
    pub fn materialize(&mut self, num: Usize<C::N>) -> Var<C::N> {
        match num {
            Usize::Const(num) => self.eval(C::N::from_canonical_usize(num)),
            Usize::Var(num) => num,
        }
    }
    pub fn register_public_value(&mut self, val: Felt<C::F>) {
        self.push_op(DslIr::RegisterPublicValue(val));
    }
    pub fn commit_public_value(&mut self, val: Felt<C::F>) {
        assert!(!self.is_sub_builder, "Cannot commit to a public value with a sub builder");
        if self.nb_public_values.is_none() {
            self.nb_public_values = Some(self.eval(C::N::zero()));
        }
        let nb_public_values = *self.nb_public_values.as_ref().unwrap();
        self.push_op(DslIr::Commit(val, nb_public_values));
        self.assign(nb_public_values, nb_public_values + C::N::one());
    }
    pub fn commit_public_values(&mut self, vals: &Array<C, Felt<C::F>>) {
        assert!(!self.is_sub_builder, "Cannot commit to public values with a sub builder");
        let len = vals.len();
        self.range(0, len).for_each(|i, builder| {
            let val = builder.get(vals, i);
            builder.commit_public_value(val);
        });
    }
    pub fn commit_vkey_hash_circuit(&mut self, var: Var<C::N>) {
        self.push_op(DslIr::CircuitCommitVkeyHash(var));
    }
    pub fn commit_committed_values_digest_circuit(&mut self, var: Var<C::N>) {
        self.push_op(DslIr::CircuitCommitCommittedValuesDigest(var));
    }
    pub fn reduce_e(&mut self, ext: Ext<C::F, C::EF>) {
        self.push_op(DslIr::ReduceE(ext));
    }
    pub fn felt2var_circuit(&mut self, felt: Felt<C::F>) -> Var<C::N> {
        let var = self.uninit();
        self.push_op(DslIr::CircuitFelt2Var(felt, var));
        var
    }
    pub fn cycle_tracker(&mut self, name: &str) {
        self.push_op(DslIr::CycleTracker(name.to_string()));
    }
    pub fn halt(&mut self) {
        self.push_op(DslIr::Halt);
    }
}
#[allow(dead_code)]
pub struct IfBuilder<'a, C: Config> {
    lhs: SymbolicVar<C::N>,
    rhs: SymbolicVar<C::N>,
    is_eq: bool,
    pub(crate) builder: &'a mut Builder<C>,
}
#[allow(dead_code)]
enum IfCondition<N> {
    EqConst(N, N),
    NeConst(N, N),
    Eq(Var<N>, Var<N>),
    EqI(Var<N>, N),
    Ne(Var<N>, Var<N>),
    NeI(Var<N>, N),
}
impl<C: Config> IfBuilder<'_, C> {
    pub fn then(mut self, mut f: impl FnMut(&mut Builder<C>)) {
        let condition = self.condition();
        let mut f_builder = Builder::<C>::new_sub_builder(
            self.builder.variable_count(),
            self.builder.nb_public_values,
            self.builder.p2_hash_num,
            self.builder.debug,
            self.builder.program_type,
        );
        f(&mut f_builder);
        self.builder.p2_hash_num = f_builder.p2_hash_num;
        let then_instructions = f_builder.into_operations();
        match condition {
            IfCondition::EqConst(lhs, rhs) => {
                if lhs == rhs {
                    self.builder.extend_ops(then_instructions);
                }
            }
            IfCondition::NeConst(lhs, rhs) => {
                if lhs != rhs {
                    self.builder.extend_ops(then_instructions);
                }
            }
            IfCondition::Eq(lhs, rhs) => {
                let op = DslIr::IfEq(Box::new((lhs, rhs, then_instructions, Default::default())));
                self.builder.push_op(op);
            }
            IfCondition::EqI(lhs, rhs) => {
                let op = DslIr::IfEqI(Box::new((lhs, rhs, then_instructions, Default::default())));
                self.builder.push_op(op);
            }
            IfCondition::Ne(lhs, rhs) => {
                let op = DslIr::IfNe(Box::new((lhs, rhs, then_instructions, Default::default())));
                self.builder.push_op(op);
            }
            IfCondition::NeI(lhs, rhs) => {
                let op = DslIr::IfNeI(Box::new((lhs, rhs, then_instructions, Default::default())));
                self.builder.push_op(op);
            }
        }
    }
    pub fn then_or_else(
        mut self,
        mut then_f: impl FnMut(&mut Builder<C>),
        mut else_f: impl FnMut(&mut Builder<C>),
    ) {
        let condition = self.condition();
        let mut then_builder = Builder::<C>::new_sub_builder(
            self.builder.variable_count(),
            self.builder.nb_public_values,
            self.builder.p2_hash_num,
            self.builder.debug,
            self.builder.program_type,
        );
        then_f(&mut then_builder);
        self.builder.p2_hash_num = then_builder.p2_hash_num;
        let then_instructions = then_builder.into_operations();
        let mut else_builder = Builder::<C>::new_sub_builder(
            self.builder.variable_count(),
            self.builder.nb_public_values,
            self.builder.p2_hash_num,
            self.builder.debug,
            self.builder.program_type,
        );
        else_f(&mut else_builder);
        self.builder.p2_hash_num = else_builder.p2_hash_num;
        let else_instructions = else_builder.into_operations();
        match condition {
            IfCondition::EqConst(lhs, rhs) => {
                if lhs == rhs {
                    self.builder.extend_ops(then_instructions);
                } else {
                    self.builder.extend_ops(else_instructions);
                }
            }
            IfCondition::NeConst(lhs, rhs) => {
                if lhs != rhs {
                    self.builder.extend_ops(then_instructions);
                } else {
                    self.builder.extend_ops(else_instructions);
                }
            }
            IfCondition::Eq(lhs, rhs) => {
                let op = DslIr::IfEq(Box::new((lhs, rhs, then_instructions, else_instructions)));
                self.builder.push_op(op);
            }
            IfCondition::EqI(lhs, rhs) => {
                let op = DslIr::IfEqI(Box::new((lhs, rhs, then_instructions, else_instructions)));
                self.builder.push_op(op);
            }
            IfCondition::Ne(lhs, rhs) => {
                let op = DslIr::IfNe(Box::new((lhs, rhs, then_instructions, else_instructions)));
                self.builder.push_op(op);
            }
            IfCondition::NeI(lhs, rhs) => {
                let op = DslIr::IfNeI(Box::new((lhs, rhs, then_instructions, else_instructions)));
                self.builder.push_op(op);
            }
        }
    }
    fn condition(&mut self) -> IfCondition<C::N> {
        unimplemented!("Deprecated")
        }
}
pub struct RangeBuilder<'a, C: Config> {
    start: Usize<C::N>,
    end: Usize<C::N>,
    step_size: usize,
    builder: &'a mut Builder<C>,
}
impl<C: Config> RangeBuilder<'_, C> {
    pub const fn step_by(mut self, step_size: usize) -> Self {
        self.step_size = step_size;
        self
    }
    pub fn for_each(self, mut f: impl FnMut(Var<C::N>, &mut Builder<C>)) {
        let step_size = C::N::from_canonical_usize(self.step_size);
        let loop_variable: Var<C::N> = self.builder.uninit();
        let mut loop_body_builder = Builder::<C>::new_sub_builder(
            self.builder.variable_count(),
            self.builder.nb_public_values,
            self.builder.p2_hash_num,
            self.builder.debug,
            self.builder.program_type,
        );
        f(loop_variable, &mut loop_body_builder);
        self.builder.p2_hash_num = loop_body_builder.p2_hash_num;
        let loop_instructions = loop_body_builder.into_operations();
        let op = DslIr::For(Box::new((
            self.start,
            self.end,
            step_size,
            loop_variable,
            loop_instructions,
        )));
        self.builder.push_op(op);
    }
}