/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */


#include <eCxx/eCxx_ColorAnimator1D.h>

#if defined(__x86_64__) || defined(__i386__)
    #define SIMULATE_LED
    #include <eCxx/eCxx_SDF.h>
#endif

#include "TestUtility.h"


//
// Test application
//
#ifdef SIMULATE_LED
int main(const int argc, const char** argv)
#else
int main()
#endif
{
    // ===== Instantiate the buffers =====
    eCxx::ColorFX1D<eCxx::RGBColor,  8, eCxx::CBP::RawBuffer> colorFX1D_rawRGB_8;
    eCxx::ColorFX1D<eCxx::RGBColor,  8                      > colorFX1D_encRGB_8;
    eCxx::ColorFX1D<eCxx::RGBColor, 16                      > colorFX1D_encRGB_16;

#ifdef SIMULATE_LED
    // ===== Print some information as needed =====

    // Get the program argument to check which simulation groups to be actually run
    // using the LED simulator
    const eCxx::uint8_t simGroup = (argc == 2 && argv[1][1] == 0) ? (argv[1][0] - '0') : 0;

    // If no simulation group specified, print the list of available groups
     if(!simGroup || simGroup > 2) {
        printf("\n"                                                         );
        printf("Available simulation groups:\n"                             );
        printf("\n"                                                         );
        printf("    #1 :  8 bit blink                      red\n"           );
        printf("          8 bit blink                      green/blue\n"    );
        printf("          8 bit breathing                  red\n"           );
        printf("          8 bit breathing                  green/blue\n"    );
        printf("          8 bit chaser    bouncing         white\n"         );
        printf("          8 bit chaser    backward-forward white\n"         );
        printf("\n"                                                         );
        printf("    #2 : 16 bit multi-blink                red-green-blue\n");
        printf("         16 bit multi-breathing            red-green-blue\n");
        printf("         16 bit random                     white\n"         );
        printf("         16 bit random                     rainbow\n"       );
        printf("         16 bit random starlight           white\n"         );
        printf("\n"                                                         );
    }

    // SDF file names
    constexpr const char* TMP_SDF_RAW_FILE_NAME = "/tmp/sdf_raw.txt";
    constexpr const char* TMP_SDF_ENC_FILE_NAME = "/tmp/sdf_enc.txt";

    // SDF file handle
    eCxx::SDFFile sdf;
#endif



    // =============================================================================================
    // ========================================== Group 1 ==========================================
    // =============================================================================================

    // ===== Instantiate the animators and initialize the segments - blink =====
    auto caeBlink1 = eCxx::makeColorAnimator1D<1>(colorFX1D_encRGB_8);
    auto caeBlink2 = eCxx::makeColorAnimator1D<2>(colorFX1D_encRGB_8);

    eCxx::Animation1DProc_Blink< decltype(caeBlink1) > blinkR_encRGB(eCxx::Ext::RGB::black, eCxx::Ext::RGB::red  );
    eCxx::Animation1DProc_Blink< decltype(caeBlink1) > blinkG_encRGB(eCxx::Ext::RGB::black, eCxx::Ext::RGB::green);
    eCxx::Animation1DProc_Blink< decltype(caeBlink1) > blinkB_encRGB(eCxx::Ext::RGB::black, eCxx::Ext::RGB::blue );

    caeBlink1.initSegment(0, blinkR_encRGB);

    caeBlink2.initSegment(0, 0, 3, 0, blinkG_encRGB);
    caeBlink2.initSegment(1, 4, 7, 1, blinkB_encRGB);

    // ===== Instantiate the animators and initialize the segments - breathing =====
    auto carBreathing1 = eCxx::makeColorAnimator1D<1>(colorFX1D_rawRGB_8);
    auto carBreathing2 = eCxx::makeColorAnimator1D<2>(colorFX1D_rawRGB_8);

    eCxx::Animation1DProc_Breathing< decltype(carBreathing1), eCxx::HSVColor > breathingR_rawRGB(
        eCxx::HSVColor(  0, 200, 0), eCxx::HSVColor(  0, 200, 200), 5
    );
    eCxx::Animation1DProc_Breathing< decltype(carBreathing1), eCxx::HSVColor > breathingG_rawRGB(
        eCxx::HSVColor(120, 200, 0), eCxx::HSVColor(120, 200, 200), 5
    );
    eCxx::Animation1DProc_Breathing< decltype(carBreathing1), eCxx::HSVColor > breathingB_rawRGB(
        eCxx::HSVColor(240, 200, 0), eCxx::HSVColor(240, 200, 200), 5
    );

    carBreathing1.initSegment(0, breathingR_rawRGB);

    carBreathing2.initSegment(0, 0, 3, 0, breathingG_rawRGB);
    carBreathing2.initSegment(1, 4, 7, 0, breathingB_rawRGB);

    // ===== Instantiate the animators and initialize the segments - chaser =====
    const eCxx::uint8_t fullValue = eCxx::cie1931lut58(31); // Use CIE-1931 lightness LUT so that color correction
    const eCxx::uint8_t halfValue = eCxx::cie1931lut58(16); // will not be needed for this animation

    auto caeChaser1 = eCxx::makeColorAnimator1D<1>(colorFX1D_encRGB_8);
    auto caeChaser2 = eCxx::makeColorAnimator1D<2>(colorFX1D_encRGB_8);

    using ChaserProc = eCxx::Animation1DProc_Chaser< decltype(caeChaser1) >;

    ChaserProc chaser_encRGB_forward(
        ChaserProc::Mode::LightForward,
        eCxx::RGBColor(        0,         0,         0),
        eCxx::RGBColor(halfValue, halfValue, halfValue),
        eCxx::RGBColor(fullValue, fullValue, fullValue)
    );
    ChaserProc chaser_encRGB_backward(
        ChaserProc::Mode::LightBackward,
        eCxx::RGBColor(        0,         0,         0),
        eCxx::RGBColor(halfValue, halfValue, halfValue),
        eCxx::RGBColor(fullValue, fullValue, fullValue)
    );
    ChaserProc chaser_encRGB_bouncing(
        ChaserProc::Mode::LightBouncing,
        eCxx::RGBColor(        0,         0,         0),
        eCxx::RGBColor(halfValue, halfValue, halfValue),
        eCxx::RGBColor(fullValue, fullValue, fullValue)
    );

    caeChaser1.initSegment(0, chaser_encRGB_bouncing);

    caeChaser2.initSegment(0, 0, 3, 0, chaser_encRGB_backward);
    caeChaser2.initSegment(1, 4, 7, 0, chaser_encRGB_forward );

#ifdef SIMULATE_LED

    // ===== Test the results using LED simulator =====
#if 1
    sdf = eCxx::beginSDF_1D_RowMajor_RGB(TMP_SDF_ENC_FILE_NAME, colorFX1D_encRGB_8, 50, -1);
    caeBlink1.reset();
    for(eCxx::CBP::QUInt i = 0; i < caeBlink1.estimatedTotalAnimationFrameCount(); ++i) {
        caeBlink1.update();
        eCxx::writeSDF_1D_RGB(sdf, colorFX1D_encRGB_8);
    }
    eCxx::endSDF(sdf);
    if(simGroup == 1) eCxx::execSDF(TMP_SDF_ENC_FILE_NAME);
#endif

#if 1
    sdf = eCxx::beginSDF_1D_RowMajor_RGB(TMP_SDF_ENC_FILE_NAME, colorFX1D_encRGB_8, 50, -1);
    caeBlink2.reset();
    for(eCxx::CBP::QUInt i = 0; i < caeBlink2.estimatedTotalAnimationFrameCount(); ++i) {
        caeBlink2.update();
        eCxx::writeSDF_1D_RGB(sdf, colorFX1D_encRGB_8);
    }
    eCxx::endSDF(sdf);
    if(simGroup == 1) eCxx::execSDF(TMP_SDF_ENC_FILE_NAME);
#endif

#if 1
    sdf = eCxx::beginSDF_1D_RowMajor_RGB(TMP_SDF_RAW_FILE_NAME, colorFX1D_rawRGB_8, 5, -1);
    carBreathing1.reset();
    for(eCxx::CBP::QUInt i = 0; i < carBreathing1.estimatedTotalAnimationFrameCount(); ++i) {
        carBreathing1.update();
        eCxx::writeSDF_1D_RGB(sdf, colorFX1D_rawRGB_8);
    }
    eCxx::endSDF(sdf);
    if(simGroup == 1) eCxx::execSDF(TMP_SDF_RAW_FILE_NAME);
#endif

#if 1
    sdf = eCxx::beginSDF_1D_RowMajor_RGB(TMP_SDF_RAW_FILE_NAME, colorFX1D_rawRGB_8, 5, -1);
    carBreathing2.reset();
    breathingG_rawRGB.set(  0, true );
    breathingB_rawRGB.set(100, false);
    for(eCxx::CBP::QUInt i = 0; i < carBreathing2.estimatedTotalAnimationFrameCount(); ++i) {
        carBreathing2.update();
        eCxx::writeSDF_1D_RGB(sdf, colorFX1D_rawRGB_8);
    }
    eCxx::endSDF(sdf);
    if(simGroup == 1) eCxx::execSDF(TMP_SDF_RAW_FILE_NAME);
#endif

#if 1
    sdf = eCxx::beginSDF_1D_RowMajor_RGB(TMP_SDF_ENC_FILE_NAME, colorFX1D_encRGB_8, 5, -1);
    caeChaser1.reset();
    for(eCxx::CBP::QUInt i = 0; i < caeChaser1.estimatedTotalAnimationFrameCount(); ++i) {
        caeChaser1.update();
        eCxx::writeSDF_1D_RGB(sdf, colorFX1D_encRGB_8);
    }
    eCxx::endSDF(sdf);
    if(simGroup == 1) eCxx::execSDF(TMP_SDF_ENC_FILE_NAME);
#endif

#if 1
    sdf = eCxx::beginSDF_1D_RowMajor_RGB(TMP_SDF_ENC_FILE_NAME, colorFX1D_encRGB_8, 10, -1);
    caeChaser2.reset();
    for(eCxx::CBP::QUInt i = 0; i < caeChaser2.estimatedTotalAnimationFrameCount(); ++i) {
        caeChaser2.update();
        eCxx::writeSDF_1D_RGB(sdf, colorFX1D_encRGB_8);
    }
    eCxx::endSDF(sdf);
    if(simGroup == 1) eCxx::execSDF(TMP_SDF_ENC_FILE_NAME);
#endif

#else // SIMULATE_LED

    // ===== Test the function calls only =====
    caeBlink1    .reset(); caeBlink1    .update();
    caeBlink2    .reset(); caeBlink2    .update();

    carBreathing1.reset(); carBreathing1.update();
    carBreathing2.reset(); carBreathing2.update();

    caeChaser1   .reset(); caeChaser1   .update();
    caeChaser2   .reset(); caeChaser2   .update();

#endif // SIMULATE_LED



    // =============================================================================================
    // ========================================== Group 2 ==========================================
    // =============================================================================================

    // ===== Instantiate the animators and initialize the segments - multi-blink =====
    auto caeBlink3 = eCxx::makeColorAnimator1D<1>(colorFX1D_encRGB_16);

    eCxx::Animation1DProc_BlinkMulti< decltype(caeBlink3), 3 > blinkM_encRGB(
        eCxx::Ext::RGB::red,
        eCxx::Ext::RGB::green,
        eCxx::Ext::RGB::blue
    );

    caeBlink3.initSegment(0, blinkM_encRGB);

    // ===== Instantiate the animators and initialize the segments - multi-breathing =====
    static const eCxx::HSVColor hsvMax(360, 200, 200);

    auto caeBreathing3 = eCxx::makeColorAnimator1D<1>(colorFX1D_encRGB_16);

    eCxx::Animation1DProc_BreathingMulti< decltype(caeBreathing3), 5, eCxx::HSVColor, eCxx::HSVColor::H > breathingM_encRGB(
        5,
        &eCxx::Ext::HSV::red,
        &eCxx::Ext::HSV::green,
        &eCxx::Ext::HSV::blue,
        &hsvMax,                // Needed to properly animate from blue to red in HSV color type
         eCxx::Ext::HSV::null   // Needed to jump directly from the last color to the first color (skip the breathing)
    );

    caeBreathing3.initSegment(0, breathingM_encRGB);

    // ===== Instantiate the animators and initialize the segments - random =====
    auto caeRandom1 = eCxx::makeColorAnimator1D<1>(colorFX1D_encRGB_16);
    auto caeRandom2 = eCxx::makeColorAnimator1D<1>(colorFX1D_encRGB_16);

    class CGRainbow : public eCxx::ColorGenerator1D_CRTPBase<
        CGRainbow,
        decltype(caeRandom1)::BufferColorType,
        decltype(caeRandom1)::BufferSizeType
    >  {
        public:
            inline void reset_impl()
            {};

            inline const ColorType generate_impl(eCxx::uint8_t, bool state)
            {
                return state
                       ? eCxx::RGBColor( eCxx::Application::random8(), eCxx::Application::random8(), eCxx::Application::random8() )
                       : eCxx::Ext::RGB::black;
            }

            inline const ColorType generate_impl(eCxx::uint8_t, eCxx::uint8_t lerpFactor)
            {
                ColorType dst;
                lerpColor(dst, eCxx::Ext::RGB::black, eCxx::RGBColor( eCxx::Application::random8(), eCxx::Application::random8(), eCxx::Application::random8() ), lerpFactor);
                return dst;
            }

            inline const ColorType generate_impl(BufferSizeType, BufferSizeType, BufferSizeType, bool state)
            { return generate_impl(0, state); }

            inline const ColorType generate_impl(BufferSizeType, BufferSizeType, BufferSizeType, eCxx::uint8_t lerpFactor)
            { return generate_impl(0, lerpFactor); }

#if defined(__x86_64__) || defined(__i386__)
            inline eCxx::CBP::QUInt estimatedTotalAnimationFrameCount_impl() const
            { return 1; }
#endif
    };

    CGRainbow cgRainbow;

    eCxx::Animation1DProc_RandomTwoColors< decltype(caeRandom1)            > randomW_encRGB(eCxx::Ext::RGB::black, eCxx::Ext::RGB::white, 32);
    eCxx::Animation1DProc_Random         < decltype(caeRandom1), CGRainbow > randomM_encRGB(cgRainbow, 32);

    caeRandom1.initSegment(0, randomW_encRGB);
    caeRandom2.initSegment(0, randomM_encRGB);


    // ===== Instantiate the animators and initialize the segments - random starlight =====
    auto caeStarlight = eCxx::makeColorAnimator1D<1>(colorFX1D_encRGB_16);

    eCxx::Animation1DProc_RandomStarlightTwoColors< decltype(caeStarlight), 3 > starlightW_encRGB(eCxx::Ext::RGB::black, eCxx::Ext::RGB::white);

    caeStarlight.initSegment(0, starlightW_encRGB);

#ifdef SIMULATE_LED

    // ===== Test the results using LED simulator =====
#if 1
    sdf = eCxx::beginSDF_1D_RowMajor_RGB(TMP_SDF_ENC_FILE_NAME, colorFX1D_encRGB_16, 50, -1);
    caeBlink3.reset();
    for(eCxx::CBP::QUInt i = 0; i < caeBlink3.estimatedTotalAnimationFrameCount(); ++i) {
        caeBlink3.update();
        eCxx::writeSDF_1D_RGB(sdf, colorFX1D_encRGB_16);
    }
    eCxx::endSDF(sdf);
    if(simGroup == 2) eCxx::execSDF(TMP_SDF_ENC_FILE_NAME);
#endif

#if 1
    sdf = eCxx::beginSDF_1D_RowMajor_RGB(TMP_SDF_ENC_FILE_NAME, colorFX1D_encRGB_16, 10, -1);
    caeBreathing3.reset();
    for(eCxx::CBP::QUInt i = 0; i < caeBreathing3.estimatedTotalAnimationFrameCount(); ++i) {
        caeBreathing3.update();
        colorFX1D_encRGB_16.cie1931lcc58(); // Perform color correction to make the result visually better
        eCxx::writeSDF_1D_RGB(sdf, colorFX1D_encRGB_16);
    }
    eCxx::endSDF(sdf);
    if(simGroup == 2) eCxx::execSDF(TMP_SDF_ENC_FILE_NAME);
#endif

#if 1
    sdf = eCxx::beginSDF_1D_RowMajor_RGB(TMP_SDF_ENC_FILE_NAME, colorFX1D_encRGB_16, 10, -1);
    caeRandom1.reset();
    for(eCxx::CBP::QUInt i = 0; i < caeRandom1.estimatedTotalAnimationFrameCount(); ++i) {
        caeRandom1.update();
        eCxx::writeSDF_1D_RGB(sdf, colorFX1D_encRGB_16);
    }
    eCxx::endSDF(sdf);
    if(simGroup == 2) eCxx::execSDF(TMP_SDF_ENC_FILE_NAME);
#endif

#if 1
    sdf = eCxx::beginSDF_1D_RowMajor_RGB(TMP_SDF_ENC_FILE_NAME, colorFX1D_encRGB_16, 10, -1);
    caeRandom2.reset();
    for(eCxx::CBP::QUInt i = 0; i < caeRandom2.estimatedTotalAnimationFrameCount(); ++i) {
        caeRandom2.update();
        eCxx::writeSDF_1D_RGB(sdf, colorFX1D_encRGB_16);
    }
    eCxx::endSDF(sdf);
    if(simGroup == 2) eCxx::execSDF(TMP_SDF_ENC_FILE_NAME);
#endif

#if 1
    sdf = eCxx::beginSDF_1D_RowMajor_RGB(TMP_SDF_ENC_FILE_NAME, colorFX1D_encRGB_16, 5, -1);
    caeStarlight.reset();
    for(eCxx::CBP::QUInt i = 0; i < caeStarlight.estimatedTotalAnimationFrameCount(); ++i) {
        caeStarlight.update();
        eCxx::writeSDF_1D_RGB(sdf, colorFX1D_encRGB_16);
    }
    eCxx::endSDF(sdf);
    if(simGroup == 2) eCxx::execSDF(TMP_SDF_ENC_FILE_NAME);
#endif

#else // SIMULATE_LED

    // ===== Test the function calls only =====
    caeBlink3    .reset(); caeBlink3    .update();
    caeBreathing3.reset(); caeBreathing3.update();

    caeRandom1   .reset(); caeRandom1   .update();
    caeRandom2   .reset(); caeRandom2   .update();

    caeStarlight .reset(); caeStarlight .update();

#endif // SIMULATE_LED



    // Done
    return 0;
}
