/*
 * 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_H
#define ECXX_CTL_GFX_COLOR_ANIMATION_1D_PROC_BREATHING_H


namespace eCxx {


//
// A simple 1-dimensional animation processor class - breathing between off and on colors
//
template <
    typename ColorAnimator1D_T,
    typename AnimProcColorT     = typename ColorAnimator1D_T::ColorFX1DType::BufferColorType,
    uint8_t  AnimChannelIndexes = AnimProcColorT::A
>
class Animation1DProc_Breathing : public ColorAnimator1D_T::Animation1DProcType {
    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;

    public:
        // The animation speed must be between 1 and 100
        inline Animation1DProc_Breathing(const AnimProcColorType& colorOff, const AnimProcColorType& colorOn, uint8_t speed);
        inline virtual ~Animation1DProc_Breathing();

        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
        //        percent and direction
        inline void set(uint8_t percent, bool direction);

#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 _colorOff;
        const AnimProcColorType _colorOn;
        const uint8_t           _speed;

              uint8_t           _percent;
              bool              _dir;
};


template <typename ColorAnimator1D_T, typename AnimProcColorT, uint8_t AnimChannelIndexes>
inline Animation1DProc_Breathing<ColorAnimator1D_T, AnimProcColorT, AnimChannelIndexes>
::Animation1DProc_Breathing(const AnimProcColorType& colorOff, const AnimProcColorType& colorOn, uint8_t speed)
: _colorOff( colorOff                                       )
, _colorOn ( colorOn                                        )
, _speed   ( (!speed) ? 1 : ( (speed > 100) ? 100 : speed ) )
, _percent ( 0                                              )
, _dir     ( true                                           )
{}


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


template <typename ColorAnimator1D_T, typename AnimProcColorT, uint8_t AnimChannelIndexes>
void Animation1DProc_Breathing<ColorAnimator1D_T, AnimProcColorT, AnimChannelIndexes>
::reset(ColorFX1DType&, BufferSizeType, BufferSizeType)
{
    _percent = 0;
    _dir     = true;
}


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

    lerpColor<AnimChannelIndexes>(dst, _colorOff, _colorOn, _percent);

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

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

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


template <typename ColorAnimator1D_T, typename AnimProcColorT, uint8_t AnimChannelIndexes>
inline void Animation1DProc_Breathing<ColorAnimator1D_T, AnimProcColorT, AnimChannelIndexes>
::set(uint8_t percent, bool direction)
{
    _percent = (percent <= 100) ? percent : 100;
    _dir     = direction;
}


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

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

    return cnt * 2 + 2;
}

#endif


} // namespace eCxx


#endif
