use core::{
    ffi::{c_char, c_int, VaList},
    ptr::NonNull,
};

use format::{FmtOpts, FmtRadix, FmtSign, FmtSize, FmtSpec};
use writer::{AllocatedStringWriter, FileWriter, FmtWriter, StringWriter};

use crate::{
    error::{CIntCountResult, EResult, TryFromExt},
    io::{
        managed::{stdout, FILE},
        raw::RawFile,
    },
    util::{PointerExt, PointerStrExt},
};

mod float;
mod format;
mod writer;

fn printf_inner<W: FmtWriter>(output: &mut W, format: &[u8], mut ap: VaList) -> EResult<usize> {
    let mut fmt = format.iter();
    let mut count = 0;

    while let Some(&c) = fmt.next() {
        if c != b'%' {
            count += output.write(&[c])?;
            continue;
        }

        let mut opts = FmtOpts::default();
        let mut cur = fmt.next();

        // Parse flag characters
        while let Some(&c) = cur {
            match c {
                b'#' => opts.alternate = true,
                b'0' => opts.pad_char = b'0',
                b'-' => opts.left_adjust = true,
                b' ' => opts.sign = FmtSign::Space,
                b'+' => opts.sign = FmtSign::Always,
                b'\'' => unimplemented!("The ' flag is not implemented"),
                _ => break,
            }
            cur = fmt.next();
        }

        // TODO Field width
        while let Some(&c) = cur {
            if c.is_ascii_digit() {
                opts.width *= 10;
                opts.width += (c - b'0') as usize;
                cur = fmt.next();
            } else {
                break;
            }
        }
        // TODO Precision

        // Length modifier
        while let Some(&c) = cur {
            match c {
                b'h' => opts.size = opts.size.shorter(),
                b'l' => opts.size = opts.size.longer(),
                b'q' => opts.size = FmtSize::LongLong,
                b'L' => todo!(),
                b'j' => todo!(),
                b'z' | b'Z' => opts.size = FmtSize::Size,
                b't' => opts.size = FmtSize::Size,
                _ => break,
            }
            cur = fmt.next();
        }

        // Conversion specifier
        let mut spec = None;
        if let Some(&c) = cur {
            match c {
                b'd' | b'i' => spec = Some(FmtSpec::Integer),
                b'o' => spec = Some(FmtSpec::Unsigned(FmtRadix::Octal)),
                b'u' => spec = Some(FmtSpec::Unsigned(FmtRadix::Decimal)),
                b'x' | b'X' => {
                    spec = Some(FmtSpec::Unsigned(FmtRadix::Hex(c.is_ascii_uppercase())))
                }
                b'e' | b'E' => spec = Some(FmtSpec::ScientificFloat(c.is_ascii_uppercase())),
                b'f' | b'F' => spec = Some(FmtSpec::Float(c.is_ascii_uppercase())),
                b'g' | b'G' => spec = Some(FmtSpec::AnyFloat(c.is_ascii_uppercase())),
                b'a' | b'A' => unimplemented!("%a/%A are not implemented"),
                b'c' => spec = Some(FmtSpec::Char),
                b's' => spec = Some(FmtSpec::String),
                b'C' => {
                    opts.size = FmtSize::Long;
                    spec = Some(FmtSpec::Char);
                }
                b'S' => {
                    opts.size = FmtSize::Long;
                    spec = Some(FmtSpec::String);
                }
                b'p' => spec = Some(FmtSpec::Pointer),
                b'n' => unsafe {
                    let arg = ap.arg::<*mut c_int>();
                    *arg = count as c_int;
                    continue;
                },
                b'm' => todo!(),
                b'%' => {
                    count += output.write(b"%")?;
                    continue;
                }
                _ => (),
            }
        }

        if let Some(spec) = spec {
            count += opts.fmt(spec, output, &mut ap)?;
        }
    }

    EResult::Ok(count)
}

