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

use super::constants::{MAX_REPRESENTABLE_BASE, MIN_REPRESENTABLE_BASE};

pub(super) type Sign = i8;
pub(super) type WindowDigit = u8;

#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct BigInt<Digit, const DIGIT_BITNESS: usize> {
    pub(super) sign: Sign,
    pub(super) digits: Vec<Digit>,
}

#[cfg(feature = "python_binding")]
impl<Digit, const DIGIT_BITNESS: usize> BigInt<Digit, DIGIT_BITNESS> {
    pub(crate) fn digits(&self) -> &[Digit] {
        &self.digits
    }
}

#[derive(Clone, Copy, Eq, PartialEq)]
pub enum CheckedDivAsFloatError {
    TooLarge,
    ZeroDivision,
}

impl CheckedDivAsFloatError {
    fn description(&self) -> &str {
        match self {
            CheckedDivAsFloatError::TooLarge => {
                "Division result too large to be expressed as floating point."
            }
            CheckedDivAsFloatError::ZeroDivision => {
                "Division by zero is undefined."
            }
        }
    }
}

impl Debug for CheckedDivAsFloatError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        formatter.write_str(self.description())
    }
}

impl Display for CheckedDivAsFloatError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        Display::fmt(&self.description(), formatter)
    }
}

#[derive(Clone, Copy, Eq, PartialEq)]
pub enum CheckedPowRemEuclidError {
    ZeroDivisor,
    NonInvertibleBase,
}

impl CheckedPowRemEuclidError {
    fn description(self) -> &'static str {
        match self {
            CheckedPowRemEuclidError::ZeroDivisor => {
                "Divisor should not be zero."
            }
            CheckedPowRemEuclidError::NonInvertibleBase => {
                "Base is not invertible for the given divisor."
            }
        }
    }
}

impl Debug for CheckedPowRemEuclidError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        formatter.write_str(self.description())
    }
}

impl Display for CheckedPowRemEuclidError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        Display::fmt(&self.description(), formatter)
    }
}

#[derive(Clone, Copy, Eq, PartialEq)]
pub enum ShlError {
    NegativeShift,
    OutOfMemory,
    TooLarge,
}

impl ShlError {
    fn description(self) -> &'static str {
        match self {
            ShlError::NegativeShift => "Shift by negative step is undefined.",
            ShlError::OutOfMemory => "Not enough memory for shift result.",
            ShlError::TooLarge => "Too large shift step.",
        }
    }
}

impl Debug for ShlError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        formatter.write_str(self.description())
    }
}

impl Display for ShlError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        Display::fmt(&self.description(), formatter)
    }
}

#[derive(Clone, Copy, Eq, PartialEq)]
pub enum ShrError {
    NegativeShift,
}

impl ShrError {
    fn description(self) -> &'static str {
        match self {
            ShrError::NegativeShift => "Shift by negative step is undefined.",
        }
    }
}

impl Debug for ShrError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        formatter.write_str(self.description())
    }
}

impl Display for ShrError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        Display::fmt(&self.description(), formatter)
    }
}

#[derive(Clone, Copy, Eq, PartialEq)]
pub enum TryFromFloatError {
    Infinity,
    NaN,
}

impl TryFromFloatError {
    fn description(self) -> &'static str {
        match self {
            TryFromFloatError::Infinity => {
                "Conversion of infinity is undefined."
            }
            TryFromFloatError::NaN => "Conversion of NaN is undefined.",
        }
    }
}

impl Debug for TryFromFloatError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        formatter.write_str(self.description())
    }
}

impl Display for TryFromFloatError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        Display::fmt(&self.description(), formatter)
    }
}

#[derive(Clone, Copy, Eq, PartialEq)]
pub enum TryFromStringError {
    BaseOutOfBounds(u32),
    InvalidDigit(char, u8),
    NoDigits,
}

impl TryFromStringError {
    fn description(self) -> String {
        match self {
            TryFromStringError::BaseOutOfBounds(base) => {
                format!(
                    "Base should be zero or in range from {MIN_REPRESENTABLE_BASE} \
                     to {MAX_REPRESENTABLE_BASE}, but found: {base}."
                )
            }
            TryFromStringError::InvalidDigit(character, base) => {
                format!("Invalid digit in base {base}: {character:?}.")
            }
            TryFromStringError::NoDigits => String::from("No digits found."),
        }
    }
}

impl Debug for TryFromStringError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        formatter.write_str(&self.description())
    }
}

impl Display for TryFromStringError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        Display::fmt(&self.description(), formatter)
    }
}

#[derive(Clone, Copy, Eq, PartialEq)]
pub enum TryIntoFloatError {
    TooLarge,
}

impl TryIntoFloatError {
    fn description(&self) -> &str {
        match self {
            TryIntoFloatError::TooLarge => {
                "Too large to convert to floating point."
            }
        }
    }
}

impl Debug for TryIntoFloatError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        formatter.write_str(self.description())
    }
}

impl Display for TryIntoFloatError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        Display::fmt(&self.description(), formatter)
    }
}

#[derive(Clone, Copy, Eq, PartialEq)]
pub enum TryIntoSignedIntegerError {
    TooLarge,
}

impl TryIntoSignedIntegerError {
    fn description(&self) -> &str {
        match self {
            TryIntoSignedIntegerError::TooLarge => {
                "Value too large to be expressed as given signed integer type."
            }
        }
    }
}

impl Debug for TryIntoSignedIntegerError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        formatter.write_str(self.description())
    }
}

impl Display for TryIntoSignedIntegerError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        Display::fmt(&self.description(), formatter)
    }
}

#[derive(Clone, Copy, Eq, PartialEq)]
pub enum TryIntoUnsignedIntegerError {
    TooLarge,
    Negative,
}

impl TryIntoUnsignedIntegerError {
    fn description(&self) -> &str {
        match self {
            TryIntoUnsignedIntegerError::Negative => {
                "Negative value cannot be expressed by unsigned integer type."
            }
            TryIntoUnsignedIntegerError::TooLarge => {
                "Value too large to be expressed by given unsigned integer type."
            }
        }
    }
}

impl Debug for TryIntoUnsignedIntegerError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        formatter.write_str(self.description())
    }
}

impl Display for TryIntoUnsignedIntegerError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        Display::fmt(&self.description(), formatter)
    }
}
