#include <TFT_eSPI.h>
#include <TFT_eWidget.h>
#include "Free_Fonts.h"
#include <FS.h>
#include "Adafruit_TinyUSB.h"

#include "Ardicon.h"
#include "Exploricon.h"
#include "Lockicon.h"
#include "Pwdownicon.h"

enum { RID_KEYBOARD = 1 };
uint8_t const desc_hid_report[] = { TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(RID_KEYBOARD)) };
uint8_t keycode[6] = { HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE, HID_KEY_NONE };

Adafruit_USBD_HID usb_hid;
TFT_eSPI tft = TFT_eSPI();

#define CALIBRATION_FILE "/TouchCalData1"
#define REPEAT_CAL true

ButtonWidget btn_1 = ButtonWidget(&tft);
ButtonWidget btn_2 = ButtonWidget(&tft);
ButtonWidget btn_3 = ButtonWidget(&tft);
ButtonWidget btn_4 = ButtonWidget(&tft);
ButtonWidget btn_5 = ButtonWidget(&tft);
ButtonWidget btn_6 = ButtonWidget(&tft);

#define BUTTON_W 128
#define BUTTON_H 128

// Create an array of button instances to use in for() loops
// This is more useful where large numbers of buttons are employed
ButtonWidget *btn[] = { &btn_1, &btn_2, &btn_3, &btn_4, &btn_5, &btn_6 };
;
uint8_t buttonCount = sizeof(btn) / sizeof(btn[0]);

void touch_calibrate();

void btn_1_pressAction(void) {
  if (btn_1.justPressed()) {
    //Serial.println("button 1 just pressed");

    keycode[0] = { HID_KEY_C };
    /*  keycode[0] = HID_KEY_A;
    keycode[1] = HID_KEY_A;
    keycode[2] = HID_KEY_A;
    keycode[3] = HID_KEY_A;
    keycode[4] = HID_KEY_A;
    keycode[5] = HID_KEY_A;*/
    //usb_hid.keyboardReport(RID_KEYBOARD, 1, keycode);
    usb_hid.keyboardReport(RID_KEYBOARD, KEYBOARD_MODIFIER_LEFTCTRL, keycode);
    delay(10);
    usb_hid.keyboardRelease(RID_KEYBOARD);
    btn_1.drawSmoothButton(true);
  }
}

void btn_1_releaseAction(void) {
  static uint32_t waitTime = 1000;
  if (btn_1.justReleased()) {
    //Serial.println("button 1 just released");
    usb_hid.keyboardRelease(RID_KEYBOARD);
    btn_1.drawSmoothButton(false);
    btn_1.setReleaseTime(millis());
    waitTime = 10000;
  }
  /* else { //make it blink
    if (millis() - btn_1.getReleaseTime() >= waitTime) {
      waitTime = 1000;
      btn_1.setReleaseTime(millis());
      btn_1.drawSmoothButton(!btn_1.getState());
    }
  }*/
}

void btn_2_pressAction(void) {
  if (btn_2.justPressed()) {
    keycode[0] = { HID_KEY_V };

    usb_hid.keyboardReport(RID_KEYBOARD, KEYBOARD_MODIFIER_LEFTCTRL, keycode);
    delay(10);
    usb_hid.keyboardRelease(RID_KEYBOARD);
    btn_2.drawSmoothButton(true);
  }
}

void btn_2_releaseAction(void) {
  static uint32_t waitTime = 1000;
  if (btn_2.justReleased()) {
    //Serial.println("button 1 just released");
    usb_hid.keyboardRelease(RID_KEYBOARD);
    btn_2.drawSmoothButton(false);
    btn_2.setReleaseTime(millis());
    waitTime = 10000;
  }
  /*else { //make it blink
    if (millis() - btn_1.getReleaseTime() >= waitTime) {
      waitTime = 1000;
      btn_1.setReleaseTime(millis());
      btn_1.drawSmoothButton(!btn_1.getState());
    }
  }*/
}

