#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <Adafruit_FT6206.h>


// WiFi-Konfiguration
String ssid = "Wokwi-GUEST";
String password = "";

// OpenAI API-Konfiguration
const String openai_api_key = "sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
const String openai_url = "https://api.openai.com/v1/chat/completions";

// Hardware-Pins
#define TFT_DC 2
#define TFT_CS 15
#define TFT_MOSI 23
#define TFT_SCLK 18
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
Adafruit_FT6206 ts = Adafruit_FT6206();

// Status in Aktion
int inAction = 0;
// 0 = init state
// 1 = connect wifi
// 2 = reconnect wifi
// 3 = chatGPT (keyboard touch input)
// 4 = chatGPT (serial input)
// 99 = idle mode (facial expressions)

// Eingabeaufforderung
String userPrompt = "";

// Funktion zum Abrufen der Antwort von ChatGPT
String getChatGPTResponse(String prompt) {
  HTTPClient http;
  http.begin(openai_url);

  // Bereite die Daten für die Anfrage vor
  String jsonData = "{\"model\":\"gpt-4o-mini\", \"messages\":[{\"role\":\"developer\", \"content\":\""+ prompt +"\"}], \"temperature\":0.5, \"max_tokens\":1048, \"top_p\":1, \"frequency_penalty\":0, \"presence_penalty\":0}";

  // Header hinzufügen
  http.addHeader("Content-Type", "application/json");
  http.addHeader("Authorization", "Bearer " + openai_api_key);

  // HTTP POST ausführen
  int httpResponseCode = http.POST(jsonData);
  if (httpResponseCode != 200) {
    Serial.print("HTTP Error: ");
    Serial.println(httpResponseCode);
    http.end();
    return "Error connecting to OpenAI!";
  }

  String response = http.getString();
  http.end();

  // Antwort parsen
  DynamicJsonDocument responseDoc(2048);
  DeserializationError error = deserializeJson(responseDoc, response);

  if (error) {
    Serial.print("deserializeJson() failed: ");
    Serial.println(error.c_str());
    return "Error parsing response!";
  }

  // Extrahiere den Inhalt der Antwort des Assistenten
  String chatResponse = responseDoc["choices"][0]["message"]["content"].as<String>();
  return chatResponse;
}

// Funktion zum schrittweisen Anzeigen der Antwort auf dem TFT-Display
void displayChatGPTResponseStepwise(String response) {
  const int maxY = 320;            // Höhe des Displays
  const int textHeight = 16;   // Höhe einer Textzeile (bei Textgröße 2)
  const int maxLines = maxY / textHeight; // Maximale Zeilenanzahl, die auf das Display passt

  // Feste Zeilen für die Überschrift
  int headerLines = 3; // "Prompt:", der Prompt selbst, und "Response:"
  int availableLines = maxLines - headerLines; // Verfügbare Zeilen für den Antworttext

  tft.fillScreen(ILI9341_BLACK);
  tft.setCursor(0, 0);
  tft.setTextSize(2);

  // Überschriften anzeigen
  tft.setTextColor(ILI9341_WHITE);
  tft.println("Prompt:");
  tft.setTextColor(ILI9341_GREEN);
  tft.println(userPrompt);
  tft.setTextColor(ILI9341_WHITE);
  tft.println("\nResponse:");

  // Cursorposition speichern, um Text unterhalb der Überschrift zu beginnen
  int cursorX = 0;
  int cursorY = tft.getCursorY();
  
  tft.setTextColor(ILI9341_CYAN);

  // Zeichen-für-Zeichen-Anzeige
  for (int i = 0; i < response.length(); i++) {
    char currentChar = response[i];
    
    // Zeilenumbruch, wenn der Text das Display horizontal überschreitet
    if (cursorX >= tft.width() - 8 * 1.5) { // 8 Pixel pro Zeichen * Breite der Textgröße 2
      cursorX = 0;
      cursorY += textHeight;
    }

    // Zeilenumbruch, wenn das Display vertikal voll ist
    if (cursorY >= maxY) {
      delay(5000); // 5 Sekunden warten
      tft.fillScreen(ILI9341_BLACK);
      tft.setCursor(0, 0);
      tft.setTextSize(2);

      // Überschriften erneut anzeigen
      tft.setTextColor(ILI9341_WHITE);
      tft.println("Prompt:");
      tft.setTextColor(ILI9341_GREEN);
      tft.println(userPrompt);
      tft.setTextColor(ILI9341_WHITE);
      tft.println("\nResponse:");

      // Cursorposition zurücksetzen
      cursorY = tft.getCursorY();
    }

    // Zeichen zeichnen
    tft.setCursor(cursorX, cursorY);
    tft.setTextColor(ILI9341_CYAN);
    tft.print(currentChar);

    // Nächste Zeichenposition berechnen
    cursorX += 8 * 1.5; // 8 Pixel pro Zeichen * Breite der Textgröße 2

    // Kurze Verzögerung für "Aufbau-Effekt"
    delay(50); // 50ms pro Zeichen
  }
}

