use std::{
    iter::{Product, Sum},
    ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign},
};

use slop_algebra::{extension::BinomialExtensionField, AbstractExtensionField, AbstractField};
use sp1_primitives::SP1Field;

use crate::ir::{BinOp, ExprExtRef, ExprRef, GLOBAL_AST};

pub(crate) type F = SP1Field;
pub(crate) type EF = BinomialExtensionField<SP1Field, 4>;
pub(crate) type Expr = ExprRef<F>;
pub(crate) type ExprExt = ExprExtRef<EF>;

impl AbstractField for Expr {
    type F = F;

    fn zero() -> Self {
        F::zero().into()
    }
    fn one() -> Self {
        F::one().into()
    }
    fn two() -> Self {
        F::two().into()
    }
    fn neg_one() -> Self {
        F::neg_one().into()
    }

    fn from_f(f: Self::F) -> Self {
        f.into()
    }
    fn from_bool(b: bool) -> Self {
        F::from_bool(b).into()
    }
    fn from_canonical_u8(n: u8) -> Self {
        F::from_canonical_u8(n).into()
    }
    fn from_canonical_u16(n: u16) -> Self {
        F::from_canonical_u16(n).into()
    }
    fn from_canonical_u32(n: u32) -> Self {
        F::from_canonical_u32(n).into()
    }
    fn from_canonical_u64(n: u64) -> Self {
        F::from_canonical_u64(n).into()
    }
    fn from_canonical_usize(n: usize) -> Self {
        F::from_canonical_usize(n).into()
    }
    fn from_wrapped_u32(n: u32) -> Self {
        F::from_wrapped_u32(n).into()
    }
    fn from_wrapped_u64(n: u64) -> Self {
        F::from_wrapped_u64(n).into()
    }

    fn generator() -> Self {
        F::generator().into()
    }
}

impl Add for Expr {
    type Output = Self;

    fn add(self, rhs: Self) -> Self::Output {
        let mut ast = GLOBAL_AST.lock().unwrap();
        ast.bin_op(BinOp::Add, self, rhs)
    }
}

impl Sub for Expr {
    type Output = Self;

    fn sub(self, rhs: Self) -> Self::Output {
        let mut ast = GLOBAL_AST.lock().unwrap();
        ast.bin_op(BinOp::Sub, self, rhs)
    }
}

impl Mul for Expr {
    type Output = Self;

    fn mul(self, rhs: Self) -> Self::Output {
        let mut ast = GLOBAL_AST.lock().unwrap();
        ast.bin_op(BinOp::Mul, self, rhs)
    }
}

impl Neg for Expr {
    type Output = Self;

    fn neg(self) -> Self::Output {
        let mut ast = GLOBAL_AST.lock().unwrap();
        ast.negate(self)
    }
}

impl Add<F> for Expr {
    type Output = Self;

    fn add(self, rhs: F) -> Self::Output {
        self + Expr::from(rhs)
    }
}

impl Sub<F> for Expr {
    type Output = Self;

    fn sub(self, rhs: F) -> Self::Output {
        self - Expr::from(rhs)
    }
}

impl Mul<F> for Expr {
    type Output = Self;

    fn mul(self, rhs: F) -> Self::Output {
        self * Expr::from(rhs)
    }
}

impl Sum for Expr {
    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
        iter.fold(F::zero().into(), Add::add)
    }
}

impl Product for Expr {
    fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
        iter.fold(F::one().into(), Mul::mul)
    }
}

impl From<F> for Expr {
    fn from(f: F) -> Self {
        Expr::IrVar(crate::ir::IrVar::Constant(f))
    }
}

impl Default for Expr {
    fn default() -> Self {
        F::zero().into()
    }
}

impl AddAssign for Expr {
    fn add_assign(&mut self, rhs: Self) {
        *self = *self + rhs;
    }
}

impl SubAssign for Expr {
    fn sub_assign(&mut self, rhs: Self) {
        *self = *self - rhs;
    }
}

impl MulAssign for Expr {
    fn mul_assign(&mut self, rhs: Self) {
        *self = *self * rhs;
    }
}

impl AbstractField for ExprExt {
    type F = EF;

    fn zero() -> Self {
        EF::zero().into()
    }
    fn one() -> Self {
        EF::one().into()
    }
    fn two() -> Self {
        EF::two().into()
    }
    fn neg_one() -> Self {
        EF::neg_one().into()
    }