void btn_3_pressAction(void) {
  if (btn_3.justPressed()) {
    //Serial.println("button 3 just pressed");
    //btn_3.drawSmoothButton(true);
    //uint8_t keycode[6] = { HID_KEY_GUI_LEFT, HID_KEY_R };
    keycode[0] = HID_KEY_GUI_LEFT;
    keycode[1] = HID_KEY_R;
    keycode[2] = HID_KEY_NONE;
    keycode[3] = HID_KEY_NONE;
    keycode[4] = HID_KEY_NONE;
    keycode[5] = HID_KEY_NONE;
    //usb_hid.keyboardReport(RID_KEYBOARD, 1, keycode);
    usb_hid.keyboardReport(RID_KEYBOARD, 0, keycode);
    delay(10);
    usb_hid.keyboardRelease(RID_KEYBOARD);
    delay(25);

    keycode[0] = HID_KEY_5;
    keycode[1] = HID_KEY_A;
    keycode[2] = HID_KEY_R;
    keycode[3] = HID_KEY_D;
    keycode[4] = HID_KEY_U;
    keycode[5] = HID_KEY_5;
    usb_hid.keyboardReport(RID_KEYBOARD, KEYBOARD_MODIFIER_LEFTSHIFT, keycode);
    delay(10);
    usb_hid.keyboardRelease(RID_KEYBOARD);
    delay(15);

    keycode[0] = HID_KEY_ENTER;
    keycode[1] = HID_KEY_NONE;
    keycode[2] = HID_KEY_NONE;
    keycode[3] = HID_KEY_NONE;
    keycode[4] = HID_KEY_NONE;
    keycode[5] = HID_KEY_NONE;
    //usb_hid.keyboardReport(RID_KEYBOARD, 1, keycode);
    usb_hid.keyboardReport(RID_KEYBOARD, 0, keycode);
    delay(10);
    usb_hid.keyboardRelease(RID_KEYBOARD);
  }
}
void btn_3_releaseAction(void) {
  /* static uint32_t waitTime = 1000;
  if (btn_3.justReleased()) {
    //Serial.println("button 3 just released");
    //btn_3.drawSmoothButton(false);
    btn_3.setReleaseTime(millis());
    waitTime = 10000;
  }
  /*else { //make it blink
    if (millis() - btn_3.getReleaseTime() >= waitTime) {
      waitTime = 1000;
      btn_3.setReleaseTime(millis());
      btn_3.drawSmoothButton(!btn_3.getState());
    }
  }*/
}


void btn_4_pressAction(void) {
  if (btn_4.justPressed()) {
    //Serial.println("button 4 just pressed");
    //btn_4.drawSmoothButton(true);
    //uint8_t keycode[6] = { HID_KEY_GUI_LEFT, HID_KEY_L };
    keycode[0] = HID_KEY_GUI_LEFT;
    keycode[1] = HID_KEY_L;
    keycode[2] = HID_KEY_NONE;
    keycode[3] = HID_KEY_NONE;
    keycode[4] = HID_KEY_NONE;
    keycode[5] = HID_KEY_NONE;
    usb_hid.keyboardReport(RID_KEYBOARD, 0, keycode);
    delay(10);
    usb_hid.keyboardRelease(RID_KEYBOARD);
  }
}
void btn_4_releaseAction(void) {
  /*static uint32_t waitTime = 1000;
  if (btn_4.justReleased()) {
    //Serial.println("button 4 just released");
    //btn_4.drawSmoothButton(false);
    btn_4.setReleaseTime(millis());
    waitTime = 10000;
  }
  /*else { //make it blink
    if (millis() - btn_4.getReleaseTime() >= waitTime) {
      waitTime = 1000;
      btn_4.setReleaseTime(millis());
      btn_4.drawSmoothButton(!btn_4.getState());
    }
  }*/
}

