use std::fmt::Debug;
use std::fmt::Display;
use std::fmt::Formatter;
use std::ops::Div;
use std::ops::Mul;
use std::ops::{Add, AddAssign, Neg, Sub, SubAssign};
use serde::{Deserialize, Serialize};

use num_bigint::BigUint;

#[derive(Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct U256([u64; 4]);

impl U256 {
    pub const fn new(hh: u64, hl: u64, lh: u64, ll: u64) -> U256 {
        U256([hh, hl, lh, ll])
    }
    pub const MAX: U256 = U256::new(u64::MAX, u64::MAX, u64::MAX, u64::MAX);
    pub fn from_bigint_truncating(input: BigUint) -> U256 {
        let digits = input.to_u64_digits();
        U256([
            u64::from(*digits.get(3).unwrap_or(&0)),
            u64::from(*digits.get(2).unwrap_or(&0)),
            u64::from(*digits.get(1).unwrap_or(&0)),
            u64::from(*digits.first().unwrap_or(&0)),
        ])
    }

    pub fn to_decimal(self) -> String {
        let value = BigUint::from(self.0[0]) << 192 |  BigUint::from(self.0[1]) << 128 | BigUint::from(self.0[2]) << 64 | BigUint::from(self.0[3]);
        format!("{value}")
    }

    pub fn to_decimal_fraction(self) -> String {
        let value: BigUint = self.into();
        let formatted = format!("{value}");
        match formatted.len() {
            18.. => {
                format!(
                    "{}.{}",
                    &formatted[..formatted.len() - 18],
                    &formatted[formatted.len() - 18..formatted.len() - 16]
                )
            }
            17 => {
                format!(".0{}", &formatted[0..1],)
            }
            _ => "0+eps".to_string(),
        }
    }

    pub fn to_bytes(&self) -> Vec<u8> {
        let mut result = Vec::new();
        for i in 0..=3 {
            for j in (0..16).rev() {
                let b = ((self.0[i] >> (j * 8)) & 0xff) as u8;
                if b != 0 || !result.is_empty() {
                    result.push(b);
                }
            }
        }
        result
    }
}

impl From<u64> for U256 {
    fn from(item: u64) -> Self {
        U256([0, 0, 0, item])
    }
}

// TODO str is using unicode stuff - maybe we should use Vec<u8> for efficiency reasons?
impl From<&str> for U256 { // TODO: switch to u64 implementation
    fn from(item: &str) -> Self {
        if item.starts_with("0x") {
            let len = item.len();
            assert!(len <= 2 + 16 + 16 + 16 + 16, "{}", len);

            
            let ll_start = if len >= 2 + 16 { len - 16 } else { 2 };
            let ll_hex = &item[ll_start..];
            // disallow + and - prefixes
            assert!(
                ll_hex.as_bytes().first() != Some(&54) && ll_hex.as_bytes().first() != Some(&43)
            );
            let ll = if ll_hex.is_empty() {
                0
            } else {
                u64::from_str_radix(ll_hex, 16).unwrap()
            };

            let lh_start = if len >= 2 + 16 + 16 { len - 32 } else { 2 };
            let lh_hex = &item[lh_start..ll_start];
            // disallow + and - prefixes
            assert!(
                lh_hex.as_bytes().first() != Some(&54) && lh_hex.as_bytes().first() != Some(&43)
            );
            let lh = if lh_hex.is_empty() {
                0
            } else {
                u64::from_str_radix(lh_hex, 16).unwrap()
            };

            let hl_start = if len >= 2 + 16 + 16 + 16 { len - 48 } else { 2 };
            let hl_hex = &item[hl_start..lh_start];
            // disallow + and - prefixes
            assert!(
                hl_hex.as_bytes().first() != Some(&54) && hl_hex.as_bytes().first() != Some(&43)
            );
            let hl = if hl_hex.is_empty() {
                0
            } else {
                u64::from_str_radix(hl_hex, 16).unwrap()
            };
            
            let hh_start = if len >= 2 + 16 + 16 + 16 + 16 { len - 64 } else { 2 };
            let hh_hex = &item[hh_start..hl_start];
            // disallow + and - prefixes
            assert!(
                hh_hex.as_bytes().first() != Some(&45)
                    && hh_hex.as_bytes().first() != Some(&43)
            );
            let hh = if hh_hex.is_empty() {
                0
            } else {
                u64::from_str_radix(hh_hex, 16).unwrap()
            };
            U256([hh, hl, lh, ll])
        } else {
            let digits = item.parse::<num_bigint::BigUint>().unwrap().to_u64_digits();
            assert!(digits.len() <= 4);
            U256([
                u64::from(*digits.get(3).unwrap_or(&0)),
                u64::from(*digits.get(2).unwrap_or(&0)),
                u64::from(*digits.get(1).unwrap_or(&0)),
                u64::from(*digits.first().unwrap_or(&0)),
            ])
        }
    }
}

impl From<U256> for BigUint {
    fn from(value: U256) -> Self {
        BigUint::from(value.0[0]) << 192 | BigUint::from(value.0[1]) << 128 |  BigUint::from(value.0[2]) << 64  |  BigUint::from(value.0[3]) 
    }
}

impl Add for U256 { // TODO: switch to u64
    type Output = Self;
    fn add(self, rhs: Self) -> Self {
        let (ll, carry0) = self.0[3].overflowing_add(rhs.0[3]);

        let (mut lh, mut carry1) = self.0[2].overflowing_add(rhs.0[2]);
        (lh, carry1) = lh.overflowing_add(carry0.into());
        
        let (mut hl, mut carry2) = self.0[1].overflowing_add(rhs.0[1]);
        (hl, carry2) = hl.overflowing_add(carry1.into());

        let mut hh = self.0[0].wrapping_add(rhs.0[0]);
        hh = hh.wrapping_add(carry2.into());

        Self([hh, hl, lh, ll])
    }
}

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

