mod add;
mod div;
mod from;
mod mul;
mod ord;
mod sub;

use std::fmt::{Display, Formatter};

#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Sign {
    Positif,
    Negatif,
}

#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Number {
    pub digits: Vec<isize>,
    pub sign: Sign,
}

impl Number {
    pub fn get_digit(n: isize, pos: usize) -> isize {
        let modulo = 10isize.pow(pos as u32 + 1);
        let divisor = modulo / 10;
        (n % modulo) / divisor
    }

    pub const fn byte_to_digit(b: u8) -> isize {
        // wrapping_sub('0' as u32) same as - 48 but less magical
        (b as isize).wrapping_sub('0' as isize)
    }

    pub fn pow(self, n: u32) -> Self {
        let mut result = self.clone();
        if ((self.digits.len() * 8) as u32) < isize::BITS {
            let number = isize::try_from(self).unwrap();
            for _i in 1..n {
                result = result * number;
            }
            result
        } else {
            let number = self.clone();
            for _i in 1..n {
                result *= number.clone();
            }
            result
        }
    }

    pub fn fact(self) -> Self {
        let mut fact = Number::from(1);
        if ((self.digits.len() * 8) as u32) < isize::BITS {
            let max = isize::try_from(self).unwrap();
            for n in 1..=max {
                fact = fact * n;
            }
            fact
        } else {
            panic!("starting number too big")
        }
    }

    pub fn fib(n: u64) -> Self {
        let mut last_two = (Self::from(1), Self::from(1));
        let mut iteration = 1;
        while iteration < n {
            last_two = (last_two.1.clone(), last_two.0 + last_two.1);
            iteration += 1;
        }

        last_two.0
    }
}

impl Display for Number {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let number_string = self
            .digits
            .iter()
            .rev()
            .map(|&digit| digit.to_string())
            .collect::<Vec<String>>()
            .join("");
        match self.sign {
            Sign::Positif => write!(f, "{number_string}"),
            Sign::Negatif => write!(f, "-{number_string}"),
        }
    }
}

#[cfg(test)]
mod number_tests {
    use crate::number::Number;

    #[test]
    fn test_get_digit() {
        let num = 12345;
        let digit_1 = Number::get_digit(num, 0);
        let digit_2 = Number::get_digit(num, 1);
        let digit_3 = Number::get_digit(num, 2);
        let digit_4 = Number::get_digit(num, 3);
        let digit_5 = Number::get_digit(num, 4);
        assert_eq!(digit_1, 5);
        assert_eq!(digit_2, 4);
        assert_eq!(digit_3, 3);
        assert_eq!(digit_4, 2);
        assert_eq!(digit_5, 1);
    }
}