//! Integers used for wide operations, larger than `u128`.

#![allow(unused)]

use core::{fmt, ops};

use crate::int::{DInt, HInt, Int, MinInt};

const WORD_LO_MASK: u64 = 0x00000000ffffffff;
const WORD_HI_MASK: u64 = 0xffffffff00000000;
const WORD_FULL_MASK: u64 = 0xffffffffffffffff;
const U128_LO_MASK: u128 = u64::MAX as u128;
const U128_HI_MASK: u128 = (u64::MAX as u128) << 64;

/// A 256-bit unsigned integer represented as 4 64-bit limbs.
///
/// Each limb is a native-endian number, but the array is little-limb-endian.
#[allow(non_camel_case_types)]
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct u256(pub [u64; 4]);

impl u256 {
    pub const MAX: Self = Self([u64::MAX, u64::MAX, u64::MAX, u64::MAX]);

    /// Reinterpret as a signed integer
    pub fn signed(self) -> i256 {
        i256(self.0)
    }
}

/// A 256-bit signed integer represented as 4 64-bit limbs.
///
/// Each limb is a native-endian number, but the array is little-limb-endian.
#[allow(non_camel_case_types)]
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct i256(pub [u64; 4]);

impl i256 {
    /// Reinterpret as an unsigned integer
    pub fn unsigned(self) -> u256 {
        u256(self.0)
    }
}

impl MinInt for u256 {
    type OtherSign = i256;

    type Unsigned = u256;

    const SIGNED: bool = false;
    const BITS: u32 = 256;
    const ZERO: Self = Self([0u64; 4]);
    const ONE: Self = Self([1, 0, 0, 0]);
    const MIN: Self = Self([0u64; 4]);
    const MAX: Self = Self([u64::MAX; 4]);
}

impl MinInt for i256 {
    type OtherSign = u256;

    type Unsigned = u256;

    const SIGNED: bool = false;
    const BITS: u32 = 256;
    const ZERO: Self = Self([0u64; 4]);
    const ONE: Self = Self([1, 0, 0, 0]);
    const MIN: Self = Self([0, 0, 0, 1 << 63]);
    const MAX: Self = Self([u64::MAX, u64::MAX, u64::MAX, u64::MAX >> 1]);
}

macro_rules! impl_common {
    ($ty:ty) => {
        impl ops::BitOr for $ty {
            type Output = Self;

            fn bitor(mut self, rhs: Self) -> Self::Output {
                self.0[0] |= rhs.0[0];
                self.0[1] |= rhs.0[1];
                self.0[2] |= rhs.0[2];
                self.0[3] |= rhs.0[3];
                self
            }
        }

        impl ops::Not for $ty {
            type Output = Self;

            fn not(self) -> Self::Output {
                Self([!self.0[0], !self.0[1], !self.0[2], !self.0[3]])
            }
        }

        impl ops::Shl<u32> for $ty {
            type Output = Self;

            fn shl(self, rhs: u32) -> Self::Output {
                unimplemented!("only used to meet trait bounds")
            }
        }
    };
}

impl_common!(i256);
impl_common!(u256);

impl ops::Shr<u32> for u256 {
    type Output = Self;

    fn shr(self, rhs: u32) -> Self::Output {
        assert!(rhs < Self::BITS, "attempted to shift right with overflow");

        if rhs == 0 {
            return self;
        }

        let mut ret = self;
        let byte_shift = rhs / 64;
        let bit_shift = rhs % 64;

        for idx in 0..4 {
            let base_idx = idx + byte_shift as usize;

            let Some(base) = ret.0.get(base_idx) else {
                ret.0[idx] = 0;
                continue;
            };

            let mut new_val = base >> bit_shift;

            if let Some(new) = ret.0.get(base_idx + 1) {
                new_val |= new.overflowing_shl(64 - bit_shift).0;
            }

            ret.0[idx] = new_val;
        }

        ret
    }
}

macro_rules! word {
    (1, $val:expr) => {
        (($val >> (32 * 3)) & Self::from(WORD_LO_MASK)) as u64
    };
    (2, $val:expr) => {
        (($val >> (32 * 2)) & Self::from(WORD_LO_MASK)) as u64
    };
    (3, $val:expr) => {
        (($val >> (32 * 1)) & Self::from(WORD_LO_MASK)) as u64
    };
    (4, $val:expr) => {
        (($val >> (32 * 0)) & Self::from(WORD_LO_MASK)) as u64
    };
}

impl HInt for u128 {
    type D = u256;

    fn widen(self) -> Self::D {
        let w0 = self & u128::from(u64::MAX);
        let w1 = (self >> u64::BITS) & u128::from(u64::MAX);
        u256([w0 as u64, w1 as u64, 0, 0])
    }

    fn zero_widen(self) -> Self::D {
        self.widen()
    }