impl Neg for U256 {
    type Output = Self;
    fn neg(self) -> Self {
        let result = U256([!self.0[0], !self.0[1], !self.0[2], !self.0[3]]);
        result + U256::from(1)
    }
}

impl Sub for U256 {
    type Output = Self;
    fn sub(self, rhs: Self) -> Self {
        self + (-rhs)
    }
}

impl Mul for U256 {
    type Output = Self;

    fn mul(self, rhs: Self) -> Self::Output {
        let self_big: BigUint = self.into();
        let rhs_big: BigUint = rhs.into();
        U256::from_bigint_truncating(self_big * rhs_big)
    }
}

impl Div for U256 {
    type Output = Self;

    fn div(self, rhs: Self) -> Self::Output {
        let self_big: BigUint = self.into();
        let rhs_big: BigUint = rhs.into();
        U256::from_bigint_truncating(self_big / rhs_big)
    }
}

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

impl Display for U256 {
    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
        if self.0[0] == 0 && self.0[1] == 0 {
            write!(f, "{:#x}{:016x}", self.0[2], self.0[3])
        } else {
            write!(f, "{:#x}{:016x}{:016x}{:016x}", self.0[0], self.0[1], self.0[2], self.0[3])
        }
    }
}

impl Debug for U256 {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        Display::fmt(self, f)
    }
}

#[cfg(test)]
mod test {
    use super::U256;
    #[test]
    fn to_string() {
        assert_eq!(format!("{}", U256::from(0)), "0x0");
        assert_eq!(
            format!("{}", U256::from(u::MAX)),
            "0xffffffffffffffffffffffffffffffff"
        );
    }

    #[test]
    fn add() {
        assert_eq!(
            format!("{}", U256::from(u128::MAX) + U256::from(u128::MAX)),
            "0x1fffffffffffffffffffffffffffffffe"
        );
        let mut x = U256::from(u128::MAX);
        x += U256::from(1);
        assert_eq!(format!("{x}"), "0x100000000000000000000000000000000");
    }

    #[test]
    fn compare() {
        assert!(U256::from(0) < U256::from(1));
        assert!(U256::from("0x100000000000000000000000000000000") > U256::from(1));
    }

    #[test]
    fn from_hex() {
        assert_eq!(U256::from("0x"), U256::from(0));
        assert_eq!(U256::from("0x1"), U256::from(1));
        assert_eq!(U256::from("0x01"), U256::from(1));
        assert_eq!(
            U256::from("0x1fffffffffffffffffffffffffffffffe"),
            U256::from(u128::MAX) + U256::from(u128::MAX)
        );
        assert_eq!(
            U256::from("0x001fffffffffffffffffffffffffffffffe"),
            U256::from(u128::MAX) + U256::from(u128::MAX)
        );
        assert_eq!(
            U256::from("0x100000000000000000000000000000000"),
            U256::from(u128::MAX) + U256::from(1)
        );
    }

    #[test]
    fn from_decimal() {
        assert_eq!(U256::from("0"), U256::from(0));
        assert_eq!(U256::from("10"), U256::from(10));
        assert_eq!(
            U256::from("680564733841876926926749214863536422910"),
            U256::from(u128::MAX) + U256::from(u128::MAX)
        );
        assert_eq!(
            U256::from("000680564733841876926926749214863536422910"),
            U256::from(u128::MAX) + U256::from(u128::MAX)
        );
        assert_eq!(
            U256::from("340282366920938463463374607431768211456"),
            U256::from(u128::MAX) + U256::from(1)
        );
    }

    #[test]
    fn to_decimal() {
        assert_eq!(U256::from("0").to_decimal(), "0");
        assert_eq!(
            U256::from("680564733841876926926749214863536422910").to_decimal(),
            "680564733841876926926749214863536422910"
        );
        assert_eq!(
            U256::from("000680564733841876926926749214863536422910").to_decimal(),
            "680564733841876926926749214863536422910"
        );
        assert_eq!(
            U256::from("340282366920938463463374607431768211456").to_decimal(),
            "340282366920938463463374607431768211456"
        );
    }

    #[test]
    fn to_mul_div() {
        let two = U256::from("2");
        let three = U256::from(3);
        let large = U256::from("0x100000000000000000000000000000000");
        assert_eq!(two * three, U256::from(6));
        assert_eq!(three / two, U256::from(1));
        assert_eq!((large * two) / two, large);
        assert_eq!(
            large / three,
            U256::from("0x55555555555555555555555555555555")
        );
        assert_eq!(large * large, U256::from("0"));
        assert_eq!(
            (large / two) * large,
            U256::from("0x8000000000000000000000000000000000000000000000000000000000000000")
        );
    }

    #[test]
    fn to_bytes() {
        let zero = U256::from("0");
        assert_eq!(zero.to_bytes(), Vec::<u8>::new());
        assert_eq!(U256::from("2").to_bytes(), vec![2]);
        assert_eq!(
            U256::from("0x100000000000000000000000000000000").to_bytes(),
            vec![1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        );
        assert_eq!(
            U256::from("0xff00000000000000000000000000000001").to_bytes(),
            vec![255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
        );
        assert_eq!(
            U256::from("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
                .to_bytes(),
            vec![255; 32]
        );
    }
}
