// Created by: NW,JPB,CAL
// Copyright (c) 1991-1999 Matra Datavision
// Copyright (c) 1999-2014 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.

#ifndef _Quantity_Color_HeaderFile
#define _Quantity_Color_HeaderFile

#include <Standard.hxx>
#include <Standard_DefineAlloc.hxx>
#include <Standard_Handle.hxx>
#include <Standard_HashUtils.hxx>
#include <Standard_ShortReal.hxx>

#include <Quantity_NameOfColor.hxx>
#include <Quantity_TypeOfColor.hxx>
#include <NCollection_Vec4.hxx>

//! This class allows the definition of an RGB color as triplet of 3 normalized floating point
//! values (red, green, blue).
//!
//! Although Quantity_Color can be technically used for pass-through storage of RGB triplet in any
//! color space, other OCCT interfaces taking/returning Quantity_Color would expect them in linear
//! space. Therefore, take a look into methods converting to and from non-linear sRGB color space,
//! if needed; for instance, application usually providing color picking within 0..255 range in sRGB
//! color space.
class Quantity_Color
{
public:
  DEFINE_STANDARD_ALLOC

  //! Creates Quantity_NOC_YELLOW color (for historical reasons).
  Quantity_Color()
      : myRgb(valuesOf(Quantity_NOC_YELLOW, Quantity_TOC_RGB))
  {
  }

  //! Creates the color from enumeration value.
  Quantity_Color(const Quantity_NameOfColor theName)
      : myRgb(valuesOf(theName, Quantity_TOC_RGB))
  {
  }

  //! Creates a color according to the definition system theType.
  //! Throws exception if values are out of range.
  Standard_EXPORT Quantity_Color(const double               theC1,
                                 const double               theC2,
                                 const double               theC3,
                                 const Quantity_TypeOfColor theType);

  //! Define color from linear RGB values.
  Standard_EXPORT explicit Quantity_Color(const NCollection_Vec3<float>& theRgb);

  //! Returns the name of the nearest color from the Quantity_NameOfColor enumeration.
  Standard_EXPORT Quantity_NameOfColor Name() const;

  //! Updates the color from specified named color.
  void SetValues(const Quantity_NameOfColor theName) noexcept
  {
    myRgb = valuesOf(theName, Quantity_TOC_RGB);
  }

  //! Return the color as vector of 3 float elements.
  constexpr const NCollection_Vec3<float>& Rgb() const noexcept { return myRgb; }

  //! Return the color as vector of 3 float elements.
  constexpr operator const NCollection_Vec3<float>&() const noexcept { return myRgb; }

  //! Returns in theC1, theC2 and theC3 the components of this color
  //! according to the color system definition theType.
  Standard_EXPORT void Values(double&                    theC1,
                              double&                    theC2,
                              double&                    theC3,
                              const Quantity_TypeOfColor theType) const;

  //! Updates a color according to the mode specified by theType.
  //! Throws exception if values are out of range.
  Standard_EXPORT void SetValues(const double               theC1,
                                 const double               theC2,
                                 const double               theC3,
                                 const Quantity_TypeOfColor theType);

  //! Returns the Red component (quantity of red) of the color within range [0.0; 1.0].
  constexpr double Red() const noexcept { return myRgb.r(); }

  //! Returns the Green component (quantity of green) of the color within range [0.0; 1.0].
  constexpr double Green() const noexcept { return myRgb.g(); }

  //! Returns the Blue component (quantity of blue) of the color within range [0.0; 1.0].
  constexpr double Blue() const noexcept { return myRgb.b(); }

  //! Returns the Hue component (hue angle) of the color
  //! in degrees within range [0.0; 360.0], 0.0 being Red.
  //! -1.0 is a special value reserved for grayscale color (S should be 0.0)
  double Hue() const noexcept { return Convert_LinearRGB_To_HLS(myRgb)[0]; }

