use core::{
    ffi::{c_char, c_double, c_float, c_int, c_long, c_longlong, c_ulong, c_ulonglong},
    ptr::{null_mut, NonNull},
};

use crate::{headers::locale::locale_t, util::cstr_matches_insensitive};

// base64

#[no_mangle]
unsafe extern "C" fn a64l(_s: *const c_char) -> c_long {
    todo!()
}

#[no_mangle]
unsafe extern "C" fn l64a(_v: c_long) -> *mut c_char {
    todo!()
}

// floats

#[no_mangle]
unsafe extern "C" fn atof(s: *const c_char) -> c_double {
    strtod(s, null_mut())
}

#[no_mangle]
unsafe extern "C" fn strtod(s: *const c_char, endptr: *mut *mut c_char) -> c_double {
    str_to_float_ext(s, endptr) as _
}

#[no_mangle]
unsafe extern "C" fn strtof(s: *const c_char, endptr: *mut *mut c_char) -> c_float {
    str_to_float_ext(s, endptr) as _
}

#[no_mangle]
unsafe extern "C" fn strtold(_s: *const c_char, _endptr: *mut *mut c_char) -> c_double {
    unimplemented!()
}

// ints

#[no_mangle]
unsafe extern "C" fn atoi(s: *const c_char) -> c_int {
    str_to_int_ext(s, null_mut(), 10)
}

#[no_mangle]
unsafe extern "C" fn atol(s: *const c_char) -> c_long {
    str_to_int_ext(s, null_mut(), 10)
}

#[no_mangle]
unsafe extern "C" fn atoll(s: *const c_char) -> c_longlong {
    str_to_int_ext(s, null_mut(), 10)
}

#[no_mangle]
unsafe extern "C" fn strtol(s: *const c_char, endptr: *mut *mut c_char, base: c_int) -> c_long {
    str_to_int_ext(s, endptr, base)
}

#[no_mangle]
unsafe extern "C" fn strtoll(
    s: *const c_char,
    endptr: *mut *mut c_char,
    base: c_int,
) -> c_longlong {
    str_to_int_ext(s, endptr, base)
}

#[no_mangle]
unsafe extern "C" fn strtoul(s: *const c_char, endptr: *mut *mut c_char, base: c_int) -> c_ulong {
    str_to_uint_ext(s, endptr, base)
}

#[no_mangle]
unsafe extern "C" fn strtoull(
    s: *const c_char,
    endptr: *mut *mut c_char,
    base: c_int,
) -> c_ulonglong {
    str_to_uint_ext(s, endptr, base)
}

// locale

#[no_mangle]
unsafe extern "C" fn strtoll_l(
    _s: *const c_char,
    _endptr: *mut *mut c_char,
    _base: c_int,
    _locale: locale_t,
) -> c_longlong {
    todo!()
}

#[no_mangle]
unsafe extern "C" fn strtoull_l(
    _s: *const c_char,
    _endptr: *mut *mut c_char,
    _base: c_int,
    _locale: locale_t,
) -> c_ulonglong {
    todo!()
}

#[no_mangle]
unsafe extern "C" fn strtof_l(
    _s: *const c_char,
    _endptr: *mut *mut c_char,
    _locale: locale_t,
) -> c_float {
    todo!()
}

#[no_mangle]
unsafe extern "C" fn strtod_l(
    _s: *const c_char,
    _endptr: *mut *mut c_char,
    _locale: locale_t,
) -> c_double {
    todo!()
}

#[no_mangle]
unsafe extern "C" fn strtold_l(
    _s: *const c_char,
    _endptr: *mut *mut c_char,
    _locale: locale_t,
) -> c_double {
    unimplemented!()
}

#[derive(Clone, Copy, PartialEq)]
enum Radix {
    Oct,
    Dec,
    Hex,
}

trait StrToInt {
    type Unsigned: StrToUint;

    fn from_unsigned(value: Self::Unsigned, sign: bool) -> Self;
}

trait StrToUint {
    const ZERO: Self;
    fn append_digit(self, digit: u8, radix: Radix) -> Self;
}

