/** \file .c
 *  \brief This module
 */

/***************************************************************************************************
 * Includes
 **************************************************************************************************/

#include "rpi_pattern.h"

#include <stdlib.h>

#include "log.h"

#include "drivers/leddriver/rpi_leddriver.h"
#include "rpi_param.h"

/***************************************************************************************************
 * Preprocessor Constants and Macros
 **************************************************************************************************/

/***************************************************************************************************
 * Type and Contant Definitions
 **************************************************************************************************/

/***************************************************************************************************
 * Persistent Variables
 **************************************************************************************************/

/**
 * Index to channel translattion
 */
int channelTranslation[LED_NCHANS] = {0, 1, 2, 3, 4, 6, 7, 5};

/**
 * RGB data buffer. Persistency is not needed but, as of it size, it must be declared in heap
 */
int rgbData[CHAN_MAXLEDS][LED_NCHANS];

/**
 * Number of led previously lighted per led bar
 */
int lightedLedCountArray[LED_NCHANS];

/**
 * Index of first previously lighted per led bar
 */
int baseLedIndexArray[LED_NCHANS];

/***************************************************************************************************
 * Internal Function Prototypes
 **************************************************************************************************/

uint16_t get_max_on_interval(uint16_t *array, unsigned int length, size_t intervalMin,
                             size_t intervalSize);

uint16_t apply_sensitivity(uint16_t value, unsigned int valueIndex);

unsigned int get_led_to_light_count(uint16_t barValue, unsigned int barIndex);

unsigned int get_first_led_to_light(unsigned int barIndex);

uint32_t get_hsv_color(unsigned int barIndex, float shiftRatio);

void increment_autoshift(unsigned int barIndex);

/***************************************************************************************************
 * External Function Definitions
 **************************************************************************************************/

void bounce_led_and_color(uint16_t *spectrumArray, unsigned int spectrumLength) {
  for (size_t ledBarIndex = 0; ledBarIndex < LED_NCHANS; ++ledBarIndex) {
    size_t intervalSize = spectrumLength / LED_NCHANS;
    uint16_t barMax = 0;
    unsigned int ledToLightCount = 0;

    barMax = get_max_on_interval(spectrumArray, spectrumLength, ledBarIndex * intervalSize,
                                 intervalSize);
    barMax = apply_sensitivity(barMax, ledBarIndex);
    ledToLightCount = get_led_to_light_count(barMax, ledBarIndex);

    uint32_t color = get_hsv_color(ledBarIndex, (ledToLightCount - 1) /
                                                    (float)param_access->pixled.chanLedCount);

    for (size_t i = 0; i < ledToLightCount; ++i) {
      rgbData[i][ledBarIndex] = color;
      rgb_txdata(rgbData[i], i);
    }
    for (size_t i = ledToLightCount; i < param_access->pixled.chanLedCount; ++i) {
      rgbData[i][ledBarIndex] = 0x000000;
      rgb_txdata(rgbData[i], i);
    }

    increment_autoshift(ledBarIndex);
  }

  leddriver_refresh();
}

void bounce_led(uint16_t *spectrumArray, unsigned int spectrumLength) {
  for (size_t ledBarIndex = 0; ledBarIndex < LED_NCHANS; ++ledBarIndex) {
    size_t intervalSize = spectrumLength / LED_NCHANS;
    uint16_t barMax = 0;
    unsigned int ledToLightCount = 0;

    barMax = get_max_on_interval(spectrumArray, spectrumLength, ledBarIndex * intervalSize,
                                 intervalSize);
    barMax = apply_sensitivity(barMax, ledBarIndex);
    ledToLightCount = get_led_to_light_count(barMax, ledBarIndex);

    for (size_t i = 0; i < ledToLightCount; ++i) {

      uint32_t color = get_hsv_color(ledBarIndex, i / (float)param_access->pixled.chanLedCount);
      rgbData[i][ledBarIndex] = color;
      rgb_txdata(rgbData[i], i);
    }
    for (size_t i = ledToLightCount; i < param_access->pixled.chanLedCount; ++i) {
      rgbData[i][ledBarIndex] = 0x000000;
      rgb_txdata(rgbData[i], i);
    }

    increment_autoshift(ledBarIndex);
  }

  leddriver_refresh();
}

void bounce_led_and_travel(uint16_t *spectrumArray, unsigned int spectrumLength) {
  for (size_t ledBarIndex = 0; ledBarIndex < LED_NCHANS; ++ledBarIndex) {
    size_t intervalSize = spectrumLength / LED_NCHANS;
    uint16_t barMax = 0;
    unsigned int ledToLightCount = 0, firstLedToLight = get_first_led_to_light(ledBarIndex);
    uint32_t color = 0;

    barMax = get_max_on_interval(spectrumArray, spectrumLength, ledBarIndex * intervalSize,
                                 intervalSize);
    barMax = apply_sensitivity(barMax, ledBarIndex) / 4;
    ledToLightCount = get_led_to_light_count(barMax, ledBarIndex) + 1;

    if (!param_access->auton.isHueAutoShiftEnabled) {
      color = get_hsv_color(ledBarIndex,
                            (ledToLightCount - 1) * 4L / (float)param_access->pixled.chanLedCount);
    }

    for (size_t i = 0; i < ledToLightCount; ++i) {
      unsigned int ledIndex = firstLedToLight + i;
      ledIndex = ledIndex < param_access->pixled.chanLedCount
                     ? ledIndex
                     : ledIndex - param_access->pixled.chanLedCount;
      if (param_access->auton.isHueAutoShiftEnabled) {
        color = get_hsv_color(ledBarIndex, ledIndex / (float)param_access->pixled.chanLedCount);
      }
      rgbData[ledIndex][ledBarIndex] = color;
      rgb_txdata(rgbData[ledIndex], ledIndex);
    }
    for (size_t i = ledToLightCount; i < param_access->pixled.chanLedCount; ++i) {
      unsigned int ledIndex = firstLedToLight + i;
      ledIndex = ledIndex < param_access->pixled.chanLedCount
                     ? ledIndex
                     : ledIndex - param_access->pixled.chanLedCount;
      rgbData[ledIndex][ledBarIndex] = 0x000000;
      rgb_txdata(rgbData[ledIndex], ledIndex);
    }

    increment_autoshift(ledBarIndex);
  }

  leddriver_refresh();
}

