Documentation
pub type Hex = u32;
pub type Rgb = (u8, u8, u8);
pub type Rgba = (u8, u8, u8, f32);

pub type Result<T> = ::std::result::Result<T, Error>;

#[derive(Debug)]
#[derive(::thiserror::Error)]
pub enum Error {
    #[error("{0}")]
    TryFromIntError(#[from] ::std::num::TryFromIntError),
    #[error("Str too short on conversion.")]
    StrTooShortOnConversion,
    #[error("Str too long on conversion.")]
    StrTooLongOnConversion,
    #[error("Illegal char on conversion.")]
    IllegalCharOnConversion,
    #[error("Value out of 24-bit rgb range.")]
    ValueOutOf24BitRGBRange
}

#[derive(Debug)]
#[derive(Clone)]
#[derive(PartialEq)]
pub enum Color {
    Hex(Hex),
    Rgb(Rgb),
    Rgba(Rgba)
}

impl Color {
    pub const fn from_rgb(raw: Rgb) -> Self {
        let r = raw.0;
        let g = raw.1;
        let b = raw.2;
        Self::Rgb((r, g, b))
    }

    pub const fn from_rgba(raw: Rgba) -> Self {
        let r = raw.0;
        let g = raw.1;
        let b = raw.2;
        let a = raw.3.clamp(0.0, 1.0);
        Self::Rgba((r, g, b, a))
    }

    pub const fn from_hex(raw: Hex) -> Self {
        if raw > 0xffffff {
            panic!("[ABORT] Value out of 24-bit RGB range");
        }
        Self::Hex(raw)
    }

    pub fn from_hex_rep(rep: &str) -> Result<Self> {
        let s: &str = rep.trim_start_matches('#');
        if s.len() != 6 {
            return Err(if s.len() < 6 {
                Error::StrTooShortOnConversion
            } else {
                Error::StrTooLongOnConversion
            })
        }
        for c in s.chars() {
            Self::only_hex_char(c)?;
        }
        let ret: u32 = u32::from_str_radix(s, 16).unwrap();
        Ok(Self::Hex(ret))
    }

    fn only_hex_char(c: char) -> Result<char> {
        if !c.is_ascii_hexdigit() {
            return Err(Error::IllegalCharOnConversion)
        }
        Ok(c)
    }

    pub const fn hex(&self) -> u32 {
        match self {
            Self::Hex(code) => *code,
            Self::Rgb((r, g, b)) | Self::Rgba((r, g, b, _)) => ((*r as u32) << 16) | ((*g as u32) << 8) | (*b as u32)
        }
    }

    pub fn hex_rep(&self) -> String {
        let hex: u32 = self.hex();
        format!("#{:06X}", hex)
    }

    pub const fn rgb(&self) -> (u8, u8, u8) {
        match self {
            Self::Hex(code) => (
                ((*code >> 16) & 0xff) as u8,
                ((*code >> 8) & 0xff) as u8,
                (*code & 0xff) as u8
            ),
            Self::Rgb((r, g, b)) | Self::Rgba((r, g, b, _)) => (*r, *g, *b)
        }
    }

    pub fn rgb_rep(&self) -> String {
        let (r, g, b) = self.rgb();
        format!("rgb({}, {}, {})", r, g, b)
    }

    pub fn rgba(&self) -> (u8, u8, u8, f32) {
        match self {
            Self::Hex(code) => (
                ((*code >> 16) & 0xff) as u8,
                ((*code >> 8) & 0xff) as u8,
                (*code & 0xff) as u8,
                1.0
            ),
            Self::Rgb((r, g, b)) => (*r, *g, *b, 1.0),
            Self::Rgba((r, g, b, a)) => (*r, *g, *b, *a)
        }
    }

    pub fn rgba_rep(&self) -> String {
        let (r, g, b, a) = self.rgba();
        format!("rgba({}, {}, {}, {})", r, g, b, a)
    }

    pub fn interpolate(&self, rhs: Self, t: f32) -> Self {
        let (rx, gx, bx, ax) = self.rgba();
        let (ry, gy, by, ay) = rhs.rgba();
        let r: u8 = (rx as f32 + (ry as f32 - rx as f32) * t).round() as u8;
        let g: u8 = (gx as f32 + (gy as f32 - gx as f32) * t).round() as u8;
        let b: u8 = (bx as f32 + (by as f32 - bx as f32) * t).round() as u8;
        let a: f32 = ax + (ay - ax) * t;
        Self::Rgba((r, g, b, a))
    }
}

impl Default for Color {
    fn default() -> Self {
        Self::from_hex(0xffffff)
    }
}

impl TryFrom<String> for Color {
    type Error = Error;

    fn try_from(value: String) -> ::std::result::Result<Self, Self::Error> {
        let ret: &str = &value;
        let ret: Self = ret.try_into()?;
        Ok(ret)
    }
}

impl TryFrom<&str> for Color {
    type Error = Error;

    fn try_from(value: &str) -> ::std::result::Result<Self, Self::Error> {
        let s = value.trim_start_matches('#');
        if s.len() != 6 {
            return Err(if s.len() < 6 {
                Error::StrTooShortOnConversion
            } else {
                Error::StrTooLongOnConversion
            })
        }
        for c in s.chars() {
            Self::only_hex_char(c)?;
        }
        let ret = u32::from_str_radix(s, 16).unwrap();
        Ok(Self::Hex(ret))
    }
}

impl TryFrom<Hex> for Color {
    type Error = Error;

    fn try_from(value: Hex) -> ::std::result::Result<Self, Self::Error> {
        if value > 0xffffff {
            return Err(Error::ValueOutOf24BitRGBRange)
        }
        Ok(Self::Hex(value))
    }
}

impl From<Rgb> for Color {
    fn from(value: Rgb) -> Self {
        Self::from_rgb(value)
    }
}

impl From<Rgba> for Color {
    fn from(value: Rgba) -> Self {
        Self::from_rgba(value)
    }
}

impl ::std::fmt::Display for Color {
    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
        match self {
            Self::Hex(code) => write!(f, "#{:06X}", code),
            Self::Rgb((r, g, b)) => write!(f, "rgb({}, {}, {})", r, g, b),
            Self::Rgba((r, g, b, a)) => write!(f, "rgba({}, {}, {}, {:.2})", r, g, b, a)
        }
    }
}