use super::*;
use crate::{LinearMonomial, MonomialDyn, QuadraticMonomial};
use num::Zero;
use std::{
    iter::Sum,
    ops::{Add, AddAssign, Neg, Sub, SubAssign},
};

impl<M1, M2> AddAssign<&PolynomialBase<M1>> for PolynomialBase<M2>
where
    M1: Monomial,
    M2: Monomial + From<M1>,
{
    fn add_assign(&mut self, rhs: &PolynomialBase<M1>) {
        for (id, c) in &rhs.terms {
            self.add_term(Into::<M2>::into(id.clone()), *c)
        }
    }
}

impl<M: Monomial> AddAssign for PolynomialBase<M> {
    fn add_assign(&mut self, mut rhs: Self) {
        if self.terms.len() < rhs.terms.len() {
            rhs += &*self;
            *self = rhs;
        } else {
            self.add_assign(&rhs);
        }
    }
}

impl<M: Monomial> AddAssign<Coefficient> for PolynomialBase<M> {
    fn add_assign(&mut self, rhs: Coefficient) {
        self.add_term(M::default(), rhs);
    }
}

impl<M: Monomial> Add<Coefficient> for PolynomialBase<M> {
    type Output = Self;
    fn add(mut self, rhs: Coefficient) -> Self::Output {
        self += rhs;
        self
    }
}

impl<M: Monomial> Add<PolynomialBase<M>> for Coefficient {
    type Output = PolynomialBase<M>;
    fn add(self, mut rhs: PolynomialBase<M>) -> Self::Output {
        rhs += self;
        rhs
    }
}

impl<M: Monomial> Add<Coefficient> for &PolynomialBase<M> {
    type Output = PolynomialBase<M>;
    fn add(self, rhs: Coefficient) -> Self::Output {
        self.clone() + rhs
    }
}

impl<M: Monomial> Add<&PolynomialBase<M>> for Coefficient {
    type Output = PolynomialBase<M>;
    fn add(self, rhs: &PolynomialBase<M>) -> Self::Output {
        self + rhs.clone()
    }
}

// Add support for PolynomialBase<M> + M operations
impl<M: Monomial> Add<M> for PolynomialBase<M> {
    type Output = Self;
    fn add(mut self, rhs: M) -> Self::Output {
        self.add_term(rhs, coeff!(1.0));
        self
    }
}

impl<M: Monomial> Add<&M> for PolynomialBase<M> {
    type Output = Self;
    fn add(mut self, rhs: &M) -> Self::Output {
        self.add_term(rhs.clone(), coeff!(1.0));
        self
    }
}

impl<M: Monomial> Add<M> for &PolynomialBase<M> {
    type Output = PolynomialBase<M>;
    fn add(self, rhs: M) -> Self::Output {
        self.clone() + rhs
    }
}

impl<M: Monomial> Add<&M> for &PolynomialBase<M> {
    type Output = PolynomialBase<M>;
    fn add(self, rhs: &M) -> Self::Output {
        self.clone() + rhs
    }
}

// Sub<Coefficient> operations
impl<M: Monomial> Sub<Coefficient> for PolynomialBase<M> {
    type Output = Self;
    fn sub(mut self, rhs: Coefficient) -> Self::Output {
        self -= rhs;
        self
    }
}

impl<M: Monomial> Sub<PolynomialBase<M>> for Coefficient {
    type Output = PolynomialBase<M>;
    fn sub(self, mut rhs: PolynomialBase<M>) -> Self::Output {
        rhs = -rhs;
        rhs += self;
        rhs
    }
}

impl<M: Monomial> Sub<Coefficient> for &PolynomialBase<M> {
    type Output = PolynomialBase<M>;
    fn sub(self, rhs: Coefficient) -> Self::Output {
        self.clone() - rhs
    }
}

impl<M: Monomial> Sub<&PolynomialBase<M>> for Coefficient {
    type Output = PolynomialBase<M>;
    fn sub(self, rhs: &PolynomialBase<M>) -> Self::Output {
        self - rhs.clone()
    }
}

