#![cfg_attr(not(unix), allow(dead_code, unused_macros))]

use std::fmt::{self, Display, Formatter};

macro_rules! bitwidths {
    ($($nam:ident),+ $(,)?) => {[$((stringify!($nam), $nam::BITS, $nam::MIN == 0)),+]};
}
macro_rules! sizes {
    ($($nam:ident),+ $(,)?) => {[$((stringify!($nam), ::std::mem::size_of::<$nam>())),+]};
}
fn maxlen2<T>(a: &[(&str, T)]) -> usize { a.iter().map(|&(nm, _)| nm.len()).max().unwrap_or(0) }
fn maxlen3<T>(a: &[(&str, T, bool)]) -> usize {
    a.iter().map(|&(nm, _, _)| nm.len()).max().unwrap_or(0)
}

pub fn print_bitwidths(bw: &[(&str, u32, bool)]) {
    let width = maxlen3(bw);
    bw.iter().for_each(|&(nm, bw, un)| {
        println!("{nm:width$} : {bw:>2} bits {:>2}signed", val_if(un, "un"))
    });
}
pub fn print_sizes(sz: &[(&str, usize)]) {
    let width = maxlen2(sz);
    sz.iter().for_each(|&(nm, sz)| println!("{nm:width$} : {sz:>3} bytes"));
}
pub fn print_signedness(nm: &str, signed: bool) {
    println!("{nm} is {}signed", if signed { "" } else { "un" })
}

pub fn display_if<T: Display>(cond: bool, v: T) -> OptionDisplay<T> {
    OptionDisplay::new(cond.then_some(v))
}
#[allow(clippy::obfuscated_if_else)]
pub fn val_if<T: Default>(cond: bool, v: T) -> T { cond.then_some(v).unwrap_or_default() }
#[derive(Copy, Clone, Debug)]
pub struct OptionDisplay<T>(Option<T>);
impl<T> OptionDisplay<T> {
    pub const fn new(v: Option<T>) -> Self { Self(v) }
}
impl<T> From<T> for OptionDisplay<T> {
    fn from(v: T) -> Self { Self(Some(v)) }
}
impl<T> Default for OptionDisplay<T> {
    fn default() -> Self { Self(None) }
}
impl<T: Display> Display for OptionDisplay<T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        if let Some(v) = &self.0 {
            Display::fmt(v, f)?;
        }
        Ok(())
    }
}

pub const fn display_fn<T: Fn(&mut Formatter<'_>) -> fmt::Result>(v: T) -> DisplayFn<T> {
    DisplayFn(v)
}
#[derive(Copy, Clone, Debug, Default)]
pub struct DisplayFn<T>(T);
impl<T> From<T> for DisplayFn<T> {
    fn from(v: T) -> Self { Self(v) }
}
impl<T: Fn(&mut Formatter<'_>) -> fmt::Result> Display for DisplayFn<T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { (self.0)(f) }
}

pub trait ResultExt: Sized {
    type Ok;
    type Err: Display;
    fn get_err(&self) -> Option<&Self::Err>;
    fn unwrap_or_else(self, f: impl FnOnce(Self::Err) -> Self::Ok) -> Self::Ok;

    fn report_error(self, msg: &str) -> Self {
        report_error_str(self.get_err(), msg);
        self
    }
    fn report_error_if(self, toggle: bool, msg: &str) -> Self {
        if toggle {
            report_error_str(self.get_err(), msg);
        }
        self
    }
    fn report_error_args(self, msg: fmt::Arguments<'_>) -> Self {
        report_error(self.get_err(), msg);
        self
    }
    fn report_error_args_if(self, toggle: bool, msg: fmt::Arguments<'_>) -> Self {
        if toggle {
            report_error(self.get_err(), msg);
        }
        self
    }
    fn set_if_error<T>(self, out: &mut T, val: T) -> Self {
        if self.get_err().is_some() {
            *out = val;
        }
        self
    }
    fn unwrap_or_exit(self, msg: &str) -> Self::Ok {
        report_error_str(self.get_err(), msg);
        self.unwrap_or_else(forget_error_and_die)
    }
    fn unwrap_or_exit_args(self, msg: fmt::Arguments<'_>) -> Self::Ok {
        report_error(self.get_err(), msg);
        self.unwrap_or_else(forget_error_and_die)
    }
}
#[inline(never)]
fn report_error<E: Display>(e: Option<&E>, msg: fmt::Arguments<'_>) {
    if let Some(e) = e {
        println!("{msg}: {e}");
    }
}
#[inline(never)]
fn report_error_str<E: Display>(e: Option<&E>, msg: &str) {
    if let Some(e) = e {
        println!("{msg}: {e}");
    }
}
fn forget_error_and_die<T, E>(e: E) -> T {
    std::mem::forget(e);
    std::process::exit(1);
}
impl<T, E: Display> ResultExt for Result<T, E> {
    type Ok = T;
    type Err = E;
    fn get_err(&self) -> Option<&E> { self.as_ref().err() }
    fn unwrap_or_else(self, f: impl FnOnce(E) -> T) -> T { self.unwrap_or_else(f) }
}
