#include "led_strip.hpp"

// LedStrip::LedStrip(/* args */) {}
// LedStrip::~LedStrip() {}

#if defined(ARDUINO_AVR_ATTINYX5) || defined(ARDUINO_AVR_ATTINYX41)
/* TODO: remove when switching to tinycore V2.0.0 */
void LedStrip::fill(uint32_t c, uint16_t first, uint16_t count) {
  uint16_t i, end;

  if (first >= LedCount) {
    return; // If first LED is past end of strip, nothing to do
  }

  // Calculate the index ONE AFTER the last pixel to fill
  if (count == 0) {
    // Fill to end of strip
    end = LedCount;
  } else {
    // Ensure that the loop won't go past the last pixel
    end = first + count;
    if (end > LedCount)
      end = LedCount;
  }

  for (i = first; i < end; i++) {
    this->setPixelColor(i, c);
  }
}

uint32_t tinyNeoPixel::ColorHSV(uint16_t hue, uint8_t sat, uint8_t val) {

  uint8_t r, g, b;

  /* Remap 0-65535 to 0-1529. Pure red is CENTERED on the 64K rollover;
   * 0 is not the start of pure red, but the midpoint...a few values above
   * zero and a few below 65536 all yield pure red (similarly, 32768 is the
   * midpoint, not start, of pure cyan). The 8-bit RGB hexcone (256 values
   * each for red, green, blue) really only allows for 1530 distinct hues
   * (not 1536, more on that below), but the full unsigned 16-bit type was
   * chosen for hue so that one's code can easily handle a contiguous color
   * wheel by allowing hue to roll over in either direction.
   */
  hue = (hue * 1530L + 32768) / 65536;
  /* Because red is centered on the rollover point (the +32768 above,
   * essentially a fixed-point +0.5), the above actually yields 0 to 1530,
   * where 0 and 1530 would yield the same thing. Rather than apply a
   * costly modulo operator, 1530 is handled as a special case below.
   *
   * So you'd think that the color "hexcone" (the thing that ramps from
   * pure red, to pure yellow, to pure green and so forth back to red,
   * yielding six slices), and with each color component having 256
   * possible values (0-255), might have 1536 possible items (6*256),
   * but in reality there's 1530. This is because the last element in
   * each 256-element slice is equal to the first element of the next
   * slice, and keeping those in there this would create small
   * discontinuities in the color wheel. So the last element of each
   * slice is dropped...we regard only elements 0-254, with item 255
   * being picked up as element 0 of the next slice. Like this:
   * Red to not-quite-pure-yellow is:        255,   0, 0 to 255, 254,   0
   * Pure yellow to not-quite-pure-green is: 255, 255, 0 to   1, 255,   0
   * Pure green to not-quite-pure-cyan is:     0, 255, 0 to   0, 255, 254
   * and so forth. Hence, 1530 distinct hues (0 to 1529), and hence why
   * the constants below are not the multiples of 256 you might expect.
   */
  // Convert hue to R,G,B (nested ifs faster than divide+mod+switch):
  if (hue < 510) { // Red to Green-1
    b = 0;
    if (hue < 255) { //   Red to Yellow-1
      r = 255;
      g = hue;       //     g = 0 to 254
    } else {         //   Yellow to Green-1
      r = 510 - hue; //     r = 255 to 1
      g = 255;
    }
  } else if (hue < 1020) { // Green to Blue-1
    r = 0;
    if (hue < 765) { //   Green to Cyan-1
      g = 255;
      b = hue - 510;  //     b = 0 to 254
    } else {          //   Cyan to Blue-1
      g = 1020 - hue; //     g = 255 to 1
      b = 255;
    }
  } else if (hue < 1530) { // Blue to Red-1
    g = 0;
    if (hue < 1275) { //   Blue to Magenta-1
      r = hue - 1020; //     r = 0 to 254
      b = 255;
    } else { //   Magenta to Red-1
      r = 255;
      b = 1530 - hue; //     b = 255 to 1
    }
  } else { // Last 0.5 Red (quicker than % operator)
    r = 255;
    g = b = 0;
  }

  // Apply saturation and value to R,G,B, pack into 32-bit result:
  uint32_t v1 = 1 + val;  // 1 to 256; allows >>8 instead of /255
  uint16_t s1 = 1 + sat;  // 1 to 256; same reason
  uint8_t s2 = 255 - sat; // 255 to 0
  return ((((((r * s1) >> 8) + s2) * v1) & 0xff00) << 8) | (((((g * s1) >> 8) + s2) * v1) & 0xff00) |
         (((((b * s1) >> 8) + s2) * v1) >> 8);
}
#endif /* defined(ARDUINO_AVR_ATTINYX5) || defined(ARDUINO_AVR_ATTINYX41) */