  //! Returns the Light component (value of the lightness) of the color within range [0.0; 1.0].
  double Light() const noexcept { return Convert_LinearRGB_To_HLS(myRgb)[1]; }

  //! Increases or decreases the intensity (variation of the lightness).
  //! The delta is a percentage. Any value greater than zero will increase the intensity.
  //! The variation is expressed as a percentage of the current value.
  Standard_EXPORT void ChangeIntensity(const double theDelta);

  //! Returns the Saturation component (value of the saturation) of the color within range
  //! [0.0; 1.0].
  double Saturation() const noexcept { return Convert_LinearRGB_To_HLS(myRgb)[2]; }

  //! Increases or decreases the contrast (variation of the saturation).
  //! The delta is a percentage. Any value greater than zero will increase the contrast.
  //! The variation is expressed as a percentage of the current value.
  Standard_EXPORT void ChangeContrast(const double theDelta);

  //! Returns TRUE if the distance between two colors is greater than Epsilon().
  bool IsDifferent(const Quantity_Color& theOther) const noexcept
  {
    return (SquareDistance(theOther) > Epsilon() * Epsilon());
  }

  //! Alias to IsDifferent().
  bool operator!=(const Quantity_Color& theOther) const noexcept { return IsDifferent(theOther); }

  //! Returns TRUE if the distance between two colors is no greater than Epsilon().
  bool IsEqual(const Quantity_Color& theOther) const noexcept
  {
    return (SquareDistance(theOther) <= Epsilon() * Epsilon());
  }

  //! Alias to IsEqual().
  bool operator==(const Quantity_Color& theOther) const noexcept { return IsEqual(theOther); }

  //! Returns the distance between two colors. It's a value between 0 and the square root of 3 (the
  //! black/white distance).
  double Distance(const Quantity_Color& theColor) const noexcept
  {
    return (NCollection_Vec3<double>(myRgb) - NCollection_Vec3<double>(theColor.myRgb)).Modulus();
  }

  //! Returns the square of distance between two colors.
  double SquareDistance(const Quantity_Color& theColor) const noexcept
  {
    return (NCollection_Vec3<double>(myRgb) - NCollection_Vec3<double>(theColor.myRgb))
      .SquareModulus();
  }

  //! Returns the percentage change of contrast and intensity between this and another color.
  //! <DC> and <DI> are percentages, either positive or negative.
  //! The calculation is with respect to this color.
  //! If <DC> is positive then <me> is more contrasty.
  //! If <DI> is positive then <me> is more intense.
  Standard_EXPORT void Delta(const Quantity_Color& theColor, double& DC, double& DI) const;

  //! Returns the value of the perceptual difference between this color
  //! and @p theOther, computed using the CIEDE2000 formula.
  //! The difference is in range [0, 100.], with 1 approximately corresponding
  //! to the minimal perceivable difference (usually difference 5 or greater is
  //! needed for the difference to be recognizable in practice).
  Standard_EXPORT double DeltaE2000(const Quantity_Color& theOther) const;

public:
  //! Returns the color from Quantity_NameOfColor enumeration nearest to specified RGB values.
  static Quantity_NameOfColor Name(const double theR, const double theG, const double theB)
  {
    const Quantity_Color aColor(theR, theG, theB, Quantity_TOC_RGB);
    return aColor.Name();
  }

  //! Returns the name of the color identified by the given Quantity_NameOfColor enumeration value.
  Standard_EXPORT static const char* StringName(const Quantity_NameOfColor theColor) noexcept;

  //! Finds color from predefined names.
  //! For example, the name of the color which corresponds to "BLACK" is Quantity_NOC_BLACK.
  //! Returns FALSE if name is unknown.
  Standard_EXPORT static bool ColorFromName(const char*           theName,
                                            Quantity_NameOfColor& theColor) noexcept;