    fn zero_widen_mul(self, rhs: Self) -> Self::D {
        let product11: u64 = word!(1, self) * word!(1, rhs);
        let product12: u64 = word!(1, self) * word!(2, rhs);
        let product13: u64 = word!(1, self) * word!(3, rhs);
        let product14: u64 = word!(1, self) * word!(4, rhs);
        let product21: u64 = word!(2, self) * word!(1, rhs);
        let product22: u64 = word!(2, self) * word!(2, rhs);
        let product23: u64 = word!(2, self) * word!(3, rhs);
        let product24: u64 = word!(2, self) * word!(4, rhs);
        let product31: u64 = word!(3, self) * word!(1, rhs);
        let product32: u64 = word!(3, self) * word!(2, rhs);
        let product33: u64 = word!(3, self) * word!(3, rhs);
        let product34: u64 = word!(3, self) * word!(4, rhs);
        let product41: u64 = word!(4, self) * word!(1, rhs);
        let product42: u64 = word!(4, self) * word!(2, rhs);
        let product43: u64 = word!(4, self) * word!(3, rhs);
        let product44: u64 = word!(4, self) * word!(4, rhs);

        let sum0: u128 = u128::from(product44);
        let sum1: u128 = u128::from(product34) + u128::from(product43);
        let sum2: u128 = u128::from(product24) + u128::from(product33) + u128::from(product42);
        let sum3: u128 = u128::from(product14)
            + u128::from(product23)
            + u128::from(product32)
            + u128::from(product41);
        let sum4: u128 = u128::from(product13) + u128::from(product22) + u128::from(product31);
        let sum5: u128 = u128::from(product12) + u128::from(product21);
        let sum6: u128 = u128::from(product11);

        let r0: u128 =
            (sum0 & u128::from(WORD_FULL_MASK)) + ((sum1 & u128::from(WORD_LO_MASK)) << 32);
        let r1: u128 = (sum0 >> 64)
            + ((sum1 >> 32) & u128::from(WORD_FULL_MASK))
            + (sum2 & u128::from(WORD_FULL_MASK))
            + ((sum3 << 32) & u128::from(WORD_HI_MASK));

        let (lo, carry) = r0.overflowing_add(r1 << 64);
        let hi = (r1 >> 64)
            + (sum1 >> 96)
            + (sum2 >> 64)
            + (sum3 >> 32)
            + sum4
            + (sum5 << 32)
            + (sum6 << 64)
            + u128::from(carry);

        u256([
            (lo & U128_LO_MASK) as u64,
            ((lo >> 64) & U128_LO_MASK) as u64,
            (hi & U128_LO_MASK) as u64,
            ((hi >> 64) & U128_LO_MASK) as u64,
        ])
    }

    fn widen_mul(self, rhs: Self) -> Self::D {
        self.zero_widen_mul(rhs)
    }

    fn widen_hi(self) -> Self::D {
        self.widen() << <Self as MinInt>::BITS
    }
}

impl HInt for i128 {
    type D = i256;

    fn widen(self) -> Self::D {
        let mut ret = self.unsigned().zero_widen().signed();
        if self.is_negative() {
            ret.0[2] = u64::MAX;
            ret.0[3] = u64::MAX;
        }
        ret
    }

    fn zero_widen(self) -> Self::D {
        self.unsigned().zero_widen().signed()
    }

    fn zero_widen_mul(self, rhs: Self) -> Self::D {
        self.unsigned().zero_widen_mul(rhs.unsigned()).signed()
    }

    fn widen_mul(self, rhs: Self) -> Self::D {
        unimplemented!("signed i128 widening multiply is not used")
    }

    fn widen_hi(self) -> Self::D {
        self.widen() << <Self as MinInt>::BITS
    }
}

impl DInt for u256 {
    type H = u128;

    fn lo(self) -> Self::H {
        let mut tmp = [0u8; 16];
        tmp[..8].copy_from_slice(&self.0[0].to_le_bytes());
        tmp[8..].copy_from_slice(&self.0[1].to_le_bytes());
        u128::from_le_bytes(tmp)
    }

    fn hi(self) -> Self::H {
        let mut tmp = [0u8; 16];
        tmp[..8].copy_from_slice(&self.0[2].to_le_bytes());
        tmp[8..].copy_from_slice(&self.0[3].to_le_bytes());
        u128::from_le_bytes(tmp)
    }
}

impl DInt for i256 {
    type H = i128;

    fn lo(self) -> Self::H {
        let mut tmp = [0u8; 16];
        tmp[..8].copy_from_slice(&self.0[0].to_le_bytes());
        tmp[8..].copy_from_slice(&self.0[1].to_le_bytes());
        i128::from_le_bytes(tmp)
    }

    fn hi(self) -> Self::H {
        let mut tmp = [0u8; 16];
        tmp[..8].copy_from_slice(&self.0[2].to_le_bytes());
        tmp[8..].copy_from_slice(&self.0[3].to_le_bytes());
        i128::from_le_bytes(tmp)
    }
}