/***************************************************************************************************
 * Internal Function Definitions
 **************************************************************************************************/

uint16_t get_max_on_interval(uint16_t *array, unsigned int length, size_t intervalMin,
                             size_t intervalSize) {
  uint16_t barMax = 0;
  for (size_t i = 0; i < intervalSize; ++i) {
    unsigned barIndex = intervalMin + i;
    if (barMax < array[barIndex]) {
      barMax = array[barIndex];
    }
  }
  return barMax;
}

uint16_t apply_sensitivity(uint16_t value, unsigned int valueIndex) {
  return value * param_access->auton.sensitivity * param_access->ledbar[valueIndex].sensitivity;
}

unsigned int get_led_to_light_count(uint16_t barValue, unsigned int barIndex) {
  unsigned int nbLightedLed = lightedLedCountArray[barIndex];
  unsigned int ledToLightCount = barValue * param_access->pixled.chanLedCount / UINT16_MAX;

  if (ledToLightCount < nbLightedLed) {
    /* apply gravity */
    ledToLightCount = ((ledToLightCount * (100 - param_access->auton.gravity)) +
                       (nbLightedLed * param_access->auton.gravity)) /
                      100;
  }
  lightedLedCountArray[barIndex] = ledToLightCount;
  return ledToLightCount;
}

unsigned int get_first_led_to_light(unsigned int barIndex) {
  unsigned int firstLed = baseLedIndexArray[barIndex];
  if (lightedLedCountArray[barIndex] != 0) {
    firstLed += lightedLedCountArray[barIndex] / 4 + 1;
    firstLed = firstLed < param_access->pixled.chanLedCount
                   ? firstLed
                   : firstLed - param_access->pixled.chanLedCount;
  }
  baseLedIndexArray[barIndex] = firstLed;
  return firstLed;
}

uint32_t get_hsv_color(unsigned int barIndex, float shiftRatio) {
  int hueShift = shiftRatio * param_access->ledbar[barIndex].hueInterval;
  uint16_t hue = param_access->ledbar[barIndex].hueBase +
                 (param_access->ledbar[barIndex].hueAutoShift) + hueShift;
  // log_trace("hueAutoShift : %010x, hueShift : %d, hue %u",
  //           param_access->ledbar[barIndex].hueAutoShift, hueShift, hue);
  uint32_t color =
      ColorHSV(hue * UINT16_MAX / HUEBASE_MAX, param_access->ledbar[barIndex].saturation,
               param_access->ledbar[barIndex].luminosity);
  return color;
}

void increment_autoshift(unsigned int barIndex) {
  static float increment[LED_NCHANS];
  if (param_access->auton.isHueAutoShiftEnabled) {
    int const hueInterval = param_access->ledbar[barIndex].hueAutoShiftInterval;
    int hueAutoShift = param_access->ledbar[barIndex].hueAutoShift;
    if ((hueInterval > 0 && param_access->ledbar[barIndex].isIncreasing) ||
        (hueInterval < 0 && !param_access->ledbar[barIndex].isIncreasing)) {
      increment[barIndex] += param_access->ledbar[barIndex].hueAutoShiftSpeed;
    } else if (hueInterval != 0) {
      increment[barIndex] -= param_access->ledbar[barIndex].hueAutoShiftSpeed;
    }

    if (abs(increment[barIndex]) > 1) {
      hueAutoShift += increment[barIndex];
      increment[barIndex] = increment[barIndex] - (int)increment[barIndex];
    }

    if ((hueInterval > 0 && hueAutoShift < 0) || (hueInterval < 0 && hueAutoShift > 0)) {
      hueAutoShift = -hueAutoShift;
      param_access->ledbar[barIndex].isIncreasing = !param_access->ledbar[barIndex].isIncreasing;
    } else if ((hueInterval > 0 && hueAutoShift > hueInterval) ||
               (hueInterval < 0 && hueAutoShift < hueInterval)) {
      hueAutoShift = hueInterval;
      param_access->ledbar[barIndex].isIncreasing = !param_access->ledbar[barIndex].isIncreasing;
    }
    if (barIndex == 0) {
      log_trace("hueInterval : %d, hueAutoShift : %d, increase : %s", hueInterval, hueAutoShift,
                param_access->ledbar[barIndex].isIncreasing ? "yes" : "no");
    }
    param_access->ledbar[barIndex].hueAutoShift = hueAutoShift;
  }
}