use core::{
    ffi::{
        c_char, c_double, c_int, c_long, c_longlong, c_uint, c_ulong, c_ulonglong, CStr, VaList,
    },
    fmt,
};

use alloc::string::String;

use crate::{error::EResult, io::Write, types::wchar_t};

use super::float::{fmt_float, fmt_float_any, fmt_float_scientific};

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum FmtSize {
    ShortShort,
    Short,
    Normal,
    Long,
    LongLong,
    Size,
    #[allow(unused)]
    LongFloat,
}

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum FmtSign {
    Default,
    Space,
    Always,
}

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum FmtSpec {
    Integer,
    Unsigned(FmtRadix),
    AnyFloat(bool),
    Float(bool),
    ScientificFloat(bool),
    String,
    Char,
    Pointer,
}

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum FmtRadix {
    Decimal,
    Octal,
    Hex(bool),
}

pub struct FmtOpts {
    pub size: FmtSize,
    pub alternate: bool,
    pub pad_char: u8,
    pub width: usize,
    pub sign: FmtSign,
    pub left_adjust: bool,
}

impl Default for FmtOpts {
    fn default() -> Self {
        Self {
            size: FmtSize::Normal,
            alternate: false,
            pad_char: b' ',
            width: 0,
            sign: FmtSign::Default,
            left_adjust: false,
        }
    }
}

impl FmtRadix {
    const CHARSET_LOWER: &'static [u8] = b"0123456789abcdef";
    const CHARSET_UPPER: &'static [u8] = b"0123456789ABCDEF";

    pub fn charset_and_divisor(self) -> (&'static [u8], u64) {
        match self {
            Self::Octal => (Self::CHARSET_LOWER, 8),
            Self::Decimal => (Self::CHARSET_LOWER, 10),
            Self::Hex(true) => (Self::CHARSET_UPPER, 16),
            Self::Hex(false) => (Self::CHARSET_LOWER, 16),
        }
    }
}

impl FmtSize {
    fn arg_int(&self, ap: &mut VaList) -> i64 {
        match self {
            Self::ShortShort => unsafe { ap.arg::<c_int>() as _ },
            // Self::ShortShort => unsafe { ap.arg::<c_char>() as _ },
            Self::Short => unsafe { ap.arg::<c_int>() as _ },
            // Self::Short => unsafe { ap.arg::<c_short>() as _ },
            Self::Normal => unsafe { ap.arg::<c_int>() as _ },
            Self::Long => unsafe { ap.arg::<c_long>() as _ },
            Self::LongLong => unsafe { ap.arg::<c_longlong>() as _ },
            Self::Size => unsafe { ap.arg::<isize>() as _ },
            Self::LongFloat => panic!("Incorrect L used with ints"),
        }
    }

    fn arg_uint(&self, ap: &mut VaList) -> u64 {
        match self {
            Self::ShortShort => unsafe { ap.arg::<c_uint>() as _ },
            // Self::ShortShort => unsafe { ap.arg::<c_uchar>() as _ },
            Self::Short => unsafe { ap.arg::<c_uint>() as _ },
            // Self::Short => unsafe { ap.arg::<c_ushort>() as _ },
            Self::Normal => unsafe { ap.arg::<c_uint>() as _ },
            Self::Long => unsafe { ap.arg::<c_ulong>() as _ },
            Self::LongLong => unsafe { ap.arg::<c_ulonglong>() as _ },
            Self::Size => unsafe { ap.arg::<usize>() as _ },
            Self::LongFloat => panic!("Incorrect L used with uints"),
        }
    }

    fn arg_float(&self, ap: &mut VaList) -> c_double {
        match self {
            Self::Normal | Self::Long => unsafe { ap.arg::<c_double>() },
            Self::LongFloat => unimplemented!("Long floats are not implemented yet"),
            _ => panic!("Incorrect size specifier used with floats"),
        }
    }

    fn arg_pointer(&self, ap: &mut VaList) -> usize {
        if *self != Self::Normal {
            panic!("Incorrect size specifier used with a pointer");
        }
        unsafe { ap.arg::<usize>() }
    }

    pub fn shorter(self) -> Self {
        match self {
            Self::Normal => Self::Short,
            Self::Short => Self::ShortShort,
            _ => self,
        }
    }

    pub fn longer(self) -> Self {
        match self {
            Self::Normal => Self::Long,
            Self::Long => Self::LongLong,
            _ => self,
        }
    }
}

impl FmtOpts {
    fn pad<W: Write + fmt::Write>(&self, output: &mut W, len: usize) -> EResult<usize> {
        let pad = self.width - core::cmp::min(self.width, len);
        for _ in 0..pad {
            output.write(&[self.pad_char])?;
        }
        EResult::Ok(pad)
    }

