#include "Animations.h"
#include <Arduino.h>
#include <memory>
#include <FastLED.h>
#include "ColorPalettes.h"
#include "esp_system.h"


typedef struct{
    float minSpeed;
    float maxSpeed;
    int rampCycles;
}SPEED_PROPERTIES;


void Animation_Init(void){
    // Create Palettes

}

// Animation Loop Template
void Animation_Loop(bool volatile& loop_active_flag, int speed, std::function<int()> callback) {
    if (!callback) {
        ESP_LOGE("Animation_Loop", "Invalid callback function");
        return;
    }

    loop_active_flag = true;
    speed = constrain(speed, 0, MaxSpeed);
    int loopDelay = max(MaxSpeed - speed, MinLoopDelay);

    ulTaskNotifyTake(pdTRUE, 0);  // Clear any pending notifications
    
    TickType_t xLastWakeTime;
    TickType_t elapsedTicks;
    TickType_t delayTicks;
    int retVal = 0;
    while(!retVal && loop_active_flag) {
        xLastWakeTime = xTaskGetTickCount();

        try {
            retVal = callback(); // Call animation function
        } catch (const std::exception& e) {
            ESP_LOGE("Animation_Loop", "Callback exception: %s", e.what());
            break;
        }
        
        if(!loop_active_flag) return;

        // Calculate remaining time with overflow protection
        elapsedTicks = xTaskGetTickCount() - xLastWakeTime;
        delayTicks = (elapsedTicks < loopDelay) ? (loopDelay - elapsedTicks) : 0;
        
        // Check for termination request
        if (ulTaskNotifyTake(pdTRUE, delayTicks)) {  break; }
    }

    loop_active_flag = false;
}

// Animation Loop Template
void Animation_Loop_Duration(bool volatile& loop_active_flag, int speed, TickType_t durationMs, std::function<int()> callback) {
    loop_active_flag = true;
    speed = constrain(speed, 0, MaxSpeed);
    if (durationMs < 0) durationMs = 0;
    
    ulTaskNotifyTake(pdTRUE, 0);  // Clear any pending notifications
    TickType_t startTicks = xTaskGetTickCount();
    TickType_t xLastWakeTime;
    for(;;) {
        xLastWakeTime = xTaskGetTickCount();
        
        // Call animation function
        int speedIncrease = 0;
        try {
            speedIncrease = callback(); // Call animation function
        } catch (const std::exception& e) {
            ESP_LOGE("Animation_Loop_Duration", "Callback exception: %s", e.what());
            break;
        } catch (...) {
            ESP_LOGE("Animation_Loop_Duration", "Callback unknown exception");
            break;
        }
        
        if(!loop_active_flag) return;

        // Calculate combined speed with bounds protection
        int totalSpeed = constrain(speed + speedIncrease, 0, MaxSpeed);

        // Calculate delay with minimum protection
        int loopDelay = MaxSpeed - totalSpeed;
        loopDelay = max(loopDelay, MinLoopDelay);

        // Calculate remaining time with overflow protection
        TickType_t elapsedTicks = xTaskGetTickCount() - xLastWakeTime;
        TickType_t delayTicks = (elapsedTicks < loopDelay) ? (loopDelay - elapsedTicks) : 0;
        
    // Delay and Check for termination request
        if (ulTaskNotifyTake(pdTRUE, delayTicks)) { break; }
        
        // Check if duration reached and wait for loop_active_flag
        if (durationMs >=0) {
            TickType_t totalElapsed = xTaskGetTickCount() - startTicks;

            if (totalElapsed >= durationMs) {
        // Auto-terminate the loop when duration reached
        break;
            }
        }
    }

    loop_active_flag = false;
}

