// Source: https://github.com/adafruit/Adafruit_ILI9341/tree/master/examples/mandelbrot

#include <Adafruit_ILI9341.h>

#ifdef ADAFRUIT_PYPORTAL
  #define TFT_D0        34 // Data bit 0 pin (MUST be on PORT byte boundary)
  #define TFT_WR        26 // Write-strobe pin (CCL-inverted timer output)
  #define TFT_DC        2 // Data/command pin
  #define TFT_CS        15 // Chip-select pin
  #define TFT_RST       4 // Reset pin
  #define TFT_RD        15 // Read-strobe pin
  #define TFT_BACKLIGHT 25
  // ILI9341 with 8-bit parallel interface:
  Adafruit_ILI9341 tft = Adafruit_ILI9341(tft8bitbus, TFT_D0, TFT_WR, TFT_DC, TFT_CS, TFT_RST, TFT_RD);
  #define USE_BUFFER   // buffer all 155 KB of data for bliting - uses passive ram but looks nicer?
#else
  // Use SPI
  #define STMPE_CS 6
  #define TFT_CS   15
  #define TFT_DC   2
  #define SD_CS    5
  Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
#endif


const int16_t
  bits        = 12,   // Fractional resolution
  pixelWidth  = 320,  // TFT dimensions
  pixelHeight = 240,
  iterations  = 20;  // Fractal iteration limit or 'dwell'
float
  centerReal  = -0.6, // Image center point in complex plane
  centerImag  =  0.0,
  rangeReal   =  3.0, // Image coverage in complex plane
  rangeImag   =  3.0;

#if defined(USE_BUFFER)
  uint16_t buffer[pixelWidth * pixelHeight];
#endif

void setup(void) {
  Serial.begin(115200);
  Serial.print("Mandelbrot drawer!");

  tft.begin();
  tft.setRotation(1);
  tft.fillScreen(ILI9341_BLACK);

  // Turn on backlight (required on PyPortal)
#if defined(TFT_BACKLIGHT)
  pinMode(TFT_BACKLIGHT, OUTPUT);
  digitalWrite(TFT_BACKLIGHT, HIGH);
#endif
}

void loop() {
  int64_t       n, a, b, a2, b2, posReal, posImag;
  uint32_t      startTime,elapsedTime;


  int32_t
    startReal   = (int64_t)((centerReal - rangeReal * 0.5)   * (float)(1 << bits)),
    startImag   = (int64_t)((centerImag + rangeImag * 0.5)   * (float)(1 << bits)),
    incReal     = (int64_t)((rangeReal / (float)pixelWidth)  * (float)(1 << bits)),
    incImag     = (int64_t)((rangeImag / (float)pixelHeight) * (float)(1 << bits));
  
  startTime = millis();
  posImag = startImag;
  for (int y = 0; y < pixelHeight; y++) {
    posReal = startReal;
    for (int x = 0; x < pixelWidth; x++) {
      a = posReal;
      b = posImag;
      for (n = iterations; n > 0 ; n--) {
        a2 = (a * a) >> bits;
        b2 = (b * b) >> bits;
        if ((a2 + b2) >= (4 << bits)) 
          break;
        b  = posImag + ((a * b) >> (bits - 1));
        a  = posReal + a2 - b2;
      }
      #if defined(USE_BUFFER)
        buffer[y * pixelWidth + x] = (n * 29)<<8 | (n * 67);
      #else
        tft.drawPixel(x, y, (n * 29)<<8 | (n * 67)); // takes 500ms with individual pixel writes
      #endif
      posReal += incReal;
    }
    posImag -= incImag;
  }
  #if defined(USE_BUFFER)
    tft.drawRGBBitmap(0, 0, buffer, pixelWidth, pixelHeight); // takes 169 ms
  #endif
  elapsedTime = millis()-startTime;
  Serial.print("Took "); Serial.print(elapsedTime); Serial.println(" ms");

  rangeReal *= 0.95;
  rangeImag *= 0.95;
}