#![cfg_attr(feature="nightly", feature(const_char_from_u32_unchecked))]
#![deny(warnings)]
#![no_std]
#[cfg(feature="load")]
use core::fmt::{self, Debug, Display, Formatter};
#[cfg(feature="load")]
use core::mem::{MaybeUninit, forget, transmute};
use core::num::NonZeroU32;
#[cfg(feature="load")]
use core::slice::{self};
#[cfg(feature="load")]
use core::sync::atomic::{AtomicBool, Ordering};
#[cfg(feature="load")]
use either::{Either, Left, Right};
#[cfg(feature="load")]
use exit_no_std::exit;
#[cfg(feature="load")]
use iter_identify_first_last::IteratorIdentifyFirstLastExt;
#[cfg(feature="load")]
use panicking::panicking;
#[cfg(feature="load")]
use pc_ints::*;
#[doc(hidden)]
pub use core::write as std_write;
#[doc(hidden)]
pub use core::writeln as std_writeln;
#[doc(hidden)]
#[inline]
pub const fn hash(w: u16, p: u16) -> u8 {
    let w = w.wrapping_add(p);
    ((w ^ (w >> 8)) & 0x007F) as u8
}
const CODE_PAGE_SIZE: u16 = 512;
#[derive(Debug, Clone)]
#[repr(C, align(8))]
pub struct CodePage(pub [u8; CODE_PAGE_SIZE as _]);
impl CodePage {
    #[cfg(feature="nightly")]
    const fn to_upper_half_char(&self, c: u8) -> Option<char> {
        let offset = 2 * c as usize;
        let hb = self.0[offset];
        let lb = self.0[offset + 1];
        if let Some(c) = NonZeroU32::new(((hb as u32) << 8) | (lb as u32)) {
            Some(unsafe { char::from_u32_unchecked(c.get()) })
        } else {
            None
        }
    }
    #[cfg(not(feature="nightly"))]
    fn to_upper_half_char(&self, c: u8) -> Option<char> {
        let offset = 2 * c as usize;
        let hb = self.0[offset];
        let lb = self.0[offset + 1];
        NonZeroU32::new(((hb as u32) << 8) | (lb as u32)).map(|x|
            unsafe { char::from_u32_unchecked(x.get()) }
        )
    }
    #[cfg(feature="nightly")]
    pub const fn to_char(&self, c: u8) -> Option<char> {
        let half = c & 0x7F;
        if c == half {
            Some(c as char)
        } else {
            self.to_upper_half_char(half)
        }
    }
    #[cfg(not(feature="nightly"))]
    pub fn to_char(&self, c: u8) -> Option<char> {
        let half = c & 0x7F;
        if c == half {
            Some(c as char)
        } else {
            self.to_upper_half_char(half)
        }
    }
    #[cfg(feature="nightly")]
    pub const fn from_char(&self, c: char) -> Option<u8> {
        if (c as u32) >> 7 == 0 {
            Some(c as u32 as u8)
        } else if (c as u32) >> 16 != 0 {
            None
        } else {
            let w = (c as u32) as u16;
            let hash_param = (self.0[510] as u16) | ((self.0[511] as u16) << 8);
            let offset = 256 + 2 * hash(w, hash_param) as usize;
            let try_1 = self.0[offset];
            if try_1 >> 7 != 0 { return None; }
            if let Some(x) = self.to_upper_half_char(try_1) {
                if x == c { return Some(0x80 | try_1); }
            }
            let try_2 = self.0[offset + 1];
            if try_2 >> 7 != 0 { return None; }
            if let Some(x) = self.to_upper_half_char(try_2) {
                if x == c { return Some(0x80 | try_2); }
            }
            None
        }
    }
    #[cfg(not(feature="nightly"))]
    pub fn from_char(&self, c: char) -> Option<u8> {
        if (c as u32) >> 7 == 0 {
            Some(c as u32 as u8)
        } else if (c as u32) >> 16 != 0 {
            None
        } else {
            let w = (c as u32) as u16;
            let hash_param = (self.0[510] as u16) | ((self.0[511] as u16) << 8);
            let offset = 256 + 2 * hash(w, hash_param) as usize;
            let try_1 = self.0[offset];
            if try_1 >> 7 != 0 { return None; }
            if self.to_upper_half_char(try_1) == Some(c) {
                return Some(0x80 | try_1);
            }
            let try_2 = self.0[offset + 1];
            if try_2 >> 7 != 0 { return None; }
            if self.to_upper_half_char(try_2) == Some(c) {
                return Some(0x80 | try_2);
            }
            None
        }
    }
    #[cfg(feature="load")]
    pub fn load_or_exit_with_msg(exit_code: u8) -> &'static CodePage {
        match Self::load() {
            Ok(cp) => cp,
            Err(e) => {
                write!(DosLastChanceWriter, "Error: {e}.").unwrap();
                exit(exit_code);
            },
        }
    }
    #[cfg(feature="load")]
    pub fn load() -> Result<&'static CodePage, CodePageLoadError> {
        let mut loaded = LoadedCodePageGuard::acquire();
        let loaded_code_page = loaded.code_page();
        if let Some(code_page) = loaded_code_page {
            return Ok(code_page);
        }
        let dos_ver = int_21h_ah_30h_dos_ver();
        if dos_ver.al_major < 3 || dos_ver.al_major == 3 && dos_ver.ah_minor < 30 {
            return Err(CodePageLoadError::Dos33Required);
        }
        let code_page_memory = int_31h_ax_0100h_rm_alloc(CODE_PAGE_SIZE.checked_add(15).unwrap() / 16)
            .map_err(|e| CodePageLoadError::CanNotAlloc { err_code: e.ax_err })?;
        let code_page_selector = RmAlloc { selector: code_page_memory.dx_selector };
        let code_page_memory = unsafe { slice::from_raw_parts_mut(
            ((code_page_memory.ax_segment as u32) << 4) as *mut u8,
            CODE_PAGE_SIZE.into()
        ) };
        let code_page_n = int_21h_ax_6601h_code_page()
            .map_err(|e| CodePageLoadError::CanNotGetSelectedCodePage { err_code: e.ax_err })?
            .bx_active;
        if !(100 ..= 999).contains(&code_page_n) {
            return Err(CodePageLoadError::UnsupportedCodePage { code_page: code_page_n });
        }
        let mut code_page: [MaybeUninit<u8>; 13] = unsafe { MaybeUninit::uninit().assume_init() };
        code_page[.. 9].copy_from_slice(unsafe { transmute(&b"CODEPAGE\\"[..]) });
        code_page[9].write(b'0' + (code_page_n / 100) as u8);
        code_page[10].write(b'0' + ((code_page_n % 100) / 10) as u8);
        code_page[11].write(b'0' + (code_page_n % 10) as u8);
        code_page[12].write(0);
        let code_page: [u8; 13] = unsafe { transmute(code_page) };
        let code_page = int_21h_ah_3Dh_open(code_page.as_ptr(), 0x00)
            .map_err(|e| CodePageLoadError::CanNotOpenCodePageFile { code_page: code_page_n, err_code: e.ax_err })?
            .ax_handle;
        let code_page = File(code_page);
        let mut code_page_buf: &mut [MaybeUninit<u8>] = unsafe { transmute(&mut code_page_memory[..]) };
        loop {
            if code_page_buf.is_empty() {
                let mut byte: MaybeUninit<u8> = MaybeUninit::uninit();
                let read = int_21h_ah_3Fh_read(code_page.0, slice::from_mut(&mut byte))
                    .map_err(|e| CodePageLoadError::CanNotReadCodePageFile { code_page: code_page_n, err_code: e.ax_err })?
                    .ax_read;
                if read != 0 {
                    return Err(CodePageLoadError::InvalidCodePageFile { code_page: code_page_n });
                }
                break;
            }
            let read = int_21h_ah_3Fh_read(code_page.0, code_page_buf)
                .map_err(|e| CodePageLoadError::CanNotReadCodePageFile { code_page: code_page_n, err_code: e.ax_err })?
                .ax_read;
            if read == 0 { break; }
            code_page_buf = &mut code_page_buf[read as usize ..];
        }
        if !code_page_buf.is_empty() {
            return Err(CodePageLoadError::InvalidCodePageFile { code_page: code_page_n });
        }
        let code_page = unsafe { &*(code_page_memory.as_ptr() as *const CodePage) };
        forget(code_page_selector);
        loaded_code_page.replace(code_page);
        Ok(code_page)
    }
    #[cfg(feature="load")]
    pub fn inkey(&self) -> Result<Option<Either<u8, char>>, InkeyErr> {
        let c = int_21h_ah_06h_dl_FFh_inkey().map_err(|_| InkeyErr)?;
        let c = match c {
            Some(x) => x.al_char,
            None => return Ok(None),
        };
        if c == 0 {
            let c = int_21h_ah_06h_dl_FFh_inkey().map_err(|_| InkeyErr)?;
            let c = c.ok_or(InkeyErr)?.al_char;
            Ok(Some(Left(c)))
        } else {
            Ok(self.to_char(c).map(Right))
        }
    }
}
#[cfg(feature="load")]
pub fn inkey() -> Result<Option<Either<u8, char>>, InkeyErr> {
    let cp = CodePage::load().map_err(|_| InkeyErr)?;
    cp.inkey()
}
#[cfg(feature="load")]
#[derive(Debug)]
pub struct InkeyErr;
#[cfg(feature="load")]
struct DosLastChanceWriter;
#[cfg(feature="load")]
impl DosLastChanceWriter {
    pub fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result {
        <Self as fmt::Write>::write_fmt(self, args)
    }
}
#[cfg(feature="load")]
impl fmt::Write for DosLastChanceWriter {
    fn write_char(&mut self, c: char) -> fmt::Result {
        let c = c as u32;
        let c = if c > 0x7F || c == '\r' as u32 {
            b'?'
        } else {
            c as u8
        };
        if c == b'\n' {
            int_21h_ah_02h_out_ch(b'\r');
        }
        int_21h_ah_02h_out_ch(c);
        Ok(())
    }
    fn write_str(&mut self, s: &str) -> fmt::Result {
        for c in s.chars() {
            self.write_char(c)?;
        }
        Ok(())
    }
}
#[cfg(feature="load")]
struct File(u16);
#[cfg(feature="load")]
impl Drop for File {
    fn drop(&mut self) {
        let r = int_21h_ah_3Eh_close(self.0);
        if r.is_err() && !panicking() {
            #[allow(clippy::panicking_unwrap)]
            r.unwrap();
        }
    }
}
#[cfg(feature="load")]
struct LoadedCodePageGuard;
#[cfg(feature="load")]
static LOADED_CODE_PAGE_GUARD: AtomicBool = AtomicBool::new(false);
#[cfg(feature="load")]
static mut LOADED_CODE_PAGE: Option<&'static CodePage> = None;
#[cfg(feature="load")]
impl LoadedCodePageGuard {
    fn acquire() -> Self {
        loop {
            if LOADED_CODE_PAGE_GUARD.compare_exchange_weak(false, true, Ordering::SeqCst, Ordering::Relaxed).is_ok() {
                break;
            }
        }
        LoadedCodePageGuard
    }
    fn code_page(&mut self) -> &mut Option<&'static CodePage> {
        unsafe { &mut LOADED_CODE_PAGE }
    }
}
#[cfg(feature="load")]
impl Drop for LoadedCodePageGuard {
    fn drop(&mut self) {
        LOADED_CODE_PAGE_GUARD.store(false, Ordering::SeqCst);
    }
}
#[cfg(feature="load")]
pub enum CodePageLoadError {
    Dos33Required,
    CanNotAlloc { err_code: u16 },
    CanNotGetSelectedCodePage { err_code: u16 },
    UnsupportedCodePage { code_page: u16 },
    CanNotOpenCodePageFile { code_page: u16, err_code: u16 },
    CanNotReadCodePageFile { code_page: u16, err_code: u16 },
    InvalidCodePageFile { code_page: u16 },
}
#[cfg(feature="load")]
impl CodePageLoadError {
    pub fn code_page(&self) -> Option<u16> {
        match self {
            CodePageLoadError::Dos33Required => None,
            CodePageLoadError::CanNotAlloc { .. } => None,
            CodePageLoadError::CanNotGetSelectedCodePage { .. } => None,
            &CodePageLoadError::UnsupportedCodePage { code_page } => Some(code_page),
            &CodePageLoadError::CanNotOpenCodePageFile { code_page, .. } => Some(code_page),
            &CodePageLoadError::CanNotReadCodePageFile { code_page, .. } => Some(code_page),
            &CodePageLoadError::InvalidCodePageFile { code_page } => Some(code_page),
        }
    }
}
#[cfg(feature="load")]
impl Display for CodePageLoadError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match self {
            CodePageLoadError::Dos33Required => write!(f, "DOS >= 3.3 reequired"),
            CodePageLoadError::CanNotAlloc { err_code } =>
                write!(f, "cannot allocate real-mode memory for code page ({err_code:04X}h)"),
            CodePageLoadError::CanNotGetSelectedCodePage { err_code } =>
                write!(f, "cannon get selected code page ({err_code:04X}h)"),
            CodePageLoadError::UnsupportedCodePage { code_page } => write!(f, "unsupported code page {code_page}"),
            CodePageLoadError::CanNotOpenCodePageFile { code_page, err_code } =>
                write!(f, "cannot open code page file 'CODEPAGE\\{code_page}' ({err_code:04X}h)"),
            CodePageLoadError::CanNotReadCodePageFile { code_page, err_code } =>
                write!(f, "cannot read code page file 'CODEPAGE\\{code_page}' ({err_code:04X}h)"),
            CodePageLoadError::InvalidCodePageFile { code_page } => write!(f, "invalid code page file 'CODEPAGE\\{code_page}'"),
        }
    }
}
#[cfg(feature="load")]
impl Debug for CodePageLoadError {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        <Self as Display>::fmt(self, f)
    }
}
#[cfg(feature="load")]
struct RmAlloc {
    selector: u16,
}
#[cfg(feature="load")]
impl Drop for RmAlloc {
    fn drop(&mut self) {
       let _ = int_31h_ax_0101h_rm_free(self.selector);
    }
}
#[cfg(feature="load")]
pub struct DosStdout { pub panic: bool }
#[cfg(feature="load")]
impl DosStdout {
    pub fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result {
        <Self as fmt::Write>::write_fmt(self, args)
    }
}
#[cfg(feature="load")]
impl fmt::Write for DosStdout {
    fn write_char(&mut self, c: char) -> fmt::Result {
        let cp = CodePage::load();
        let cp = if self.panic { cp.unwrap() } else { cp.map_err(|_| fmt::Error)? };
        let c = cp.from_char(c).unwrap_or(b'?');
        match c {
            b'\r' => Ok(()),
            b'\n' => match int_21h_ah_40h_write(1, b"\r\n") {
                Err(_) => Err(fmt::Error),
                Ok(AxWritten { ax_written }) if ax_written < 2 => Err(fmt::Error),
                _ => Ok(()),
            },
            c => match int_21h_ah_40h_write(1, &[c]) {
                Err(_) | Ok(AxWritten { ax_written: 0 }) => Err(fmt::Error),
                _ => Ok(()),
            }
        }
    }
    fn write_str(&mut self, s: &str) -> fmt::Result {
        let cp = CodePage::load();
        let cp = if self.panic { cp.unwrap() } else { cp.map_err(|_| fmt::Error)? };
        let mut buf = [0; 128];
        for (skip_newline, s) in s.split('\n').identify_last() {
            let mut i = 0;
            for (is_last, c) in s.chars().identify_last() {
                buf[i] = cp.from_char(c).unwrap_or(b'?');
                i += 1;
                if is_last || i == buf.len() {
                    match int_21h_ah_40h_write(1, &buf[.. i]) {
                        Err(_) => return Err(fmt::Error),
                        Ok(AxWritten { ax_written }) if usize::from(ax_written) < i => return Err(fmt::Error),
                        _ => { },
                    }
                    i = 0;
                }
            }
            if !skip_newline {
                match int_21h_ah_40h_write(1, b"\r\n") {
                    Err(_) => return Err(fmt::Error),
                    Ok(AxWritten { ax_written }) if ax_written < 2 => return Err(fmt::Error),
                    _ => { },
                }
            }
        }
        Ok(())
    }
}
#[cfg(feature="load")]
#[macro_export]
macro_rules! print {
    (
        $($arg:tt)*
    ) => {
        $crate::std_write!($crate::DosStdout { panic: true }, $($arg)*).unwrap()
    };
}
#[cfg(feature="load")]
#[macro_export]
macro_rules! println {
    (
    ) => {
        $crate::std_writeln!($crate::DosStdout { panic: true }).unwrap()
    };
    (
        $($arg:tt)*
    ) => {
        $crate::std_writeln!($crate::DosStdout { panic: true }, $($arg)*).unwrap()
    };
}