impl Radix {
    pub fn from_c(c: c_int) -> Option<Self> {
        match c {
            8 => Some(Self::Oct),
            10 => Some(Self::Dec),
            16 => Some(Self::Hex),
            _ => None,
        }
    }

    pub const fn multiplier(&self) -> u8 {
        match self {
            Self::Oct => 8,
            Self::Dec => 10,
            Self::Hex => 16,
        }
    }
}

impl StrToInt for i32 {
    type Unsigned = u32;

    fn from_unsigned(value: Self::Unsigned, sign: bool) -> Self {
        match sign {
            true => value as Self,
            false => -(value as Self),
        }
    }
}

impl StrToInt for i64 {
    type Unsigned = u64;

    fn from_unsigned(value: Self::Unsigned, sign: bool) -> Self {
        match sign {
            true => value as Self,
            false => -(value as Self),
        }
    }
}

impl StrToUint for u32 {
    const ZERO: Self = 0;

    fn append_digit(self, digit: u8, radix: Radix) -> Self {
        let mul = radix.multiplier();
        self * (mul as Self) + (digit as Self)
    }
}

impl StrToUint for u64 {
    const ZERO: Self = 0;

    fn append_digit(self, digit: u8, radix: Radix) -> Self {
        let mul = radix.multiplier();
        self * (mul as Self) + (digit as Self)
    }
}

unsafe fn str_to_int_ext<I: StrToInt>(
    src: *const c_char,
    endptr: *mut *mut c_char,
    base: c_int,
) -> I {
    let src = NonNull::new(src.cast_mut()).expect("str_to_int: NULL string");
    let radix = Radix::from_c(base);
    let (value, end) = str_to_int(src.cast(), radix);
    if !endptr.is_null() {
        endptr.write(end.as_ptr().cast());
    }
    value
}

unsafe fn str_to_uint_ext<U: StrToUint>(
    src: *const c_char,
    endptr: *mut *mut c_char,
    base: c_int,
) -> U {
    let src = NonNull::new(src.cast_mut()).expect("str_to_uint: NULL string");
    let radix = Radix::from_c(base);
    let (value, end) = str_to_uint(src.cast(), radix, true);
    if !endptr.is_null() {
        endptr.write(end.as_ptr().cast());
    }
    value
}

unsafe fn str_to_float_ext(src: *const c_char, endptr: *mut *mut c_char) -> f64 {
    let src = NonNull::new(src.cast_mut()).expect("str_to_float: NULL string");
    let (value, end) = str_to_float(src.cast());
    if !endptr.is_null() {
        endptr.write(end.as_ptr().cast());
    }
    value
}

unsafe fn str_to_int<I: StrToInt>(mut src: NonNull<u8>, radix: Option<Radix>) -> (I, NonNull<u8>) {
    // Skip whitespace
    while src.read().is_ascii_whitespace() {
        src = src.add(1);
    }

    // Get sign
    let sign = match src.read() {
        b'+' => {
            src = src.add(1);
            true
        }
        b'-' => {
            src = src.add(1);
            false
        }
        _ => true,
    };

    let (unsigned, endptr) = str_to_uint::<I::Unsigned>(src, radix, false);

    (I::from_unsigned(unsigned, sign), endptr)
}

