/*  
    Theengs LEDManager - We Unite Sensors in One Open-Source Interface
  
    Copyright: (c)Florian ROBERT

    This file is part of LEDManager library.
    
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 3 of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program; if not, write to the Free Software Foundation,
    Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include "LEDManager.h"

LEDManager::LEDManager() : globalBrightness(255) {}

void LEDManager::addLEDStrip(int pin, int numLeds) {
  LEDStrip ledStrip;
  ledStrip.pin = pin;
#ifdef LED_ADDRESSABLE
  if (LED_ADDRESSABLE_POWER > -1) {
    pinMode(LED_ADDRESSABLE_POWER, OUTPUT);
    digitalWrite(LED_ADDRESSABLE_POWER, HIGH);
  }
  ledStrip.strip = new Adafruit_NeoPixel(numLeds, pin, NEO_GRB + NEO_KHZ800);
  ledStrip.strip->begin();
  ledStrip.strip->show();
  ledStrip.strip->setBrightness(globalBrightness);
#else
  pinMode(pin, OUTPUT);
#endif

  ledStrip.ledStates.resize(numLeds, {OFF, OFF, 0, 0, 0, 0, 0, false, 255, FADE_AMOUNT, {OFF, 0, -1, false}});

  ledStrips.push_back(ledStrip);
}

void LEDManager::setMode(int stripIndex, int ledIndex, Mode mode, uint32_t color, int durationOrBlinkCount) {
  if (stripIndex >= 0 && stripIndex < ledStrips.size()) {
    if (ledIndex == -1) {
      for (int i = 0; i < ledStrips[stripIndex].ledStates.size(); i++) {
        setModeForSingleLED(stripIndex, i, mode, color, durationOrBlinkCount);
      }
    } else if (ledIndex >= 0 && ledIndex < ledStrips[stripIndex].ledStates.size()) {
      setModeForSingleLED(stripIndex, ledIndex, mode, color, durationOrBlinkCount);
    }
  } else if (stripIndex == -1) {
    for (int i = 0; i < ledStrips.size(); i++) {
      setMode(i, ledIndex, mode, color, durationOrBlinkCount);
    }
  }
}

void LEDManager::setModeForSingleLED(int stripIndex, int ledIndex, Mode mode, uint32_t color, int durationOrBlinkCount) {
  auto& state = ledStrips[stripIndex].ledStates[ledIndex];

  if ((state.mode == BLINK || state.mode == PULSE) && state.durationOrBlinkCount > 0) {
    state.queuedState = {mode, color, durationOrBlinkCount, true};
    return;
  }

  if (mode != BLINK && mode != PULSE) {
    state.previousMode = state.mode;
    state.previousColor = state.color;
  }
  state.mode = mode;
  state.color = color;
  state.durationOrBlinkCount = durationOrBlinkCount;
  state.lastUpdateTime = millis();
  state.blinkCounter = 0;
  state.isBlinkOn = false;
  state.brightness = globalBrightness;
  state.fadeAmount = FADE_AMOUNT;
  state.queuedState.isQueued = false;
}

void LEDManager::setBrightness(uint8_t brightness) {
#ifdef LED_ADDRESSABLE
  globalBrightness = brightness;
  for (auto& ledStrip : ledStrips) {
    ledStrip.strip->setBrightness(brightness);
    for (auto& state : ledStrip.ledStates) {
      state.brightness = brightness;
    }
  }
#endif
}

void LEDManager::setLEDColor(int stripIndex, int ledIndex, uint32_t color) {
  if (stripIndex < 0 || stripIndex >= ledStrips.size()) return;
  if (ledIndex < 0 || ledIndex >= ledStrips[stripIndex].ledStates.size()) return;

  uint8_t r, g, b;
  extractRGB(color, r, g, b);

  r = (r * globalBrightness) / 255;
  g = (g * globalBrightness) / 255;
  b = (b * globalBrightness) / 255;

#ifdef LED_ADDRESSABLE
  ledStrips[stripIndex].strip->setPixelColor(ledIndex, r, g, b);
  ledStrips[stripIndex].strip->show();
#else
  // For non-addressable LEDs, we'll use the first LED of each strip
  if (ledIndex == 0) {
    int pin = ledStrips[stripIndex].pin;
    analogWrite(pin, (r + g + b) / 3); // Average of RGB for single-color LED
  }
#endif
}

void LEDManager::update() {
  for (int stripIndex = 0; stripIndex < ledStrips.size(); stripIndex++) {
    for (int ledIndex = 0; ledIndex < ledStrips[stripIndex].ledStates.size(); ledIndex++) {
      auto& state = ledStrips[stripIndex].ledStates[ledIndex];
      switch (state.mode) {
        case BLINK:
          handleBlink(stripIndex, ledIndex);
          break;
        case PULSE:
          handlePulse(stripIndex, ledIndex);
          break;
        case STATIC:
          handleStatic(stripIndex, ledIndex);
          break;
        case OFF:
          setLEDColor(stripIndex, ledIndex, 0);
          break;
      }
    }
#ifdef LED_ADDRESSABLE
    ledStrips[stripIndex].strip->show();
#endif
  }
}

void LEDManager::handleBlink(int stripIndex, int ledIndex) {
  auto& state = ledStrips[stripIndex].ledStates[ledIndex];
  unsigned long currentTime = millis();
  if (currentTime - state.lastUpdateTime >= BLINK_INTERVAL) {
    state.lastUpdateTime = currentTime;
    if (state.durationOrBlinkCount > 0) {
      if (state.blinkCounter == 0) {
        state.blinkCounter = state.durationOrBlinkCount * 2;
      } else {
        state.blinkCounter--;
        if (state.blinkCounter == 0) {
          if (state.queuedState.isQueued) {
            applyQueuedState(stripIndex, ledIndex);
          } else {
            returnToPreviousState(stripIndex, ledIndex);
          }
          return;
        }
      }
    }

    state.isBlinkOn = !state.isBlinkOn;
    if (state.isBlinkOn) {
      setLEDColor(stripIndex, ledIndex, state.color);
    } else {
      setLEDColor(stripIndex, ledIndex, 0);
    }
  }
}

void LEDManager::handlePulse(int stripIndex, int ledIndex) {
  auto& state = ledStrips[stripIndex].ledStates[ledIndex];
  unsigned long currentTime = millis();
  if (currentTime - state.lastUpdateTime >= PULSE_INTERVAL) {
    state.lastUpdateTime = currentTime;
    state.brightness += state.fadeAmount;
    if (state.brightness <= 0 || state.brightness >= 255) {
      state.fadeAmount = -state.fadeAmount;
      state.brightness = constrain(state.brightness, 0, 255);
      if (state.durationOrBlinkCount > 0) {
        state.durationOrBlinkCount--;
        if (state.durationOrBlinkCount == 0) {
          if (state.queuedState.isQueued) {
            applyQueuedState(stripIndex, ledIndex);
          } else {
            returnToPreviousState(stripIndex, ledIndex);
          }
          return;
        }
      }
    }
    uint32_t adjustedColor = (state.color & 0xFF000000) |
                             (((state.color & 0x00FF0000) * state.brightness / 255) & 0x00FF0000) |
                             (((state.color & 0x0000FF00) * state.brightness / 255) & 0x0000FF00) |
                             (((state.color & 0x000000FF) * state.brightness / 255) & 0x000000FF);
    setLEDColor(stripIndex, ledIndex, adjustedColor);
  }
}

void LEDManager::handleStatic(int stripIndex, int ledIndex) {
  auto& state = ledStrips[stripIndex].ledStates[ledIndex];
  unsigned long currentTime = millis();
  if (state.durationOrBlinkCount > 0) {
    if (currentTime - state.lastUpdateTime >= static_cast<unsigned long>(state.durationOrBlinkCount) * 1000) {
      if (state.queuedState.isQueued) {
        applyQueuedState(stripIndex, ledIndex);
      } else {
        returnToPreviousState(stripIndex, ledIndex);
      }
      return;
    }
  }
  setLEDColor(stripIndex, ledIndex, state.color);
}

void LEDManager::returnToPreviousState(int stripIndex, int ledIndex) {
  auto& state = ledStrips[stripIndex].ledStates[ledIndex];
  state.mode = state.previousMode;
  state.color = state.previousColor;
  state.durationOrBlinkCount = -1; // Set to infinite for the previous state
  state.lastUpdateTime = millis();
  state.blinkCounter = 0;
  state.isBlinkOn = false;
  state.brightness = globalBrightness;
  state.fadeAmount = FADE_AMOUNT;
}

void LEDManager::applyQueuedState(int stripIndex, int ledIndex) {
  auto& state = ledStrips[stripIndex].ledStates[ledIndex];
  state.mode = state.queuedState.mode;
  state.color = state.queuedState.color;
  state.durationOrBlinkCount = state.queuedState.durationOrBlinkCount;
  state.lastUpdateTime = millis();
  state.blinkCounter = 0;
  state.isBlinkOn = false;
  state.brightness = globalBrightness;
  state.fadeAmount = FADE_AMOUNT;
  state.queuedState.isQueued = false;
}

void LEDManager::extractRGB(uint32_t color, uint8_t& r, uint8_t& g, uint8_t& b) {
  r = (color >> 16) & 0xFF;
  g = (color >> 8) & 0xFF;
  b = color & 0xFF;
}