#[no_mangle]
unsafe extern "C" fn asprintf(
    dst: *mut *mut c_char,
    fmt: *const c_char,
    mut args: ...
) -> CIntCountResult {
    vasprintf(dst, fmt, args.as_va_list())
}

#[no_mangle]
unsafe extern "C" fn vasprintf(
    dst: *mut *mut c_char,
    fmt: *const c_char,
    args: VaList,
) -> CIntCountResult {
    let dst = dst.ensure_mut();
    let fmt = fmt.ensure_cstr();
    if !(*dst).is_null() {
        // Not sure if the previous pointer needs to be free()d
        todo!()
    }
    let mut writer = AllocatedStringWriter::new(usize::MAX);
    let count = printf_inner(&mut writer, fmt.to_bytes(), args)?;
    let ptr = writer.finish()?;
    *dst = ptr.as_ptr();
    CIntCountResult::success(count.try_into().unwrap())
}

#[no_mangle]
unsafe extern "C" fn dprintf(fd: c_int, fmt: *const c_char, mut args: ...) -> CIntCountResult {
    vdprintf(fd, fmt, args.as_va_list())
}

#[no_mangle]
unsafe extern "C" fn vdprintf(fd: c_int, fmt: *const c_char, args: VaList) -> CIntCountResult {
    let mut file = RawFile::e_try_from(fd)?;
    let fmt = fmt.ensure_cstr();
    let mut writer = FileWriter(&mut file);
    let count = printf_inner(&mut writer, fmt.to_bytes(), args)?;
    CIntCountResult::success(count.try_into().unwrap())
}

#[no_mangle]
unsafe extern "C" fn printf(fmt: *const c_char, mut args: ...) -> CIntCountResult {
    vfprintf(stdout, fmt, args.as_va_list())
}

#[no_mangle]
unsafe extern "C" fn vprintf(fmt: *const c_char, args: VaList) -> CIntCountResult {
    vfprintf(stdout, fmt, args)
}

#[no_mangle]
unsafe extern "C" fn fprintf(fp: *mut FILE, fmt: *const c_char, mut args: ...) -> CIntCountResult {
    vfprintf(fp, fmt, args.as_va_list())
}

#[no_mangle]
unsafe extern "C" fn vfprintf(fp: *mut FILE, fmt: *const c_char, args: VaList) -> CIntCountResult {
    let fp = fp.ensure_mut();
    let fmt = fmt.ensure_cstr();
    let mut writer = FileWriter(fp);
    let count = printf_inner(&mut writer, fmt.to_bytes(), args)?;
    CIntCountResult::success(count.try_into().unwrap())
}

#[no_mangle]
unsafe extern "C" fn sprintf(
    buffer: *mut c_char,
    fmt: *const c_char,
    mut args: ...
) -> CIntCountResult {
    vsnprintf(buffer, usize::MAX, fmt, args.as_va_list())
}

#[no_mangle]
unsafe extern "C" fn snprintf(
    buffer: *mut c_char,
    capacity: usize,
    fmt: *const c_char,
    mut args: ...
) -> CIntCountResult {
    vsnprintf(buffer, capacity, fmt, args.as_va_list())
}

#[no_mangle]
unsafe extern "C" fn vsprintf(
    buffer: *mut c_char,
    fmt: *const c_char,
    args: VaList,
) -> CIntCountResult {
    vsnprintf(buffer, usize::MAX, fmt, args)
}

#[no_mangle]
unsafe extern "C" fn vsnprintf(
    buffer: *mut c_char,
    capacity: usize,
    fmt: *const c_char,
    args: VaList,
) -> CIntCountResult {
    let buffer = NonNull::new(buffer).expect("vsnprintf(): buffer == NULL");
    let mut writer = StringWriter::new(buffer, capacity);
    let fmt = fmt.ensure_cstr();
    let count = printf_inner(&mut writer, fmt.to_bytes(), args)?;
    writer.finish();
    CIntCountResult::success(count.try_into().unwrap())
}
