use core::ffi::{c_char, VaList};

use char_set::ScanCharSet;
use format::{ScanOpt, ScanRadix, ScanSize, ScanSpec};
use reader::{GetChar, ScanReader};

use crate::{
    error::{CEofResult, EResult},
    headers::errno::Errno,
    io::managed::{stdin, FILE},
    util::{PointerExt, PointerStrExt},
};

mod char_set;
mod format;
mod reader;

pub enum ConversionError {
    Error(Errno),
    ConversionFailed,
}

pub struct FormatError;

impl From<Errno> for ConversionError {
    fn from(value: Errno) -> Self {
        Self::Error(value)
    }
}

fn scanf_inner<G: GetChar>(
    stream: &mut ScanReader<G>,
    format: &[u8],
    mut ap: VaList,
) -> EResult<usize> {
    let mut it = format.iter();
    let mut matched = 0;
    let mut skip_space = false;

    while let Some(&ch) = it.next() {
        if ch == b' ' {
            // Any amount of whitespace
            if skip_space {
                continue;
            }

            // Skip following whitespace in format
            skip_space = true;

            // Skip whitespace in stream
            while let Some(ic) = stream.peek()? {
                if !ic.is_ascii_whitespace() {
                    break;
                }

                stream.next()?;
            }

            continue;
        } else if ch != b'%' {
            // Regular character
            skip_space = false;

            let ic = stream.peek()?;
            if !ic.map(|ic| ic == ch).unwrap_or(false) {
                break;
            }

            stream.next()?;

            continue;
        }

        skip_space = false;

        // %*123lld
        let mut opts = ScanOpt::default();
        let mut cur = it.next();

        if let Some(&ch) = cur {
            if ch == b'*' {
                opts.discard = true;
                cur = it.next();
            }
        }

        while let Some(&ch) = cur {
            if ch.is_ascii_digit() {
                opts.width
                    .replace(opts.width.unwrap_or(0) * 10 + (ch - b'0') as usize);
                cur = it.next();
            } else {
                break;
            }
        }

        while let Some(&ch) = cur {
            match ch {
                b'h' => opts.size = opts.size.shorter(),
                b'l' => opts.size = opts.size.longer(),
                b'z' => opts.size = ScanSize::Size,
                // TODO ptrdiff_t
                b't' => todo!(),
                // TODO intmax_t/uintmax_t
                b'j' => todo!(),
                // TODO floats
                b'L' | b'q' => todo!(),
                _ => break,
            }

            cur = it.next();
        }

        let spec = if let Some(&ch) = cur {
            match ch {
                b'i' => ScanSpec::Signed(ScanRadix::Auto),
                b'd' => ScanSpec::Signed(ScanRadix::Decimal),
                b'x' | b'X' => ScanSpec::Unsigned(ScanRadix::Hex),
                b'u' => ScanSpec::Unsigned(ScanRadix::Decimal),
                b'o' => ScanSpec::Unsigned(ScanRadix::Oct),
                b's' => ScanSpec::Word,
                b'[' => {
                    let (new_it, set) = match ScanCharSet::from_format_iter(it) {
                        Ok(v) => v,
                        Err(FormatError) => return EResult::Err(Errno::ESUCCESS),
                    };
                    it = new_it;
                    ScanSpec::Chars(set)
                }
                b'c' => ScanSpec::AnyChar,
                b'n' => ScanSpec::ConsumedCount,
                b'p' => ScanSpec::Pointer,
                b'%' => ScanSpec::Percent,
                b'f' | b'e' | b'g' | b'E' | b'a' => todo!(),
                // Unrecognized specifier
                _ => return EResult::Err(Errno::ESUCCESS),
            }
        } else {
            // No specifier after %
            return EResult::Err(Errno::ESUCCESS);
        };

        matched += match opts.scan(stream, spec, &mut ap) {
            Ok(matched) => matched,
            Err(ConversionError::ConversionFailed) => break,
            Err(ConversionError::Error(err)) => return EResult::Err(err),
        };
    }

    // TODO report end of stream before anything is matched

    EResult::Ok(matched)
}

#[no_mangle]
unsafe extern "C" fn scanf(format: *const c_char, mut args: ...) -> CEofResult {
    vfscanf(stdin, format, args.as_va_list())
}

#[no_mangle]
unsafe extern "C" fn fscanf(fp: *mut FILE, format: *const c_char, mut args: ...) -> CEofResult {
    vfscanf(fp, format, args.as_va_list())
}

#[no_mangle]
unsafe extern "C" fn vscanf(format: *const c_char, args: VaList) -> CEofResult {
    vfscanf(stdin, format, args)
}

#[no_mangle]
unsafe extern "C" fn vfscanf(fp: *mut FILE, format: *const c_char, args: VaList) -> CEofResult {
    let fp = fp.ensure_mut();
    let format = format.ensure_cstr();
    let mut reader = ScanReader::new(fp);
    let count = scanf_inner(&mut reader, format.to_bytes(), args)?;
    CEofResult::success(count.try_into().unwrap())
}

#[no_mangle]
unsafe extern "C" fn sscanf(
    input: *const c_char,
    format: *const c_char,
    mut args: ...
) -> CEofResult {
    vsscanf(input, format, args.as_va_list())
}

#[no_mangle]
unsafe extern "C" fn vsscanf(
    input: *const c_char,
    format: *const c_char,
    args: VaList,
) -> CEofResult {
    let input = input.ensure_cstr();
    let format = format.ensure_cstr();
    let mut str_it = input.to_bytes().iter();
    let mut reader = ScanReader::new(&mut str_it);
    let count = scanf_inner(&mut reader, format.to_bytes(), args)?;
    CEofResult::success(count.try_into().unwrap())
}
