// Copyleft (c) 2012, Zach Vorhies
// Public domain, no rights reserved.
// This object holds a frame buffer and effects can be applied. This is a higher level
// object than the TCL class which this object uses for drawing.

//#include "./tcl.h"
#include <Arduino.h>
#include "FastLED.h"
#include "fl/log/log.h"
#include "fl/ui/ui.h"
#include "fl/math/math.h"

#include "../shared/color.h"
#include "../shared/framebuffer.h"
#include "../shared/settings.h"
#include "./LedRopeTCL.h"
#include "../shared/led_layout_array.h"



#define CHIPSET WS2812
#define PIN_DATA 1
#define PIN_CLOCK 2

namespace {

fl::UIButton buttonAllWhite("All white");

fl::ScreenMap init_screenmap() {
  LedColumns cols = LedLayoutArray();
  const int length = cols.length;
  int sum = 0;
  int y_max = 0;
  for (int i = 0; i < length; ++i) {
    sum += cols.array[i];
    int stagger = i % 2 ? 4 : 0;
    int col_top = (cols.array[i] - 1) * 8 + stagger;
    if (col_top > y_max) {
      y_max = col_top;
    }
  }
  fl::ScreenMap screen_map(sum, 0.8f);
  int curr_idx = 0;
  for (int i = 0; i < length; ++i) {
    int n = cols.array[i];
    int stagger = i % 2 ? 4 : 0;
    for (int k = 0; k < n; ++k) {
      // Wiring is serpentine by column: even columns wind one way, odd
      // columns the other, so a single rope can snake up and down.
      int j = i % 2 ? (n - 1 - k) : k;
      // Mirror y (horizontal flip + 180 deg rotation) so the rendered
      // keyboard matches the physical Luminescent Grand orientation.
      fl::vec2f xy(i*4, y_max - (j*8 + stagger));
      screen_map.set(curr_idx++, xy);
    }
  }

  return screen_map;
}

}


///////////////////////////////////////////////////////////////////////////////
void LedRopeTCL::PreDrawSetup() {
  if (!mLazyInitialized) {
    // This used to do something, now it does nothing.
    mLazyInitialized = true;
  }
}

///////////////////////////////////////////////////////////////////////////////
void LedRopeTCL::RawBeginDraw() {
  PreDrawSetup();
  mLedBuffer.clear();
}

///////////////////////////////////////////////////////////////////////////////
void LedRopeTCL::RawDrawPixel(const Color3i& c) {
  RawDrawPixel(c.r_, c.g_, c.b_);
}

///////////////////////////////////////////////////////////////////////////////
void LedRopeTCL::RawDrawPixel(byte r, byte g, byte b) {
  if (mLedBuffer.size() >= mScreenMap.getLength()) {
    return;
  }
  if (buttonAllWhite.isPressed()) {
    r = 0xff;
    g = 0xff;
    b = 0xff;
  }
  fl::CRGB c(r, g, b);
  mLedBuffer.push_back(fl::CRGB(r, g, b));
}

///////////////////////////////////////////////////////////////////////////////
void LedRopeTCL::RawDrawPixels(const Color3i& c, int n) {
  for (int i = 0; i < n; ++i) {
    RawDrawPixel(c);
  }
}

///////////////////////////////////////////////////////////////////////////////
void LedRopeTCL::set_draw_offset(int val) {
  mDrawOffset = constrain(val, 0, mFrameBuffer.length());
}


///////////////////////////////////////////////////////////////////////////////
void LedRopeTCL::RawCommitDraw() {
  FL_WARN("\n\n############## COMMIT DRAW ################\n\n");
  if (!mControllerAdded) {
    mControllerAdded = true;
    fl::CRGB* leds = mLedBuffer.data();
    size_t n_leds = mLedBuffer.size();
    FastLED.addLeds<APA102, PIN_DATA, PIN_CLOCK>(leds, n_leds).setScreenMap(mScreenMap);
  }
  FL_WARN("FastLED.show");
  FastLED.show();
}

///////////////////////////////////////////////////////////////////////////////
LedRopeTCL::LedRopeTCL(int n_pixels)
	: mDrawOffset(0), mLazyInitialized(false), mFrameBuffer(n_pixels) {
  mScreenMap = init_screenmap();
  mLedBuffer.reserve(mScreenMap.getLength());
}

///////////////////////////////////////////////////////////////////////////////
LedRopeTCL::~LedRopeTCL() {
}

///////////////////////////////////////////////////////////////////////////////
void LedRopeTCL::Draw() {
  RawBeginDraw();

  const Color3i* begin = GetIterator(0);
  const Color3i* middle = GetIterator(mDrawOffset);
  const Color3i* end = GetIterator(length() - 1);

  for (const Color3i* it = middle; it != end; ++it) {
    RawDrawPixel(*it);
  }
  for (const Color3i* it = begin; it != middle; ++it) {
    RawDrawPixel(*it);
  }
  RawCommitDraw();
}

///////////////////////////////////////////////////////////////////////////////
void LedRopeTCL::DrawSequentialRepeat(int repeat) {
  RawBeginDraw();

  const Color3i* begin = GetIterator(0);
  const Color3i* middle = GetIterator(mDrawOffset);
  const Color3i* end = GetIterator(length());
  for (const Color3i* it = middle; it != end; ++it) {
    for (int i = 0; i < repeat; ++i) {
      RawDrawPixel(it->r_, it->g_, it->b_);
    }
  }
  for (const Color3i* it = begin; it != middle; ++it) {
    for (int i = 0; i < repeat; ++i) {
      RawDrawPixel(it->r_, it->g_, it->b_);
    }
  }
  RawCommitDraw();
}

///////////////////////////////////////////////////////////////////////////////
void LedRopeTCL::DrawRepeat(const int* value_array, int array_length) {
  RawBeginDraw();

  // Make sure that the number of colors to repeat does not exceed the length
  // of the rope.
  const int len = fl::min(array_length, mFrameBuffer.length());

  for (int i = 0; i < len; ++i) {
     const Color3i* cur_color = GetIterator(i);  // Current color.
     const int repeat_count = value_array[i];
     // Repeatedly send the same color down the led rope.
     for (int k = 0; k < repeat_count; ++k) {
       RawDrawPixel(cur_color->r_, cur_color->g_, cur_color->b_);
     }
  }
  // Finish the drawing.
  RawCommitDraw();
}

