/* niceGauge
   by VolosR
   GitHub: https://github.com/VolosR/TDisplayDashboard/blob/main/niceGauge/niceGauge.ino
   YouTube: https://www.youtube.com/watch?v=cBtsLxZ13hQ
   based on T-Display S3 (ESP32) development board: https://www.lilygo.cc/products/t-display-s3?bg_ref=7LJo9u5wwo
 **/

#include <TFT_eSPI.h>
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite sprite = TFT_eSprite(&tft);

//............INPUT PINS...............switches and buttons
#define THROTTLE   43
#define BRAKE      44
#define LEFT       17 // turn indicator - left
#define RIGHT      18 // turn indicator - right
#define SHORT      16 // headlights - dipped
#define LONG       21 // headlights - high beams
#define GEARUP     12
#define GEARDOWN   13
#define HORN       10
#define BRIGHTNESS 14

//............OUTPUT PINS..............lights, pointes, horn
#define left_pointer  1  // turn indicator - left
#define right_pointer 11 // turn indicator - right
#define head_lights   2  // headlights
#define buzzer        3  // horn

//.....................................colors
#define backColor   0x0026
#define gaugeColor  0x055D
#define dataColor   0x0311
#define purple      0xEA16
#define needleColor 0xF811

//.....................................dont edit this
int cx = 75;
int cy = 75;
int r = 72;
int ir = 70;
int n = 0;
int angle = 0;

// collection of points (x,y) for outer circle of Speed gauge
float x[360];
float y[360];
// collection of points (x,y) for inner circle of Speed gauge
float px[360];
float py[360];
// collection of points (x,y) for text markers of Speed gauge
float lx[360];
float ly[360];
// collection of points (x,y) for needle (low) marker of Speed gauge
float nx[360];
float ny[360];
// collection of points (x,y) for outer circle of RPM gauge
float x2[360];
float y2[360];
// collection of points (x,y) for inner cicrcle of RPM gauge
float px2[360];
float py2[360];
// collection of points (x,y) for text markers of RPM gauge
float lx2[360];
float ly2[360];
// collection of points (x,y) for needle (low) markers of RPM gauge
float nx2[360];
float ny2[360];

//.....................................runtime variables
double rad = 0.01745;
unsigned short color1;
unsigned short color2;
float sA;
float rA;
int blinkPeriod = 500;
unsigned long currentTimeL = 0;
unsigned long currentTimeR = 0;
int brightnesses[5] = {40, 80, 120, 150, 240};
int selectedBrightness = 3;
int deb1 = 0;
int deb2 = 0;
int debB = 0;

//.....................................gears
int gearMaxSpeed[8] = {12, 0, 60, 90, 120, 150, 190, 236};
String gears[8] = {"R", "N", "1", "2", "3", "4", "5", "6"};
int selectedGear = 1;

//.....................................colors
unsigned short blockColor[4] = {0x0312, 0x0290, 0x01EC, 0x016A};
unsigned short dirColor[2] = {0x0312, TFT_ORANGE};
unsigned short lightColor[3] = {0x01EC, 0x0FA8, 0xB79F};

//.....................................important variables
bool leftPointer = 0;
bool rightPointer = 0;
bool braking;
int lights = 0; // 0 is lights off, 1 is short light, 2 is long lights
float speedAngle = 0; //...speed variable 0-240
float rpmAngle = 5; //.....RPM variable 0-9