    pub fn left_pad<W: Write + fmt::Write>(&self, output: &mut W, len: usize) -> EResult<usize> {
        if !self.left_adjust {
            self.pad(output, len)
        } else {
            EResult::Ok(0)
        }
    }

    pub fn right_pad<W: Write + fmt::Write>(&self, output: &mut W, len: usize) -> EResult<usize> {
        if self.left_adjust {
            self.pad(output, len)
        } else {
            EResult::Ok(0)
        }
    }

    pub fn fmt<W: Write + fmt::Write>(
        &self,
        spec: FmtSpec,
        output: &mut W,
        ap: &mut VaList,
    ) -> EResult<usize> {
        let mut buffer = [0; 64];

        let len = match spec {
            FmtSpec::Integer => {
                let val = self.size.arg_int(ap);
                fmt_signed_int(val, &mut buffer)
            }
            FmtSpec::Unsigned(radix) => {
                let val = self.size.arg_uint(ap);
                fmt_unsigned_int(val, &mut buffer, radix)
            }
            FmtSpec::Pointer => {
                let val = self.size.arg_pointer(ap);
                if val == 0 {
                    buffer[..5].copy_from_slice(b"(nil)");
                    5
                } else {
                    fmt_unsigned_int(val as u64, &mut buffer, FmtRadix::Hex(false))
                }
            }
            // TODO string precision
            FmtSpec::String if self.size == FmtSize::Normal => {
                let val = unsafe { ap.arg::<*const c_char>() };
                if val.is_null() {
                    buffer[..5].copy_from_slice(b"(nil)");
                    5
                } else {
                    let val = unsafe { CStr::from_ptr(val) };
                    let bytes = val.to_bytes();
                    let lpad = self.left_pad(output, bytes.len())?;
                    let len = output.write(bytes)?;
                    let rpad = self.right_pad(output, bytes.len())?;
                    return EResult::Ok(lpad + len + rpad);
                }
            }
            FmtSpec::String if self.size == FmtSize::Long => {
                let mut val = unsafe { ap.arg::<*const wchar_t>() };
                if val.is_null() {
                    buffer[..5].copy_from_slice(b"(nil)");
                    5
                } else {
                    let mut string = String::new();
                    unsafe {
                        while *val != 0 {
                            let c = match char::from_u32(*val as _) {
                                Some(c) => c,
                                None => {
                                    // TODO EILSEQ
                                    todo!();
                                }
                            };
                            string.push(c);
                            val = val.add(1);
                        }
                    }

                    let bytes = string.as_bytes();
                    let lpad = self.left_pad(output, bytes.len())?;
                    let len = output.write(bytes)?;
                    let rpad = self.right_pad(output, bytes.len())?;
                    return EResult::Ok(lpad + len + rpad);
                }
            }
            FmtSpec::Char if self.size == FmtSize::Normal => {
                // let ch = unsafe { ap.arg::<c_char>() } as u8;
                let ch = unsafe { ap.arg::<c_int>() } as u8;
                buffer[0] = ch;
                1
            }
            FmtSpec::Char if self.size == FmtSize::Long => {
                todo!();
            }
            FmtSpec::Float(upper) => {
                let val = self.size.arg_float(ap);
                return fmt_float(val, output, upper, self);
            }
            FmtSpec::AnyFloat(upper) => {
                let val = self.size.arg_float(ap);
                return fmt_float_any(val, output, upper, self);
            }
            FmtSpec::ScientificFloat(upper) => {
                let val = self.size.arg_float(ap);
                return fmt_float_scientific(val, output, upper, self);
            }
            // Incorrect cases
            FmtSpec::Char => {
                panic!("Incorrect size specifier used with a C char");
            }
            FmtSpec::String => {
                panic!("Incorrect size specifier used with a C string");
            }
        };

        let lpad = self.left_pad(output, len)?;
        let count = output.write(&buffer[..len])?;
        let rpad = self.right_pad(output, len)?;

        EResult::Ok(lpad + count + rpad)
    }
}

fn fmt_unsigned_int(mut value: u64, output: &mut [u8], radix: FmtRadix) -> usize {
    if value == 0 {
        output[0] = b'0';
        return 1;
    }

    let (charset, divisor) = radix.charset_and_divisor();
    let mut pos = 0;

    while value != 0 {
        output[pos] = charset[(value % divisor) as usize];
        value /= divisor;
        pos += 1;
    }

    output[..pos].reverse();
    pos
}

fn fmt_signed_int(value: i64, output: &mut [u8]) -> usize {
    if value < 0 {
        output[0] = b'-';
        1 + fmt_unsigned_int(
            value.wrapping_neg() as u64,
            &mut output[1..],
            FmtRadix::Decimal,
        )
    } else {
        fmt_unsigned_int(value as u64, output, FmtRadix::Decimal)
    }
}
