#include <SPI.h>             // Arduino SPI library
#include <ESP32Servo.h>      // Servobibliothek.
#include <TFT_eSPI.h>        // Hardware-specific library
#include "Free_Fonts.h"

constexpr const char *STATUS_TEXT[] {"Aus", "Ein", "Fehler", "o.k."};
constexpr const char *AXIS_TEXT[] {"   X-Achse", "   Y-Achse", "   Z-Achse", "   Taster", "   Werkzeug"};

enum StatusTextIdx { aus, ein, fehler, ok };
namespace AxisTextIdx {
enum ATI { esX, esY, esZ, taster, werkzeug };
}
namespace AssemblyGroupIdx {
enum AGI { enaIn, esX, esY, esZ, taster, werkzeug, spindel };
}
using STI = StatusTextIdx;
using ATI = AxisTextIdx::ATI;
using AGI = AssemblyGroupIdx::AGI;

// Struktur definieren
struct TftStringData {
  const char *text;
  const int32_t xPos;
  const int32_t yPos;
  const uint8_t fontNr;
};

// Array aus obiger Strukturdefinition initialisieren
TftStringData statusScreen[] {
    {"Status",     120, 0,   4},
    {"Steuerung",  0,   26,  4},
    {"Endsch. X",  0,   52,  4},
    {"Endsch. Y",  0,   78,  4},
    {"Endsch. Z",  0,  104,  4},
    {"3D-Taster",  0,  130,  4},
    {"WKZ-Laenge", 0,  156,  4},
    {"Spindel",    0,  182,  4},
    {"Drehzahl",   0,  208,  4}
};

struct AssemblyGroup {
  uint8_t pin;
  uint16_t color;
  TftStringData tft;
};

AssemblyGroup asg[] {
    {12, TFT_GREEN, {STATUS_TEXT[STI::ein], 200, statusScreen[1].yPos, statusScreen[1].fontNr}}, // enable Input
    {14, TFT_GREEN, {STATUS_TEXT[STI::ein], 200, statusScreen[2].yPos, statusScreen[2].fontNr}}, // es X
    {39, TFT_GREEN, {STATUS_TEXT[STI::ok], 200, statusScreen[3].yPos, statusScreen[3].fontNr} }, // es Y
    {34, TFT_GREEN, {STATUS_TEXT[STI::ok], 200, statusScreen[4].yPos, statusScreen[4].fontNr} }, // es Z
    {35, TFT_GREEN, {STATUS_TEXT[STI::ok], 200, statusScreen[5].yPos, statusScreen[5].fontNr} }, // taster
    {26, TFT_GREEN, {STATUS_TEXT[STI::ok], 200, statusScreen[6].yPos, statusScreen[6].fontNr} }, // werkzeug
    {27, TFT_GREEN, {STATUS_TEXT[STI::ok], 200, statusScreen[7].yPos, statusScreen[7].fontNr} }, // spindel
};

unsigned long intervall = 4000;    // Länge der Anzeige Status
unsigned long intervall1 = 4000;   // Länge der Pause für Bereitschaft Fahrtenregler
unsigned long altMillis = 0;       // Speicher für alte Zeit
unsigned long neuMillis;           // akt. Zeit übernehmen
int Status_Motor = 0;              // Var. für Statuszustand Spindel_in

String DZ;   // Var. für Drehzahl

unsigned long Farbe;          // Farbe= Schriftfarbe
int Dz_in = A0;               // Eingang Drehzahl-Spindel 0-10V
int EStop_in = 32;            // Eingang Notaus
int Reserve_in = 33;          // Eingang Reserve Relais
int Spindel_out = 16;         // Ausgang Spindel_an
int Reserve_Relais_out = 4;   // Ausgang Reserve Relais
int Reserve_out = 4;          // Ausgang Reserve
int ESC_out = 13;             // Ausgang PWM ESC
int TFT_BL = 15;              // Ausgang Beleuchtung TFT
int Drehzahl = 0;             // Var. Drehzahl (Drehzahl 0)

TFT_eSPI tft = TFT_eSPI();   // Invoke custom library
Servo ESC;                    // Der ESC-Controller wird als Objekt mit dem Namen "ESC" festgelegt

// Achsen freifahren
void Freifahren(const char *const axis) {
  tft.fillScreen(TFT_BLACK);
  tft.setCursor(0, 0);
  tft.setFreeFont(FF19);
  tft.println("");
  tft.setTextColor(TFT_BLUE);
  tft.println("     Aktion");
  tft.println("  erforderlich!");
  tft.println("");
  tft.setTextColor(TFT_RED);
  tft.println(axis);
  tft.setTextColor(TFT_GREEN);
  tft.println("   freifahren");
  delay(2000);
  tft.fillScreen(TFT_BLACK);
}