void setup() {
  pinMode(THROTTLE, INPUT_PULLUP);
  pinMode(BRAKE, INPUT_PULLUP);
  pinMode(LEFT, INPUT_PULLUP);
  pinMode(RIGHT, INPUT_PULLUP);
  pinMode(GEARUP, INPUT_PULLUP);
  pinMode(GEARDOWN, INPUT_PULLUP);
  pinMode(SHORT, INPUT_PULLUP);
  pinMode(LONG, INPUT_PULLUP);
  pinMode(HORN, INPUT_PULLUP);
  pinMode(BRIGHTNESS, INPUT_PULLUP);

  pinMode(left_pointer, OUTPUT);
  pinMode(right_pointer, OUTPUT);
  pinMode(head_lights, OUTPUT);
  pinMode(buzzer, OUTPUT);

  tft.init();

  tft.setRotation(1);
  tft.fillScreen(backColor);
  sprite.createSprite(320, 150);
  sprite.setSwapBytes(true);
  sprite.setTextDatum(4);
  sprite.setTextColor(TFT_WHITE, backColor);
  sprite.setTextDatum(4);

  ledcSetup(0, 10000, 8);
  ledcAttachPin(38, 0);
  ledcWrite(0, brightnesses[selectedBrightness]); // brightnes of screen

  ledcSetup(1, 10000, 8);
  ledcAttachPin(head_lights, 1);
  //ledcWrite(1, 10);

  int a = 120;
  for (int i = 0; i < 360; i++) {
    // points (x,y) along outer circle of Speed gauge in 1 degree increments
    x[i] = ((r - 10) * cos(rad * a)) + cx;
    y[i] = ((r - 10) * sin(rad * a)) + cy;
    // points (x,y) along inner circle of Speed gauge in 1 degree increments
    px[i] = ((r - 14) * cos(rad * a)) + cx;
    py[i] = ((r - 14) * sin(rad * a)) + cy;
    // points (x,y) along circle for text labels of Speed gauge in 1 degree increments
    lx[i] = ((r - 24) * cos(rad * a)) + cx;
    ly[i] = ((r - 24) * sin(rad * a)) + cy;
    // points (x,y) along circle for needle (low) of Speed gauge in 1 degree increments
    nx[i] = ((r - 36) * cos(rad * a)) + cx;
    ny[i] = ((r - 36) * sin(rad * a)) + cy;
    // points (x,y) along outer circle of RPM gauge in 1 degree increments
    x2[i] = ((r - 10) * cos(rad * a)) + 320 - cx;
    y2[i] = ((r - 10) * sin(rad * a)) + cy;
    // points (x,y) along inner circle of RPM gauge in 1 degree increments
    px2[i] = ((r - 14) * cos(rad * a)) + 320 - cx;
    py2[i] = ((r - 14) * sin(rad * a)) + cy;
    // points (x,y) along circle for text labels of RPM gauge in 1 degree increments
    lx2[i] = ((r - 24) * cos(rad * a)) + 320 - cx;
    ly2[i] = ((r - 24) * sin(rad * a)) + cy;
    // points (x,y) along circle for needle (low) of RPM gauge in 1 degree increments
    nx2[i] = ((r - 36) * cos(rad * a)) + 320 - cx;
    ny2[i] = ((r - 36) * sin(rad * a)) + cy;

    a++;
    if (a == 360) {
      a = 0;
    }
  }
}

