/*
 * 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_INTERPOLATOR_H
#define ECXX_CTL_GFX_COLOR_INTERPOLATOR_H


#include "ColorUtility.h"


namespace eCxx {


//
// Internal helper structures
//
namespace internal {

    template <typename ColorT, uint8_t ChannelIndex, bool ChannelAvailable>
    struct LERPColorImpl;

    template <typename ColorT, uint8_t ChannelIndex>
    struct LERPColorImpl<ColorT, ChannelIndex, false> {
        static inline void _(ColorT&, const ColorT&, const ColorT&, uint8_t) {}
        static inline void _(ColorT&, const ColorT&, const ColorT&, uint8_t, uint8_t) {}
    };

    template <typename ColorT>
    struct LERPColorImpl<ColorT, CCI::_1, true> {
        static inline void _(ColorT& dst, const ColorT& from, const ColorT& to, uint8_t progressPercent)
        {
            SelectColorChannel<ColorT, CCI::_1>::_(dst) = eCxx::lerp<uint16_t>(
                SelectColorChannel<ColorT, CCI::_1>::_(from),
                SelectColorChannel<ColorT, CCI::_1>::_(to  ),
                progressPercent,
                100
            );
        }

        static inline void _(ColorT& dst, const ColorT& from, const ColorT& to, uint8_t stepNumber, uint8_t lastStepNumber)
        {
            SelectColorChannel<ColorT, CCI::_1>::_(dst) = eCxx::lerp<uint16_t>(
                SelectColorChannel<ColorT, CCI::_1>::_(from),
                SelectColorChannel<ColorT, CCI::_1>::_(to  ),
                stepNumber,
                lastStepNumber
            );
        }
    };

    template <typename ColorT>
    struct LERPColorImpl<ColorT, CCI::_2, true> {
        static inline void _(ColorT& dst, const ColorT& from, const ColorT& to, uint8_t progressPercent)
        {
            SelectColorChannel<ColorT, CCI::_2>::_(dst) = eCxx::lerp<uint16_t>(
                SelectColorChannel<ColorT, CCI::_2>::_(from),
                SelectColorChannel<ColorT, CCI::_2>::_(to  ),
                progressPercent,
                100
            );
        }

        static inline void _(ColorT& dst, const ColorT& from, const ColorT& to, uint8_t stepNumber, uint8_t lastStepNumber)
        {
            SelectColorChannel<ColorT, CCI::_2>::_(dst) = eCxx::lerp<uint16_t>(
                SelectColorChannel<ColorT, CCI::_2>::_(from),
                SelectColorChannel<ColorT, CCI::_2>::_(to  ),
                stepNumber,
                lastStepNumber
            );
        }
    };

    template <typename ColorT>
    struct LERPColorImpl<ColorT, CCI::_3, true> {
        static inline void _(ColorT& dst, const ColorT& from, const ColorT& to, uint8_t progressPercent)
        {
            SelectColorChannel<ColorT, CCI::_3>::_(dst) = eCxx::lerp<uint16_t>(
                SelectColorChannel<ColorT, CCI::_3>::_(from),
                SelectColorChannel<ColorT, CCI::_3>::_(to  ),
                progressPercent,
                100
            );
        }

        static inline void _(ColorT& dst, const ColorT& from, const ColorT& to, uint8_t stepNumber, uint8_t lastStepNumber)
        {
            SelectColorChannel<ColorT, CCI::_3>::_(dst) = eCxx::lerp<uint16_t>(
                SelectColorChannel<ColorT, CCI::_3>::_(from),
                SelectColorChannel<ColorT, CCI::_3>::_(to  ),
                stepNumber,
                lastStepNumber
            );
        }
    };

    template <typename ColorT>
    struct LERPColorImpl<ColorT, CCI::_4, true> {
        static inline void _(ColorT& dst, const ColorT& from, const ColorT& to, uint8_t progressPercent)
        {
            SelectColorChannel<ColorT, CCI::_4>::_(dst) = eCxx::lerp<uint16_t>(
                SelectColorChannel<ColorT, CCI::_4>::_(from),
                SelectColorChannel<ColorT, CCI::_4>::_(to  ),
                progressPercent,
                100
            );
        }

        static inline void _(ColorT& dst, const ColorT& from, const ColorT& to, uint8_t stepNumber, uint8_t lastStepNumber)
        {
            SelectColorChannel<ColorT, CCI::_4>::_(dst) = eCxx::lerp<uint16_t>(
                SelectColorChannel<ColorT, CCI::_4>::_(from),
                SelectColorChannel<ColorT, CCI::_4>::_(to  ),
                stepNumber,
                lastStepNumber
            );
        }
    };

} // namespace internal

template <typename ColorT, uint8_t ChannelIndex>
struct LERPColor : internal::LERPColorImpl< ColorT, ChannelIndex, !!(ChannelIndex & ColorT::ChannelAvailable) > {};


//
// Color channel linear interpolators
//
template <typename ColorT>
inline void lerpColor(ColorT& dst, const ColorT& from, const ColorT& to, uint8_t progressPercent)
{
    if(progressPercent > 100) progressPercent = 100;

    LERPColor<ColorT, CCI::_1>::_(dst, from, to, progressPercent);
    LERPColor<ColorT, CCI::_2>::_(dst, from, to, progressPercent);
    LERPColor<ColorT, CCI::_3>::_(dst, from, to, progressPercent);
    LERPColor<ColorT, CCI::_4>::_(dst, from, to, progressPercent);

    dst.postXPolicy();
}


template <typename ColorT>
inline void lerpColor(ColorT& dst, const ColorT& from, const ColorT& to, uint8_t stepNumber, uint8_t lastStepNumber)
{
    if(stepNumber > lastStepNumber) stepNumber = lastStepNumber;

    LERPColor<ColorT, CCI::_1>::_(dst, from, to, stepNumber, lastStepNumber);
    LERPColor<ColorT, CCI::_2>::_(dst, from, to, stepNumber, lastStepNumber);
    LERPColor<ColorT, CCI::_3>::_(dst, from, to, stepNumber, lastStepNumber);
    LERPColor<ColorT, CCI::_4>::_(dst, from, to, stepNumber, lastStepNumber);

    dst.postXPolicy();
}


template <uint8_t ChannelIndexes, typename ColorT>
inline void lerpColor(ColorT& dst, const ColorT& from, const ColorT& to, uint8_t progressPercent)
{
    if(ChannelIndexes == ColorT::A) {
        lerpColor(dst, from, to, progressPercent);
        return;
    }

    if(progressPercent > 100) progressPercent = 100;

    CopyColorChannelWx<ColorT, CCI::_1, ChannelIndexes & CCI::_1>::_(dst, from);
    CopyColorChannelWx<ColorT, CCI::_2, ChannelIndexes & CCI::_2>::_(dst, from);
    CopyColorChannelWx<ColorT, CCI::_3, ChannelIndexes & CCI::_3>::_(dst, from);
    CopyColorChannelWx<ColorT, CCI::_4, ChannelIndexes & CCI::_4>::_(dst, from);

    LERPColor<ColorT, ChannelIndexes & CCI::_1>::_(dst, from, to, progressPercent);
    LERPColor<ColorT, ChannelIndexes & CCI::_2>::_(dst, from, to, progressPercent);
    LERPColor<ColorT, ChannelIndexes & CCI::_3>::_(dst, from, to, progressPercent);
    LERPColor<ColorT, ChannelIndexes & CCI::_4>::_(dst, from, to, progressPercent);

    dst.postXPolicy();
}


template <uint8_t ChannelIndexes, typename ColorT>
inline void lerpColor(ColorT& dst, const ColorT& from, const ColorT& to, uint8_t stepNumber, uint8_t lastStepNumber)
{
    if(ChannelIndexes == ColorT::A) {
        lerpColor(dst, from, to, stepNumber, lastStepNumber);
        return;
    }

    if(stepNumber > lastStepNumber) stepNumber = lastStepNumber;

    CopyColorChannelWx<ColorT, CCI::_1, ChannelIndexes & CCI::_1>::_(dst, from);
    CopyColorChannelWx<ColorT, CCI::_2, ChannelIndexes & CCI::_2>::_(dst, from);
    CopyColorChannelWx<ColorT, CCI::_3, ChannelIndexes & CCI::_3>::_(dst, from);
    CopyColorChannelWx<ColorT, CCI::_4, ChannelIndexes & CCI::_4>::_(dst, from);

    LERPColor<ColorT, ChannelIndexes & CCI::_1>::_(dst, from, to, stepNumber, lastStepNumber);
    LERPColor<ColorT, ChannelIndexes & CCI::_2>::_(dst, from, to, stepNumber, lastStepNumber);
    LERPColor<ColorT, ChannelIndexes & CCI::_3>::_(dst, from, to, stepNumber, lastStepNumber);
    LERPColor<ColorT, ChannelIndexes & CCI::_4>::_(dst, from, to, stepNumber, lastStepNumber);

    dst.postXPolicy();
}


// Color channel adjuster
template <uint8_t MaxValue = 255>
inline uint8_t adjustColor(uint8_t color, int8_t adjustmentFactor)
{
    const int16_t c = static_cast<int16_t>(color) + adjustmentFactor;

    if( c < 0                              ) return        0;
    if( c > static_cast<int16_t>(MaxValue) ) return MaxValue;

    return c;
}


template <uint16_t MaxValue = 65535>
inline uint16_t adjustColor(uint16_t color, int8_t adjustmentFactor)
{
    const int32_t c = static_cast<int32_t>(color) + adjustmentFactor;

    if( c < 0                              ) return        0;
    if( c > static_cast<int32_t>(MaxValue) ) return MaxValue;

    return c;
}


template <uint16_t MaxValue = 65535>
inline uint16_t adjustColor16(uint16_t color, int16_t adjustmentFactor)
{
    const int32_t c = static_cast<int32_t>(color) + adjustmentFactor;

    if( c < 0                              ) return        0;
    if( c > static_cast<int32_t>(MaxValue) ) return MaxValue;

    return c;
}


//
// Color channel scaler
//     # Scale values of   0 to 127 means multiply the color by 0.0 to 1.0.
//     # Scale values of 128 to 255 means multiply the color by 1.0 to 2.0.
inline uint8_t scaleColor(uint8_t color, uint8_t scaleFactor)
{ return static_cast<uint16_t>(color) * ( static_cast<uint16_t>(scaleFactor) + 1 ) / 128; }


template <uint8_t MaxValue = 255>
inline uint8_t scaleColor(uint8_t color, uint8_t scaleFactor)
{
    if(MaxValue == 255) return scaleColor(color, scaleFactor);

    const uint16_t c = static_cast<uint16_t>(color) * ( static_cast<uint16_t>(scaleFactor) + 1 ) / 128;

    if(c > MaxValue) return MaxValue;

    return c;
}


inline uint16_t scaleColor(uint16_t color, uint8_t scaleFactor)
{ return static_cast<uint32_t>(color) * ( static_cast<uint32_t>(scaleFactor) + 1 ) / 128; }


template <uint16_t MaxValue = 65535>
inline uint16_t scaleColor(uint16_t color, uint8_t scaleFactor)
{
    if(MaxValue == 65535) return scaleColor(color, scaleFactor);

    const uint32_t c = static_cast<uint32_t>(color) * ( static_cast<uint32_t>(scaleFactor) + 1 ) / 128;

    if(c > MaxValue) return MaxValue;

    return c;
}


} // namespace eCxx


#endif
