use std::{
    env,
    fs::{self, File},
    io::{BufRead, BufReader, Result, Write},
    path::Path,
};

macro_rules! template {
    () => {
        "\
                                                                                                // \
         Generated by build.rs, DO NOT edit

/// Linux specific error codes defined in `errno.h`.
#[repr(i32)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum LinuxError {{
{0}}}

impl TryFrom<i32> for LinuxError {{
    type Error = i32;

    fn try_from(value: i32) -> Result<Self, Self::Error> {{
        use self::LinuxError::*;
        match value {{
{1}            _ => Err(value),
        }}
    }}
}}

impl LinuxError {{
    /// Returns the error description.
    pub const fn as_str(&self) -> &'static str {{
        use self::LinuxError::*;
        match self {{
{2}        }}
    }}

    /// Returns the error code value in `i32`.
    pub const fn code(self) -> i32 {{
        self as i32
    }}
}}
"
    };
}

fn main() {
    let out_dir = env::var_os("OUT_DIR").unwrap();
    gen_linux_errno(&Path::new(&out_dir).join("linux_errno.rs")).unwrap();
}

fn gen_linux_errno(dest_path: &Path) -> Result<()> {
    let mut enum_define = Vec::new();
    let mut try_from_i32 = Vec::new();
    let mut detail_info = Vec::new();

    let file = File::open("src/errno.h")?;
    for line in BufReader::new(file).lines().map_while(Result::ok) {
        if line.starts_with("#define") {
            let mut iter = line.split_whitespace();
            if let Some(name) = iter.nth(1) {
                if let Some(num) = iter.next() {
                    let description = if let Some(pos) = line.find("/* ") {
                        String::from(line[pos + 3..].trim_end_matches(" */"))
                    } else {
                        format!("Error number {num}")
                    };
                    writeln!(enum_define, "    /// {description}\n    {name} = {num},")?;
                    writeln!(try_from_i32, "            {num} => Ok({name}),")?;
                    writeln!(detail_info, "            {name} => \"{description}\",")?;
                }
            }
        }
    }

    fs::write(
        dest_path,
        format!(
            template!(),
            String::from_utf8_lossy(&enum_define),
            String::from_utf8_lossy(&try_from_i32),
            String::from_utf8_lossy(&detail_info)
        ),
    )?;

    Ok(())
}
