#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os, rustc_private))]

use std::{
    fs::File,
    io::{self, BufRead, Read, Seek, SeekFrom, Write},
};

#[cfg(unix)]
pub mod unix {
    use std::fmt;

    #[derive(Clone, Copy, PartialEq, Eq, Debug)]
    pub struct FileMode(u32);

    impl From<u32> for FileMode {
        fn from(value: u32) -> Self {
            Self(value)
        }
    }

    impl fmt::Display for FileMode {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            macro_rules! print_bit {
                ($res:expr, $val:expr, $self:expr, $bit:expr) => {
                    if $self.0 & $bit == $bit {
                        $res = $val;
                    }
                };
            }

            let mut buf = ['-'; 9];

            print_bit!(buf[0], 'r', self, 0o400);
            print_bit!(buf[1], 'w', self, 0o200);
            print_bit!(buf[2], 'x', self, 0o100);
            print_bit!(buf[3], 'r', self, 0o040);
            print_bit!(buf[4], 'w', self, 0o020);
            print_bit!(buf[5], 'x', self, 0o010);
            print_bit!(buf[6], 'r', self, 0o004);
            print_bit!(buf[7], 'w', self, 0o002);
            print_bit!(buf[8], 'x', self, 0o001);

            for ch in buf.iter() {
                fmt::Display::fmt(ch, f)?;
            }
            Ok(())
        }
    }
}

// TODO replace this
pub trait ToExitCode {
    fn to_exit_code(&self) -> i32;
}

impl<T> ToExitCode for io::Result<T> {
    fn to_exit_code(&self) -> i32 {
        match self {
            Ok(_) => 0,
            _ => 1,
        }
    }
}

pub enum Input {
    Stdin(io::Stdin),
    File(io::BufReader<File>),
}

pub enum Output {
    Stdout(io::Stdout),
    File(io::BufWriter<File>),
}

impl Input {
    pub fn open_str(arg: &str) -> io::Result<Self> {
        if arg == "-" {
            Ok(Self::Stdin(io::stdin()))
        } else {
            let file = File::open(arg)?;
            let reader = io::BufReader::new(file);
            Ok(Self::File(reader))
        }
    }

    pub fn read_line(&mut self, line: &mut String) -> io::Result<usize> {
        match self {
            Self::Stdin(f) => f.read_line(line),
            Self::File(f) => f.read_line(line),
        }
    }
}

impl From<io::Stdin> for Input {
    fn from(value: io::Stdin) -> Self {
        Self::Stdin(value)
    }
}

impl From<File> for Input {
    fn from(value: File) -> Self {
        Self::File(io::BufReader::new(value))
    }
}

impl Read for Input {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        match self {
            Self::Stdin(value) => value.read(buf),
            Self::File(value) => value.read(buf),
        }
    }
}

impl Seek for Input {
    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
        match self {
            Self::Stdin(_) => todo!(),
            Self::File(value) => value.seek(pos),
        }
    }
}

impl Output {
    pub fn open_str(arg: &str) -> io::Result<Self> {
        if arg == "-" {
            Ok(Self::Stdout(io::stdout()))
        } else {
            let file = File::create(arg)?;
            let writer = io::BufWriter::new(file);
            Ok(Self::File(writer))
        }
    }
}

impl From<io::Stdout> for Output {
    fn from(value: io::Stdout) -> Self {
        Self::Stdout(value)
    }
}

impl From<File> for Output {
    fn from(value: File) -> Self {
        Self::File(io::BufWriter::new(value))
    }
}

impl Write for Output {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        match self {
            Self::Stdout(value) => value.write(buf),
            Self::File(value) => value.write(buf),
        }
    }

    fn flush(&mut self) -> io::Result<()> {
        match self {
            Self::Stdout(value) => value.flush(),
            Self::File(value) => value.flush(),
        }
    }
}

impl Seek for Output {
    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
        match self {
            Self::Stdout(_) => todo!(),
            Self::File(value) => value.seek(pos),
        }
    }
}