void LedStrip::setup(bool is_diag_mode) {
  pinMode(LedPin, OUTPUT);
  if (is_diag_mode) {
    setPixelColor(0, 255, 0, 0);
    fill(Color(45, 0, 0));
    show();
    delay(300);
    fill(Color(0, 45, 0));
    show();
    delay(300);
    fill(Color(0, 0, 45));
    show();
    delay(300);
  }
  this->refresh();
}

void LedStrip::shiftHue(int shift) {
  hue += (shift << 11);
  /*             | encoder |  hue  | sat
   * values      |   20    | 65536 | 256
   * step        |    1    |  3277 |
   * shift       |    0    |    11 |   4
   * rounded     |         |  2048 |  16
   * total steps |         |    32 |  16
   */

#if defined(__BUILD_DEBUG__) && defined(ARDUINO_AVR_UNO)
  if (shift != 0) {
    debugSerial.print(__func__);
    debugSerial.print(" : shift=");
    debugSerial.print(shift, DEC);
    debugSerial.print(", hue=");
    debugSerial.println(hue, DEC);
  }
#endif /*defined(__BUILD_DEBUG__) && defined(ARDUINO_AVR_UNO)*/
}

void LedStrip::shiftSaturation(int shift) {
  saturation += (shift << 4);

#if defined(__BUILD_DEBUG__) && defined(ARDUINO_AVR_UNO)
  if (shift != 0) {
    debugSerial.print(__func__);
    debugSerial.print(" : shift=");
    debugSerial.print(shift, DEC);
    debugSerial.print(", saturation=");
    debugSerial.println(saturation, DEC);
  }
#endif /*defined(__BUILD_DEBUG__) && defined(ARDUINO_AVR_UNO)*/
}

void LedStrip::shiftBrightness(int shift) {
  brightness += (shift << 4);

#if defined(__BUILD_DEBUG__) && defined(ARDUINO_AVR_UNO)
  if (shift != 0) {
    debugSerial.print(__func__);
    debugSerial.print(" : shift=");
    debugSerial.print(shift, DEC);
    debugSerial.print(", brightness=");
    debugSerial.println(brightness, DEC);
  }
#endif /*defined(__BUILD_DEBUG__) && defined(ARDUINO_AVR_UNO)*/
}

void LedStrip::shiftFirstLedOn(int shift) {
  if (shift > 0) {
    if (firstLedOn > LedCount) {
      firstLedOn = 0;
    } else {
      ++firstLedOn;
    }
  } else {
    if (firstLedOn < 1) {
      firstLedOn = LedCount - 1;
    } else {
      --firstLedOn;
    }
  }

#if defined(__BUILD_DEBUG__) && defined(ARDUINO_AVR_UNO)
  if (shift != 0) {
    debugSerial.print(__func__);
    debugSerial.print(" : shift=");
    debugSerial.print(shift, DEC);
    debugSerial.print(", firstLedOn=");
    debugSerial.println(firstLedOn, DEC);
  }
#endif /*defined(__BUILD_DEBUG__) && defined(ARDUINO_AVR_UNO)*/
}

