/*
 * Copyright 2019-2026 elementary, Inc. (https://elementary.io)
 * Copyright 2011–2013 Robert Dyer
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */

namespace Gala.Drawing {
    /**
    * A class containing an RGBA color and methods for more powerful color manipulation.
    */
    public class Color : GLib.Object {
#if !HAS_MUTTER47
        public const Clutter.Color LIGHT_BACKGROUND = { 250, 250, 250, 255};
        public const Clutter.Color DARK_BACKGROUND = { 51, 51, 51, 255};
        public const Clutter.Color LIGHT_BORDER = { 0, 0, 0, 51};
        public const Clutter.Color DARK_BORDER = { 0, 0, 0, 191};
        public const Clutter.Color LIGHT_HIGHLIGHT = { 255, 255, 255, 255};
        public const Clutter.Color DARK_HIGHLIGHT = { 255, 255, 255, 13};
        public const Clutter.Color TOOLTIP_BACKGROUND = { 26, 26, 26, 230};
        public const Clutter.Color TOOLTIP_TEXT_COLOR = { 255, 255, 255, 255};
        public const Clutter.Color TOOLTIP_TEXT_SHADOW_COLOR = { 0, 0, 0, 153};
#else
        public const Cogl.Color LIGHT_BACKGROUND = { 250, 250, 250, 255};
        public const Cogl.Color DARK_BACKGROUND = { 51, 51, 51, 255};
        public const Cogl.Color LIGHT_BORDER = { 0, 0, 0, 51};
        public const Cogl.Color DARK_BORDER = { 0, 0, 0, 191};
        public const Cogl.Color LIGHT_HIGHLIGHT = { 255, 255, 255, 255};
        public const Cogl.Color DARK_HIGHLIGHT = { 255, 255, 255, 13};
        public const Cogl.Color TOOLTIP_BACKGROUND = { 26, 26, 26, 230};
        public const Cogl.Color TOOLTIP_TEXT_COLOR = { 255, 255, 255, 255};
        public const Cogl.Color TOOLTIP_TEXT_SHADOW_COLOR = { 0, 0, 0, 153};
#endif

        /**
        * The value of the red channel, with 0 being the lowest value and 1.0 being the greatest value.
        */
        public double R; // vala-lint=naming-convention

        /**
        * The value of the green channel, with 0 being the lowest value and 1.0 being the greatest value.
        */
        public double G; // vala-lint=naming-convention

        /**
        * The value of the blue channel, with 0 being the lowest value and 1.0 being the greatest value.
        */
        public double B; // vala-lint=naming-convention

        /**
        * The value of the alpha channel, with 0 being the lowest value and 1.0 being the greatest value.
        */
        public double A; // vala-lint=naming-convention

        /**
        * Extracts the alpha value from the integer value
        * serialized by {@link Gala.Drawing.Color.to_int}.
        *
        * @return the alpha channel value as a uint8 ranging from 0 - 255.
        */
        public static uint8 alpha_from_int (int color) {
            return (uint8)((color >> 24) & 0xFF);
        }

        /**
        * Extracts the red value from the integer value
        * serialized by {@link Gala.Drawing.Color.to_int}.
        *
        * @return the red channel value as a uint8 ranging from 0 - 255.
        */
        public static uint8 red_from_int (int color) {
            return (uint8)((color >> 16) & 0xFF);
        }

        /**
        * Extracts the green value from the integer value
        * serialized by {@link Gala.Drawing.Color.to_int}.
        *
        * @return the green channel value as a uint8 ranging from 0 - 255.
        */
        public static uint8 green_from_int (int color) {
            return (uint8)((color >> 8) & 0xFF);
        }

        /**
        * Extracts the blue value from the integer value
        * serialized by {@link Gala.Drawing.Color.to_int}.
        *
        * @return the blue channel value as a uint8 ranging from 0 - 255.
        */
        public static uint8 blue_from_int (int color) {
            return (uint8)(color & 0xFF);
        }

        /**
        * Constructs a new {@link Gala.Drawing.Color} with the supplied values.
        *
        * @param R the value of the red channel as a double
        * @param G the value of the green channel as a double
        * @param B the value of the blue channel as a double
        * @param A the value of the alpha channel as a double
        */
        public Color (double R, double G, double B, double A) { // vala-lint=naming-convention
            this.R = R;
            this.G = G;
            this.B = B;
            this.A = A;
        }

