enneagram 1.3.0

A digital enneagram
use core::{ops::Deref, str::FromStr};
use std::{fmt::Display, num::ParseIntError};

#[cfg(feature = "artwork")]
use ratatui_3d::Rgb;

#[cfg(not(feature = "artwork"))]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Rgb(u8, u8, u8);

use serde::{Deserialize, Serialize};

#[cfg(feature = "artwork")]
use crate::config::Fallback;
use crate::config::{Config, Property};

#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ColorConfig
{
    #[serde(skip_serializing_if = "Option::is_none")]
    sky: Option<Color>,
    #[serde(skip_serializing_if = "Option::is_none")]
    surface: Option<Color>,
    #[serde(skip_serializing_if = "Option::is_none")]
    wire: Option<Color>,
    #[serde(skip_serializing_if = "Option::is_none")]
    dyed: Option<Color>,
    #[serde(skip_serializing_if = "Option::is_none")]
    glare: Option<Color>,
    #[serde(skip_serializing_if = "Option::is_none")]
    sun: Option<Color>,
    #[serde(skip_serializing_if = "Option::is_none")]
    shine: Option<Color>
}

#[cfg(feature = "artwork")]
impl ColorConfig
{
    crate::config::getter!([_, c.color].sky.deref() -> Rgb);

    crate::config::getter!([_, c.color].surface.deref() -> Rgb);

    crate::config::getter!([_, c.color].wire.deref() -> Rgb);

    crate::config::getter!([_, c.color].dyed.deref() -> Rgb);

    crate::config::getter!([_, c.color].glare.deref() -> Rgb);

    crate::config::getter!([_, c.color].sun.deref() -> Rgb);

    crate::config::getter!([_, c.color].shine.deref() -> Rgb);

    pub fn line(&self, fallback: &Fallback, is_dyed: bool) -> Rgb
    {
        if is_dyed
        {
            self.dyed(fallback)
        }
        else
        {
            self.wire(fallback)
        }
    }
}

impl Property<ColorConfig> for ColorConfig
{
    fn property<'a>(&'a self, _fallback: &'a Fallback) -> &'a ColorConfig
    {
        self
    }
}
impl<C> Property<ColorConfig> for C
where
    C: Property<Config>
{
    fn property<'a>(&'a self, fallback: &'a Fallback) -> &'a ColorConfig
    {
        self.property(fallback).color(fallback)
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Color(Rgb);

#[cfg(feature = "artwork")]
impl Deref for Color
{
    type Target = Rgb;

    fn deref(&self) -> &Self::Target
    {
        &self.0
    }
}

impl Display for Color
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
    {
        let Self(Rgb(r, g, b)) = self;
        
        write!(f, "{r:02X}{g:02X}{b:02X}")
    }
}

#[derive(Debug)]
pub enum ParseColorError {
    ParseInt {
        data: String,
        error: ParseIntError
    },
    Transparent {
        data: String,
        rgba: u32
    }
}
impl Display for ParseColorError
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
    {
        match self
        {
            Self::ParseInt { data, error } => write!(f, "Unable to parse RBG hexadecimal color '{data}'. {error}"),
            Self::Transparent { data, rgba } => write!(f, "RGB color '{data}' a.k.a. #{rgba:#010x} cannot have alpha-channel.")
        }
    }
}

impl FromStr for Color
{
    type Err = ParseColorError;

    fn from_str(src: &str) -> Result<Self, Self::Err>
    {
        let rgb = u32::from_str_radix(src, 16).map_err(|error| ParseColorError::ParseInt { data: src.to_string(), error })?;

        if rgb > 0xFFFFFF
        {
            return Err(ParseColorError::Transparent { data: src.to_string(), rgba: rgb })
        }

        Ok(Self(Rgb((rgb >> 16) as u8, (rgb >> 8) as u8, rgb as u8)))
    }
}

impl Serialize for Color
{
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer
    {
        self.to_string().serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for Color
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>
    {
        Self::from_str(&String::deserialize(deserializer)?).map_err(|err| serde::de::Error::custom(err.to_string()))
    }
}

#[cfg(test)]
mod test
{
    use core::str::FromStr;

    use super::Rgb;

    use crate::config::color_config::Color;

    #[test]
    fn test_hex()
    {
        let rgb = Color(Rgb(0, 10, 0));

        let hex = rgb.to_string();

        println!("{}", hex);

        let rgb_decoded = Color::from_str(&hex).unwrap();

        assert_eq!(rgb, rgb_decoded)
    }
}