// Generic macro for implementing binary operations for monomial types
macro_rules! impl_monomial_op {
    ($op_trait:ident, $op_method:ident, $monomial:ty) => {
        impl $op_trait for $monomial {
            type Output = PolynomialBase<$monomial>;
            fn $op_method(self, rhs: Self) -> Self::Output {
                PolynomialBase::from(self).$op_method(PolynomialBase::from(rhs))
            }
        }

        impl $op_trait<&$monomial> for $monomial {
            type Output = PolynomialBase<$monomial>;
            fn $op_method(self, rhs: &Self) -> Self::Output {
                PolynomialBase::from(self).$op_method(PolynomialBase::from(rhs.clone()))
            }
        }

        impl $op_trait<$monomial> for &$monomial {
            type Output = PolynomialBase<$monomial>;
            fn $op_method(self, rhs: $monomial) -> Self::Output {
                PolynomialBase::from(self.clone()).$op_method(PolynomialBase::from(rhs))
            }
        }

        impl $op_trait for &$monomial {
            type Output = PolynomialBase<$monomial>;
            fn $op_method(self, rhs: Self) -> Self::Output {
                PolynomialBase::from(self.clone()).$op_method(PolynomialBase::from(rhs.clone()))
            }
        }

        // Operations with Coefficient
        impl $op_trait<Coefficient> for $monomial {
            type Output = PolynomialBase<$monomial>;
            fn $op_method(self, rhs: Coefficient) -> Self::Output {
                PolynomialBase::from(self).$op_method(rhs)
            }
        }

        impl $op_trait<$monomial> for Coefficient {
            type Output = PolynomialBase<$monomial>;
            fn $op_method(self, rhs: $monomial) -> Self::Output {
                self.$op_method(PolynomialBase::from(rhs))
            }
        }

        impl $op_trait<Coefficient> for &$monomial {
            type Output = PolynomialBase<$monomial>;
            fn $op_method(self, rhs: Coefficient) -> Self::Output {
                PolynomialBase::from(self.clone()).$op_method(rhs)
            }
        }

        impl $op_trait<&$monomial> for Coefficient {
            type Output = PolynomialBase<$monomial>;
            fn $op_method(self, rhs: &$monomial) -> Self::Output {
                self.$op_method(PolynomialBase::from(rhs.clone()))
            }
        }

        // Operations with PolynomialBase
        impl $op_trait<PolynomialBase<$monomial>> for $monomial {
            type Output = PolynomialBase<$monomial>;
            fn $op_method(self, rhs: PolynomialBase<$monomial>) -> Self::Output {
                // Special handling for different operation types
                impl_monomial_op!(@internal $op_trait, $op_method, self, rhs)
            }
        }

        impl $op_trait<&PolynomialBase<$monomial>> for $monomial {
            type Output = PolynomialBase<$monomial>;
            fn $op_method(self, rhs: &PolynomialBase<$monomial>) -> Self::Output {
                self.$op_method(rhs.clone())
            }
        }

        impl $op_trait<PolynomialBase<$monomial>> for &$monomial {
            type Output = PolynomialBase<$monomial>;
            fn $op_method(self, rhs: PolynomialBase<$monomial>) -> Self::Output {
                // Special handling for different operation types
                impl_monomial_op!(@internal $op_trait, $op_method, self.clone(), rhs)
            }
        }

        impl $op_trait<&PolynomialBase<$monomial>> for &$monomial {
            type Output = PolynomialBase<$monomial>;
            fn $op_method(self, rhs: &PolynomialBase<$monomial>) -> Self::Output {
                self.clone().$op_method(rhs.clone())
            }
        }
    };

    // Internal helper for optimized Add implementation
    (@internal Add, add, $self:expr, $rhs:expr) => {{
        let mut rhs = $rhs;
        rhs.add_term($self, coeff!(1.0));
        rhs
    }};

    // Internal helper for Sub implementation
    (@internal Sub, sub, $self:expr, $rhs:expr) => {{
        let mut result = PolynomialBase::from($self);
        result -= $rhs;
        result
    }};
}

