use std::ops::{Add, Div, Mul, Sub};

use crate::{CheckedSub, Exit, ReadableVec, Result, StoredVec, VecValue};

use super::super::EagerVec;

impl<V> EagerVec<V>
where
    V: StoredVec,
{
    pub fn compute_add(
        &mut self,
        max_from: V::I,
        added: &impl ReadableVec<V::I, V::T>,
        adder: &impl ReadableVec<V::I, V::T>,
        exit: &Exit,
    ) -> Result<()>
    where
        V::T: Add<Output = V::T>,
    {
        self.compute_transform2(
            max_from,
            added,
            adder,
            |(i, v1, v2, ..)| (i, (v1 + v2)),
            exit,
        )
    }

    pub fn compute_subtract(
        &mut self,
        max_from: V::I,
        subtracted: &impl ReadableVec<V::I, V::T>,
        subtracter: &impl ReadableVec<V::I, V::T>,
        exit: &Exit,
    ) -> Result<()>
    where
        V::T: CheckedSub,
    {
        self.compute_transform2(
            max_from,
            subtracted,
            subtracter,
            |(i, v1, v2, ..)| {
                (
                    i,
                    v1.checked_sub(v2)
                        .expect("subtraction underflow in compute_subtract"),
                )
            },
            exit,
        )
    }

    pub fn compute_multiply<A, B>(
        &mut self,
        max_from: V::I,
        multiplied: &impl ReadableVec<V::I, A>,
        multiplier: &impl ReadableVec<V::I, B>,
        exit: &Exit,
    ) -> Result<()>
    where
        A: VecValue,
        B: VecValue,
        V::T: From<A> + Mul<B, Output = V::T>,
    {
        self.compute_transform2(
            max_from,
            multiplied,
            multiplier,
            |(i, v1, v2, ..)| (i, V::T::from(v1) * v2),
            exit,
        )
    }

    pub fn compute_divide<A, B>(
        &mut self,
        max_from: V::I,
        divided: &impl ReadableVec<V::I, A>,
        divider: &impl ReadableVec<V::I, B>,
        exit: &Exit,
    ) -> Result<()>
    where
        A: VecValue,
        B: VecValue,
        V::T: From<A> + Div<B, Output = V::T>,
    {
        self.compute_transform2(
            max_from,
            divided,
            divider,
            |(i, v1, v2, ..)| (i, V::T::from(v1) / v2),
            exit,
        )
    }

    pub fn compute_percentage<A, B>(
        &mut self,
        max_from: V::I,
        divided: &impl ReadableVec<V::I, A>,
        divider: &impl ReadableVec<V::I, B>,
        exit: &Exit,
    ) -> Result<()>
    where
        A: VecValue,
        B: VecValue,
        V::T: From<A>
            + From<B>
            + From<u8>
            + Mul<V::T, Output = V::T>
            + Div<V::T, Output = V::T>
            + Sub<V::T, Output = V::T>
            + Copy,
    {
        self.compute_percentage_(max_from, divided, divider, exit, false)
    }

    pub fn compute_percentage_difference<A, B>(
        &mut self,
        max_from: V::I,
        divided: &impl ReadableVec<V::I, A>,
        divider: &impl ReadableVec<V::I, B>,
        exit: &Exit,
    ) -> Result<()>
    where
        A: VecValue,
        B: VecValue,
        V::T: From<A>
            + From<B>
            + From<u8>
            + Mul<V::T, Output = V::T>
            + Div<V::T, Output = V::T>
            + Sub<V::T, Output = V::T>
            + Copy,
    {
        self.compute_percentage_(max_from, divided, divider, exit, true)
    }

    fn compute_percentage_<A, B>(
        &mut self,
        max_from: V::I,
        divided: &impl ReadableVec<V::I, A>,
        divider: &impl ReadableVec<V::I, B>,
        exit: &Exit,
        as_difference: bool,
    ) -> Result<()>
    where
        A: VecValue,
        B: VecValue,
        V::T: From<A>
            + From<B>
            + From<u8>
            + Mul<V::T, Output = V::T>
            + Div<V::T, Output = V::T>
            + Sub<V::T, Output = V::T>
            + Copy,
    {
        let multiplier = V::T::from(100u8);
        self.compute_transform2(
            max_from,
            divided,
            divider,
            move |(i, v1, v2, ..)| {
                let divided = V::T::from(v1);
                let divider = V::T::from(v2);
                let v = divided * multiplier;
                let mut v = v / divider;
                if as_difference {
                    v = v - multiplier;
                }
                (i, v)
            },
            exit,
        )
    }
}
