#include "SPI.h"
#include "GD.h"

byte i;
float x = 0.5;
float y = 0.25;
float z = 0.125;
float oldx = 0;
float oldy = 0;
float oldz = 0;
float h = 0;
float dt = 0.005;
float alpha = 15.6;
float beta = 28.58;
float a = -1.14286;
float b = -0.714286;

void setup()
{
  Serial.begin(1000000);
  GD.begin();
  GD.ascii();
  GD.wr16(RAM_SPRPAL + (0 * 2), RGB(0, 0, 255));
  GD.wr16(RAM_SPRPAL + (1 * 2), RGB(255, 255, 255));
  GD.fill(RAM_SPRIMG, 0, 256);
  GD.wr(RAM_SPRIMG + 0x78, 1);

  GD.wr16(PALETTE16A + (0 * 2), TRANSPARENT);
  GD.wr16(PALETTE16A + (1 * 2), RGB(255, 255, 255));

  GD.putstr(13, 1, "Chua chaotic oscillator");
}

struct point { int x, y; };

class PtCollection {
  public:
    point pts[20];
    int np;
    PtCollection(void) { np = 0; }
    void add(int x, int y) {
      pts[np].x = x;
      pts[np].y = y;
      np++;
    }
    void clear(void) { np = 0; }
    void remove(void) { np--; }
    int isfull(void) { return np == 20; }

    void bounds(int &minx, int &miny, int &maxx, int &maxy) {
      minx = 9999;
      maxx = -9999;
      miny = 9999;
      maxy = -9999;
      for (int i = 0; i < np; i++) {
        minx = min(pts[i].x, minx);
        miny = min(pts[i].y, miny);
        maxx = max(pts[i].x, maxx);
        maxy = max(pts[i].y, maxy);
      }
    }
    bool is16x16(void) {
      int minx, maxx, miny, maxy;
      bounds(minx, miny, maxx, maxy);
      return ((maxx - minx) < 16) && ((maxy - miny) < 16);
    }
};

static class PtCollection ptc;
static byte color, colorc = 1;

static void flush(void)
{
  int minx, maxx, miny, maxy;
  ptc.bounds(minx, miny, maxx, maxy);

  uint16_t page = RAM_SPRIMG + ((i >> 1) << 8);
  static byte pixels[256];
  GD.__start(page);
  for (int k = 0; k < 256; k++)
    pixels[k] = SPI.transfer(0);
  GD.__end();

  int pal = (i & 1);
  byte mask = ~(15 << (4 * pal));
  for (int k = 0; k < 256; k++)
    pixels[k] &= mask;

  mask = (color << (4 * pal));
  for (int k = 0; k < ptc.np; k++) {
    int x = ptc.pts[k].x - minx;
    int y = ptc.pts[k].y - miny;
    pixels[(y << 4) + x] |= mask;
  }

  GD.__wstart(page);
  for (int k = 0; k < 256; k++)
    SPI.transfer(pixels[k]);
  GD.__end();

  if (--colorc == 0) {
    colorc = 10;
    if (++color == 16)
      color = 1;
    static byte shades[16];
    shades[color] = 0;    // freshest;
    for (byte i = 1; i < 16; i++) {
      byte l = max(0, 255 - 16 * shades[i]);
      GD.wr16(PALETTE16A + (i * 2), RGB(255, l, l));
      shades[i]++;
    }
  }
  GD.sprite(i, minx, miny, (i >> 1), 0x4 | (pal << 1), 0);
  i = (i + 1) & 127;

  ptc.clear();
  static int reduce; if ((++reduce % 7) == 0) {
    // static int ss; GD.screenshot(ss); ss++;
  }
}

void loop()
{
  oldx = x;
  oldy = y;
  oldz = z;
  h = (b * x) + (0.5 * (a - b) * (abs(x+1) - abs(x-1)));            
  x = oldx + dt * (alpha * (oldy - oldx - h));
  y = oldy + dt * (oldx - oldy + oldz);
  z = oldz + dt * (-beta * oldy);          
  int ix = 200+(80*x);
  int iy = 150+(4*80*y);
  ptc.add(ix, iy);
  if (!ptc.is16x16()) {
    ptc.remove();
    flush();
    ptc.add(ix, iy);
  } else if (ptc.isfull())
    flush();
}
