/*
 * 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_CHASER_H
#define ECXX_CTL_GFX_COLOR_ANIMATION_1D_PROC_CHASER_H


namespace eCxx {


//
// A simple 1-dimensional animation processor class - 3 colors (off, dim, bright) light/dark chaser
//
template <
    typename ColorAnimator1D_T,
    typename AnimProcColorT = typename ColorAnimator1D_T::ColorFX1DType::BufferColorType
>
class Animation1DProc_Chaser : 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;

        enum class Mode : uint8_t {
            LightForward,
            LightBackward,
            LightBouncing,
            DarkForward,
            DarkBackward,
            DarkBouncing
        };

    public:
        inline Animation1DProc_Chaser(Mode mode, const AnimProcColorType& colorOff, const AnimProcColorType& colorDim, const AnimProcColorType& colorBright);
        inline virtual ~Animation1DProc_Chaser();

        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
        //        position and direction
        inline void set(CBP::DSInt currentPosition, bool direction);

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

    private:
        static inline bool isDarkMode(Mode mode) noexcept
        { return mode == Mode::DarkForward || mode == Mode::DarkBackward || mode == Mode::DarkBouncing; }

    private:
        const Mode              _mode;
        const AnimProcColorType _colorOff;
        const AnimProcColorType _colorDim;
        const AnimProcColorType _colorBright;

              CBP::DSInt        _minPos;
              CBP::DSInt        _maxPos;
              CBP::DSInt        _curPos;
              bool              _dir;
};


template <typename ColorAnimator1D_T, typename AnimProcColorT>
inline Animation1DProc_Chaser<ColorAnimator1D_T, AnimProcColorT>
::Animation1DProc_Chaser(Mode mode, const AnimProcColorType& colorOff, const AnimProcColorType& colorDim, const AnimProcColorType& colorBright)
: _mode       ( mode                                         )
, _colorOff   ( isDarkMode(mode) ? colorBright : colorOff    )
, _colorDim   ( colorDim                                     )
, _colorBright( isDarkMode(mode) ? colorOff    : colorBright )
, _minPos     ( 0                                            )
, _maxPos     ( 0                                            )
, _curPos     ( 0                                            )
, _dir        ( true                                         )
{}


template <typename ColorAnimator1D_T, typename AnimProcColorT>
inline Animation1DProc_Chaser<ColorAnimator1D_T, AnimProcColorT>
::~Animation1DProc_Chaser()
{}


template <typename ColorAnimator1D_T, typename AnimProcColorT>
void Animation1DProc_Chaser<ColorAnimator1D_T, AnimProcColorT>
::reset(ColorFX1DType&, BufferSizeType elemBeginIdx, BufferSizeType elemEndIdx)
{
    _minPos = elemBeginIdx;
    _maxPos = elemEndIdx;

    _curPos = _minPos - 1;
    _dir    = true;

    if(_mode == Mode::LightBackward || _mode == Mode::DarkBackward) _curPos = _maxPos + 1;
}


template <typename ColorAnimator1D_T, typename AnimProcColorT>
void Animation1DProc_Chaser<ColorAnimator1D_T, AnimProcColorT>
::update(ColorFX1DType& colorFX1D, BufferSizeType, BufferSizeType)
{
    // Set the color(s)
    for(CBP::DSInt i = _minPos; i <= _maxPos; ++i) {
        // Set the dim color for the position before the current position
        if(_curPos > _minPos && i == _curPos - 1) {
            colorFX1D.set(_curPos - 1, _colorDim);
            continue;
        }
        // Set the dim color for the position after the current position
        if(_curPos < _maxPos && i == _curPos + 1) {
            colorFX1D.set(_curPos + 1, _colorDim);
            continue;
        }
        // Set the bright color for the current position
        if(i == _curPos) {
            colorFX1D.set(_curPos, _colorBright);
            continue;
        }
        // Set the off color(s)
        colorFX1D.set(i, _colorOff);
    }

    // Adjust the position
    switch(_mode) {
        case Mode::LightForward : /* FALLTHROUGH */
        case Mode::DarkForward  :
            if(_curPos <= _maxPos) ++_curPos;
            else                   _curPos = _minPos - 1;
            break;

        case Mode::LightBackward : /* FALLTHROUGH */
        case Mode::DarkBackward  :
            if(_curPos >= _minPos) --_curPos;
            else                   _curPos = _maxPos + 1;
            break;

        case Mode::LightBouncing : /* FALLTHROUGH */
        case Mode::DarkBouncing  :
            if(_dir) {
                if(_curPos <= _maxPos) ++_curPos;
                else                   _dir = false;
            }
            else {
                if(_curPos >= _minPos) --_curPos;
                else                   _dir = true;
            }
            break;
    }
}


template <typename ColorAnimator1D_T, typename AnimProcColorT>
inline void Animation1DProc_Chaser<ColorAnimator1D_T, AnimProcColorT>
::set(CBP::DSInt currentPosition, bool direction)
{
    _curPos = currentPosition;
    _dir    = direction;

         if(_curPos > _maxPos + 1) _curPos = _maxPos + 1;
    else if(_curPos < _minPos - 1) _curPos = _minPos - 1;
}


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

template <typename ColorAnimator1D_T, typename AnimProcColorT>
CBP::QUInt Animation1DProc_Chaser<ColorAnimator1D_T, AnimProcColorT>
::estimatedTotalAnimationFrameCount() const
{
    // Calculate the number of elements
    const CBP::DSInt elemCount = _maxPos - _minPos + 1;

    // Bouncing mode
    if(_mode == Mode::LightBouncing || _mode == Mode::DarkBouncing) {
        return elemCount * 2 + 2 + 2;
    }

    // Non bouncing mode
    return elemCount + 2;
}

#endif


} // namespace eCxx


#endif