void btn_5_pressAction(void) {
  if (btn_5.justPressed()) {
    //Serial.println("button 5 just pressed");
    //btn_5.drawSmoothButton(true);
    //uint8_t keycode[6] = { HID_KEY_GUI_LEFT, HID_KEY_E };
    keycode[0] = HID_KEY_GUI_LEFT;
    keycode[1] = HID_KEY_E;
    keycode[2] = HID_KEY_NONE;
    keycode[3] = HID_KEY_NONE;
    keycode[4] = HID_KEY_NONE;
    keycode[5] = HID_KEY_NONE;
    usb_hid.keyboardReport(RID_KEYBOARD, 0, keycode);
    delay(10);
    usb_hid.keyboardRelease(RID_KEYBOARD);
  }
}
void btn_5_releaseAction(void) {
  /*static uint32_t waitTime = 1000;
  if (btn_5.justReleased()) {
    //Serial.println("button 5 just released");
    //btn_5.drawSmoothButton(false);
    btn_5.setReleaseTime(millis());
    waitTime = 10000;
  }
  /*else { //make it blink
    if (millis() - btn_5.getReleaseTime() >= waitTime) {
      waitTime = 1000;
      btn_5.setReleaseTime(millis());
      btn_5.drawSmoothButton(!btn_5.getState());
    }
  }*/
}

//shutdown -s -t 3
void btn_6_pressAction(void) {
  if (btn_6.justPressed()) {
    //Serial.println("button 6 just pressed");
    //btn_6.drawSmoothButton(true);
    //uint8_t keycode[6] = { HID_KEY_GUI_LEFT, HID_KEY_R };
    keycode[0] = HID_KEY_GUI_LEFT;
    keycode[1] = HID_KEY_R;
    keycode[2] = HID_KEY_NONE;
    keycode[3] = HID_KEY_NONE;
    keycode[4] = HID_KEY_NONE;
    keycode[5] = HID_KEY_NONE;
    usb_hid.keyboardReport(RID_KEYBOARD, 0, keycode);
    delay(10);
    usb_hid.keyboardRelease(RID_KEYBOARD);
    delay(25);
    keycode[0] = HID_KEY_SPACE;
    keycode[1] = HID_KEY_SPACE;
    keycode[2] = HID_KEY_SPACE;
    keycode[3] = HID_KEY_C;
    keycode[4] = HID_KEY_M;
    keycode[5] = HID_KEY_D;
    usb_hid.keyboardReport(RID_KEYBOARD, 0, keycode);
    delay(10);
    usb_hid.keyboardRelease(RID_KEYBOARD);
    delay(25);
    keycode[0] = HID_KEY_ENTER;
    keycode[1] = HID_KEY_NONE;
    keycode[2] = HID_KEY_NONE;
    keycode[3] = HID_KEY_NONE;
    keycode[4] = HID_KEY_NONE;
    keycode[5] = HID_KEY_NONE;
    usb_hid.keyboardReport(RID_KEYBOARD, 0, keycode);
    delay(10);
    usb_hid.keyboardRelease(RID_KEYBOARD);
    delay(100);
    keycode[0] = HID_KEY_S;
    keycode[1] = HID_KEY_H;
    keycode[2] = HID_KEY_U;
    keycode[3] = HID_KEY_T;
    keycode[4] = HID_KEY_D;
    keycode[5] = HID_KEY_O;
    usb_hid.keyboardReport(RID_KEYBOARD, 0, keycode);
    delay(10);
    usb_hid.keyboardRelease(RID_KEYBOARD);
    delay(25);
    keycode[0] = HID_KEY_W;
    keycode[1] = HID_KEY_N;
    keycode[2] = HID_KEY_SPACE;
    keycode[3] = HID_KEY_KEYPAD_SUBTRACT;
    keycode[4] = HID_KEY_S;
    keycode[5] = HID_KEY_SPACE;
    usb_hid.keyboardReport(RID_KEYBOARD, 0, keycode);
    delay(10);
    usb_hid.keyboardRelease(RID_KEYBOARD);
    delay(25);
    keycode[0] = HID_KEY_KEYPAD_SUBTRACT;
    keycode[1] = HID_KEY_T;
    keycode[2] = HID_KEY_SPACE;
    keycode[3] = HID_KEY_3;
    keycode[4] = HID_KEY_NONE;
    keycode[5] = HID_KEY_NONE;
    usb_hid.keyboardReport(RID_KEYBOARD, 0, keycode);
    delay(10);
    usb_hid.keyboardRelease(RID_KEYBOARD);
    delay(25);
    keycode[0] = HID_KEY_ENTER;
    keycode[1] = HID_KEY_NONE;
    keycode[2] = HID_KEY_NONE;
    keycode[3] = HID_KEY_NONE;
    keycode[4] = HID_KEY_NONE;
    keycode[5] = HID_KEY_NONE;
    usb_hid.keyboardReport(RID_KEYBOARD, 0, keycode);
    delay(10);
    usb_hid.keyboardRelease(RID_KEYBOARD);
  }
}
void btn_6_releaseAction(void) {
  /*static uint32_t waitTime = 1000;
  if (btn_6.justReleased()) {
    //Serial.println("button 6 just released");
    //btn_6.drawSmoothButton(false);
    btn_6.setReleaseTime(millis());
    waitTime = 10000;
  }
  /*else { //make it blink
    if (millis() - btn_6.getReleaseTime() >= waitTime) {
      waitTime = 1000;
      btn_6.setReleaseTime(millis());
      btn_6.drawSmoothButton(!btn_6.getState());
    }
  }*/
}