// Animation Loop Template
void Animation_Loop_Cycles(bool volatile& loop_active_flag, int speed, uint32_t loop_cycles, std::function<int()> callback) {
    loop_active_flag = true;
    uint32_t loop_cycle_count = 0;
    speed = constrain(speed, 0, MaxSpeed);
    
    ulTaskNotifyTake(pdTRUE, 0);  // Clear any pending notifications
    TickType_t startTicks = xTaskGetTickCount();
    TickType_t xLastWakeTime;
    TickType_t elapsedTicks;
    TickType_t delayTicks;
    for(;;) {
        xLastWakeTime = xTaskGetTickCount();
        
        int speedIncrease = 0;
        try {
            speedIncrease = callback(); // Call animation function
        } catch (const std::exception& e) {
            ESP_LOGE("Animation_Loop_Cycles", "Callback exception: %s", e.what());
            break;
        } catch (...) {
            ESP_LOGE("Animation_Loop_Cycles", "Callback unknown exception");
            break;
        }
        
        if(!loop_active_flag) return;

        // Calculate combined speed with bounds protection
        int totalSpeed = constrain(speed + speedIncrease, 0, MaxSpeed);

        // Calculate delay with minimum protection
        int loopDelay = MaxSpeed - totalSpeed;
        loopDelay = max(loopDelay, MinLoopDelay);

        // Calculate remaining time with overflow protection
        elapsedTicks = xTaskGetTickCount() - xLastWakeTime;
        delayTicks = (elapsedTicks < loopDelay) ? (loopDelay - elapsedTicks) : 0;
        
        // Delay and Check for termination request
        if (ulTaskNotifyTake(pdTRUE, delayTicks)) { break; }

        // Check if cycles reached and exit
        if (loop_cycle_count >= loop_cycles) {
            break;
        }

        loop_cycle_count++;
    }

    loop_active_flag = false;
}


/********************************************************************************
 * 
 *                     Animations
 * 
 *******************************************************************************/

void Anim_Rainbow(bool volatile& activeFlag, CRGB* leds, int size, int speed){
    // Initialize rainbow pattern once
    fill_rainbow_circular(leds, size, 0);

    CRGB temp;
    Animation_Loop(activeFlag, speed, [&]() -> int {
        // Rotate pixels by 1 position
        temp = leds[0];  // Save first pixel
        memmove(&leds[0], &leds[1], (size-1) * sizeof(CRGB));
        leds[size-1] = temp;  // Move first pixel to end
        
        FastLED.show();
        return 0;
    });
}

// Fire parameters (adjustable)
const uint8_t FIRE_COOLING = 66;
const uint8_t FIRE_SPARKING = 62;
const uint8_t FIRE_brightness = 255;

void Anim_Fire(bool volatile& activeFlag, CRGB* leds, int size, int speed, const CRGBPalette16& firePalette, int shift = 0) {
    if (!leds || size <= 0) return;

    // Calculate half size for mirroring
    const int halfSize = size / 2;
    if (halfSize <= 0) return;

    // Create heat array for half the size
    uint8_t* heat = new (std::nothrow) uint8_t[halfSize];
    if (!heat) return;
    memset(heat, 0, halfSize * sizeof(uint8_t));
    
    CRGB color;
    uint8_t colorindex;
    int pos1, pos2, y;
    Animation_Loop(activeFlag, speed, [&]() -> int {
        // Random cooling
        for(int i = 0; i < halfSize; i++) {
            heat[i] = qsub8(heat[i], random8(0, ((FIRE_COOLING * 10) / halfSize) + 2));
        }
        
        // Heat rises and diffuses
        for(int k = halfSize - 1; k >= 2; k--) {
            heat[k] = (heat[k - 1] + heat[k - 2] + heat[k - 2]) / 3;
        }
        
        // Randomly ignite new sparks at bottom
        if(random8() < FIRE_SPARKING) {
            // ensure y is in-bounds for small strips
            y = min<int>(random8(7), halfSize - 1);
            heat[y] = qadd8(heat[y], random8(160, 240));
        }

        // Map heat to colors with mirroring and shifting
        for(int j = 0; j < halfSize; j++) {
            colorindex = scale8(heat[j], 240);
            color = ColorFromPalette(firePalette, colorindex, FIRE_brightness, LINEARBLEND);
            
            // Apply shift and wrap around
            pos1 = (j + shift + size) % size;
            pos2 = (size - 1 - j + shift + size) % size;
            
            leds[pos1] = color;
            leds[pos2] = color;  // Mirror
        }
        
        FastLED.show();
        return 0;
    });

    delete[] heat;
}