void resetMyDisplay(){
  tft.fillScreen(ILI9341_BLACK);
  tft.setTextColor(ILI9341_CYAN);
  tft.setCursor(0, 0);
  //tft.println("listening...");
  inAction = 99;
}

// Funktionen für die verschiedenen Gesichtsausdrücke
void drawEyes(int x, int y, int eyeSize, int eyeYPos, int expressionType) {

  tft.fillScreen(ILI9341_BLACK);

  // Augenposition in der Mitte
  int leftEyeX = x - 50;
  int rightEyeX = x + 50;
  int eyeY = y + eyeYPos;

  // Hintergrundfarbe für Augen entfernen (falls nötig)
  tft.fillRect(leftEyeX - eyeSize, eyeY - eyeSize, 2 * eyeSize + 20, 2 * eyeSize + eyeSize / 2, ILI9341_BLACK);
  tft.fillRect(rightEyeX - eyeSize, eyeY - eyeSize, 2 * eyeSize + 20, 2 * eyeSize + eyeSize / 2, ILI9341_BLACK);

  // Augen zeichnen (wird je nach Ausdruck variieren)
  if (expressionType == 0) { // Normale Augen (runde Form)
    tft.fillCircle(leftEyeX, eyeY, eyeSize, ILI9341_CYAN);
    tft.fillCircle(rightEyeX, eyeY, eyeSize, ILI9341_CYAN);
  } else if (expressionType == 1) { // Augen zusammengekniffen (z.B. Lächeln)
    tft.fillCircle(leftEyeX, eyeY, eyeSize * 1.2, ILI9341_CYAN);
    tft.fillCircle(rightEyeX, eyeY, eyeSize * 1.2, ILI9341_CYAN);
    tft.fillCircle(leftEyeX, eyeY + 40, eyeSize * 1.2, ILI9341_BLACK);
    tft.fillCircle(rightEyeX, eyeY + 40, eyeSize * 1.2, ILI9341_BLACK);
  }  else if (expressionType == 2) { // Normale Augen (runde Form) nach links
    tft.fillCircle(leftEyeX - 20, eyeY, eyeSize * 1.2, ILI9341_CYAN);
    tft.fillCircle(rightEyeX - 20, eyeY + 20, eyeSize, ILI9341_CYAN);
  } else if (expressionType == 3) { // Augen weit aufgerissen (z.B. überrascht)
    tft.fillCircle(leftEyeX, eyeY, eyeSize * 1.8, ILI9341_CYAN);
    tft.fillCircle(rightEyeX, eyeY, eyeSize * 1.8, ILI9341_CYAN);
  } else if (expressionType == 4) { // Traurige Augen (mit Rechtecken)
    tft.fillRoundRect(leftEyeX - eyeSize, eyeY - eyeSize / 2, 2 * eyeSize, eyeSize / 2, 10, ILI9341_CYAN);
    tft.fillRoundRect(rightEyeX - eyeSize, eyeY - eyeSize / 2, 2 * eyeSize, eyeSize / 2, 10, ILI9341_CYAN);
  }  else if (expressionType == 5) { // Normale Augen (runde Form) nach rechts
    tft.fillCircle(leftEyeX + 20, eyeY + 20, eyeSize, ILI9341_CYAN);
    tft.fillCircle(rightEyeX + 20, eyeY, eyeSize * 1.2, ILI9341_CYAN);
  }  else if (expressionType == 6) { // Normale Augen (runde Form) nach unten
    tft.fillCircle(leftEyeX, eyeY - 40, eyeSize, ILI9341_CYAN);
    tft.fillCircle(rightEyeX, eyeY - 40, eyeSize, ILI9341_CYAN);
  }  else if (expressionType == 7) { // Normale Augen (runde Form) nach oben
    tft.fillCircle(leftEyeX, eyeY + 40, eyeSize, ILI9341_CYAN);
    tft.fillCircle(rightEyeX, eyeY + 40, eyeSize, ILI9341_CYAN);
  } else if (expressionType == 8) { // Zwinkernde Augen (Kreis + Rechteck)
    tft.fillCircle(leftEyeX, eyeY, eyeSize, ILI9341_CYAN);  // Kreis für das linke Auge
    tft.fillCircle(leftEyeX, eyeY + 40, eyeSize, ILI9341_BLACK);
    tft.fillRoundRect(rightEyeX - eyeSize, eyeY, 2 * eyeSize, eyeSize / 2, 10, ILI9341_CYAN);
  } else if (expressionType == 9) { // Wütende Augen (mit schmaleren Rechtecken)
    tft.fillTriangle(leftEyeX - eyeSize, eyeY - eyeSize / 3, leftEyeX + eyeSize, eyeY - eyeSize / 3, 
                     leftEyeX - eyeSize, eyeY - eyeSize * 2, ILI9341_RED);
    tft.fillTriangle(rightEyeX - eyeSize, eyeY - eyeSize / 3 , rightEyeX + eyeSize, eyeY - eyeSize / 3, 
                     rightEyeX + eyeSize, eyeY - eyeSize * 2, ILI9341_RED);
  } else if (expressionType == 10) { // Verwirrte Augen (Kreis + Rechteck)
    tft.fillCircle(leftEyeX, eyeY, eyeSize, ILI9341_YELLOW);
    tft.fillCircle(rightEyeX, eyeY, eyeSize * 1.5, ILI9341_YELLOW);
  } else{

  }
}