unsafe fn str_to_uint<U: StrToUint>(
    mut src: NonNull<u8>,
    radix: Option<Radix>,
    skip_whitespace: bool,
) -> (U, NonNull<u8>) {
    if skip_whitespace {
        // Skip whitespace
        while src.read().is_ascii_whitespace() {
            src = src.add(1);
        }
    }

    // Guess radix if None
    let ch = src.read();
    let radix = match (ch, radix) {
        (b'0', None) => {
            let ch = src.add(1).read();
            if ch == b'x' || ch == b'X' {
                src = src.add(2);
                Radix::Hex
            } else {
                Radix::Oct
            }
        }
        (_, None) => {
            // Not 0, 0x or 0X
            Radix::Dec
        }
        (b'0', Some(Radix::Hex)) => {
            let ch = src.add(1).read();
            if ch == b'x' || ch == b'X' {
                src = src.add(2);
            }
            Radix::Hex
        }
        (_, Some(radix)) => radix,
    };

    let mut value = U::ZERO;

    loop {
        let ch = src.read();
        if ch == 0 {
            break;
        }

        let digit = match (ch, radix) {
            (b'0'..=b'9', Radix::Dec | Radix::Hex) => ch - b'0',
            (b'0'..=b'7', Radix::Oct) => ch - b'0',
            (b'a'..=b'f', Radix::Hex) => ch - b'a' + 10,
            (b'A'..=b'F', Radix::Hex) => ch - b'A' + 10,
            (_, _) => return (value, src),
        };

        value = value.append_digit(digit, radix);
        src = src.add(1);
    }

    (value, src)
}

unsafe fn str_to_float(mut src: NonNull<u8>) -> (f64, NonNull<u8>) {
    // Skip leading whitespace
    while src.read().is_ascii_whitespace() {
        src = src.add(1);
    }

    // Get sign
    let sign = match src.read() {
        b'+' => {
            src = src.add(1);
            true
        }
        b'-' => {
            src = src.add(1);
            false
        }
        _ => true,
    };

    // Either:
    // * decimal float
    // * hex float
    // * INF/INFINITY, disregarding case
    // * NAN, disregarding case

    if is_nan(src) {
        return (f64::NAN, src.add(3));
    }
    if let Some(len) = is_inf(src) {
        return match sign {
            false => (f64::NEG_INFINITY, src.add(len)),
            true => (f64::INFINITY, src.add(len)),
        };
    }

    match is_hex(src) {
        true => unimplemented!(),
        false => str_to_float_dec(src, sign),
    }
}

unsafe fn str_to_float_dec(mut src: NonNull<u8>, sign: bool) -> (f64, NonNull<u8>) {
    // 123.456e123

    let mut int_part = 0u64;
    let mut frac_part = 0u64;
    let mut frac_digits = 0;

    // Read integer part
    loop {
        let ch = src.read();
        let digit = match ch {
            b'0'..=b'9' => ch - b'0',
            b'.' | b'E' | b'e' => break,
            _ => return (assemble_float(sign, int_part, 0, 0), src),
        };

        int_part = int_part * 10 + digit as u64;
        src = src.add(1);
    }

    // maybe .
    let ch = src.read();
    if ch == b'.' {
        src = src.add(1);

        // Parse fractional part
        loop {
            let ch = src.read();
            let digit = match ch {
                b'0'..=b'9' => ch - b'0',
                b'e' | b'E' => break,
                _ => return (assemble_float(sign, int_part, frac_part, frac_digits), src),
            };

            frac_part = frac_part * 10 + digit as u64;
            frac_digits += 1;
            src = src.add(1);
        }
    }

    // maybe exponent
    let ch = src.read();
    if ch == b'e' || ch == b'E' {
        todo!()
    }

    todo!()
}

unsafe fn is_nan(s: NonNull<u8>) -> bool {
    cstr_matches_insensitive(s, b"nan")
}

unsafe fn is_inf(s: NonNull<u8>) -> Option<usize> {
    const INFINITY: &[u8] = b"infinity";
    const INF: &[u8] = b"inf";

    if cstr_matches_insensitive(s, INFINITY) {
        Some(INFINITY.len())
    } else if cstr_matches_insensitive(s, INF) {
        Some(INF.len())
    } else {
        None
    }
}

unsafe fn is_hex(s: NonNull<u8>) -> bool {
    cstr_matches_insensitive(s, b"0x")
}

fn powi(f: f64, i: i32) -> f64 {
    let mut v = 1.0;
    for _ in 0..i {
        v *= f;
    }
    v
}

fn assemble_float(sign: bool, i: u64, f: u64, n: i32) -> f64 {
    let i = i as f64;
    let f = f as f64 / powi(10.0, n);
    let v = i + f;
    match sign {
        false => -v,
        true => v,
    }
}