void initButtons() {
  uint16_t buttonPadding = 20;
  uint16_t startX = buttonPadding;
  uint16_t startY = buttonPadding;
  uint16_t xIncrement = BUTTON_W + buttonPadding;
  uint16_t yIncrement = BUTTON_H + buttonPadding;

  // Row 1
  btn_1.initButtonUL(startX, startY, BUTTON_W, BUTTON_H, TFT_WHITE, TFT_RED, TFT_YELLOW, "COPY", 1);
  btn_1.setPressAction(btn_1_pressAction);
  btn_1.setReleaseAction(btn_1_releaseAction);
  btn_1.drawSmoothButton(false, 3, TFT_BLACK);  // 3 is outline width, TFT_BLACK is the surrounding background colour for anti-aliasing

  btn_2.initButtonUL(startX + xIncrement, startY, BUTTON_W, BUTTON_H, TFT_WHITE, TFT_RED, TFT_YELLOW, "PASTE", 1);
  btn_2.setPressAction(btn_2_pressAction);
  btn_2.setReleaseAction(btn_2_releaseAction);
  btn_2.drawSmoothButton(false, 3, TFT_BLACK);  // 3 is outline width, TFT_BLACK is the surrounding background colour for anti-aliasing

  // Row 2
  btn_3.initButtonUL(startX, startY + yIncrement, BUTTON_W, BUTTON_H, TFT_WHITE, TFT_BLACK, TFT_BLACK, "B-3", 1);
  btn_3.setPressAction(btn_3_pressAction);
  btn_3.setReleaseAction(btn_3_releaseAction);
  btn_3.drawSmoothButton(false, 3, TFT_BLACK);  // 3 is outline width, TFT_BLACK is the surrounding background colour for anti-aliasing
  tft.setSwapBytes(true);
  tft.pushImage(startX, startY + yIncrement, ardicon_w, ardicon_h, ardicon);

  btn_4.initButtonUL(startX + xIncrement, startY + yIncrement, BUTTON_W, BUTTON_H, TFT_WHITE, TFT_RED, TFT_BLACK, "B-4", 1);
  btn_4.setPressAction(btn_4_pressAction);
  btn_4.setReleaseAction(btn_4_releaseAction);
  btn_4.drawSmoothButton(false, 3, TFT_BLACK);  // 3 is outline width, TFT_BLACK is the surrounding background colour for anti-aliasing
  tft.pushImage(startX + xIncrement, startY + yIncrement, lock_w, lock_h, lock);

  // Row 3
  btn_5.initButtonUL(startX, startY + 2 * yIncrement, BUTTON_W, BUTTON_H, TFT_WHITE, TFT_RED, TFT_BLACK, "B-5", 1);
  btn_5.setPressAction(btn_5_pressAction);
  btn_5.setReleaseAction(btn_5_releaseAction);
  btn_5.drawSmoothButton(false, 3, TFT_BLACK);  // 3 is outline width, TFT_BLACK is the surrounding background colour for anti-aliasing
  tft.pushImage(startX, startY + 2 * yIncrement, explorer_w, explorer_h, explorer);

  btn_6.initButtonUL(startX + xIncrement, startY + 2 * yIncrement, BUTTON_W, BUTTON_H, TFT_WHITE, TFT_RED, TFT_BLACK, "B-6", 1);
  btn_6.setPressAction(btn_6_pressAction);
  btn_6.setReleaseAction(btn_6_releaseAction);
  btn_6.drawSmoothButton(false, 3, TFT_BLACK);  // 3 is outline width, TFT_BLACK is the surrounding background colour for anti-aliasing
  tft.pushImage(startX + xIncrement, startY + 2 * yIncrement, pwdownicon_w, pwdownicon_h, pwdownicon);
}