void Anim_Color_Sectors(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, uint8_t numRepeats, int speed) {
    // Validate inputs
    if (size <= 0 || numRepeats <= 0 || colorPack.size <= 0) return;
    
    // Calculate sector size
    int sectorSize = size / (colorPack.size * numRepeats);
    if (sectorSize < 1) sectorSize = 1;
    
    // Initialize pattern
    for (int repeat = 0; repeat < numRepeats; repeat++) {
        for (int color = 0; color < colorPack.size; color++) {
            int startIdx = (repeat * colorPack.size + color) * sectorSize;
            int endIdx = startIdx + sectorSize;
            if (endIdx > size) endIdx = size;
            
            // Fill sector with the specified color
            fill_solid(&leds[startIdx], endIdx - startIdx, colorPack.col[color]);
        }
    }
    
    // Animate rotation
    bool direction = true;  // true = forward, false = backward
    int loopCounter = 0;
    CRGB temp;
    Animation_Loop(activeFlag, speed, [&]() -> int {

        if (direction) {
            temp = leds[0];
            memmove(&leds[0], &leds[1], (size-1) * sizeof(CRGB));
            leds[size-1] = temp;
        } else {
            temp = leds[size-1];
            memmove(&leds[1], &leds[0], (size-1) * sizeof(CRGB));
            leds[0] = temp;
        }
        
        // Direction Switching Logic
        loopCounter++;
        if(loopCounter >= (size * CYCLES_PER_DIRECTION)){
            direction = !direction;
            loopCounter = 0;
        }

        FastLED.show();
        return 0;
    });
}



/********************************************************************************
 *                     Comets Animation
 *******************************************************************************/
#define COMET_SIZE_FACTOR 0.2
#define COMET_FADE_FACTOR1 64 /* longer tail */
#define COMET_FADE_FACTOR2 192 /* shorter tail */
#define COMET_FADE_FACTOR COMET_FADE_FACTOR2
#define MAX_COMETS 16  // Maximum number of comets supported
/*
void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, int speed, bool randomDecay, bool shorterTail, int cometMultiplier = 1) {
    // Validate inputs
    int numComets = colorPack.size;
    if (size <= 0 || numComets <= 0 || colorPack.size <= 0 || cometMultiplier <= 0) return;

    // Calculate comet size
    int cometSize = (size / (numComets * cometMultiplier)) * COMET_SIZE_FACTOR;
    if (cometSize < 1) cometSize = 1;

    // Set fade factor
    uint8_t fadeFactor = shorterTail ? COMET_FADE_FACTOR2 : COMET_FADE_FACTOR1;

    // Initialize comet positions to be equally spaced
    std::unique_ptr<int[]> cometPositions(new (std::nothrow) int[numComets * cometMultiplier]);
    if (!cometPositions) return;
    int totalComets = numComets * cometMultiplier;
    int spacing = size / totalComets;
    for (int i = 0; i < totalComets; i++) {
        cometPositions[i] = i * spacing;
    }

    // Animation loop
    bool direction = true;  // true = forward, false = backward
    int loopCounter = 0;
    try {
        Animation_Loop(activeFlag, speed, [&]() -> int {
            // Fade all LEDs
            for (int i = 0; i < size; i++) {
                if(!randomDecay) {
                    leds[i].fadeToBlackBy(fadeFactor);
                } else if(random(10) > 5){
                    leds[i].fadeToBlackBy(fadeFactor);
                }
            }

            // Move and draw comets
            for (int i = 0; i < totalComets; i++) {
                if (direction) {
                    cometPositions[i] = (cometPositions[i] + 1) % size;
                } else {
                    cometPositions[i] = (cometPositions[i] - 1 + size) % size;
                }

                // Draw comet with solid color
                CRGB color = colorPack.col[i % colorPack.size];
                for (int j = 0; j < cometSize; j++) {
                    int pos = (cometPositions[i] - j + size) % size;
                    leds[pos] += color;
                }
            }

            loopCounter++;
            if(loopCounter >= (size * CYCLES_PER_DIRECTION)){
                direction = !direction;
                loopCounter = 0;
            }

            FastLED.show();
            return 0;
        });
    } catch (const std::exception& e) {
        ESP_LOGE("Anim_Comets", "Exception in Animation_Loop: %s", e.what());
    } catch (...) {
        ESP_LOGE("Anim_Comets", "Unknown exception in Animation_Loop");
    }
    // No need to delete cometPositions as it is managed by std::unique_ptr
}
*/


