use core::{ffi::c_double, fmt, num::FpCategory};

use alloc::{format, string::String};

use crate::{error::EResult, headers::errno::Errno, io::Write};

use super::format::FmtOpts;

fn abs(f: c_double) -> c_double {
    if f.is_sign_negative() {
        -f
    } else {
        f
    }
}

fn float_exp(mut val: c_double) -> (c_double, isize) {
    let mut exp: isize = 0;
    while abs(val) >= 10.0 {
        val /= 10.0;
        exp += 1;
    }
    while c_double::EPSILON < abs(val) && abs(val) < 1.0 {
        val *= 10.0;
        exp -= 1;
    }
    (val, exp)
}

fn fmt_float_exp<W: Write + fmt::Write>(
    val: c_double,
    exp: isize,
    exp_fmt: u8,
    output: &mut W,
    precision: usize,
    _opts: &FmtOpts,
) -> EResult<usize> {
    let mut exp2 = exp;
    let mut exp_len = 1;
    while exp2 >= 10 {
        exp2 /= 10;
        exp_len += 1;
    }

    let string = fmt_float_string(val, precision);

    let f_len = output.write(string.as_bytes())?;

    match write!(output, "{}{:+03}", exp_fmt as char, exp) {
        Ok(()) => (),
        // TODO proper error code
        Err(_) => return EResult::Err(Errno::EINVAL),
    };

    EResult::Ok(f_len + 2 + 2.max(exp_len))
}

fn fmt_float_string(val: c_double, precision: usize) -> String {
    let string = format!("{val:.precision$}");
    // TODO trim
    string
}

fn fmt_float_finite<W: Write + fmt::Write>(
    val: c_double,
    output: &mut W,
    precision: usize,
    opts: &FmtOpts,
) -> EResult<usize> {
    let s = fmt_float_string(val, precision);
    let lpad = opts.left_pad(output, s.len())?;
    let flen = output.write(s.as_bytes())?;
    let rpad = opts.right_pad(output, s.len())?;
    EResult::Ok(lpad + flen + rpad)
}

fn fmt_float_nonfinite<W: Write + fmt::Write>(
    val: c_double,
    output: &mut W,
    upper: bool,
    opts: &FmtOpts,
) -> EResult<usize> {
    let mut len = 0;
    if val.is_sign_negative() {
        len += output.write(b"-")?;
    }

    let nonfinite_str = match val.classify() {
        FpCategory::Infinite => match upper {
            false => b"inf",
            true => b"INF",
        },
        FpCategory::Nan => match upper {
            false => b"nan",
            true => b"NaN",
        },
        _ => unreachable!(),
    };

    let lpad = opts.left_pad(output, len + nonfinite_str.len())?;
    let flen = output.write(nonfinite_str)?;
    let rpad = opts.right_pad(output, len + nonfinite_str.len())?;

    EResult::Ok(len + lpad + flen + rpad)
}

pub fn fmt_float<W: Write + fmt::Write>(
    val: c_double,
    output: &mut W,
    upper: bool,
    opts: &FmtOpts,
) -> EResult<usize> {
    if val.is_finite() {
        fmt_float_finite(val, output, 6, opts)
    } else {
        fmt_float_nonfinite(val, output, upper, opts)
    }
}

pub fn fmt_float_scientific<W: Write + fmt::Write>(
    val: c_double,
    output: &mut W,
    upper: bool,
    opts: &FmtOpts,
) -> EResult<usize> {
    if val.is_finite() {
        let (val, exp) = float_exp(val);
        let exp_fmt = match upper {
            false => b'e',
            true => b'E',
        };
        let precision = 6;

        fmt_float_exp(val, exp, exp_fmt, output, precision, opts)
    } else {
        fmt_float_nonfinite(val, output, upper, opts)
    }
}

pub fn fmt_float_any<W: Write + fmt::Write>(
    val: c_double,
    output: &mut W,
    upper: bool,
    opts: &FmtOpts,
) -> EResult<usize> {
    if val.is_finite() {
        let (log, exp) = float_exp(val);
        let exp_fmt = match upper {
            false => b'e',
            true => b'E',
        };
        let precision: usize = 6;
        let use_exp_format = exp < -4 || exp >= precision as isize;

        if use_exp_format {
            let precision = precision.saturating_sub(1);
            fmt_float_exp(log, exp, exp_fmt, output, precision, opts)
        } else {
            let len = 1 + core::cmp::max(0, exp) as usize;
            let precision = precision.saturating_sub(len);
            fmt_float_finite(val, output, precision, opts)
        }
    } else {
        fmt_float_nonfinite(val, output, upper, opts)
    }
}
