
#include <Arduino.h>

#include <M5GFX.h>
M5GFX display;

//#include <M5UnitOLED.h>
//M5UnitOLED display; // default setting
//M5UnitOLED display ( 21, 22, 400000 ); // SDA, SCL, FREQ

//#include <M5UnitLCD.h>
//M5UnitLCD display;  // default setting
//M5UnitLCD display  ( 21, 22, 400000 ); // SDA, SCL, FREQ

//#include <M5UnitGLASS2.h>
//M5UnitGLASS2 display;  // default setting
//M5UnitGLASS2 display ( 21, 22, 400000 ); // SDA, SCL, FREQ

//#include <M5AtomDisplay.h>
//M5AtomDisplay display;  // default setting
//M5AtomDisplay display ( 320, 180 ); // width, height

// #include <M5UnitPoEP4HDMI.h>
// M5UnitPoEP4HDMI display; // 1280x720@60 or 1920x1080@30 only; default 1280x720@60


static constexpr float deg_to_rad = 0.017453292519943295769236907684886;
static constexpr int TFT_METER_GREY = 0x5AEB;
static constexpr int LOOP_PERIOD = 35; // Display updates every 35 ms

int value[6] = {0, 0, 0, 0, 0, 0};
int old_value[6] = { -1, -1, -1, -1, -1, -1};
int liner_count = 6;
int d = 0;

int liner_height;
int meter_height;
int needle_x;
int needle_y;
int needle_r;
int osx, osy; // Saved x & y coords
uint32_t updateTime = 0;       // time for next update

void plotNeedle(int value)
{
  float sdeg = map(value, 0, 100, -135, -45); // Map value to angle
  // Calcualte tip of needle coords
  float sx = cosf(sdeg * deg_to_rad);
  float sy = sinf(sdeg * deg_to_rad);

  display.setClipRect(0, 0, display.width(), meter_height - 5);
  // Erase old needle image
  display.drawLine(needle_x - 1, needle_y, osx - 1, osy, TFT_WHITE);
  display.drawLine(needle_x + 1, needle_y, osx + 1, osy, TFT_WHITE);
  display.drawLine(needle_x    , needle_y, osx    , osy, TFT_WHITE);

  // Re-plot text under needle
  display.setTextColor(TFT_BLACK);
  if (display.width() > 100)
  {
    display.setFont(&fonts::Font4);
    display.setTextDatum(textdatum_t::middle_center);
    display.drawString("%RH", needle_x, needle_y>>1);
  }

  display.setFont(&fonts::Font2);
  display.setTextDatum(textdatum_t::bottom_left);
  display.drawString("%RH", display.width() - 36, meter_height - 8);

  display.setTextColor(TFT_BLACK, TFT_WHITE);
  display.setTextDatum(textdatum_t::bottom_right);
  display.drawNumber(value, 36, meter_height - 8);


  // Store new needle end coords for next erase
  osx = roundf(sx * needle_r) + needle_x;
  osy = roundf(sy * needle_r) + needle_y;

  // Draw the needle in the new postion, magenta makes needle a bit bolder
  // draws 3 lines to thicken needle
  display.drawLine(needle_x - 1, needle_y, osx - 1, osy, TFT_RED);
  display.drawLine(needle_x + 1, needle_y, osx + 1, osy, TFT_RED);
  display.drawLine(needle_x    , needle_y, osx    , osy, TFT_MAGENTA);

  display.clearClipRect();
}

void analogMeter()
{
  display.fillRect(0, 0, display.width()   , meter_height  , TFT_WHITE);
  display.drawRect(1, 1, display.width()-2 , meter_height-2, TFT_BLACK);

  int r3 = needle_y * 13 / 15;
  int r2 = needle_y * 12 / 15;
  int r1 = needle_y * 11 / 15;
  needle_r = r1 - 3;
  display.fillArc(needle_x, needle_y, r1, r3, 270, 292, TFT_GREEN);
  display.fillArc(needle_x, needle_y, r1, r3, 292, 315, TFT_ORANGE);
  display.fillArc(needle_x, needle_y, r1, r1, 225, 315, TFT_BLACK);

  display.setTextColor(TFT_BLACK);
  display.setFont(&fonts::Font2);
  display.setTextDatum(textdatum_t::bottom_center);
  for (int i = 0; i <= 20; i++)
  {
    display.fillArc(needle_x, needle_y, r1, (i % 5) ? r2 : r3, 225 + i * 4.5f, 225 + i * 4.5f, TFT_BLACK);
    if (0 == (i % 5) && display.width() > 100)
    {
      display.drawNumber(i * 5, needle_x - cosf((45+i*4.5) * deg_to_rad) * r3
                              , needle_y - sinf((45+i*4.5) * deg_to_rad) * r3 - 2);
    }
  }
}