void Anim_Comets(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colorPack, int speed, bool randomDecay, bool shorterTail, int cometMultiplier = 1) {
    // Validate inputs
    int numComets = colorPack.size;
    int totalComets = numComets * cometMultiplier;
    
    if (size <= 0 || numComets <= 0 || colorPack.size <= 0 || cometMultiplier <= 0 || totalComets > MAX_COMETS) {
        ESP_LOGE("Anim_Comets", "Invalid input parameters or too many comets (max: %d, requested: %d)", MAX_COMETS, totalComets);
        return;
    }

    // Calculate comet size
    int cometSize = (size / totalComets) * COMET_SIZE_FACTOR;
    if (cometSize < 1) cometSize = 1;

    // Set fade factor
    uint8_t fadeFactor = shorterTail ? COMET_FADE_FACTOR2 : COMET_FADE_FACTOR1;

    // Initialize comet positions with fixed array, evenly distributed
    int cometPositions[MAX_COMETS] = {0};
    for (int i = 0; i < totalComets; i++) {
        // Even distribution even when size not divisible by totalComets
        cometPositions[i] = (i * size) / totalComets;
    }

    // Animation loop
    bool direction = true;  // true = forward, false = backward
    int loopCounter = 0;
    int pos;
    CRGB color;
    try {
        Animation_Loop(activeFlag, speed, [&]() -> int {
            // Fade all LEDs
            for (int i = 0; i < size; i++) {
                if (!randomDecay) {
                    leds[i].fadeToBlackBy(fadeFactor);
                } else if (getRandomValue(10) > 5) {
                    leds[i].fadeToBlackBy(fadeFactor);
                }
            }

        // Move and draw comets
            for (int i = 0; i < totalComets; i++) {
                if (direction) {
                    cometPositions[i] = (cometPositions[i] + 1) % size;
                } else {
                    cometPositions[i] = (cometPositions[i] - 1 + size) % size;
                }

                // Draw comet with solid color
                color = colorPack.col[i % colorPack.size];
                for (int j = 0; j < cometSize; j++) {
            // Tail follows the direction of movement
            pos = direction ? (cometPositions[i] - j) : (cometPositions[i] + j);
            pos = (pos % size + size) % size; // safe modulus
                    leds[pos] += color;
                }
            }

            // Direction change
            if (++loopCounter >= (size * CYCLES_PER_DIRECTION)) {
                direction = !direction;
                loopCounter = 0;
            }

            FastLED.show();
            return 0;
        });
    } catch (const std::exception& e) {
        ESP_LOGE("Anim_Comets", "Exception in Animation_Loop: %s", e.what());
    } catch (...) {
        ESP_LOGE("Anim_Comets", "Unknown exception in Animation_Loop");
    }
}


