/*
 * 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_ANIMATION_1D_PROC_BREATHING_MULTI_H
#define ECXX_CTL_GFX_COLOR_ANIMATION_1D_PROC_BREATHING_MULTI_H


namespace eCxx {


//
// A simple 1-dimensional animation processor class - breathing between multiple colors
//
template <
    typename ColorAnimator1D_T,
    uint8_t  ColorArraySizeN,
    typename AnimProcColorT     = typename ColorAnimator1D_T::ColorFX1DType::BufferColorType,
    uint8_t  AnimChannelIndexes = AnimProcColorT::A
>
class Animation1DProc_BreathingMulti : public ColorAnimator1D_T::Animation1DProcType {
    static_assert(
        (ColorArraySizeN >= 2) && (ColorArraySizeN <= 32),
        "template argument substituting ColorArraySizeN is not between 2 and 32"
    );

    public:
        using AnimProcColorType   = AnimProcColorT;
        using ColorAnimator1DType = ColorAnimator1D_T;
        using ColorFX1DType       = typename ColorAnimator1D_T::Animation1DProcType::ColorFX1DType;
        using BufferColorType     = typename ColorAnimator1D_T::Animation1DProcType::BufferColorType;
        using BufferSizeType      = typename ColorAnimator1D_T::Animation1DProcType::BufferSizeType;

        static constexpr uint8_t ColorArrayElementCount = ColorArraySizeN;

    public:
        // # The animation speed must be between 1 and 100.
        // # This class does not take ownership of the given color object pointers; therefore, please
        //   ensure that all color objects remain valid during the lifetime of this class instance.
        // # Use nullptr to jump from the previous color directly to the next color (skip the breathing):
        //       + Using nullptr for the first color is not allowed.
        //       + Consecutive nullptrs are also not allowed.
        template <
            typename... ColorTs,
            typename    IsAllSame = typename eCxx::isAllSame<AnimProcColorType, ColorTs...>::Type
        >
        inline Animation1DProc_BreathingMulti(uint8_t speed, const ColorTs*... colors);

        inline virtual ~Animation1DProc_BreathingMulti();

        virtual void reset(ColorFX1DType& colorFX1D, BufferSizeType elemBeginIdx, BufferSizeType elemEndIdx);
        virtual void update(ColorFX1DType& colorFX1D, BufferSizeType elemBeginIdx, BufferSizeType elemEndIdx);

        // NOTE : Can be called manually anytime after reset() to change the current
        //        index and percent
        inline void set(uint8_t index, uint8_t percent);

#if defined(__x86_64__) || defined(__i386__)
        // NOTE : This animation processor implementation does not require reset() to be
        //        called first for this function to work properly
        virtual CBP::QUInt estimatedTotalAnimationFrameCount() const;
#endif

    private:
        const AnimProcColorType* const _colors[ColorArrayElementCount];
        const uint8_t                  _speed;

              uint8_t                  _index;
              uint8_t                  _percent;
};


template <typename ColorAnimator1D_T, uint8_t ColorArraySizeN, typename AnimProcColorT, uint8_t AnimChannelIndexes> template <typename... ColorTs, typename IsAllSame>
inline Animation1DProc_BreathingMulti<ColorAnimator1D_T, ColorArraySizeN, AnimProcColorT, AnimChannelIndexes>
::Animation1DProc_BreathingMulti(uint8_t speed, const ColorTs*... colors)
: _colors         { colors...                                      }
, _speed          ( (!speed) ? 1 : ( (speed > 100) ? 100 : speed ) )
, _index          ( 0                                              )
, _percent        ( 0                                              )
{}


template <typename ColorAnimator1D_T, uint8_t ColorArraySizeN, typename AnimProcColorT, uint8_t AnimChannelIndexes>
inline Animation1DProc_BreathingMulti<ColorAnimator1D_T, ColorArraySizeN, AnimProcColorT, AnimChannelIndexes>
::~Animation1DProc_BreathingMulti()
{}


template <typename ColorAnimator1D_T, uint8_t ColorArraySizeN, typename AnimProcColorT, uint8_t AnimChannelIndexes>
void Animation1DProc_BreathingMulti<ColorAnimator1D_T, ColorArraySizeN, AnimProcColorT, AnimChannelIndexes>
::reset(ColorFX1DType&, BufferSizeType, BufferSizeType)
{
    _index   = 0;
    _percent = 0;
}


template <typename ColorAnimator1D_T, uint8_t ColorArraySizeN, typename AnimProcColorT, uint8_t AnimChannelIndexes>
void Animation1DProc_BreathingMulti<ColorAnimator1D_T, ColorArraySizeN, AnimProcColorT, AnimChannelIndexes>
::update(ColorFX1DType& colorFX1D, BufferSizeType elemBeginIdx, BufferSizeType elemEndIdx)
{
    // Determine the current color
    AnimProcColorType dst;

    const uint8_t i1 = _index;
    const uint8_t i2 = ( (i1 + 1) < ColorArrayElementCount ) ? (i1 + 1) : 0;

    // If the second color is nullptr set the current color with the first color
    if( _colors[i2] == nullptr ) {
        dst = *_colors[i1];
    }
    // Otherwise, interpolate the current color from the first and second colors
    else {
        lerpColor<AnimChannelIndexes>(dst, *_colors[i1], *_colors[i2], _percent);
    }

    // Convert the color type
    const typename ColorFX1DType::BufferColorType& color = dst;

    // Adjust the percentage
    if(_percent < 100) {
        const int16_t newPercent = static_cast<int16_t>(_percent) + static_cast<int16_t>(_speed);
        _percent = (newPercent <= 100) ? newPercent : 100;
    }
    else {
        if(++_index >= ColorArrayElementCount) _index = 0;
        _percent = 0;
    }

    if( _colors[i2] == nullptr ) {
        if( (_index += 2) >= ColorArrayElementCount) _index = 0;
        _percent = 0;
    }

    // Set the color(s)
    for(BufferSizeType i = elemBeginIdx; i <= elemEndIdx; ++i) {
        colorFX1D.set(i, color);
    }
}


template <typename ColorAnimator1D_T, uint8_t ColorArraySizeN, typename AnimProcColorT, uint8_t AnimChannelIndexes>
inline void Animation1DProc_BreathingMulti<ColorAnimator1D_T, ColorArraySizeN, AnimProcColorT, AnimChannelIndexes>
::set(uint8_t index, uint8_t percent)
{
    _index   = (index   <  ColorArrayElementCount) ? index   : (ColorArrayElementCount - 1);
    _percent = (percent <= 100                   ) ? percent : 100;
}


#if defined(__x86_64__) || defined(__i386__)

template <typename ColorAnimator1D_T, uint8_t ColorArraySizeN, typename AnimProcColorT, uint8_t AnimChannelIndexes>
CBP::QUInt Animation1DProc_BreathingMulti<ColorAnimator1D_T, ColorArraySizeN, AnimProcColorT, AnimChannelIndexes>
::estimatedTotalAnimationFrameCount() const
{
    uint8_t cnt = 100 / _speed;
    if(_speed * cnt < 100) ++cnt;

    uint8_t cntNull = 0;
    for(uint8_t i = 0; i < ColorArrayElementCount; ++i) {
        if( !_colors[i] ) ++cntNull;
    }

    return cntNull ? ( cnt * (ColorArrayElementCount - cntNull - 1) + ColorArrayElementCount - cntNull - 1 )
                   : ( cnt *  ColorArrayElementCount                + ColorArrayElementCount               );
}

#endif


} // namespace eCxx


#endif