void draw() {
  sprite.fillSprite(backColor); // clear sprite by filling with black

  // draw four rectangles vertically in the center of the screen
  // these will later be used for:
  // fuel level gauge   (120,28)
  // low fuel indicator (120,52)
  // light icon         (120,76)
  // odometer           (120,100)
  for (int i = 0; i < 4; i++) {
    sprite.fillRect(120, 28 + (24 * i), 80, 22, blockColor[i]);
  }

  // draw a number of orange rectagles corresponding to select level of illumination
  // Level 1 (8,6)
  // Level 2 (12,6)
  // Level 3 (16,6)
  // Level 4 (20,6)
  // Level 5 (24,6)
  for (int i = 0; i < selectedBrightness; i++) {
    sprite.fillSmoothRoundRect(8 + (4 * i), 6, 2, 9, 1, TFT_ORANGE, backColor);
  }

  // clear SPEED & RPM gauge by filling with black circle
  sprite.fillSmoothCircle(cx, cy, r + 2, backColor);
  sprite.fillSmoothCircle(320 - cx, cy, r + 2, backColor);

  // draw turn indicator - left
  sprite.fillTriangle(126, 14, 136, 7, 136, 21, dirColor[leftPointer]);
  sprite.fillRect(136, 11, 8, 7, dirColor[leftPointer]);

  // draw turn indicator - left
  sprite.fillTriangle(126 + 68, 14, 136 + 48, 7, 136 + 48, 21, dirColor[rightPointer]);
  sprite.fillRect(176, 11, 8, 7, dirColor[rightPointer]);

  // draw number of filled/empty squares corresponding to fuel level
  // Level 1 (144,36)
  // Level 2 (151,36)
  // Level 3 (158,36)
  // Level 4 (165,36)
  // Level 5 (172,36)
  for (int i = 0; i < 5; i++) {
    // fuel level is hard coded as first three squares (i <= 2)
    if (i <= 2) {
      sprite.fillRect(144 + (7 * i), 36, 5, 5, TFT_WHITE);
    } else {
      sprite.drawRect(144 + (7 * i), 36, 5, 5, TFT_WHITE);
    }
  }

  // draw low fuel indicator
  sprite.fillSmoothRoundRect(155, 54, 9, 16, 2, TFT_WHITE, blockColor[1]);
  sprite.fillSmoothRoundRect(166, 56, 2, 14, 2, TFT_WHITE, blockColor[1]);
  sprite.fillSmoothRoundRect(156, 56, 7, 5, 1, blockColor[1], TFT_WHITE);
  sprite.drawLine(153, 69, 166, 69, TFT_WHITE);

  // draw light blue ring of SPEED & RPM gauge (outer most ring)
  sprite.drawSmoothArc(cx, cy, r, ir, 30, 330, gaugeColor, backColor);
  sprite.drawSmoothArc(320 - cx, cy, r, ir, 30, 330, gaugeColor, backColor);

  // draw white ring of SPEED & RPM gauge (first inner ring)
  sprite.drawSmoothArc(cx, cy, r - 5, r - 6, 30, 330, TFT_WHITE, backColor);
  sprite.drawSmoothArc(320 - cx, cy, r - 5, r - 6, 30, 330, TFT_WHITE, backColor);

  // draw purple ring of SPEED gauge connecting last few ticks to create danger area
  sprite.drawSmoothArc(cx, cy, r - 9, r - 8, 270, 330, purple, backColor);
  // todo - add red ranger area for RPM gauge

  // draw light blue ring of SPEED & RPM gauge (inner most ring)
  sprite.drawSmoothArc(cx, cy, r - 38, ir - 37, 10, 350, gaugeColor, backColor);
  sprite.drawSmoothArc(320 - cx, cy, r - 38, ir - 37, 10, 350, gaugeColor, backColor);

  // draw 26 tick marks and 13 values for SPEED gauge
  for (int i = 0; i < 26; i++) {
    if (i < 20) {
      color1 = gaugeColor;
      color2 = TFT_WHITE;
    } else {
      color1 = purple;
      color2 = purple;
    }

    // draw minor & major tick marks with alternating color
    // and values for major tick marks
    if (i % 2 == 0) {
      sprite.drawWedgeLine(x[i * 12], y[i * 12], px[i * 12], py[i * 12], 2, 1, color1);
      sprite.setTextColor(color2, backColor);
      sprite.drawString(String(i * 10), lx[i * 12], ly[i * 12]);
    } else {
      sprite.drawWedgeLine(x[i * 12], y[i * 12], px[i * 12], py[i * 12], 1, 1, color2);
    }
  }

  // draw 19 tick marks and 10 values for RPM gauge
  for (int i = 0; i < 19; i++) {
    if (i < 20) {
      color1 = gaugeColor;
      color2 = TFT_WHITE;
    } else {
      // not enough tick marks to enter this block (todo - add red danger area here)
      color1 = purple;
      color2 = purple;
    }

    // draw minor & major tick marks with alternating color
    // and values for major tick marks
    if (i % 2 == 0) {
      sprite.drawWedgeLine(x2[i * 16], y2[i * 16], px2[i * 16], py2[i * 16], 2, 1, color1);
      sprite.setTextColor(color2, backColor);
      sprite.drawString(String(i / 2), lx2[i * 16], ly2[i * 16]);
    } else {
      sprite.drawWedgeLine(x2[i * 16], y2[i * 16], px2[i * 16], py2[i * 16], 1, 1, color2);
    }
  }

  // draw needle indicators for SPEED & RPM gauges
  sA = speedAngle * 1.2;
  rA = 2 * rpmAngle * 1.6;
  sprite.drawWedgeLine(px[(int)sA], py[(int)sA], nx[(int)sA], ny[(int)sA], 2, 2, needleColor);
  sprite.drawWedgeLine(px2[(int)rA], py2[(int)rA], nx2[(int)rA], ny2[(int)rA], 2, 2, needleColor);

  // draw speed value and measurement units in the center of SPEED gauge
  sprite.setTextColor(TFT_WHITE, backColor);
  sprite.drawString(String((int)speedAngle), cx, cy, 4);
  sprite.drawString("KM/H", cx, cy + 16);

  // draw selected gear value in the center of RPM gauge
  sprite.drawString(String(gears[selectedGear]), 320 - cx, cy, 4);
  sprite.drawString("GEAR", 320 - cx, cy + 16);

  // draw odometer reading
  sprite.setTextColor(TFT_WHITE, blockColor[3]);
  sprite.drawString("14356", 160, 110);

  // draw unit scaling for RPM gauge
  sprite.setTextColor(TFT_ORANGE, backColor);
  sprite.drawString("x1000", 320 - cx, 136);
  sprite.drawString("RPM", 320 - cx, 126);

  // drawing parking brake symbol (between turn indicators)
  if (braking == true) {
    sprite.drawSmoothArc(160, 10, 9, 8, 10, 350, TFT_RED, backColor);
    sprite.drawSmoothArc(160, 10, 12, 11, 50, 130, TFT_RED, backColor);
    sprite.drawSmoothArc(160, 10, 12, 11, 230, 310, TFT_RED, backColor);
    //sprite.drawCircle(320-cx, cy-20, 6, TFT_RED);
    sprite.setTextColor(TFT_RED, backColor);
    sprite.drawString("!", 160, 12, 2);
  }

  // drawing headlight indicator
  sprite.fillSmoothRoundRect(152, 82, 14, 10, 7, lightColor[lights], lightColor[0]);
  sprite.fillRect(161, 82, 5, 10, lightColor[0]);
  sprite.drawLine(163, 82, 167, 84 - lights, lightColor[lights]);
  sprite.drawLine(163, 85, 167, 87 - lights, lightColor[lights]);
  sprite.drawLine(163, 88, 167, 90 - lights, lightColor[lights]);
  sprite.drawLine(163, 91, 167, 93 - lights, lightColor[lights]);

  // draw dot in top right corner (not sure what this is for)
  sprite.fillSmoothCircle(300, 10, 4, TFT_RED, backColor);

  // finally push computed sprite to screen
  sprite.pushSprite(0, 10);
}