  //! Finds color from predefined names.
  //! @param theColorNameString the color name
  //! @param theColor a found color
  //! @return false if the color name is unknown, or true if the search by color name was successful
  static bool ColorFromName(const char* theColorNameString, Quantity_Color& theColor) noexcept
  {
    Quantity_NameOfColor aColorName = Quantity_NOC_BLACK;
    if (!ColorFromName(theColorNameString, aColorName))
    {
      return false;
    }
    theColor = aColorName;
    return true;
  }

public:
  //!@name Routines converting colors between different encodings and color spaces

  //! Parses the string as a hex color (like "#FF0" for short sRGB color, or "#FFFF00" for sRGB
  //! color)
  //! @param theHexColorString the string to be parsed
  //! @param theColor a color that is a result of parsing
  //! @return true if parsing was successful, or false otherwise
  Standard_EXPORT static bool ColorFromHex(const char* theHexColorString, Quantity_Color& theColor);

  //! Returns hex sRGB string in format "#FFAAFF".
  static TCollection_AsciiString ColorToHex(const Quantity_Color& theColor,
                                            const bool            theToPrefixHash = true) noexcept
  {
    NCollection_Vec3<float> anSRgb = Convert_LinearRGB_To_sRGB((NCollection_Vec3<float>)theColor);
    NCollection_Vec3<int>   anSRgbInt(anSRgb * 255.0f + NCollection_Vec3<float>(0.5f));
    char                    aBuff[10];
    Sprintf(aBuff,
            theToPrefixHash ? "#%02X%02X%02X" : "%02X%02X%02X",
            anSRgbInt.r(),
            anSRgbInt.g(),
            anSRgbInt.b());
    return aBuff;
  }

  //! Converts sRGB components into HLS ones.
  Standard_EXPORT static NCollection_Vec3<float> Convert_sRGB_To_HLS(
    const NCollection_Vec3<float>& theRgb) noexcept;

  //! Converts HLS components into RGB ones.
  Standard_EXPORT static NCollection_Vec3<float> Convert_HLS_To_sRGB(
    const NCollection_Vec3<float>& theHls);

  //! Converts Linear RGB components into HLS ones.
  static NCollection_Vec3<float> Convert_LinearRGB_To_HLS(
    const NCollection_Vec3<float>& theRgb) noexcept
  {
    return Convert_sRGB_To_HLS(Convert_LinearRGB_To_sRGB(theRgb));
  }

  //! Converts HLS components into linear RGB ones.
  static NCollection_Vec3<float> Convert_HLS_To_LinearRGB(
    const NCollection_Vec3<float>& theHls) noexcept
  {
    return Convert_sRGB_To_LinearRGB(Convert_HLS_To_sRGB(theHls));
  }

  //! Converts linear RGB components into CIE Lab ones.
  Standard_EXPORT static NCollection_Vec3<float> Convert_LinearRGB_To_Lab(
    const NCollection_Vec3<float>& theRgb) noexcept;

  //! Converts CIE Lab components into CIE Lch ones.
  Standard_EXPORT static NCollection_Vec3<float> Convert_Lab_To_Lch(
    const NCollection_Vec3<float>& theLab) noexcept;

  //! Converts CIE Lab components into linear RGB ones.
  //! Note that the resulting values may be out of the valid range for RGB.
  Standard_EXPORT static NCollection_Vec3<float> Convert_Lab_To_LinearRGB(
    const NCollection_Vec3<float>& theLab) noexcept;

  //! Converts CIE Lch components into CIE Lab ones.
  Standard_EXPORT static NCollection_Vec3<float> Convert_Lch_To_Lab(
    const NCollection_Vec3<float>& theLch) noexcept;