impl_monomial_op!(Add, add, LinearMonomial);
impl_monomial_op!(Add, add, QuadraticMonomial);
impl_monomial_op!(Add, add, MonomialDyn);

impl_monomial_op!(Sub, sub, LinearMonomial);
impl_monomial_op!(Sub, sub, QuadraticMonomial);
impl_monomial_op!(Sub, sub, MonomialDyn);

impl<M: Monomial> Add for PolynomialBase<M> {
    type Output = Self;
    fn add(mut self, mut rhs: Self) -> Self::Output {
        if self.terms.len() < rhs.terms.len() {
            rhs += self;
            rhs
        } else {
            self += rhs;
            self
        }
    }
}

impl<M1: Monomial, M2: Monomial + From<M1>> Add<&PolynomialBase<M1>> for PolynomialBase<M2> {
    type Output = Self;
    fn add(mut self, rhs: &PolynomialBase<M1>) -> Self::Output {
        self += rhs;
        self
    }
}

impl<M: Monomial> Add for &PolynomialBase<M> {
    type Output = PolynomialBase<M>;
    fn add(self, rhs: Self) -> Self::Output {
        if self.terms.len() < rhs.terms.len() {
            rhs.clone() + self
        } else {
            self.clone() + rhs
        }
    }
}

impl<M: Monomial> Add<PolynomialBase<M>> for &PolynomialBase<M> {
    type Output = PolynomialBase<M>;
    fn add(self, rhs: PolynomialBase<M>) -> Self::Output {
        rhs + self
    }
}

// Special implementations for MonomialDyn + LinearMonomial/QuadraticMonomial
impl Add<&PolynomialBase<LinearMonomial>> for &PolynomialBase<MonomialDyn> {
    type Output = PolynomialBase<MonomialDyn>;
    fn add(self, rhs: &PolynomialBase<LinearMonomial>) -> Self::Output {
        let mut result = self.clone();
        for (monomial, coeff) in rhs {
            result.add_term(MonomialDyn::from(*monomial), *coeff);
        }
        result
    }
}

impl Add<&PolynomialBase<QuadraticMonomial>> for &PolynomialBase<MonomialDyn> {
    type Output = PolynomialBase<MonomialDyn>;
    fn add(self, rhs: &PolynomialBase<QuadraticMonomial>) -> Self::Output {
        let mut result = self.clone();
        for (monomial, coeff) in rhs {
            result.add_term(MonomialDyn::from(*monomial), *coeff);
        }
        result
    }
}

impl Add<&PolynomialBase<LinearMonomial>> for &PolynomialBase<QuadraticMonomial> {
    type Output = PolynomialBase<QuadraticMonomial>;
    fn add(self, rhs: &PolynomialBase<LinearMonomial>) -> Self::Output {
        let mut result = self.clone();
        for (monomial, coeff) in rhs {
            result.add_term(QuadraticMonomial::from(*monomial), *coeff);
        }
        result
    }
}

impl<M: Monomial> Sum for PolynomialBase<M> {
    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
        iter.fold(Self::default(), Add::add)
    }
}

impl<M: Monomial> Neg for PolynomialBase<M> {
    type Output = Self;
    fn neg(mut self) -> Self::Output {
        for c in self.terms.values_mut() {
            *c = -(*c);
        }
        self
    }
}

impl<M1: Monomial, M2: Monomial + From<M1>> SubAssign<&PolynomialBase<M1>> for PolynomialBase<M2> {
    fn sub_assign(&mut self, rhs: &PolynomialBase<M1>) {
        for (id, c) in &rhs.terms {
            self.add_term(Into::<M2>::into(id.clone()), -(*c));
        }
    }
}

impl<M: Monomial> SubAssign for PolynomialBase<M> {
    fn sub_assign(&mut self, rhs: Self) {
        if self.terms.len() < rhs.terms.len() {
            *self = -rhs + &*self;
        } else {
            self.sub_assign(&rhs);
        }
    }
}

