use core::str;
use std::{
  ffi::CStr,
  fmt::{Debug, Display},
};

use anyhow::{bail, Context};
use tracing::warn;
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};

#[derive(Clone, Copy, Debug, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(C)]
pub struct Bool(u8);

impl Bool {
  pub fn new(value: bool) -> Bool {
    Bool(if value { 1 } else { 0 })
  }
  pub fn get(&self) -> bool {
    self.0 != 0
  }
}

impl From<bool> for Bool {
  fn from(value: bool) -> Self {
    Bool::new(value)
  }
}

impl From<Bool> for bool {
  fn from(value: Bool) -> Self {
    value.get()
  }
}

#[derive(
  Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned,
)]
#[repr(C)]
pub struct String<const N: usize>([u8; N]);

impl<const N: usize> String<N> {
  pub fn as_str(&self) -> &str {
    self.try_as_str().expect("wasn't a string")
  }
  pub fn try_as_str(&self) -> anyhow::Result<&str> {
    if self.0.contains(&0) {
      let cstr = CStr::from_bytes_until_nul(&self.0).context("interpreting bytes as c-string")?;
      cstr.to_str().context("verifying string has utf-8")
    } else {
      std::str::from_utf8(&self.0).context("verifying string has utf8")
    }
  }
  pub fn assert_valid(&self) -> anyhow::Result<()> {
    self.try_as_str().map(drop)
  }
}

impl<const N: usize> TryFrom<&str> for String<N> {
  type Error = anyhow::Error;
  fn try_from(value: &str) -> Result<Self, Self::Error> {
    let mut buf = [0; N];
    if value.len() > N {
      bail!("seggs")
    }

    value.write_to_prefix(&mut buf).unwrap();

    Ok(Self(buf))
  }
}

impl<const N: usize> Display for String<N> {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    Display::fmt(self.try_as_str().expect("failed to parse string"), f)
  }
}
impl<const N: usize> Debug for String<N> {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    Debug::fmt(self.try_as_str().expect("failed to parse string"), f)
  }
}