// Tastatur Layout
int keyOption = 0;
const char* keyboard0[3][10] = {
  {"Q", "W", "E", "R", "T", "Z", "U", "I", "O", "P"},
  {"A", "S", "D", "F", "G", "H", "J", "K", "L", "dl"},
  {"Y", "X", "C", "V", "B", "N", "M", " ", "..","en"}
};
const char* keyboard1[3][10] = {
  {"q", "w", "e", "r", "t", "z", "u", "i", "o", "p"},
  {"a", "s", "d", "f", "g", "h", "j", "k", "l", "dl"},
  {"y", "x", "c", "v", "b", "n", "m", " ", "..", "en"}
};
const char* keyboard2[3][10] = {
  {"1", "2", "3", "4", "5", "6", "7", "8", "9", "0"},
  {"!", "§", "$", "%", "&", "/", "@", "()", "=", "*"},
  {"?", ",", ";", ".", ":", "-", "_", "+", "..","en"}
};

// Tastenkoordinaten
int keyWidth = 24;
int keyHeight = 24;
int startX = 0;
int startY = 248;  // Untere Hälfte des Displays

// Variable für die Eingabe
String userInput = "";

// Funktion zum Zeichnen der Tastatur
void drawKeyboard() {
  for (int row = 0; row < 3; row++) {
    for (int col = 0; col < 10; col++) {
      // Berechne Position der Taste
      int x = startX + col * keyWidth;
      int y = startY + row * keyHeight;

      // Zeichne die Taste
      tft.fillRect(x, y, keyWidth, keyHeight, ILI9341_BLACK);
      tft.drawRect(x, y, keyWidth, keyHeight, ILI9341_GREEN);
      tft.setCursor(x + 7, y + 5);  // Zentrierte Position
      tft.setTextColor(ILI9341_GREEN);
      tft.setTextSize(2);
      if(keyOption == 0){
        tft.print(keyboard0[row][col]);
      } else if (keyOption == 1){
        tft.print(keyboard1[row][col]);
      } else if (keyOption == 2){
        tft.print(keyboard2[row][col]);
      }
    }
  }
  
  if(keyOption == 0){
    keyOption = 1;
  } else if (keyOption == 1){
    keyOption = 2;
  } else if (keyOption == 2){
    keyOption = 0;
  }else{

  }
}


