//! Tools for configuring the look of the compositor.

use serde::{Deserialize, Serialize};

/// A color.
///
/// When specifying RGBA values of a color, the RGB values can either be specified
/// *straight* or *premultiplied*. Premultiplied means that the RGB values have already
/// been multiplied by the alpha value.
///
/// Given a color, to reduce its opacity by half,
///
/// - if you're working with premultiplied values, you would multiply each component by `0.5`;
/// - if you're working with straight values, you would multiply only the alpha component by `0.5`.
///
/// When using hexadecimal notation, `#RRGGBBAA`, the RGB values are usually straight.
// values are stored premultiplied
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
pub struct Color {
    r: f32,
    g: f32,
    b: f32,
    a: f32,
}

fn to_f32(c: u8) -> f32 {
    c as f32 / 255f32
}

fn to_u8(c: f32) -> u8 {
    (c * 255f32) as u8
}

fn validate_f32(f: f32) -> bool {
    f >= 0.0 && f <= 1.0
}

fn validate_f32_all(f: [f32; 4]) -> bool {
    if !f.into_iter().all(validate_f32) {
        log::warn!(
            "f32 values {:?} are not in the valid color range. Using solid black instead xyz",
            f
        );
        return false;
    }
    true
}

impl Color {
    /// Solid black.
    pub const BLACK: Self = Self {
        r: 0.0,
        g: 0.0,
        b: 0.0,
        a: 1.0,
    };

    /// Creates a new color from `u8` RGB values.
    pub fn new(r: u8, g: u8, b: u8) -> Self {
        Self {
            r: to_f32(r),
            g: to_f32(g),
            b: to_f32(b),
            a: 1.0,
        }
    }

    /// Creates a new color from straight `u8` RGBA values.
    pub fn new_straight(r: u8, g: u8, b: u8, a: u8) -> Self {
        Self::new_f32_straight(to_f32(r), to_f32(g), to_f32(b), to_f32(a))
    }

    /// Creates a new color from premultiplied `f32` RGBA values.
    pub fn new_f32_premultiplied(r: f32, g: f32, b: f32, a: f32) -> Self {
        if !validate_f32_all([r, g, b, a]) {
            Self::BLACK
        } else if r > a || g > a || b > a {
            log::warn!(
                "f32 values {:?} are not valid for a premultiplied color. Using solid black instead.",
                [r, g, b, a]
            );
            Self::BLACK
        } else {
            Self { r, g, b, a }
        }
    }

    /// Creates a new color from straight `f32` RGBA values.
    pub fn new_f32_straight(r: f32, g: f32, b: f32, a: f32) -> Self {
        if !validate_f32_all([r, g, b, a]) {
            Self::BLACK
        } else {
            Self {
                r: r * a,
                g: g * a,
                b: b * a,
                a,
            }
        }
    }

    /// Creates a new color from `f32` RGB values.
    pub fn new_f32(r: f32, g: f32, b: f32) -> Self {
        Self { r, g, b, a: 1.0 }
    }

    /// Converts the color to its premultiplied `f32` RGBA values.
    pub fn to_f32_premultiplied(&self) -> [f32; 4] {
        [self.r, self.g, self.b, self.a]
    }

    /// Converts the color to its straight `f32` RGBA values.
    pub fn to_f32_straight(&self) -> [f32; 4] {
        if self.a == 0.0 {
            [0.0, 0.0, 0.0, 0.0]
        } else {
            let a = self.a;
            [self.r / a, self.g / a, self.b / a, a]
        }
    }

    /// Converts the color to its straight `u8` RGBA values.
    pub fn to_u8_straight(&self) -> [u8; 4] {
        let [r, g, b, a] = self.to_f32_straight();
        [to_u8(r), to_u8(g), to_u8(b), to_u8(a)]
    }
}

/// Resets all sizes to their defaults.
pub fn reset_sizes() {
    get!().reset_sizes();
}

/// Resets all colors to their defaults.
pub fn reset_colors() {
    get!().reset_colors();
}

/// Returns the current font.
pub fn get_font() -> String {
    get!().get_font()
}

/// Sets the font.
///
/// Default: `monospace 8`.
///
/// See also [`set_bar_font`] and [`set_title_font`].
///
/// The font name should be specified in [pango][pango] syntax.
///
/// [pango]: https://docs.gtk.org/Pango/type_func.FontDescription.from_string.html
pub fn set_font(font: &str) {
    get!().set_font(font)
}

/// Sets the font used by the bar.
///
/// If this function is not called, the font set by [`set_font`] is used. See that
/// function for more details.
pub fn set_bar_font(font: &str) {
    get!().set_bar_font(font)
}

/// Sets the font used by window titles.
///
/// If this function is not called, the font set by [`set_font`] is used. See that
/// function for more details.
pub fn set_title_font(font: &str) {
    get!().set_title_font(font)
}

/// Resets the fonts to the defaults.
///
/// Currently the default is `monospace 8`.
pub fn reset_font() {
    get!().reset_font()
}

#[non_exhaustive]
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Default)]
pub enum BarPosition {
    #[default]
    Top,
    Bottom,
}

/// Sets the position of the bar.
///
/// Default: `Top`.
pub fn set_bar_position(position: BarPosition) {
    get!().set_bar_position(position);
}

/// Gets the position of the bar.
pub fn get_bar_position() -> BarPosition {
    get!(BarPosition::Top).get_bar_position()
}

/// Sets the proportional fonts used by egui windows.
///
/// The default is `["sans-serif", "Noto Sans", "Noto Color Emoji"]`.
pub fn set_egui_proportional_fonts<'a>(fonts: impl IntoIterator<Item = &'a str>) {
    get!().set_egui_fonts(Some(fonts.into_iter().collect()), None);
}