void setup() {
  //Serial.begin(115200);
  usb_hid.setPollInterval(2);
  usb_hid.setReportDescriptor(desc_hid_report, sizeof(desc_hid_report));
  usb_hid.begin();
  while (!USBDevice.mounted()) delay(1);
  do delay(50);
  while (!usb_hid.ready());

  tft.begin();
  tft.invertDisplay(1);
  tft.setRotation(0);
  tft.fillScreen(TFT_BLACK);
  tft.setFreeFont(FF18);

  touch_calibrate();
  tft.fillScreen(TFT_BLACK);
  tft.setFreeFont(FF18);
  initButtons();
}

void loop() {
  static uint32_t scanTime = millis();
  uint16_t t_x = 9999, t_y = 9999;  // To store the touch coordinates

  // Scan keys every 50ms at most
  if (millis() - scanTime >= 50) {
    // Pressed will be set true if there is a valid touch on the screen
    bool pressed = tft.getTouch(&t_x, &t_y);
    scanTime = millis();
    for (uint8_t b = 0; b < buttonCount; b++) {
      if (pressed) {
        if (btn[b]->contains(t_x, t_y)) {
          btn[b]->press(true);
          btn[b]->pressAction();
        }
      } else {
        btn[b]->press(false);
        btn[b]->releaseAction();
      }
    }
  }
}


void touch_calibrate() {
  uint16_t calData[5];
  uint8_t calDataOK = 0;

  // check file system exists
  if (!LittleFS.begin()) {
    // Serial.println("Formating file system");
    LittleFS.format();
    LittleFS.begin();
  }

  // check if calibration file exists and size is correct
  if (LittleFS.exists(CALIBRATION_FILE)) {
    if (REPEAT_CAL) {
      // Delete if we want to re-calibrate
      LittleFS.remove(CALIBRATION_FILE);
    } else {
      File f = LittleFS.open(CALIBRATION_FILE, "r");
      if (f) {
        if (f.readBytes((char *)calData, 14) == 14)
          calDataOK = 1;
        f.close();
      }
    }
  }

  if (calDataOK && !REPEAT_CAL) {
    // calibration data valid
    tft.setTouch(calData);
  } else {
    // data not valid so recalibrate
    tft.fillScreen(TFT_BLACK);
    tft.setCursor(20, 0);
    tft.setTextFont(2);
    tft.setTextSize(1);
    tft.setTextColor(TFT_WHITE, TFT_BLACK);

    tft.println("Touch corners as indicated");

    tft.setTextFont(1);
    tft.println();

    if (REPEAT_CAL) {
      tft.setTextColor(TFT_RED, TFT_BLACK);
      tft.println("Set REPEAT_CAL to false to stop this running again!");
    }

    tft.calibrateTouch(calData, TFT_MAGENTA, TFT_BLACK, 15);

    tft.setTextColor(TFT_GREEN, TFT_BLACK);
    tft.println("Calibration complete!");

    // store data
    File f = LittleFS.open(CALIBRATION_FILE, "w");
    if (f) {
      f.write((const unsigned char *)calData, 14);
      f.close();
    }
  }
}