// Aktion bei Steuerung aus
void Steuerung_ein() {
  tft.fillScreen(TFT_BLACK);
  tft.setCursor(0, 0);
  tft.setFreeFont(FF19);
  tft.println("");
  tft.setTextColor(TFT_BLUE);
  tft.println("    Aktion");
  tft.println("  erforderlich!");
  tft.println("");
  tft.setTextColor(TFT_GREEN);
  tft.println("   Steuerung");
  tft.println("  einschalten!");
  delay(2000);
  tft.fillScreen(TFT_BLACK);
}

// UP Status anzeigen
void Anzeige() {
  int index {0};
  tft.setTextPadding(tft.textWidth("555555"));
  tft.setTextColor(TFT_RED, TFT_BLACK);
  for (const auto &sts : statusScreen) {
    if (index == 1) { tft.setTextColor(TFT_BLUE, TFT_BLACK); }
    tft.drawString(sts.text, sts.xPos, sts.yPos, sts.fontNr);
    ++index;
  }
  // tft.drawString("Drehzahl", 10, 208, 4);
  for (const auto &part : asg) {
    tft.setTextColor(part.color);
    tft.drawString(part.tft.text, part.tft.xPos, part.tft.yPos, part.tft.fontNr);
  }
  tft.setTextColor(TFT_ORANGE, TFT_BLACK, true);
 tft.drawString(DZ, 200, 208, 4);
}

// UP Satus Eingänge
void Status() {
  if (digitalRead(asg[AGI::enaIn].pin) == LOW) {   // Status Eingang Enable
    asg[AGI::enaIn].color = TFT_RED;
    asg[AGI::enaIn].tft.text = STATUS_TEXT[STI::aus];
    Steuerung_ein();
  } else {
    asg[AGI::enaIn].color = TFT_GREEN;
    asg[AGI::enaIn].tft.text = STATUS_TEXT[STI::ein];
  }
  if (digitalRead(asg[AGI::esX].pin) == HIGH) {   // Status Eingang Endschalter X-
    asg[AGI::esX].color = TFT_RED;
    asg[AGI::esX].tft.text = STATUS_TEXT[STI::fehler];
    Freifahren(AXIS_TEXT[ATI::esX]);
  } else {
    asg[AGI::esX].color = TFT_GREEN;
    asg[AGI::esX].tft.text = STATUS_TEXT[STI::ok];
  }
  if (digitalRead(asg[AGI::esY].pin) == HIGH) {   // Status Eingang Endschalter Y-
    asg[AGI::esY].color = TFT_RED;
    asg[AGI::esY].tft.text = STATUS_TEXT[STI::fehler];
    Freifahren(AXIS_TEXT[ATI::esY]);
  } else {
    asg[AGI::esY].color = TFT_GREEN;
    asg[AGI::esY].tft.text = STATUS_TEXT[STI::ok];
  }
  if (digitalRead(asg[AGI::esZ].pin) == HIGH) {   // Status Eingang Endschalter Z-
    asg[AGI::esZ].color = TFT_RED;
    asg[AGI::esZ].tft.text = STATUS_TEXT[STI::fehler];
    Freifahren(AXIS_TEXT[ATI::esZ]);
  } else {
    asg[AGI::esZ].color = TFT_GREEN;
    asg[AGI::esZ].tft.text = STATUS_TEXT[STI::ok];
  }
  if (digitalRead(asg[AGI::taster].pin) == HIGH) {   // Status Eingang 3D Taster
    asg[AGI::taster].color = TFT_RED;
    asg[AGI::taster].tft.text = STATUS_TEXT[STI::fehler];
    Freifahren(AXIS_TEXT[ATI::taster]);
  } else {
    asg[AGI::taster].color = TFT_GREEN;
    asg[AGI::taster].tft.text = STATUS_TEXT[STI::ok];
  }
  if (digitalRead(asg[AGI::werkzeug].pin) == LOW) {   // Status Eingang Werkzeuglänge
    asg[AGI::werkzeug].color = TFT_RED;
    asg[AGI::werkzeug].tft.text = STATUS_TEXT[STI::fehler];
    Freifahren(AXIS_TEXT[ATI::werkzeug]);
  } else {
    asg[AGI::werkzeug].color = TFT_GREEN;
    asg[AGI::werkzeug].tft.text = STATUS_TEXT[STI::ok];
  }
  if (digitalRead(asg[AGI::spindel].pin) == HIGH) {   // Status Eingang Spindel
    asg[AGI::spindel].color = TFT_RED;
    asg[AGI::spindel].tft.text = STATUS_TEXT[STI::aus];
  } else {
    asg[AGI::spindel].color = TFT_GREEN;
    asg[AGI::spindel].tft.text = STATUS_TEXT[STI::ein];
  }
}