        /**
        * Constructs a new {@link Gala.Drawing.Color} from a string.
        *
        * The string can be either one of:
        *
        * * A standard name (Taken from the X11 rgb.txt file).
        * * A hexadecimal value in the form “#rgb”, “#rrggbb”, “#rrrgggbbb” or ”#rrrrggggbbbb”
        * * A RGB color in the form “rgb(r,g,b)” (In this case the color will have full opacity)
        * * A RGBA color in the form “rgba(r,g,b,a)”
        *
        * For more details on formatting and how this function works see {@link Clutter.Color.from_string}
        *
        * @param color the string specifying the color
        */
        public Color.from_string (string color) {
#if !HAS_MUTTER47
            var parsed_color = Clutter.Color.from_string (color);
#else
            var parsed_color = Cogl.Color.from_string (color);
#endif
            R = parsed_color.red / 255.0;
            G = parsed_color.green / 255.0;
            B = parsed_color.blue / 255.0;
            A = parsed_color.alpha / 255.0;
        }

        /**
        * Constructs a new {@link Gala.Drawing.Color} from an integer.
        *
        * This constructor should be used when deserializing the previously serialized
        * color by {@link Gala.Drawing.Color.to_int}.
        *
        * For more details on what format the color integer representation has, see {@link Gala.Drawing.Color.to_int}.
        *
        * If you would like to deserialize the A, R, G and B values from the integer without
        * creating a new instance of {@link Gala.Drawing.Color}, you can use the available
        * *_from_int static method collection such as {@link Gala.Drawing.Color.alpha_from_int}.
        *
        * @param color the integer specifying the color
        */
        public Color.from_int (int color) {
            R = (double)red_from_int (color) / (double)uint8.MAX;
            G = (double)green_from_int (color) / (double)uint8.MAX;
            B = (double)blue_from_int (color) / (double)uint8.MAX;
            A = (double)alpha_from_int (color) / (double)uint8.MAX;
        }

        /**
        * Changes the hue of this color to the supplied one.
        *
        * @param hue the hue to change this color to
        *
        * @return the new {@link Gala.Drawing.Color}
        */
        public Color set_hue (double hue) requires (hue >= 0 && hue <= 360) {
            double h, s, v;
            rgb_to_hsv (R, G, B, out h, out s, out v);
            h = hue;
            hsv_to_rgb (h, s, v, out R, out G, out B);

            return this;
        }

        /**
        * Changes the saturation of this color to the supplied one.
        *
        * @param sat the saturation to change this color to
        *
        * @return the new {@link Gala.Drawing.Color}
        */
        public Color set_sat (double sat) requires (sat >= 0 && sat <= 1) {
            double h, s, v;
            rgb_to_hsv (R, G, B, out h, out s, out v);
            s = sat;
            hsv_to_rgb (h, s, v, out R, out G, out B);

            return this;
        }

        /**
        * Changes the value of this color to the supplied one.
        *
        * @param val the value to change this color to
        *
        * @return the new {@link Gala.Drawing.Color}
        */
        public Color set_val (double val) requires (val >= 0 && val <= 1) {
            double h, s, v;
            rgb_to_hsv (R, G, B, out h, out s, out v);
            v = val;
            hsv_to_rgb (h, s, v, out R, out G, out B);

            return this;
        }

        /**
        * Changes the value of the alpha channel.
        *
        * @param alpha the value of the alpha channel
        *
        * @return the new {@link Gala.Drawing.Color}
        */
        public Color set_alpha (double alpha) requires (alpha >= 0 && alpha <= 1) {
            A = alpha;
            return this;
        }

        /**
        * Get the value.
        * @return the hue of this color, as a double value
        */
        public double get_hue () {
            double h, s, v;
            rgb_to_hsv (R, G, B, out h, out s, out v);
            return h;
        }

        /**
        * Get the value.
        * @return the saturation of this color, as a double value
        */
        public double get_sat () {
            double h, s, v;
            rgb_to_hsv (R, G, B, out h, out s, out v);
            return s;
        }

        /**
        * Get the value.
        *
        * @return the value of this color, as a double value
        */
        public double get_val () {
            double h, s, v;
            rgb_to_hsv (R, G, B, out h, out s, out v);
            return v;
        }

        /**
        * Adds the supplied hue value to this color's hue value.
        *
        * @param val the hue to add to this color's hue
        *
        * @return the new {@link Gala.Drawing.Color}
        */
        public Color add_hue (double val) {
            double h, s, v;
            rgb_to_hsv (R, G, B, out h, out s, out v);
            h = (((h + val) % 360) + 360) % 360;
            hsv_to_rgb (h, s, v, out R, out G, out B);

            return this;
        }