TS_Point rotateTouch(TS_Point point, uint8_t rotation, uint16_t screenWidth, uint16_t screenHeight) {
    TS_Point rotatedPoint;

    switch (rotation) {
        case 0: // Keine Rotation
            rotatedPoint.x = point.x;
            rotatedPoint.y = point.y;
            break;

        case 1: // 90 Grad
            rotatedPoint.x = screenHeight - point.y;
            rotatedPoint.y = point.x;
            break;

        case 2: // 180 Grad
            rotatedPoint.x = screenWidth - point.x;
            rotatedPoint.y = screenHeight - point.y;
            break;

        case 3: // 270 Grad
            rotatedPoint.x = point.y;
            rotatedPoint.y = screenWidth - point.x;
            break;

        default: // Standard: Keine Rotation
            rotatedPoint.x = point.x;
            rotatedPoint.y = point.y;
            break;
    }

    return rotatedPoint;
}

// Funktion zum Überprüfen der Berührung und Aktualisieren der Eingabe
void checkTouch() {
  static unsigned long lastTouchTime = 0; // Zeit der letzten Berührung
  const unsigned long debounceDelay = 200; // Entprell-Zeit in Millisekunden

  if (ts.touched()) {
    unsigned long currentTime = millis(); // Aktuelle Zeit abrufen

    // Entprellung: Ignoriere Berührung, wenn sie zu schnell hintereinander erfolgt
    if (currentTime - lastTouchTime < debounceDelay) {
      return;
    }
    lastTouchTime = currentTime;

    TS_Point p = ts.getPoint();

    // Zurück zur "Startseite" (Display löschen), wenn in den Bereich links oben gedrückt wird
    if (p.x >= 220 && p.y >= 300) {
      resetMyDisplay();
      return; // Verlasse die Funktion, um weitere Aktionen zu verhindern
    }

    // Eingabeaufforderung öffnen
    if (p.x <= 20 && p.y >= 300) {
      if(inAction == 99){
        inAction = 3;
      }
      tft.fillScreen(ILI9341_BLACK);
      drawKeyboard();
      userInput = ""; // Eingabe zurücksetzen
      return; // Verlasse die Funktion, um weitere Aktionen zu verhindern
    }
    
    // Rotation anwenden
    uint8_t rotation = 2; // Beispiel: 180 Grad Rotation
    p = rotateTouch(p, rotation, 240, 320);
    
    // Überprüfen, ob die Berührung innerhalb der Tasten liegt
    for (int row = 0; row < 3; row++) {
      for (int col = 0; col < 10; col++) {
        int x = startX + col * keyWidth;
        int y = startY + row * keyHeight;

        if (p.x >= x && p.x <= x + keyWidth && p.y >= y && p.y <= y + keyHeight) {
          String character = "";
          if(keyOption == 1){
            character = keyboard0[row][col];
          } else if (keyOption == 2) {
            character = keyboard1[row][col];
          } else if (keyOption == 0) {
            character = keyboard2[row][col];
          }
          if (character == "en") {
            // ENTER-Taste gedrückt -----------------------------------------------------------------------------
            tft.fillScreen(ILI9341_BLACK);
            if(inAction == 3){
              userPrompt = userInput;
              userInput = "";
              String response = getChatGPTResponse(userPrompt);
              displayChatGPTResponseStepwise(response);
            } else if (inAction == 2){
              // Index des Slashes finden
              int slashIndex = userInput.indexOf('/');

              if (slashIndex != -1) {  // Überprüfen, ob der Slash existiert
                ssid = userInput.substring(0, slashIndex).c_str();  // Teilstring vor dem Slash (ssid)
                password = userInput.substring(slashIndex + 1).c_str();  // Teilstring nach dem Slash (password)
                connectWifi();
              }else {
                tft.println("wrong ssid/password...");
              }
            }
          } else if (character == "dl") {
            // Rücktaste gedrückt
            if (userInput.length() > 0) {
              userInput.remove(userInput.length() - 1); // Letztes Zeichen löschen
              tft.fillRect(0, 0, 240, 248, ILI9341_BLACK); // Eingabebereich leeren
              tft.setCursor(0, 0);
              tft.setTextColor(ILI9341_GREEN);
              tft.setTextSize(2);
              tft.print(userInput);
            }
          } else if (character == "..") {
            drawKeyboard();
          } else {
            userInput += character; // Füge den Buchstaben hinzu
            // Nur den Eingabebereich aktualisieren
            tft.fillRect(0, 0, 240, 30, ILI9341_BLACK); // Eingabebereich leeren
            tft.setCursor(0, 0);
            tft.setTextColor(ILI9341_GREEN);
            tft.setTextSize(2);
            tft.print(userInput);
          }
          return; // Verlasse die Funktion, um weitere Aktionen zu verhindern
        }
      }
    }
  }
}