impl<M: Monomial> SubAssign<Coefficient> for PolynomialBase<M> {
    fn sub_assign(&mut self, rhs: Coefficient) {
        self.add_term(M::default(), -rhs);
    }
}

impl<M: Monomial> Sub for PolynomialBase<M> {
    type Output = Self;
    fn sub(mut self, rhs: Self) -> Self::Output {
        self -= rhs;
        self
    }
}

impl<M1: Monomial, M2: Monomial + From<M1>> Sub<&PolynomialBase<M1>> for PolynomialBase<M2> {
    type Output = Self;
    fn sub(mut self, rhs: &PolynomialBase<M1>) -> Self::Output {
        self -= rhs;
        self
    }
}

impl<M: Monomial> Sub for &PolynomialBase<M> {
    type Output = PolynomialBase<M>;
    fn sub(self, rhs: Self) -> Self::Output {
        if self.terms.len() < rhs.terms.len() {
            -rhs.clone() + self
        } else {
            self.clone() - rhs
        }
    }
}

impl<M: Monomial> Sub<PolynomialBase<M>> for &PolynomialBase<M> {
    type Output = PolynomialBase<M>;
    fn sub(self, rhs: PolynomialBase<M>) -> Self::Output {
        -rhs + self
    }
}

// Add support for PolynomialBase<M> - M operations
impl<M: Monomial> Sub<M> for PolynomialBase<M> {
    type Output = Self;
    fn sub(mut self, rhs: M) -> Self::Output {
        self.add_term(rhs, coeff!(-1.0));
        self
    }
}

impl<M: Monomial> Sub<&M> for PolynomialBase<M> {
    type Output = Self;
    fn sub(mut self, rhs: &M) -> Self::Output {
        self.add_term(rhs.clone(), coeff!(-1.0));
        self
    }
}

impl<M: Monomial> Sub<M> for &PolynomialBase<M> {
    type Output = PolynomialBase<M>;
    fn sub(self, rhs: M) -> Self::Output {
        self.clone() - rhs
    }
}

impl<M: Monomial> Sub<&M> for &PolynomialBase<M> {
    type Output = PolynomialBase<M>;
    fn sub(self, rhs: &M) -> Self::Output {
        self.clone() - rhs
    }
}

impl<M: Monomial> Zero for PolynomialBase<M> {
    fn zero() -> Self {
        Self::default()
    }
    fn is_zero(&self) -> bool {
        self.terms.is_empty()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use ::approx::assert_abs_diff_eq;
    use proptest::prelude::*;

    type Linear = PolynomialBase<LinearMonomial>;

    proptest! {
        /// Check four implementations of Add yields the same result
        #[test]
        fn add_ref(a: Linear, b: Linear) {
            let ans = a.clone() + b.clone();
            assert_abs_diff_eq!(&a + &b, ans);
            assert_abs_diff_eq!(&a + b.clone(), ans);
            assert_abs_diff_eq!(a + &b, ans);
        }

        /// Check four implementations of Sub yields the same result
        #[test]
        fn sub_ref(a: Linear, b: Linear) {
            let ans = a.clone() - b.clone();
            assert_abs_diff_eq!(&a - &b, ans);
            assert_abs_diff_eq!(&a - b.clone(), ans);
            assert_abs_diff_eq!(a - &b, ans);
        }

        #[test]
        fn zero(a: Linear) {
            assert_abs_diff_eq!(&a + Linear::zero(), &a);
            assert_abs_diff_eq!(&a - Linear::zero(), &a);
            assert_abs_diff_eq!(&a - &a, Linear::zero());
        }

        #[test]
        fn add_commutative(a: Linear, b: Linear) {
            assert_abs_diff_eq!(&a + &b, &b + &a);
        }

        #[test]
        fn add_associative(a: Linear, b: Linear, c: Linear) {
            assert_abs_diff_eq!(&a + (&b + &c), (&a + &b) + &c);
        }
    }
}