        /**
        * Changes this color's saturation to the supplied saturation, if it is greater than this color's saturation.
        *
        * @param sat the saturation to change this color to
        *
        * @return the new {@link Gala.Drawing.Color}
        */
        public Color set_min_sat (double sat) requires (sat >= 0 && sat <= 1) {
            double h, s, v;
            rgb_to_hsv (R, G, B, out h, out s, out v);
            s = double.max (s, sat);
            hsv_to_rgb (h, s, v, out R, out G, out B);

            return this;
        }

        /**
        * Changes this color's value to the supplied value, if it is greater than this color's value.
        *
        * @param val the value to change this color to
        *
        * @return the new {@link Gala.Drawing.Color}
        */
        public Color set_min_value (double val) requires (val >= 0 && val <= 1) {
            double h, s, v;
            rgb_to_hsv (R, G, B, out h, out s, out v);
            v = double.max (v, val);
            hsv_to_rgb (h, s, v, out R, out G, out B);

            return this;
        }

        /**
        * Changes this color's saturation to the supplied saturation, if it is smaller than this color's saturation.
        *
        * @param sat the hue to change this color to
        *
        * @return the new {@link Gala.Drawing.Color}
        */
        public Color set_max_sat (double sat) requires (sat >= 0 && sat <= 1) {
            double h, s, v;
            rgb_to_hsv (R, G, B, out h, out s, out v);
            s = double.min (s, sat);
            hsv_to_rgb (h, s, v, out R, out G, out B);

            return this;
        }

        /**
        * Changes this color's value to the supplied value, if it is smaller than this color's value.
        *
        * @param val the value to change this color to
        *
        * @return the new {@link Gala.Drawing.Color}
        */
        public Color set_max_val (double val) requires (val >= 0 && val <= 1) {
            double h, s, v;
            rgb_to_hsv (R, G, B, out h, out s, out v);
            v = double.min (v, val);
            hsv_to_rgb (h, s, v, out R, out G, out B);

            return this;
        }

        /**
        * Multiplies this color's saturation by the supplied amount.
        *
        * @param amount the amount to multiply the saturation by
        *
        * @return the new {@link Gala.Drawing.Color}
        */
        public Color multiply_sat (double amount) requires (amount >= 0) {
            double h, s, v;
            rgb_to_hsv (R, G, B, out h, out s, out v);
            s = double.min (1, s * amount);
            hsv_to_rgb (h, s, v, out R, out G, out B);

            return this;
        }

        /**
        * Brightens this color's value by the supplied amount.
        *
        * @param amount the amount to brighten the value by
        *
        * @return the new {@link Gala.Drawing.Color}
        */
        public Color brighten_val (double amount) requires (amount >= 0 && amount <= 1) {
            double h, s, v;
            rgb_to_hsv (R, G, B, out h, out s, out v);
            v = double.min (1, v + (1 - v) * amount);
            hsv_to_rgb (h, s, v, out R, out G, out B);

            return this;
        }

        /**
        * Darkens this color's value by the supplied amount.
        *
        * @param amount the amount to darken the value by
        *
        * @return the new {@link Gala.Drawing.Color}
        */
        public Color darken_val (double amount) requires (amount >= 0 && amount <= 1) {
            double h, s, v;
            rgb_to_hsv (R, G, B, out h, out s, out v);
            v = double.max (0, v - (1 - v) * amount);
            hsv_to_rgb (h, s, v, out R, out G, out B);

            return this;
        }

        /**
        * Darkens this color's value by the supplied amount * color's saturation.
        *
        * @param amount the amount to darken the value by
        *
        * @return the new {@link Gala.Drawing.Color}
        */
        public Color darken_by_sat (double amount) requires (amount >= 0 && amount <= 1) {
            double h, s, v;
            rgb_to_hsv (R, G, B, out h, out s, out v);
            v = double.max (0, v - amount * s);
            hsv_to_rgb (h, s, v, out R, out G, out B);

            return this;
        }

        private void rgb_to_hsv (
            double r, double g, double b, out double h, out double s, out double v
        ) requires (r >= 0 && r <= 1) requires (g >= 0 && g <= 1) requires (b >= 0 && b <= 1) {
            var min = double.min (r, double.min (g, b));
            var max = double.max (r, double.max (g, b));

            v = max;
            if (v == 0) {
                h = 0;
                s = 0;
                return;
            }

            // normalize value to 1
            r /= v;
            g /= v;
            b /= v;

            min = double.min (r, double.min (g, b));
            max = double.max (r, double.max (g, b));

            var delta = max - min;
            s = delta;
            if (s == 0) {
                h = 0;
                return;
            }

            // normalize saturation to 1
            r = (r - min) / delta;
            g = (g - min) / delta;
            b = (b - min) / delta;

            if (max == r) {
                h = 0 + 60 * (g - b);
                if (h < 0)
                    h += 360;
            } else if (max == g) {
                h = 120 + 60 * (b - r);
            } else {
                h = 240 + 60 * (r - g);
            }
        }