void LedStrip::shiftLedOnLenght(int shift) {
  if (shift > 0) {
    if (ledOnCount < LedCount) {
      ++ledOnCount;
    } else {
      ledOnCount = 1;
    }
  } else {
    if (ledOnCount > 1) {
      --ledOnCount;
    } else {
      ledOnCount = LedCount;
    }
  }

#if defined(__BUILD_DEBUG__) && defined(ARDUINO_AVR_UNO)
  if (shift != 0) {
    debugSerial.print(__func__);
    debugSerial.print(" : shift=");
    debugSerial.print(shift, DEC);
    debugSerial.print(", ledOnCount=");
    debugSerial.println(ledOnCount, DEC);
  }
#endif /*defined(__BUILD_DEBUG__) && defined(ARDUINO_AVR_UNO)*/
}

void LedStrip::refresh() {
  unsigned int lastLedOn = {firstLedOn + ledOnCount};
  /* 0 <= Chunk 1 < x <= Chunk 2 < y <= Chunk 3 < ledcount*/
  unsigned int chunk_index[4] = {0, 0, 0, LedCount};
  bool isChunkOn;

#if defined(__BUILD_DEBUG__)
  debugSerial.print("Color hue,sat,bri=");
  debugSerial.print(this->hue);
  debugSerial.print(",");
  debugSerial.print(this->saturation);
  debugSerial.print(",");
  debugSerial.println(this->brightness);
#endif /* defined(__BUILD_DEBUG__) */

  /* reset ring */
  // this->fill(0);

  if (lastLedOn < LedCount) {
    isChunkOn = false;
    /* firstPhysicalLedOn */
    chunk_index[1] = firstLedOn;
    /* lastPhysicalLedOn */
    chunk_index[2] = chunk_index[1] + ledOnCount;
  } else {
    isChunkOn = true;
    /* firstPhysicalLedOff */
    chunk_index[1] = firstLedOn + ledOnCount - LedCount;
    /* lastPhysicalLedOff */
    chunk_index[2] = chunk_index[1] + LedCount - ledOnCount;
  }

#if defined(__BUILD_DEBUG__)
  debugSerial.print("LedStrip=>");
  debugSerial.print(chunk_index[0]);
  debugSerial.print("<");
  debugSerial.print(isChunkOn ? "ON" : "OFF");
  debugSerial.print(">");
  debugSerial.print(chunk_index[1]);
  debugSerial.print("<");
  debugSerial.print(isChunkOn ? "OFF" : "ON");
  debugSerial.print(">");
  debugSerial.print(chunk_index[2]);
  debugSerial.print("<");
  debugSerial.print(isChunkOn ? "ON" : "OFF");
  debugSerial.print(">");
  debugSerial.print(chunk_index[3]);
  debugSerial.print("<");
  debugSerial.print(isChunkOn ? "OFF" : "ON");
  debugSerial.println(">");
#endif /* defined(__BUILD_DEBUG__) */

  for (size_t i = 0; i < 3; ++i) {
    unsigned int firstLedOnChunk = chunk_index[i];
    unsigned int lastLedOnChunk = chunk_index[i + 1];
    uint32_t color;

    if (isChunkOn) {
      color = ColorHSV(this->hue, this->saturation, this->brightness);
    } else {
      color = 0;
    }

    for (size_t j = firstLedOnChunk; j < lastLedOnChunk; ++j) {
      this->setPixelColor(j, color);
    }

    isChunkOn = !isChunkOn; 
  }
  
  this->show();
}

void LedStrip::display_mode(unsigned int mode_index) {
  for (size_t i = 0; i < 3; ++i) {
    this->fill(0);
    this->show();
    delay(150);
    for (size_t i = 0; i < mode_index; ++i) {
      this->setPixelColor(i, ColorHSV(0, 255, 16));
    }
    this->show();
    delay(150);
  }
  this->refresh();
}