  //! Convert the color value to ARGB integer value, with alpha equals to 0.
  //! So the output is formatted as 0x00RRGGBB.
  //! Note that this unpacking does NOT involve non-linear sRGB -> linear RGB conversion,
  //! as would be usually expected for RGB color packed into 4 bytes.
  //! @param[in] theColor  color to convert
  //! @param[out] theARGB  result color encoded as integer
  static constexpr void Color2argb(const Quantity_Color& theColor, int& theARGB) noexcept
  {
    const NCollection_Vec3<int> aColor(static_cast<int>(255.0f * theColor.myRgb.r() + 0.5f),
                                       static_cast<int>(255.0f * theColor.myRgb.g() + 0.5f),
                                       static_cast<int>(255.0f * theColor.myRgb.b() + 0.5f));
    theARGB = (((aColor.r() & 0xff) << 16) | ((aColor.g() & 0xff) << 8) | (aColor.b() & 0xff));
  }

  //! Convert integer ARGB value to Color. Alpha bits are ignored.
  //! Note that this packing does NOT involve linear -> non-linear sRGB conversion,
  //! as would be usually expected to preserve higher (for human eye) color precision in 4 bytes.
  static void Argb2color(const int theARGB, Quantity_Color& theColor) noexcept
  {
    const NCollection_Vec3<double> aColor(static_cast<double>((theARGB & 0xff0000) >> 16),
                                          static_cast<double>((theARGB & 0x00ff00) >> 8),
                                          static_cast<double>((theARGB & 0x0000ff)));
    theColor.SetValues(aColor.r() / 255.0,
                       aColor.g() / 255.0,
                       aColor.b() / 255.0,
                       Quantity_TOC_sRGB);
  }

  //! Convert linear RGB component into sRGB using OpenGL specs formula (double precision), also
  //! known as gamma correction.
  static double Convert_LinearRGB_To_sRGB(double theLinearValue) noexcept
  {
    return theLinearValue <= 0.0031308 ? theLinearValue * 12.92
                                       : std::pow(theLinearValue, 1.0 / 2.4) * 1.055 - 0.055;
  }

  //! Convert linear RGB component into sRGB using OpenGL specs formula (single precision), also
  //! known as gamma correction.
  static float Convert_LinearRGB_To_sRGB(float theLinearValue) noexcept
  {
    return theLinearValue <= 0.0031308f ? theLinearValue * 12.92f
                                        : powf(theLinearValue, 1.0f / 2.4f) * 1.055f - 0.055f;
  }

  //! Convert sRGB component into linear RGB using OpenGL specs formula (double precision), also
  //! known as gamma correction.
  static double Convert_sRGB_To_LinearRGB(double thesRGBValue) noexcept
  {
    return thesRGBValue <= 0.04045 ? thesRGBValue / 12.92
                                   : std::pow((thesRGBValue + 0.055) / 1.055, 2.4);
  }

  //! Convert sRGB component into linear RGB using OpenGL specs formula (single precision), also
  //! known as gamma correction.
  static float Convert_sRGB_To_LinearRGB(float thesRGBValue) noexcept
  {
    return thesRGBValue <= 0.04045f ? thesRGBValue / 12.92f
                                    : powf((thesRGBValue + 0.055f) / 1.055f, 2.4f);
  }

  //! Convert linear RGB components into sRGB using OpenGL specs formula.
  template <typename T>
  static NCollection_Vec3<T> Convert_LinearRGB_To_sRGB(const NCollection_Vec3<T>& theRGB) noexcept
  {
    return NCollection_Vec3<T>(Convert_LinearRGB_To_sRGB(theRGB.r()),
                               Convert_LinearRGB_To_sRGB(theRGB.g()),
                               Convert_LinearRGB_To_sRGB(theRGB.b()));
  }

  //! Convert sRGB components into linear RGB using OpenGL specs formula.
  template <typename T>
  static NCollection_Vec3<T> Convert_sRGB_To_LinearRGB(const NCollection_Vec3<T>& theRGB) noexcept
  {
    return NCollection_Vec3<T>(Convert_sRGB_To_LinearRGB(theRGB.r()),
                               Convert_sRGB_To_LinearRGB(theRGB.g()),
                               Convert_sRGB_To_LinearRGB(theRGB.b()));
  }