        private void hsv_to_rgb (
            double h, double s, double v, out double r, out double g, out double b
        ) requires (h >= 0 && h <= 360) requires (s >= 0 && s <= 1) requires (v >= 0 && v <= 1) {
            r = 0;
            g = 0;
            b = 0;

            if (s == 0) {
                r = v;
                g = v;
                b = v;
            } else {
                var sec_num = (int) Math.floor (h / 60);
                var frac_sec = h / 60.0 - sec_num;

                var p = v * (1 - s);
                var q = v * (1 - s * frac_sec);
                var t = v * (1 - s * (1 - frac_sec));

                switch (sec_num) {
                case 0:
                    r = v;
                    g = t;
                    b = p;
                    break;
                case 1:
                    r = q;
                    g = v;
                    b = p;
                    break;
                case 2:
                    r = p;
                    g = v;
                    b = t;
                    break;
                case 3:
                    r = p;
                    g = q;
                    b = v;
                    break;
                case 4:
                    r = t;
                    g = p;
                    b = v;
                    break;
                case 5:
                    r = v;
                    g = p;
                    b = q;
                    break;
                }
            }
        }

        /**
        * {@inheritDoc}
        */
        public string settings_serialize () {
            return "%d;;%d;;%d;;%d".printf ((int) (R * uint8.MAX),
                (int) (G * uint8.MAX),
                (int) (B * uint8.MAX),
                (int) (A * uint8.MAX));
        }

        /**
        * {@inheritDoc}
        */
        public void settings_deserialize (string s) {
            var parts = s.split (";;");

            R = double.min (uint8.MAX, double.max (0, int.parse (parts [0]))) / uint8.MAX;
            G = double.min (uint8.MAX, double.max (0, int.parse (parts [1]))) / uint8.MAX;
            B = double.min (uint8.MAX, double.max (0, int.parse (parts [2]))) / uint8.MAX;
            A = double.min (uint8.MAX, double.max (0, int.parse (parts [3]))) / uint8.MAX;
        }

        /**
        * Returns a textual specification of this in the form `rgb (r, g, b)` or `rgba (r, g, b, a)`,
        * where “r”, “g”, “b” and “a” represent the red, green, blue and alpha values respectively.
        *
        * r, g, and b are represented as integers in the range 0 to 255, and a is represented as
        * floating point value in the range 0 to 1.
        *
        * Note: that this string representation may lose some precision, since r, g and b are represented
        * as 8-bit integers. If this is a concern, you should use a different representation.
        *
        * This returns the same string as a {@link Clutter.Color} would return in {@link Clutter.Color.to_string}
        *
        * @return the text string
        */
        public string to_string () {
#if !HAS_MUTTER47
            Clutter.Color color = { (uint8) (R * 255), (uint8) (G * 255), (uint8) (B * 255), (uint8) (A * 255) };
#else
            Cogl.Color color = { (uint8) (R * 255), (uint8) (G * 255), (uint8) (B * 255), (uint8) (A * 255) };
#endif
            return color.to_string ();
        }

        /**
        * Converts this to a 32 bit integer.
        *
        * This function can be useful for serializing the color so that it can be stored
        * and retrieved easily with hash tables and lists.
        *
        * The returned integer will contain the four channels
        * that define the {@link Gala.Drawing.Color} class: alpha, red, green and blue.
        *
        * Each channel is represented by 8 bits.
        * The first 8 bits of the integer contain the alpha channel while all other 24 bits represent
        * red, green and blue channels respectively.
        *
        * The format written as a string would look like this:
        *
        * //AAAAAAAA RRRRRRRR GGGGGGGG BBBBBBBB//
        *
        * where //A// is one bit of alpha channel, //R// of red channel, //G// of green channel and //B// of blue channel.
        *
        * @return a 32 bit integer representing this
        */
        public int to_int () {
            uint8 red = (uint8)(R * uint8.MAX);
            uint8 green = (uint8)(G * uint8.MAX);
            uint8 blue = (uint8)(B * uint8.MAX);
            uint8 alpha = (uint8)(A * uint8.MAX);

            return (alpha << 24) | (red << 16) | (green << 8) | blue;
        }
    }
}
