/*
    This file is part of Msc-generator.
    Copyright (C) 2008-2024 Zoltan Turanyi
    Distributed under GNU Affero General Public License.

    Msc-generator is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Msc-generator is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with Msc-generator.  If not, see <http://www.gnu.org/licenses/>.
*/

/** @file cgen_color.h The declaration of classes for colors.
 * @ingroup libcgencommon_files */

#if !defined(COLOR_H)
#define COLOR_H

#include <map>
#include <string>
#include <string_view>
#include "error.h"

/** Describes a color with transparency. */
struct ColorType {
    /** How to interpret a color value */
    enum EColorType
    {
        INVALID,   ///<An invalid color specification
        COMPLETE,  ///<A completely valid color spec
        OVERLAY    ///<This is meant as an overlay on top of another color
    };

    unsigned char r = 0; ///<Red component
    unsigned char g = 0; ///<Green component
    unsigned char b = 0; ///<Blue component
    unsigned char a = 255; ///<Alpha component governing transparency, 255 is opaque
    EColorType    type = INVALID; ///<The current interpretation
    constexpr ColorType() noexcept = default;
    constexpr ColorType(unsigned char R, unsigned char G, unsigned char B, unsigned char A=255, EColorType t=COMPLETE) noexcept
        : r(R), g(G), b(B), a(A), type(t) {}
    constexpr ColorType(const ColorType &o, EColorType t) noexcept
        : r(o.r), g(o.g), b(o.b), a(o.a), type(t) {}
    explicit ColorType(std::string_view) noexcept;
    /** Converts a 32-bit unsigned value to a color: 0xaarrggbb, same as CAIRO_FORMAT_ARGB32. */
    constexpr explicit ColorType(uint32_t p) noexcept : r((p>>16)&255), g((p>>8)&255), b(p&255), a((p>>24)&255), type(COMPLETE) {}
    /** Converts the color to a 32-bit value: 0xaarrggbb, same as CAIRO_FORMAT_ARGB32. */
    constexpr uint32_t ConvertToUnsigned() const noexcept {return (uint32_t(a)<<24) + (uint32_t(r)<<16) + (uint32_t(g)<<8) + uint32_t(b);}
    /** If we are both invalid or fully transparent, we are equal.
     * Invalid values are always smaller than anything else.
     * Fully transparent values are always smaller than any other color of the same EColorType.
     * Else tie on type, a, r, g, b.*/
    constexpr std::strong_ordering operator <=>(const ColorType& o) const noexcept {
        if (type==INVALID && o.type==INVALID) return std::strong_ordering::equal;
        if (type==INVALID) return std::strong_ordering::less;
        if (o.type==INVALID) return std::strong_ordering::greater;
        if (type<o.type) return std::strong_ordering::less;
        if (type>o.type) return std::strong_ordering::greater;
        if (a==0 && o.a==0) return std::strong_ordering::equal;
        if (a==0) return std::strong_ordering::less;
        if (o.a==0) return std::strong_ordering::greater;
        return std::tie(a, r, g, b)<=>std::tie(o.a, o.r, o.g, o.b);
    }
    constexpr bool operator==(const ColorType& x) const noexcept { return std::is_eq(operator<=>(x)); }
    constexpr bool operator <(const ColorType &x) const noexcept { return std::is_lt(operator<=>(x)); }
    std::string Print(void) const; ///<Prints the color description of color into a string.
    /** Creates a lighter version with same transparency.
     * @param [in] p How much lighter we shall be. Zero means no change, 1 is completely white./
     * @returns The lighter version.*/
    constexpr ColorType Lighter(double p) const noexcept
        {return ColorType((unsigned char)(r+p*(255-r)), (unsigned char)(g+p*(255-g)), (unsigned char)(b+p*(255-b)), (unsigned char)a, type);}
    /** Creates a darker version with same transparency.
     * @param [in] p How much darker we shall be. Zero means no change, 1 is completely black./
     * @returns The lighter version.*/
    constexpr ColorType Darker(double p) const noexcept
        {return ColorType((unsigned char)(r*(1-p)), (unsigned char)(g*(1-p)), (unsigned char)(b*(1-p)), (unsigned char)a, type);}
    /** Creates a more transparent version.
     * @param [in] p How much more transparent we shall be. Zero means no change, 1 is completely transparent./
     * @returns The lighter version.*/
    constexpr ColorType MoreTransparent(double p) const noexcept
        {return ColorType(r, g, b, (unsigned char)(a*(1-p)), type);}
    /** Creates a less transparent version.
     * @param [in] p How much less transparent we shall be. Zero means no change, 1 is completely opaque./
     * @returns The lighter version.*/
    constexpr ColorType MoreOpaque(double p) const noexcept
        {return ColorType(r, g, b, (unsigned char)(a+p*(255-a)), type);}
    /** Creates a non-transparent version as if on white background*/
    constexpr ColorType FlattenAlpha(void) const
	    {return ColorType(unsigned(255-r)*(255-a)/255+r, unsigned(255-g)*(255-a)/255+g, unsigned(255-b)*(255-a)/255+b, 255, type);}
    /** Overlays one color on top of another */
    ColorType operator +(const ColorType &c) const noexcept;
    /** Overlays one color on top of another */
    ColorType &operator +=(const ColorType &o) noexcept
        { return *this = *this+o; }
    /** True if the color is completely transparent and will not
     *  in any way change the canvas when painted with.*/
    constexpr bool IsFullyTransparent() const noexcept { return type==INVALID || a==0; }
    /** True if the color is completely opaque and will completely
    *  covert the canvas when painted with.*/
    constexpr bool IsFullyOpaque() const noexcept { return type!=INVALID && a==255; }

    static constexpr ColorType black() { return ColorType(0, 0, 0); }
    static constexpr ColorType white() { return ColorType(255, 255, 255); }
    static constexpr ColorType red() { return ColorType(255, 0, 0); }
    static constexpr ColorType green() { return ColorType(0, 255, 0); }
    static constexpr ColorType blue() { return ColorType(0, 0, 255); }
    static constexpr ColorType gray() { return ColorType(128, 128, 128); }
    static constexpr ColorType lgray() { return ColorType(192, 192, 192); }
    static constexpr ColorType none() { return ColorType(0, 0, 0, 0); }

    /** Mix 2 colors. We keep our own type. If we are invalid, we remain so.
     * If o is invalid, we become invalid. We clamp ratio to [0..1].*/
    constexpr ColorType MixWith(ColorType o, double ratio_of_o = 0.5) const noexcept {
        if (o.type == INVALID) return {};
        if (ratio_of_o<0) ratio_of_o = 0;
        if (ratio_of_o>1) ratio_of_o = 1;
        const double X = 1-ratio_of_o, Y = ratio_of_o;
        return {(unsigned char)(r*X + o.r*Y), (unsigned char)(g*X + o.g*Y),
                (unsigned char)(b*X + o.b*Y), (unsigned char)(a*X + o.a*Y), type};
    }
};

/** A collection of named colors*/
class ColorSet : public std::map<std::string, ColorType, std::less<>>
{
public:
    /** Merges a set of colors into us by applying members that are set in `o`*/
    ColorSet &operator +=(const ColorSet&o) {for (auto &c : o) (*this)[c.first] = c.second; return *this;}
    bool AddColor(std::string_view, std::string_view, MscError &error, const FileLineColRange &linenum);
    ColorType GetColor(std::string_view s) const;
};


#endif //COLOR_H