// Motor an
void Motor() {
  if (digitalRead(asg[AGI::spindel].pin == LOW)) {   // Wenn Spindel_in LOW und Status_Motor 0
    digitalWrite(Spindel_out, LOW);                  // ESC 5V einschalten
  } else {
    digitalWrite(Spindel_out, HIGH);   // ESC 5V ausschalten
    Status_Motor = 0;                  // Status_Motor 0
  }
}

// Reserve
void Reserve() {
  if (digitalRead(Reserve_in) == LOW) {
    digitalWrite(Reserve_Relais_out, LOW);   // Reserve Relais an
    digitalWrite(Reserve_out, LOW);          // Reserve Ausgang LOW
  } else {
    digitalWrite(Reserve_Relais_out, HIGH);   // Reserve Relais aus
    digitalWrite(Reserve_out, HIGH);          // Reserve Ausgang HIGH
  }
}

// Drehzahlreglung
void Umdrehungen() {
  Drehzahl = map(analogRead(Dz_in), 0, 4094, 0, 180);                 // umwandeln 0 - 1V in 0 - 180 Grd.
  DZ = map(analogRead(Dz_in), 0, 4049, 0, 25000);                     // umwandeln 0 - 1V in 0 - 25000 U/min
  neuMillis = millis();                                               // akt. Zeit übernehmen
  if ((neuMillis - altMillis) >= intervall1 && Status_Motor == 0) {   // wenn x ms abgelaugfen
    altMillis = neuMillis;                                            // akt. Zeit in altMillis
    ESC.write(Drehzahl);                                              // Erzeugung PWM
    Status_Motor = 1;
  } else {
    ESC.write(Drehzahl);   // Erzeugung PWM
  }
}

void setup() {

  for (const auto &part : asg) { pinMode(part.pin, INPUT_PULLUP); }
  pinMode(EStop_in, INPUT_PULLUP);          // Pin EStop Eingang Pullup
  pinMode(ESC_out, OUTPUT);                 // PIN ESC_out Ausgang
  pinMode(TFT_BL, OUTPUT);                  // PIN TFT_BL Beleuchtung TFT Display
  pinMode(Spindel_out, OUTPUT);             // PIN Spindel Relais out (Relaisausgang)
  pinMode(Reserve_Relais_out, OUTPUT);      // PIN Reserve Relais out (Relaisausgang)
  pinMode(Reserve_out, OUTPUT);             // PIN Reserve out
  digitalWrite(Spindel_out, HIGH);          // Spindel_out auf High
  digitalWrite(Reserve_Relais_out, HIGH);   // Reserve_Relais auf High
  Serial.begin(9600);

  digitalWrite(TFT_BL, HIGH);
  tft.init();
  tft.setRotation(3);
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_RED, TFT_BLACK);
  tft.setFreeFont(FF24);
  tft.drawString("Systemstart!", 10, 2);
  tft.setFreeFont(FF19);
  tft.setTextColor(TFT_GREEN, TFT_BLACK);
  tft.drawString("Steuerung wird", 15, 60);
  tft.drawString("aktiviert!", 15, 100);
  tft.setTextColor(TFT_ORANGE, TFT_BLACK);
  for (byte i = 0; i <= 4; i++) {
    tft.setTextColor(TFT_ORANGE, TFT_BLACK);
    tft.drawString("Bitte warten!", 60, 160);
    delay(1000);
    tft.fillRect(0, 159, 318, 30, TFT_BLACK);
    delay(500);
  }
  tft.fillScreen(TFT_BLACK);

  ESC.attach(ESC_out, 1000, 2000);   // Pin, minimale, maximale Pulsweite in Mikrosekunden
  // attachInterrupt(digitalPinToInterrupt(asg[AGI::spindel].pin), Motor, CHANGE);   // Motor Interrupt aktiviert
  // attachInterrupt(digitalPinToInterrupt(Reserve_in), Reserve, CHANGE);            // Reserve Interrupt aktiviert
}

// Hauptschleife
void loop() {
  neuMillis = millis();                         // akt. Zeit übernehmen
  if ((neuMillis - altMillis) >= intervall) {   // wenn  x ms abgelaugfen
    altMillis = neuMillis;                      // akt. Zeit in altMillis
    Status();                                   // Fehler und Aktion wird angezeigt
  }
  Anzeige();       // Anzeige wird aktualisiert
  Umdrehungen();   // Aktualisierung Drehzahl
  Motor();         // Motor ein aus schalten
}