int blinking = 1;

void loop() {
  // check lights status
  if (digitalRead(SHORT) == 0) {
    lights = 1;
  } else if (digitalRead(LONG) == 0) {
    lights = 2;
  } else {
    lights = 0;
  }

  ledcWrite(1, lights * 4);

  if (digitalRead(HORN) == 0) {
    digitalWrite(buzzer, 1);
  } else {
    digitalWrite(buzzer, 0);
  }

  braking = !(digitalRead(BRAKE));

  if (digitalRead(BRIGHTNESS) == 0) {
    if (debB == 0) {
      debB = 1;
      selectedBrightness++;
      if (selectedBrightness == 5) {
        selectedBrightness = 0;
      }
      ledcWrite(0, brightnesses[selectedBrightness]); // brightnes of screen
    }
  } else {
    debB = 0;
  }

  if (digitalRead(GEARUP) == 0) {
    if (deb1 == 0) {
      deb1 = 1;
      if (selectedGear < 7) {
        selectedGear++;
      }
      if (speedAngle > 10) {
        speedAngle = speedAngle - 4;
      }
    }
  } else {
    deb1 = 0;
  }

  if (digitalRead(GEARDOWN) == 0) {
    if (deb2 == 0) {
      deb2 = 1;
      if (selectedGear > 0) {
        selectedGear--;
      }
      if (speedAngle > 10) {
        speedAngle = speedAngle - 4;
      }
    }
  } else {
    deb2 = 0;
  }

  if (digitalRead(LEFT) == 0) {
    if (millis() > currentTimeL + blinkPeriod) {
      leftPointer = !leftPointer;
      digitalWrite(left_pointer, leftPointer);
      currentTimeL = millis();
    }
  } else {
    leftPointer = 0;
    digitalWrite(left_pointer, leftPointer);
  }

  if (digitalRead(RIGHT) == 0) {
    if (millis() > currentTimeR + blinkPeriod) {
      rightPointer = !rightPointer;
      digitalWrite(right_pointer, rightPointer);
      currentTimeR = millis();
    }
  } else {
    rightPointer = 0;
    digitalWrite(right_pointer, rightPointer);
  }

  if (braking == true && speedAngle > 4) {
    speedAngle = speedAngle - 4;
  }

  if (speedAngle < 0) {
    speedAngle = 0;
  }

  draw();

  if (digitalRead(THROTTLE) == 0 && speedAngle < gearMaxSpeed[selectedGear]) {
    speedAngle = speedAngle + 2 - (0.24 * selectedGear);
  }

  if (digitalRead(THROTTLE) == 1 && speedAngle > 0) {
    speedAngle--;
  }

  if (digitalRead(THROTTLE) == 0 && rpmAngle < 75) {
    rpmAngle = rpmAngle + 1 - (0.1 * selectedGear);
  }

  if (digitalRead(THROTTLE) == 1 && rpmAngle > 0) {
    if (rpmAngle >= 3) {
      rpmAngle = rpmAngle - 3;
    } else {
      rpmAngle = 0;
    }
  }
}