/// Sets the monospace fonts used by egui windows.
///
/// The default is `["monospace", "Noto Sans Mono", "Noto Color Emoji"]`.
pub fn set_egui_monospace_fonts<'a>(fonts: impl IntoIterator<Item = &'a str>) {
    get!().set_egui_fonts(None, Some(fonts.into_iter().collect()));
}

/// Elements of the compositor whose color can be changed.
pub mod colors {
    use {
        crate::theme::Color,
        serde::{Deserialize, Serialize},
    };

    /// An element of the GUI whose color can be changed.
    #[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
    pub struct Colorable(#[doc(hidden)] pub u32);

    impl Colorable {
        /// Sets the color to an RGB value.
        pub fn set(self, r: u8, g: u8, b: u8) {
            let color = Color::new(r, g, b);
            get!().set_color(self, color);
        }

        /// Sets the color to a `Color` that might contain an alpha component.
        pub fn set_color(self, color: Color) {
            get!().set_color(self, color);
        }

        /// Gets the current color.
        pub fn get(self) -> Color {
            get!(Color::BLACK).get_color(self)
        }
    }

    macro_rules! colors {
        ($($(#[$attr:meta])* const $n:expr => $name:ident,)*) => {
            $(
                $(#[$attr])*
                pub const $name: Colorable = Colorable($n);
            )*
        }
    }

    colors! {
        /// The title background color of an unfocused window.
        ///
        /// Default: `#222222`.
        const 01 => UNFOCUSED_TITLE_BACKGROUND_COLOR,
        /// The title background color of a focused window.
        ///
        /// Default: `#285577`.
        const 02 => FOCUSED_TITLE_BACKGROUND_COLOR,
        /// The title background color of an unfocused window that was the last focused
        /// window in its container.
        ///
        /// Default: `#5f676a`.
        const 03 => FOCUSED_INACTIVE_TITLE_BACKGROUND_COLOR,
        /// The background color of the desktop.
        ///
        /// Default: `#001019`.
        ///
        /// You can use an application such as [swaybg][swaybg] to further customize the background.
        ///
        /// [swaybg]: https://github.com/swaywm/swaybg
        const 04 => BACKGROUND_COLOR,
        /// The background color of the bar.
        ///
        /// Default: `#000000`.
        const 05 => BAR_BACKGROUND_COLOR,
        /// The color of the 1px separator below window titles.
        ///
        /// Default: `#333333`.
        const 06 => SEPARATOR_COLOR,
        /// The color of the border between windows.
        ///
        /// Default: `#3f474a`.
        const 07 => BORDER_COLOR,
        /// The title text color of an unfocused window.
        ///
        /// Default: `#888888`.
        const 08 => UNFOCUSED_TITLE_TEXT_COLOR,
        /// The title text color of a focused window.
        ///
        /// Default: `#ffffff`.
        const 09 => FOCUSED_TITLE_TEXT_COLOR,
        /// The title text color of an unfocused window that was the last focused
        /// window in its container.
        ///
        /// Default: `#ffffff`.
        const 10 => FOCUSED_INACTIVE_TITLE_TEXT_COLOR,
        /// The color of the status text in the bar.
        ///
        /// Default: `#ffffff`.
        const 11 => BAR_STATUS_TEXT_COLOR,
        /// The title background color of an unfocused window that might be captured.
        ///
        /// Default: `#220303`.
        const 12 => CAPTURED_UNFOCUSED_TITLE_BACKGROUND_COLOR,
        /// The title background color of a focused window that might be captured.
        ///
        /// Default: `#772831`.
        const 13 => CAPTURED_FOCUSED_TITLE_BACKGROUND_COLOR,
        /// The title background color of a window that has requested attention.
        ///
        /// Default: `#23092c`.
        const 14 => ATTENTION_REQUESTED_BACKGROUND_COLOR,
        /// Color used to highlight parts of the UI.
        ///
        /// Default: `#9d28c67f`.
        const 15 => HIGHLIGHT_COLOR,
    }

    /// Sets the color of GUI element.
    pub fn set_color(element: Colorable, color: Color) {
        get!().set_color(element, color);
    }

    /// Gets the color of GUI element.
    pub fn get_color(element: Colorable) -> Color {
        get!(Color::BLACK).get_color(element)
    }
}

/// Elements of the compositor whose size can be changed.
pub mod sized {
    use serde::{Deserialize, Serialize};

    /// An element of the GUI whose size can be changed.
    #[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
    pub struct Resizable(#[doc(hidden)] pub u32);

    impl Resizable {
        /// Gets the current size.
        pub fn get(self) -> i32 {
            get!(0).get_size(self)
        }

        /// Sets the size.
        pub fn set(self, size: i32) {
            get!().set_size(self, size)
        }
    }

    macro_rules! sizes {
        ($($(#[$attr:meta])* const $n:expr => $name:ident,)*) => {
            $(
                $(#[$attr])*
                pub const $name: Resizable = Resizable($n);
            )*
        }
    }

    sizes! {
        /// The height of window titles.
        ///
        /// Default: 17
        const 01 => TITLE_HEIGHT,
        /// The width of borders between windows.
        ///
        /// Default: 4
        const 02 => BORDER_WIDTH,
        /// The height of the bar.
        ///
        /// Defaults to the TITLE_HEIGHT if not set explicitly.
        ///
        /// Default: 17
        const 03 => BAR_HEIGHT,
        /// The width of the bar's separator.
        ///
        /// Default: 1
        const 04 => BAR_SEPARATOR_WIDTH,
    }
}