  //! Convert linear RGB component into sRGB using approximated uniform gamma coefficient 2.2.
  static float Convert_LinearRGB_To_sRGB_approx22(float theLinearValue) noexcept
  {
    return powf(theLinearValue, 2.2f);
  }

  //! Convert sRGB component into linear RGB using approximated uniform gamma coefficient 2.2
  static float Convert_sRGB_To_LinearRGB_approx22(float thesRGBValue) noexcept
  {
    return powf(thesRGBValue, 1.0f / 2.2f);
  }

  //! Convert linear RGB components into sRGB using approximated uniform gamma coefficient 2.2
  static NCollection_Vec3<float> Convert_LinearRGB_To_sRGB_approx22(
    const NCollection_Vec3<float>& theRGB) noexcept
  {
    return NCollection_Vec3<float>(Convert_LinearRGB_To_sRGB_approx22(theRGB.r()),
                                   Convert_LinearRGB_To_sRGB_approx22(theRGB.g()),
                                   Convert_LinearRGB_To_sRGB_approx22(theRGB.b()));
  }

  //! Convert sRGB components into linear RGB using approximated uniform gamma coefficient 2.2
  static NCollection_Vec3<float> Convert_sRGB_To_LinearRGB_approx22(
    const NCollection_Vec3<float>& theRGB) noexcept
  {
    return NCollection_Vec3<float>(Convert_sRGB_To_LinearRGB_approx22(theRGB.r()),
                                   Convert_sRGB_To_LinearRGB_approx22(theRGB.g()),
                                   Convert_sRGB_To_LinearRGB_approx22(theRGB.b()));
  }

  //! Converts HLS components into sRGB ones.
  static void HlsRgb(const double theH,
                     const double theL,
                     const double theS,
                     double&      theR,
                     double&      theG,
                     double&      theB) noexcept
  {
    const NCollection_Vec3<float> anRgb =
      Convert_HLS_To_sRGB(NCollection_Vec3<float>((float)theH, (float)theL, (float)theS));
    theR = anRgb[0];
    theG = anRgb[1];
    theB = anRgb[2];
  }

  //! Converts sRGB components into HLS ones.
  static void RgbHls(const double theR,
                     const double theG,
                     const double theB,
                     double&      theH,
                     double&      theL,
                     double&      theS) noexcept
  {
    const NCollection_Vec3<float> aHls =
      Convert_sRGB_To_HLS(NCollection_Vec3<float>((float)theR, (float)theG, (float)theB));
    theH = aHls[0];
    theL = aHls[1];
    theS = aHls[2];
  }

public:
  //! Returns the value used to compare two colors for equality; 0.0001 by default.
  Standard_EXPORT static double Epsilon() noexcept;

  //! Set the value used to compare two colors for equality.
  Standard_EXPORT static void SetEpsilon(const double theEpsilon) noexcept;

  //! Dumps the content of me into the stream
  Standard_EXPORT void DumpJson(Standard_OStream& theOStream, int theDepth = -1) const;

  //! Inits the content of me from the stream
  Standard_EXPORT bool InitFromJson(const Standard_SStream& theSStream, int& theStreamPos);

private:
  //! Returns the values of a predefined color according to the mode.
  Standard_EXPORT static NCollection_Vec3<float> valuesOf(const Quantity_NameOfColor theName,
                                                          const Quantity_TypeOfColor theType);

private:
  NCollection_Vec3<float> myRgb;
};

namespace std
{
template <>
struct hash<Quantity_Color>
{
  std::size_t operator()(const Quantity_Color& theColor) const noexcept
  {
    unsigned char aByteArr[3] = {static_cast<unsigned char>(255 * theColor.Red()),
                                 static_cast<unsigned char>(255 * theColor.Green()),
                                 static_cast<unsigned char>(255 * theColor.Blue())};
    return opencascade::hashBytes(aByteArr, sizeof(aByteArr));
  }
};
} // namespace std

#endif // _Quantity_Color_HeaderFile