    fn from_f(f: Self::F) -> Self {
        f.into()
    }
    fn from_bool(b: bool) -> Self {
        EF::from_bool(b).into()
    }
    fn from_canonical_u8(n: u8) -> Self {
        EF::from_canonical_u8(n).into()
    }
    fn from_canonical_u16(n: u16) -> Self {
        EF::from_canonical_u16(n).into()
    }
    fn from_canonical_u32(n: u32) -> Self {
        EF::from_canonical_u32(n).into()
    }
    fn from_canonical_u64(n: u64) -> Self {
        EF::from_canonical_u64(n).into()
    }
    fn from_canonical_usize(n: usize) -> Self {
        EF::from_canonical_usize(n).into()
    }
    fn from_wrapped_u32(n: u32) -> Self {
        EF::from_wrapped_u32(n).into()
    }
    fn from_wrapped_u64(n: u64) -> Self {
        EF::from_wrapped_u64(n).into()
    }

    fn generator() -> Self {
        EF::generator().into()
    }
}

impl AbstractExtensionField<Expr> for ExprExt {
    const D: usize = <EF as AbstractExtensionField<F>>::D;

    fn from_base(b: Expr) -> Self {
        let mut ast = GLOBAL_AST.lock().unwrap();
        ast.ext_from_base(b)
    }

    fn from_base_slice(_: &[Expr]) -> Self {
        todo!()
    }

    fn from_base_fn<F: FnMut(usize) -> Expr>(_: F) -> Self {
        todo!()
    }

    fn as_base_slice(&self) -> &[Expr] {
        todo!()
    }
}

impl From<Expr> for ExprExt {
    fn from(e: Expr) -> Self {
        let mut ast = GLOBAL_AST.lock().unwrap();
        ast.ext_from_base(e)
    }
}

impl Add<Expr> for ExprExt {
    type Output = Self;

    fn add(self, rhs: Expr) -> Self::Output {
        let mut ast = GLOBAL_AST.lock().unwrap();
        ast.bin_op_base_ext(BinOp::Add, self, rhs)
    }
}

impl Sub<Expr> for ExprExt {
    type Output = Self;

    fn sub(self, rhs: Expr) -> Self::Output {
        let mut ast = GLOBAL_AST.lock().unwrap();
        ast.bin_op_base_ext(BinOp::Sub, self, rhs)
    }
}

impl Mul<Expr> for ExprExt {
    type Output = Self;

    fn mul(self, rhs: Expr) -> Self::Output {
        let mut ast = GLOBAL_AST.lock().unwrap();
        ast.bin_op_base_ext(BinOp::Mul, self, rhs)
    }
}

impl MulAssign<Expr> for ExprExt {
    fn mul_assign(&mut self, rhs: Expr) {
        *self = *self * rhs;
    }
}

impl AddAssign<Expr> for ExprExt {
    fn add_assign(&mut self, rhs: Expr) {
        *self = *self + rhs;
    }
}

impl SubAssign<Expr> for ExprExt {
    fn sub_assign(&mut self, rhs: Expr) {
        *self = *self - rhs;
    }
}

impl Add for ExprExt {
    type Output = Self;

    fn add(self, rhs: Self) -> Self::Output {
        let mut ast = GLOBAL_AST.lock().unwrap();
        ast.bin_op_ext(BinOp::Add, self, rhs)
    }
}

impl Sub for ExprExt {
    type Output = Self;

    fn sub(self, rhs: Self) -> Self::Output {
        let mut ast = GLOBAL_AST.lock().unwrap();
        ast.bin_op_ext(BinOp::Sub, self, rhs)
    }
}

impl Mul for ExprExt {
    type Output = Self;

    fn mul(self, rhs: Self) -> Self::Output {
        let mut ast = GLOBAL_AST.lock().unwrap();
        ast.bin_op_ext(BinOp::Mul, self, rhs)
    }
}

impl Neg for ExprExt {
    type Output = Self;

    fn neg(self) -> Self::Output {
        let mut ast = GLOBAL_AST.lock().unwrap();
        ast.neg_ext(self)
    }
}

impl From<EF> for ExprExt {
    fn from(f: EF) -> Self {
        ExprExtRef::ExtConstant(f)
    }
}

impl Default for ExprExt {
    fn default() -> Self {
        EF::zero().into()
    }
}

impl AddAssign for ExprExt {
    fn add_assign(&mut self, rhs: Self) {
        *self = *self + rhs;
    }
}

impl SubAssign for ExprExt {
    fn sub_assign(&mut self, rhs: Self) {
        *self = *self - rhs;
    }
}

impl MulAssign for ExprExt {
    fn mul_assign(&mut self, rhs: Self) {
        *self = *self * rhs;
    }
}

impl Sum for ExprExt {
    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
        iter.fold(EF::zero().into(), Add::add)
    }
}

impl Product for ExprExt {
    fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
        iter.fold(EF::one().into(), Mul::mul)
    }
}
