/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */


#ifndef ECXX_CTL_GFX_COLOR_SPACE_H
#define ECXX_CTL_GFX_COLOR_SPACE_H


#include <eCxx/CTL/MathUtility.h>


namespace eCxx {


//
// CIE-1931 lightness LUTs
//
//     CIE1931L_MSB2_O3 : input = 2-bit value ; output = 3-bit value
//     CIE1931L_MSB2_O4 : input = 2-bit value ; output = 4-bit value
//     CIE1931L_MSB2_O5 : input = 2-bit value ; output = 5-bit value
//     CIE1931L_MSB2_O6 : input = 2-bit value ; output = 6-bit value
//     CIE1931L_MSB2_O7 : input = 2-bit value ; output = 7-bit value
//     CIE1931L_MSB2_O8 : input = 2-bit value ; output = 8-bit value
//
//     CIE1931L_MSB3_O5 : input = 3-bit value ; output = 5-bit value
//     CIE1931L_MSB3_O6 : input = 3-bit value ; output = 6-bit value
//     CIE1931L_MSB3_O7 : input = 3-bit value ; output = 7-bit value
//     CIE1931L_MSB3_O8 : input = 3-bit value ; output = 8-bit value
//
//     CIE1931L_MSB4_O7 : input = 4-bit value ; output = 7-bit value
//     CIE1931L_MSB4_O8 : input = 4-bit value ; output = 8-bit value
//
//     CIE1931L_MSB5_O8 : input = 5-bit value ; output = 8-bit value
//
typedef const struct __CIE1931LVal__ {
    const uint8_t v;
    constexpr __CIE1931LVal__(uint8_t v_) : v(v_) {}
} CIE1931LVal;

extern CIE1931LVal PROGMEM CIE1931L_MSB2_O3[ 4];
extern CIE1931LVal PROGMEM CIE1931L_MSB2_O4[ 4];
extern CIE1931LVal PROGMEM CIE1931L_MSB2_O5[ 4];
extern CIE1931LVal PROGMEM CIE1931L_MSB2_O6[ 4];
extern CIE1931LVal PROGMEM CIE1931L_MSB2_O7[ 4];
extern CIE1931LVal PROGMEM CIE1931L_MSB2_O8[ 4];

extern CIE1931LVal PROGMEM CIE1931L_MSB3_O5[ 8];
extern CIE1931LVal PROGMEM CIE1931L_MSB3_O6[ 8];
extern CIE1931LVal PROGMEM CIE1931L_MSB3_O7[ 8];
extern CIE1931LVal PROGMEM CIE1931L_MSB3_O8[ 8];

extern CIE1931LVal PROGMEM CIE1931L_MSB4_O7[16];
extern CIE1931LVal PROGMEM CIE1931L_MSB4_O8[16];

extern CIE1931LVal PROGMEM CIE1931L_MSB5_O8[32];


//
// CIE-1931 lightness related functions
//

// Read value from CIE-1931 lightness LUT
// WARNING : No range checks are performed on the given index.
template <uint8_t N>
inline uint8_t cie1931lut(CIE1931LVal (&lut)[N], uint8_t index)
{ return PGMReadByte()(lut + index); }

inline uint8_t cie1931lut23(uint8_t v) { return cie1931lut(CIE1931L_MSB2_O3, v); }
inline uint8_t cie1931lut24(uint8_t v) { return cie1931lut(CIE1931L_MSB2_O4, v); }

inline uint8_t cie1931lut35(uint8_t v) { return cie1931lut(CIE1931L_MSB3_O5, v); }
inline uint8_t cie1931lut36(uint8_t v) { return cie1931lut(CIE1931L_MSB3_O6, v); }
inline uint8_t cie1931lut37(uint8_t v) { return cie1931lut(CIE1931L_MSB3_O7, v); }
inline uint8_t cie1931lut38(uint8_t v) { return cie1931lut(CIE1931L_MSB3_O8, v); }

inline uint8_t cie1931lut47(uint8_t v) { return cie1931lut(CIE1931L_MSB4_O7, v); }
inline uint8_t cie1931lut48(uint8_t v) { return cie1931lut(CIE1931L_MSB4_O8, v); }

inline uint8_t cie1931lut58(uint8_t v) { return cie1931lut(CIE1931L_MSB5_O8, v); }

// Perform color correction using CIE-1931 lightness to approximate the response of the human eye
//     # The input  value will be right-shifted as necessary to fit the index space of the selected LUT.
//     # The output value will be right-aligned.
template <uint8_t N>
inline uint8_t cie1931lcc(CIE1931LVal (&lut)[N], uint8_t v)
{
    constexpr uint8_t BitCount = eCxx::log2(N);

    return PGMReadByte()( lut + ( v >> (8 - BitCount) ) );
}

inline uint8_t cie1931lcc23(uint8_t v) { return cie1931lcc(CIE1931L_MSB2_O3, v); }
inline uint8_t cie1931lcc24(uint8_t v) { return cie1931lcc(CIE1931L_MSB2_O4, v); }

inline uint8_t cie1931lcc35(uint8_t v) { return cie1931lcc(CIE1931L_MSB3_O5, v); }
inline uint8_t cie1931lcc36(uint8_t v) { return cie1931lcc(CIE1931L_MSB3_O6, v); }
inline uint8_t cie1931lcc37(uint8_t v) { return cie1931lcc(CIE1931L_MSB3_O7, v); }
inline uint8_t cie1931lcc38(uint8_t v) { return cie1931lcc(CIE1931L_MSB3_O8, v); }

inline uint8_t cie1931lcc47(uint8_t v) { return cie1931lcc(CIE1931L_MSB4_O7, v); }
inline uint8_t cie1931lcc48(uint8_t v) { return cie1931lcc(CIE1931L_MSB4_O8, v); }

inline uint8_t cie1931lcc58(uint8_t v) { return cie1931lcc(CIE1931L_MSB5_O8, v); }


//
// Maximum values for color channels
//
namespace CBP {

static constexpr uint16_t MaxKValue = 40000;

static constexpr uint16_t MaxHValue =   360; // NOTE : 360 refers to the same hue as 0
static constexpr uint8_t  MaxSValue =   200;
static constexpr uint8_t  MaxVValue =   200;
static constexpr uint8_t  MaxLValue =   200;

} // namespace CBP


//
// Color conversion functions
//

// Convert Kelvin to RGB
//                               Red   : 0 - 255
//     Kelvin : 0 - 40000   =>   Green : 0 - 255
//                               Blue  : 0 - 255
extern void k2rgb(uint8_t& r, uint8_t& g, uint8_t& b, uint16_t k);

// Convert HSV to RGB (NOTE : 360 refers to the same hue as 0)
//     Hue        : 0 - 360        Red   : 0 - 255
//     Saturation : 0 - 200   =>   Green : 0 - 255
//     Value      : 0 - 200        Blue  : 0 - 255
extern void hsv2rgb(uint8_t& r, uint8_t& g, uint8_t& b, uint16_t h, uint8_t s, uint8_t v);

// Convert HSL to RGB (NOTE : 360 refers to the same hue as 0)
//     Hue        : 0 - 360        Red   : 0 - 255
//     Saturation : 0 - 200   =>   Green : 0 - 255
//     Luminance  : 0 - 200        Blue  : 0 - 255
extern void hsl2rgb(uint8_t& r, uint8_t& g, uint8_t& b, uint16_t h, uint8_t s, uint8_t l);

// Convert HSL to HSV (NOTE : 360 refers to the same hue as 0)
//     Hue        : 0 - 360        Hue        : 0 - 360
//     Saturation : 0 - 200   =>   Saturation : 0 - 200
//     Luminance  : 0 - 200        Value      : 0 - 200
extern void hsl2hsv(uint16_t& vh, uint8_t& vs, uint8_t& vv, uint16_t lh, uint8_t ls, uint8_t ll);

// Convert RGB to RGBW
//     Red   : 0 - 255        Red   : 0 - 255
//     Green : 0 - 255   =>   Green : 0 - 255
//     Blue  : 0 - 255        Blue  : 0 - 255
//                            White : 0 - 255
extern void rgb2rgbw(uint8_t& r, uint8_t& g, uint8_t& b, uint8_t& w, uint8_t r_, uint8_t g_, uint8_t b_);

// Convert RGB to W
//     Red   : 0 - 255
//     Green : 0 - 255   =>   White : 0 - 255
//     Blue  : 0 - 255
extern void rgb2w(uint8_t& w, uint8_t r_, uint8_t g_, uint8_t b_);

// Convert HTML color
//                                Red   : 0 - 255
//     0x000000 - 0xFFFFFF   =>   Green : 0 - 255
//                                Blue  : 0 - 255
extern void html2rgb(uint8_t& r, uint8_t& g, uint8_t& b, uint32_t val);

// Convert HTML color
//                                  Red   : 0 - 255
//     "#000000" - "#FFFFFF"   =>   Green : 0 - 255
//                                  Blue  : 0 - 255
extern void html2rgb(uint8_t& r, uint8_t& g, uint8_t& b, const char* str);


} // namespace eCxx


#endif