void Anim_TimedFill(bool volatile& activeFlag, CRGB* leds, int size, CRGB baseCol, CRGB fillCol, int totalDurationMs, int shift = 0) {
    if (!leds || size <= 1 || totalDurationMs <= 0) return;

    const int halfSize = size / 2;
    const float msPerLed = totalDurationMs / (float)halfSize;
    unsigned long startTime = millis();

    fill_solid(leds, size, baseCol);

    int prevLedsToLight = 0;
    unsigned long currentTime;
    unsigned long elapsedTime;
    int ledsToLight, pos;
    Animation_Loop(activeFlag, 90, [&]() -> int {
        currentTime = millis();
        elapsedTime = currentTime - startTime;
        
        // Calculate how many LEDs should be lit based on elapsed time
        ledsToLight = (elapsedTime / msPerLed);
        if (ledsToLight > halfSize) ledsToLight = halfSize;
        
        // Fill LEDs up to current position
        for (int i = 0; i < ledsToLight; i++) {
            pos = (i + shift + size) % size;
            leds[pos] = fillCol;
            leds[(size - 1 - i + shift + size) % size] = fillCol; // Correct mirroring calculation
        }
        
        // Update LEDs only when necessary
        if(prevLedsToLight < ledsToLight){
            FastLED.show();
        }
        prevLedsToLight = ledsToLight;
        
        // Return 1 when complete
        return (ledsToLight >= halfSize) ? 1 : 0;
    });
}


#define MIN_BRIGHTNESS 2
void Anim_ColorBreath(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colors, uint32_t timeMs, int speed) {
    if (!leds || size <= 0 || colors.size <= 0 || timeMs <= 0) return;

    uint8_t colorIndex = 0;
    unsigned long startTime = millis();
    const uint32_t halfTime = timeMs / 2;

    const uint8_t origBright = FastLED.getBrightness();
    FastLED.setBrightness(255);
    
    uint8_t breath = MIN_BRIGHTNESS;
    uint32_t elapsedTime;
    unsigned long currentTime;
    CRGB outColor;
    Animation_Loop(activeFlag, speed, [&]() -> int {
        // Elapsed time in the current breath cycle
        currentTime = millis();
        elapsedTime = currentTime - startTime;

        // Triangle wave brightness: up for half, down for half
        if (elapsedTime < halfTime) {
            breath = map(elapsedTime, 0, halfTime, MIN_BRIGHTNESS, 255); // Brighten
        } else {
            breath = map(elapsedTime, halfTime, timeMs, 255, MIN_BRIGHTNESS); // Dim
        }

        // Combine breath with original global brightness (rounded)
        uint16_t prod = static_cast<uint16_t>(breath) * static_cast<uint16_t>(origBright);
        uint8_t finalBright = static_cast<uint8_t>((prod + 127) / 255);

        // Apply perceptual scaling once with combined brightness
        outColor = colors.col[colorIndex];
        outColor.nscale8_video(finalBright);

        // Fill all LEDs with scaled color
        fill_solid(leds, size, outColor);
        FastLED.show();

        if (elapsedTime >= timeMs) {
            colorIndex = (colorIndex + 1) % colors.size;
            startTime = currentTime;
        }

        return 0;
    });

    FastLED.setBrightness(origBright);
}


void Anim_GradientRotate(bool volatile& activeFlag, CRGB* leds, int size, const COLOR_PACK& colors, int speed) {
    if (!leds || size <= 0 || colors.size < 2) return;

    CRGB color1, color2; 

    // Create initial gradient: evenly distribute blends across the strip
    // For i in [0..size-1], compute position in color space [0..colors.size)
    for (int i = 0; i < size; i++) {
        uint32_t pos256 = (uint32_t)i * (uint32_t)colors.size * 256u / (uint32_t)size; // 8.8 fixed point
        int segment = (int)(pos256 >> 8);                              // 0..colors.size-1
        uint8_t blendPos = (uint8_t)(pos256 & 0xFF);                    // 0..255
        int nextSegment = (segment + 1) % colors.size;

        // Local copies for blending
        color1 = colors.col[segment];
        color2 = colors.col[nextSegment];
        leds[i] = blend(color1, color2, blendPos);
    }

    // Rotation animation loop
    CRGB temp;
    Animation_Loop(activeFlag, speed, [&]() -> int {
        // Rotate one position
        temp = leds[0];
        memmove(&leds[0], &leds[1], (size-1) * sizeof(CRGB));
        leds[size-1] = temp;
        
        FastLED.show();
        return 0;
    });
}


uint32_t getRandomValue(uint32_t maxValue) {
    return esp_random() % maxValue;
}