void plotLinear(const char *label, int x, int y, int w, int h)
{
  display.drawRect(x, y, w, h, TFT_METER_GREY);
  display.fillRect(x + 2, y + 18, w - 3, h - 36, TFT_WHITE);
  display.setTextColor(TFT_CYAN, TFT_BLACK);
  display.setTextDatum(textdatum_t::middle_center);
  display.setFont(&fonts::Font2);
  display.setTextPadding(display.textWidth("100"));
  display.drawString(label, x + w / 2, y + 10);

  int plot_height = h - (19*2);

  for (int i = 0; i <= 10; i ++)
  {
    display.drawFastHLine(x + w/2, y + 19 + (i+1) * plot_height / 12, w*(3+(0==(i%5)))/16, TFT_BLACK);
  }
}

void plotPointer(void)
{
  display.setTextColor(TFT_GREEN, TFT_BLACK);
  display.setTextDatum(textdatum_t::middle_right);

  int plot_height = liner_height - (19*2);
  int pw = (display.width() / liner_count) / 3;

  for (int i = 0; i < liner_count; i++)
  {
    display.drawNumber(value[i], display.width() * (i + 0.8) / liner_count, display.height() - 10);
    int dx = display.width() * i / liner_count + 3;
    int dy = meter_height + 19 + (old_value[i]+10) * plot_height / 120;
    display.fillTriangle(dx, dy - 5, dx, dy + 5, dx + pw, dy, TFT_WHITE);
    old_value[i] = value[i];
    dy = meter_height + 19 + (old_value[i]+10) * plot_height / 120;
    display.fillTriangle(dx, dy - 5, dx, dy + 5, dx + pw, dy, TFT_RED);
  }
}

void setup(void)
{
  display.begin();
  display.setEpdMode(epd_mode_t::epd_fastest);
  if (display.width() > display.height())
  {
    display.setRotation(display.getRotation() ^ 1);
  }

  display.startWrite();
  display.fillScreen(TFT_BLACK);

  liner_height = display.height() * 3 / 5;
  meter_height = display.height() * 2 / 5;
  needle_x = display.width() / 2;
  needle_y = (meter_height*2 + display.width()) / 3;
  osx = needle_x;
  osy = needle_y;
  liner_count = std::min<int>(6, display.width() / 40);

  analogMeter(); // Draw analogue meter
  int w = display.width() / liner_count;
  char name[] = "A0";
  // Draw linear meters
  for (int i = 0; i < liner_count; i++)
  {
    name[1] = '0' + i;
    plotLinear(name, display.width() * i / liner_count, meter_height, w, liner_height);
  }
  display.endWrite();

  updateTime = millis(); // Next update time
}

void loop(void)
{
  auto msec = millis();
  if (updateTime <= msec)
  {
    d = (msec >> 3) % 360;

    // Create a Sine wave for testing
    value[0] = 50 + roundf(50 * sinf((d +   0) * deg_to_rad));
    value[1] = 50 + roundf(50 * sinf((d +  60) * deg_to_rad));
    value[2] = 50 + roundf(50 * sinf((d + 120) * deg_to_rad));
    value[3] = 50 + roundf(50 * sinf((d + 180) * deg_to_rad));
    value[4] = 50 + roundf(50 * sinf((d + 240) * deg_to_rad));
    value[5] = 50 + roundf(50 * sinf((d + 300) * deg_to_rad));

    //value[0] = map(analogRead(36), 0, 4095, 0, 100); // Test with value form GPIO36

    if (!display.displayBusy())
    {
      updateTime = msec + LOOP_PERIOD;
    //unsigned long t = millis();
      display.startWrite();
      plotPointer();
      plotNeedle(roundf(value[0]));
      display.endWrite();
    //Serial.println(millis()-t); // Print time taken for meter update
    }
  }
}
