use proportion::*;


#[derive(Copy, Clone, Default, PartialEq, Eq, Hash)]
pub struct Colour {
    /// Colour encoded as `0xAARRGGBB`.
    pub value: u32,
}

impl Colour {
    /// Construct a Colour from an RGB u32.
    pub const fn rgb(value: u32) -> Self {
        Self {
            value: value | 0xff000000,
        }
    }

    /// Construct a Colour from an RGBA u32.
    pub const fn rgba(value: u32) -> Self {
        Self { value }
    }

    /// Construct a Colour from a brightness value.
    pub const fn grey(value: u32) -> Self {
        Self::rgb((value & 0xf) * 0x111111)
    }

    /// Construct a Colour from separate red, green, and blue channel values.
    pub const fn from_rgb(red: u8, green: u8, blue: u8) -> Self {
        let mut value = 0x0000ff00;
        value |= red as u32;
        value <<= 8; value |= green as u32;
        value <<= 8; value |= blue as u32;
        Self { value }
    }

    /// Construct a Colour from separate red, green, blue, and alpha channel values.
    pub const fn from_rgba(red: u8, green: u8, blue: u8, alpha: u8) -> Self {
        let mut value = alpha  as u32;
        value <<= 8; value |= red   as u32;
        value <<= 8; value |= green as u32;
        value <<= 8; value |= blue  as u32;
        Self { value }
    }

    /// Construct a colour by blending two colours by a proportion.
    pub fn mix<I: proportion::Internal>(bg: Self, fg: Self, proportion: Proportion<I>) -> Self {
        let proportion = Proportion::<u8>::new(proportion.as_u8());
        let bg_mix = proportion.invert();
        let fg_mix = proportion;
        let red = (bg.red() * bg_mix) + (fg.red() * fg_mix);
        let green = (bg.green() * bg_mix) + (fg.green() * fg_mix);
        let blue = (bg.blue() * bg_mix) + (fg.blue() * fg_mix);
        Self::from_rgb(red, green, blue)
    }

    /// Construct a colour by blending two colours by the alpha of the fg colour.
    pub fn mix_with_alpha(bg: Self, fg: Self) -> Self {
        let proportion = Proportion::new(fg.alpha());
        let bg_mix = proportion.invert();
        let fg_mix = proportion;
        let red = (bg.red() * bg_mix) + (fg.red() * fg_mix);
        let green = (bg.green() * bg_mix) + (fg.green() * fg_mix);
        let blue = (bg.blue() * bg_mix) + (fg.blue() * fg_mix);
        Self::from_rgb(red, green, blue)
    }

    /// Get an array of red, green, and blue channel values.
    pub fn as_rgb_array(&self) -> [u8; 3] {
        [self.red(), self.green(), self.blue()]
    }

    /// Get an array of red, green, blue, and alpha channel values.
    pub fn as_rgba_array(&self) -> [u8; 4] {
        [self.red(), self.green(), self.blue(), self.alpha()]
    }

    pub fn as_rgb_hex(&self) -> String {
        format!("{:02x}{:02x}{:02x}",
            self.red(), self.green(), self.blue())
    }

    pub fn as_rgba_hex(&self) -> String {
        format!("{:02x}{:02x}{:02x}{:02x}",
            self.red(), self.green(), self.blue(), self.alpha())
    }

    pub fn red(&self) -> u8 {
        (self.value >> 16) as u8
    }

    pub fn green(&self) -> u8 {
        (self.value >> 8) as u8
    }

    pub fn blue(&self) -> u8 {
        self.value as u8
    }

    pub fn alpha(&self) -> u8 {
        (self.value >> 24) as u8
    }

    pub fn is_opaque(&self) -> bool {
        self.alpha() == 0xff
    }

    pub fn with_red(&self, red: u8) -> Self {
        Colour::rgba((self.value & 0xffffff00) | (red as u32) << 16)
    }

    pub fn with_green(&self, green: u8) -> Self {
        Colour::rgba((self.value & 0xffff00ff) | (green as u32) << 8)
    }

    pub fn with_blue(&self, blue: u8) -> Self {
        Colour::rgba((self.value & 0xff00ffff) | blue as u32)
    }

    pub fn with_alpha(&self, alpha: u8) -> Self {
        Colour::rgba((self.value & 0x00ffffff) | (alpha as u32) << 24)
    }

    pub fn set_red(&mut self, red: u8) {
        self.value = (self.value & 0xffffff00) | (red as u32) << 16
    }

    pub fn set_green(&mut self, green: u8) {
        self.value = (self.value & 0xffff00ff) | (green as u32) << 8
    }

    pub fn set_blue(&mut self, blue: u8) {
        self.value = (self.value & 0xff00ffff) | blue as u32
    }

    pub fn set_alpha(&mut self, alpha: u8) {
        self.value = (self.value & 0x00ffffff) | (alpha as u32) << 24
    }
}

impl Colour {
    pub const TRANSPARENT: Self = Self::rgba(0x00000000);
    pub const WHITE: Self = Self::rgb(0xffffff);
    pub const BLACK: Self = Self::rgb(0x000000);

    pub const LIGHT_GREY: Self = Self::rgb(0xc0c0c0);
    pub const MID_GREY: Self = Self::rgb(0x808080);
    pub const DARK_GREY: Self = Self::rgb(0x404040);

    pub const RED: Self = Self::rgb(0xff0000);
    pub const GREEN: Self = Self::rgb(0x00ff00);
    pub const BLUE: Self = Self::rgb(0x0000ff);

    pub const YELLOW: Self = Self::rgb(0xffff00);
    pub const MAGENTA: Self = Self::rgb(0xff00ff);
    pub const CYAN: Self = Self::rgb(0x00ffff);

    pub const ORANGE: Self = Self::rgb(0xff8000);
    pub const PURPLE: Self = Self::rgb(0x800080);
    pub const TEAL: Self = Self::rgb(0x008080);
}

impl From<u32> for Colour {
    fn from(value: u32) -> Self {
        Colour { value }
    }
}

impl From<Colour> for u32 {
    fn from(colour: Colour) -> Self {
        colour.value
    }
}

impl AsRef<u32> for Colour {
    fn as_ref(&self) -> &u32 {
        &self.value
    }
}

impl std::fmt::Debug for Colour {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        write!(f,
            "Colour {{ r:{} g:{} b:{}, a:{} }}",
            self.red(),
            self.green(),
            self.blue(),
            self.alpha()
        )
    }
}

impl std::fmt::Display for Colour {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        write!(f,
            "#{:02x}{:02x}{:02x}{:02x}",
            self.red(),
            self.green(),
            self.blue(),
            self.alpha()
        )
    }
}