int connectWifi(){

  inAction = 1;

  Serial.println("SSID: " + ssid);
  Serial.println("Password: " + password);

  // WiFi verbinden
  WiFi.begin(ssid, password, 6);
  tft.begin();
  tft.setRotation(0);

  tft.setTextColor(ILI9341_CYAN);
  tft.setTextSize(2);
  tft.setCursor(0, 0);
  tft.print("Connecting to WiFi");

  unsigned long startTime = millis(); // Startzeit speichern
  unsigned long timeout = 18000; // 18 Sekunden Timeout

  while (WiFi.status() != WL_CONNECTED) {
    if (millis() - startTime > timeout) {
      // Wenn 10 Sekunden vergangen sind, beenden und Fehler anzeigen
      tft.fillScreen(ILI9341_BLACK); // Bildschirm löschen
      tft.setCursor(0, 0);
      tft.setTextColor(ILI9341_RED); // Fehlerfarbe
      tft.println("WiFi connection failed!");
      Serial.println("WiFi connection failed!");
      WiFi.mode(WIFI_OFF);  // Setzt den Wi-Fi-Modus aus
      delay(1000);  // Warten
      WiFi.mode(WIFI_STA);  // Setze den Wi-Fi-Modus wieder auf Station
      WiFi.disconnect();
      return 0; // Verlasse die Funktion (kein weiteres Setup)
    }

    delay(100);
    tft.print(".");
  }
  
  delay(1000);  // Warten
  // Wenn WiFi verbunden ist
  tft.fillScreen(ILI9341_BLACK);
  tft.setCursor(0, 0);
  tft.setTextColor(ILI9341_GREEN); // Erfolgsfarbe
  tft.println("WiFi Connected!");
  tft.println("IP: " + WiFi.localIP().toString());
  delay(1000);  // Warten
  // Warten auf Benutzereingabe
  resetMyDisplay();
  return 1;
}

void setup() {
  Serial.begin(115200);
  randomSeed(analogRead(0)); // Zufallsquelle initialisieren


  if (!ts.begin(40)) { // Sensitivität kann angepasst werden
    Serial.println("FT6206 Touchscreen konnte nicht initialisiert werden!");
    while (1); // Endlosschleife bei Fehler
  } else {
      Serial.println("FT6206 Touchscreen erfolgreich initialisiert.");
  }

  if(connectWifi() == 1){
    // Wenn WiFi verbunden ist
    tft.fillScreen(ILI9341_BLACK);
    tft.setCursor(0, 0);
    tft.setTextColor(ILI9341_GREEN); // Erfolgsfarbe
    tft.println("WiFi Connected!");
    tft.println("IP: " + WiFi.localIP().toString());
    // Warten auf Benutzereingabe
    resetMyDisplay();
  } else{
    inAction = 2;
    tft.println("enter ssid/password...");
  }

  
}


// Globale Variablen für den Timer
unsigned long lastActionTime = 0;  // Letzte Ausführungszeit des Ausdrucks
const unsigned long interval = 3000;  // Intervall von 3 Sekunden

void loop() {

  checkTouch();  // Überprüfen, ob eine Taste berührt wurde

  // Prüfe auf Eingabe im Seriellen Monitor
  if (Serial.available() > 0) {

    inAction = 4;

    tft.fillScreen(ILI9341_BLACK);
    tft.setCursor(0, 0);
    tft.setTextSize(2);

    // Benutzerprompt lesen
    userPrompt = Serial.readStringUntil('\n');
    userPrompt.trim(); // Leerzeichen und Zeilenumbrüche entfernen

    // Anfrage senden
    String response = getChatGPTResponse(userPrompt);
    displayChatGPTResponseStepwise(response);
  }


  // Zufällige Ausdrücke anzeigen (alle 3 Sekunden)
  if (inAction == 99) {
    unsigned long currentTime = millis();  // Aktuelle Zeit abfragen

    // Prüfe, ob 3 Sekunden vergangen sind
    if (currentTime - lastActionTime >= interval) {
      lastActionTime = currentTime;  // Zeit aktualisieren
      int expressionType = random(0, 8);
      drawEyes(120, 160, 30, 0, expressionType);
    }
  }

}