#include <Adafruit_ILI9341.h>
#include "motif.h"
#include "rgb_plasma.h"

Adafruit_ILI9341 tft = Adafruit_ILI9341(10, 9);
Plasma plasma;
PROGMEM RLE565 * const motifs[] = {&smmotif, &bigmotif};
RLE565 *this_motif = motifs[0];

// To support different hardware or different plot effects
// create your own version of this function which takes the
// same parameters.
// It will be called once for each row of decompressed image data.
void draw_ILI9341(int16_t x, int16_t y, uint16_t *buffer, uint16_t width) {
  tft.drawRGBBitmap(x, y, buffer, width, 1);
}

void setup(void) {
  Serial.begin(115200);
  tft.begin();

  // draw the big motif centred on the screen
  int16_t x = ILI9341_TFTWIDTH / 2 - bigmotif.width() / 2;
  int16_t y = ILI9341_TFTHEIGHT / 2 - bigmotif.height() / 2;
  bigmotif.plot(draw_ILI9341, x, y);
  delay(1000);

  // draw lots of small motifs
  for (int16_t y = smmotif.height() / -3; y < ILI9341_TFTHEIGHT; y += smmotif.height())
    for (int16_t x = smmotif.width() / -3; x < ILI9341_TFTWIDTH; x += smmotif.width())
      smmotif.plot(draw_ILI9341, x, y);
}


void loop() {
  static int16_t last_ymin = 0;
  static int16_t last_ymax = ILI9341_TFTHEIGHT;
  int16_t h = this_motif->height();
  int16_t w = this_motif->width();
  int16_t x, y;

  // delay(1000);
  plasma.reset();

  // either centre or randomise the position of the motif
  if (rand() & 0x1) {
    x = (rand() % (ILI9341_TFTWIDTH - w / 3)) - w / 3;
    y = (rand() % (ILI9341_TFTHEIGHT - h / 3 )) - h / 3;
  } else {
    x = ILI9341_TFTWIDTH / 2 - w / 2;
    y = ILI9341_TFTHEIGHT / 2 - h / 2;
  }

  // calc the smallest span of rows which covers both
  // the previous motif position and the current one.
  int16_t ymin = min(ymin, last_ymin);
  int16_t ymax = min(y + h, ILI9341_TFTHEIGHT);
  ymin = max(ymin, 0);
  ymax = max(ymax, last_ymax);
  
  // fill the "empty" area above the motif
  if (ymin < y) {
    // overwrite the extent of the previous motif
    plasma.ystepn(ymin);
    yfill_plasma(ymin, y, ILI9341_TFTWIDTH);
  } else {
    // just skip to start of this motif
    plasma.ystepn(y);
  }

  // mix the motif with the plasma
  this_motif->plot(composite_ILI9341, x, y);

  // fill the "empty" area below the motif
  if (y + h < ymax) {
    // is there a gap before the area to be filled?
    if (last_ymin > y + h) {
      // skip the gap, then fill
      delay(2000);  // 😮 Look! There are two of them!
      plasma.ystepn(last_ymin - y - h);
      yfill_plasma(last_ymin, last_ymax, ILI9341_TFTWIDTH);
    } else {
      // no gap
      yfill_plasma(y + h, last_ymax, ILI9341_TFTWIDTH);
    }
  }

  // remember which bits need to be cleared next time
  last_ymin = max(y, 0);
  last_ymax = min(y + h, ILI9341_TFTHEIGHT);

  if (rand() & 1) {
    // switch to a random motif from the list
    uint8_t motif_num = rand() % (sizeof(motifs) / sizeof(motifs[0]));
    this_motif = pgm_read_ptr_near(&motifs[motif_num]);

  } else if (rand() & 1) {
    last_ymin = 0, last_ymax = ILI9341_TFTHEIGHT;
    plasma.randomise();

    if (rand() & 1)
      plasma.p.paint.r = plasma.p.paint.g = plasma.p.paint.b = 0;
    else
      plasma.p.paint.r = plasma.p.paint.g = plasma.p.paint.b = 255;

    if (rand() & 1)
      plasma.compositeoperator = alphamask_op;
    else
      plasma.compositeoperator = invert_op;
  }
}

void yfill_plasma(int16_t y0, int16_t y1, uint16_t width) {
  uint16_t buffer[width];
  for (auto y = y0; y < y1; y++) {
    memset(buffer, 0, width * 2);
    composite_ILI9341(0, y, buffer, width);
  }
}

// here's another plot function for the RLE decompressor
// this one composites the image with RGB plasma
void composite_ILI9341(int16_t x, int16_t y, uint16_t *buffer, uint16_t width) {
  if (y < 0) return;
  plasma.ystep();
  if (x > 0) {
    // fill plasma to the left of the RLE image
    int16_t left = x < ILI9341_TFTWIDTH ? x : ILI9341_TFTWIDTH;
    uint16_t tmp[left];
    memset(tmp, 0, left * 2);
    plasma.composite_row(left, tmp);
    tft.drawRGBBitmap(x - left, y, tmp, left, 1);
  } else if (x < 0) {
    buffer -= x;
    width += x;
    x = 0;
  }

  // mix plasma with image
  plasma.composite_row(width, buffer);
  tft.drawRGBBitmap(x, y, buffer, width, 1);

  int16_t right = ILI9341_TFTWIDTH - x - width;
  if (right > 0) {
    // fill plasma to the right of the image
    right = right < ILI9341_TFTWIDTH ? right : ILI9341_TFTWIDTH;
    uint16_t tmp[right];
    memset(tmp, 0, right * 2);
    plasma.composite_row(right, tmp);
    tft.drawRGBBitmap(ILI9341_TFTWIDTH - right, y, tmp, right, 1);
  }
}

uint16_t freemem() {
  extern int __heap_start; //start of the heap
  extern int *__brkval; //highest point of heap
  static uint16_t freemin = -1;
  uint16_t free;
  free = (int) &free - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
  if (free < freemin) {
    freemin = free;
    Serial.print(free);
    Serial.println(" free");
  }
  return free;
}