/*
 * 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_2D_PROC_RANDOM_STARLIGHT_H
#define ECXX_CTL_GFX_COLOR_ANIMATION_2D_PROC_RANDOM_STARLIGHT_H


#include <eCxx/CTL/GFX/ColorGenerator2D_OffOn.h>


namespace eCxx {


//
// A simple 2-dimensional animation processor class - random starlight using ColorGenerator2D<T>
//
template <
    typename ColorAnimator2D_T,
    typename ColorGeneratorImplementationT,
    uint8_t  StarPointCountN,
    typename AnimProcColorT = typename ColorAnimator2D_T::ColorFX2DType::BufferColorType
>
class Animation2DProc_RandomStarlight : public ColorAnimator2D_T::Animation2DProcType {
    static_assert(
        (StarPointCountN >= 1) && (StarPointCountN <= 250),
        "template argument substituting StarPointCountN is not between 1 and 250"
    );

    static_assert(
        StarPointCountN <= ColorAnimator2D_T::ColorFX2DType::BufferElementCount,
        "template argument substituting StarPointCountN is larger than the number of elements contained by the type that substitutes the template argument ColorAnimator2D_T::ColorFX2DType"
    );

    public:
        using AnimProcColorType    = AnimProcColorT;
        using ColorAnimator2DType  = ColorAnimator2D_T;
        using ColorFX2DType        = typename ColorAnimator2D_T::Animation2DProcType::ColorFX2DType;
        using BufferColorType      = typename ColorAnimator2D_T::Animation2DProcType::BufferColorType;
        using BufferSizeType       = typename ColorAnimator2D_T::Animation2DProcType::BufferSizeType;

        using ColorGenerator2DType = ColorGenerator2D_CRTPBase<ColorGeneratorImplementationT, AnimProcColorType, BufferSizeType>;

        static constexpr uint8_t StarPointCount = StarPointCountN;

    public:
        inline Animation2DProc_RandomStarlight(ColorGenerator2DType& colorGen);
        inline virtual ~Animation2DProc_RandomStarlight();

        virtual void reset(ColorFX2DType& colorFX2D, BufferSizeType elemXBeginIdx, BufferSizeType elemYBeginIdx, BufferSizeType elemXEndIdx, BufferSizeType elemYEndIdx);
        virtual void update(ColorFX2DType& colorFX2D, BufferSizeType elemXBeginIdx, BufferSizeType elemYBeginIdx, BufferSizeType elemXEndIdx, BufferSizeType elemYEndIdx);

#if defined(__x86_64__) || defined(__i386__)
        // NOTE : Some color generator implementations which are used by this animation
        //        processor implementation may require reset() to be called first for this
        //        function to work properly
        virtual CBP::QUInt estimatedTotalAnimationFrameCount() const;
#endif

    private:
        struct StarPoint {
            BufferSizeType x;
            BufferSizeType y;
            uint8_t        state;
            int8_t         speed;
        };

        static constexpr int8_t StarSpeed_L =  5; // The speeds must be selected in a way
        static constexpr int8_t StarSpeed_M = 10; // so that 100 is divisible by them
        static constexpr int8_t StarSpeed_H = 20; //

    private:
        ColorGenerator2DType* const _colorGen;

        StarPoint                   _starPoint[StarPointCount];
};


template <typename ColorAnimator2D_T, typename ColorGeneratorImplementationT, uint8_t StarPointCountN, typename AnimProcColorT>
inline Animation2DProc_RandomStarlight<ColorAnimator2D_T, ColorGeneratorImplementationT, StarPointCountN, AnimProcColorT>
::Animation2DProc_RandomStarlight(ColorGenerator2DType& colorGen)
: _colorGen(&colorGen)
{}


template <typename ColorAnimator2D_T, typename ColorGeneratorImplementationT, uint8_t StarPointCountN, typename AnimProcColorT>
inline Animation2DProc_RandomStarlight<ColorAnimator2D_T, ColorGeneratorImplementationT, StarPointCountN, AnimProcColorT>
::~Animation2DProc_RandomStarlight()
{}


template <typename ColorAnimator2D_T, typename ColorGeneratorImplementationT, uint8_t StarPointCountN, typename AnimProcColorT>
void Animation2DProc_RandomStarlight<ColorAnimator2D_T, ColorGeneratorImplementationT, StarPointCountN, AnimProcColorT>
::reset(ColorFX2DType&, BufferSizeType, BufferSizeType, BufferSizeType, BufferSizeType)
{
    _colorGen->reset();
    memset( _starPoint, 0, sizeof(_starPoint) );
}


template <typename ColorAnimator2D_T, typename ColorGeneratorImplementationT, uint8_t StarPointCountN, typename AnimProcColorT>
void Animation2DProc_RandomStarlight<ColorAnimator2D_T, ColorGeneratorImplementationT, StarPointCountN, AnimProcColorT>
::update(ColorFX2DType& colorFX2D, BufferSizeType elemXBeginIdx, BufferSizeType elemYBeginIdx, BufferSizeType elemXEndIdx, BufferSizeType elemYEndIdx)
{
    for(uint8_t i = 0; i < StarPointCount; ++i) {
        // Get the current star point
        StarPoint& sp = _starPoint[i];
        // Create a new star point as needed
        if(!sp.speed) {
            sp.x     = Application::random16(0, ColorFX2DType::XElementCount - 1);
            sp.y     = Application::random16(0, ColorFX2DType::YElementCount - 1);
            sp.state = 0;
            switch( Application::random8(0, 2) ) {
                case 0 : sp.speed = StarSpeed_L; break;
                case 1 : sp.speed = StarSpeed_M; break;
                case 2 : sp.speed = StarSpeed_H; break;
            }
        }
        // Draw the star point
        colorFX2D.setXY(
            sp.x, sp.y,
            _colorGen->generate(
                elemXBeginIdx, elemYBeginIdx, elemXEndIdx, elemYEndIdx, sp.x, sp.y, sp.state
            )
        );
        // Update the star point as needed
        if(sp.speed > 0) {
            if(sp.state < 100) {
                const int16_t newState = static_cast<int16_t>(sp.state) + sp.speed;
                sp.state = (newState <= 100) ? newState : 100;
            }
            else {
                sp.speed = -sp.speed;
            }
        }
        else if(sp.speed < 0) {
            if(sp.state > 0) {
                const int16_t newState = static_cast<int16_t>(sp.state) + sp.speed;
                sp.state = (newState >= 0) ? newState : 0;
            }
            else {
                sp.speed = 0;
            }
        }
    }
}


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

template <typename ColorAnimator2D_T, typename ColorGeneratorImplementationT, uint8_t StarPointCountN, typename AnimProcColorT>
CBP::QUInt Animation2DProc_RandomStarlight<ColorAnimator2D_T, ColorGeneratorImplementationT, StarPointCountN, AnimProcColorT>
::estimatedTotalAnimationFrameCount() const
{ return (100 / StarSpeed_L * 2 + 2) * 5; }

#endif


//
// A simple 2-dimensional animation processor class - random starlight between off and on colors
//
template <
    typename ColorAnimator2D_T,
    uint8_t  StarPointCountN,
    typename AnimProcColorT     = typename ColorAnimator2D_T::ColorFX2DType::BufferColorType,
    uint8_t  AnimChannelIndexes = AnimProcColorT::A
>
class Animation2DProc_RandomStarlightTwoColors : public Animation2DProc_RandomStarlight<
    ColorAnimator2D_T,
    ColorGenerator2D_OffOn<AnimProcColorT, typename ColorAnimator2D_T::Animation2DProcType::BufferSizeType>,
    StarPointCountN,
    AnimProcColorT
> {
    public:
        using Animation2DProcType      = Animation2DProc_RandomStarlight<
                                             ColorAnimator2D_T,
                                             ColorGenerator2D_OffOn<
                                                 AnimProcColorT,
                                                 typename ColorAnimator2D_T::Animation2DProcType::BufferSizeType
                                             >,
                                             StarPointCountN,
                                             AnimProcColorT
                                         >;

        using ColorGenerator2DImplType = ColorGenerator2D_OffOn<
                                             AnimProcColorT,
                                             typename ColorAnimator2D_T::Animation2DProcType::BufferSizeType,
                                             AnimChannelIndexes
                                         >;

        using AnimProcColorType    = typename Animation2DProcType::AnimProcColorType;
        using ColorAnimator2DType  = typename Animation2DProcType::ColorAnimator2DType;
        using ColorFX2DType        = typename Animation2DProcType::ColorFX2DType;
        using BufferColorType      = typename Animation2DProcType::BufferColorType;
        using BufferSizeType       = typename Animation2DProcType::BufferSizeType;

        using ColorGenerator2DType = typename Animation2DProcType::ColorGenerator2DType;

    public:
        inline Animation2DProc_RandomStarlightTwoColors(const AnimProcColorType& colorOff, const AnimProcColorType& colorOn);
        inline virtual ~Animation2DProc_RandomStarlightTwoColors();

    private:
        ColorGenerator2DImplType _colorGenInst;
};


template <typename ColorAnimator2D_T, uint8_t StarPointCountN, typename AnimProcColorT, uint8_t AnimChannelIndexes>
inline Animation2DProc_RandomStarlightTwoColors<ColorAnimator2D_T, StarPointCountN, AnimProcColorT, AnimChannelIndexes>
::Animation2DProc_RandomStarlightTwoColors(const AnimProcColorType& colorOff, const AnimProcColorType& colorOn)
: Animation2DProcType(_colorGenInst    )
, _colorGenInst      (colorOff, colorOn)
{}


template <typename ColorAnimator2D_T, uint8_t StarPointCountN, typename AnimProcColorT, uint8_t AnimChannelIndexes>
inline Animation2DProc_RandomStarlightTwoColors<ColorAnimator2D_T, StarPointCountN, AnimProcColorT, AnimChannelIndexes>
::~Animation2DProc_RandomStarlightTwoColors()
{}


} // namespace eCxx


#endif
