#include "FS.h"
#include <SD.h>
#include "SPI.h"
#include <TFT_eSPI.h>
#include <Wire.h>      // this is needed for FT6206

// #define TFT_CS 15
// #define TFT_DC 2
// #define TFT_MOSI 23
// #define TFT_SCLK 18

TFT_eSPI tft = TFT_eSPI();  // Invoke library, pins defined in User_Setup.h
// #define ILI9341_RED TFT_RED
// #define ILI9341_YELLOW TFT_YELLOW
// #define ILI9341_GREEN TFT_GREEN
// #define ILI9341_CYAN TFT_CYAN
// #define ILI9341_BLUE TFT_BLUE
// #define ILI9341_MAGENTA TFT_MAGENTA

uint8_t SPI_CS_PIN = 5;

unsigned long t;
unsigned long cs;
unsigned long s;
unsigned long m;
unsigned long h;
unsigned long d;
String msgTime;
short msgPos;

const uint16_t TFT_TEMPAD_DARK = 0x38C0;
const uint16_t TFT_TEMPAD_MEDIUM1 = 0x6121;
const uint16_t TFT_TEMPAD_MEDIUM2 = 0x7920;
const uint16_t TFT_TEMPAD_BRIGHT = 0xFBA0;

void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
    Serial.printf("Listing directory: %s\n", dirname);

    File root = fs.open(dirname);
    if(!root){
        Serial.println("Failed to open directory");
        return;
    }
    if(!root.isDirectory()){
        Serial.println("Not a directory");
        return;
    }

    File file = root.openNextFile();
    while(file){
        if(file.isDirectory()){
            Serial.print("  DIR : ");
            Serial.println(file.name());
            if(levels){
                listDir(fs, file.name(), levels -1);
            }
        } else {
            Serial.print("  FILE: ");
            Serial.print(file.name());
            Serial.print("  SIZE: ");
            Serial.println(file.size());
        }
        file = root.openNextFile();
    }
}

void createDir(fs::FS &fs, const char * path){
    Serial.printf("Creating Dir: %s\n", path);
    if(fs.mkdir(path)){
        Serial.println("Dir created");
    } else {
        Serial.println("mkdir failed");
    }
}

void removeDir(fs::FS &fs, const char * path){
    Serial.printf("Removing Dir: %s\n", path);
    if(fs.rmdir(path)){
        Serial.println("Dir removed");
    } else {
        Serial.println("rmdir failed");
    }
}

void readFile(fs::FS &fs, const char * path){
    Serial.printf("Reading file: %s\n", path);

    File file = fs.open(path);
    if(!file){
        Serial.println("Failed to open file for reading");
        return;
    }

    Serial.print("Read from file: ");
    while(file.available()){
        Serial.write(file.read());
    }
    file.close();
}

void writeFile(fs::FS &fs, const char * path, const char * message){
    Serial.printf("Writing file: %s\n", path);

    File file = fs.open(path, FILE_WRITE);
    if(!file){
        Serial.println("Failed to open file for writing");
        return;
    }
    if(file.print(message)){
        Serial.println("File written");
    } else {
        Serial.println("Write failed");
    }
    file.close();
}

void appendFile(fs::FS &fs, const char * path, const char * message){
    Serial.printf("Appending to file: %s\n", path);

    File file = fs.open(path, FILE_APPEND);
    if(!file){
        Serial.println("Failed to open file for appending");
        return;
    }
    if(file.print(message)){
        Serial.println("Message appended");
    } else {
        Serial.println("Append failed");
    }
    file.close();
}

void renameFile(fs::FS &fs, const char * path1, const char * path2){
    Serial.printf("Renaming file %s to %s\n", path1, path2);
    if (fs.rename(path1, path2)) {
        Serial.println("File renamed");
    } else {
        Serial.println("Rename failed");
    }
}

void deleteFile(fs::FS &fs, const char * path){
    Serial.printf("Deleting file: %s\n", path);
    if(fs.remove(path)){
        Serial.println("File deleted");
    } else {
        Serial.println("Delete failed");
    }
}

void testFileIO(fs::FS &fs, const char * path){
    File file = fs.open(path);
    static uint8_t buf[512];
    size_t len = 0;
    uint32_t start = millis();
    uint32_t end = start;
    if(file){
        len = file.size();
        size_t flen = len;
        start = millis();
        while(len){
            size_t toRead = len;
            if(toRead > 512){
                toRead = 512;
            }
            file.read(buf, toRead);
            len -= toRead;
        }
        end = millis() - start;
        Serial.printf("%u bytes read for %u ms\n", flen, end);
        file.close();
    } else {
        Serial.println("Failed to open file for reading");
    }


    file = fs.open(path, FILE_WRITE);
    if(!file){
        Serial.println("Failed to open file for writing");
        return;
    }

    size_t i;
    start = millis();
    for(i=0; i<2048; i++){
        file.write(buf, 512);
    }
    end = millis() - start;
    Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end);
    file.close();
}

void printDeviceInfo(){
  Serial.println("*** Device info ***");

  esp_chip_info_t chip_info;
  esp_chip_info(&chip_info);
  const char* ModelStrings[] PROGMEM = {"UNCNOWN", "ESP32"};
  Serial.printf("Model: %s\r\n", ModelStrings[chip_info.model]);
  Serial.printf("Chip model: %s\r\n", ESP.getChipModel());
  Serial.printf("Chip revision: %d\r\n", ESP.getChipRevision());
  Serial.printf("Revision: %d\r\n", chip_info.revision);
  Serial.println("PSRAM size: " + String(ESP.getPsramSize()));
  Serial.println("PSRAM free size: " + String(ESP.getFreePsram()));
  Serial.println("Features:");
  if (chip_info.features & CHIP_FEATURE_EMB_FLASH) Serial.println("  Embedded Flash");
  if (chip_info.features & CHIP_FEATURE_WIFI_BGN ) Serial.println("  Wifi-BGN"      );
  if (chip_info.features & CHIP_FEATURE_BLE      ) Serial.println("  BLE"           );
  if (chip_info.features & CHIP_FEATURE_BT       ) Serial.println("  Bluetooth"     );
  Serial.printf("Cores: %d\r\n", chip_info.cores);
  Serial.printf("CPU freq: %d MGz\r\n", ESP.getCpuFreqMHz());
  Serial.printf("Flash chip size: %d\r\n", ESP.getFlashChipSize());
  Serial.printf("Flash speed: %d Gz\r\n", ESP.getFlashChipSpeed());
  Serial.printf("Flash memmory: %d MB %s flash\n", spi_flash_get_chip_size()/(1024*1024),
    (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embeded" : "external");
  Serial.printf("Data image size: %d\r\n", ESP.getSketchSize());
  
  Serial.println("*** End of device info ***");

}

void testSDCard(){
  /*
   * Connect the SD card to the following pins:
   *
   * SD Card | ESP32
   *    D2       -
   *    D3       SS
   *    CMD      MOSI
   *    VSS      GND
   *    VDD      3.3V
   *    CLK      SCK
   *    VSS      GND
   *    D0       MISO
   *    D1       -
   */
  if(!SD.begin(SPI_CS_PIN,SPI,12000000,"/sd",5,false)){
    Serial.println("Card Mount Failed");
    tft.drawString("SD card Mount Failed", 0, 16, 2);
    return;
  }
  uint8_t cardType = SD.cardType();
  if(cardType == CARD_NONE){
      Serial.println("No SD card attached");
      return;
  }
  String SDCardType;
  Serial.print("SD Card Type: ");
  if(cardType == CARD_MMC){
      SDCardType = "MMC";
  } else if(cardType == CARD_SD){
      SDCardType = "SDSC";
  } else if(cardType == CARD_SDHC){
      SDCardType = "SDHC";
  } else {
      SDCardType = "UNKNOWN";
  }
  Serial.println(SDCardType);
  tft.drawString("SD card type is " + SDCardType, 0, 16, 2);
  unsigned long cardSize = SD.cardSize() / (1024 * 1024);
  String strCardSize = String(cardSize);
  Serial.printf("SD Card Size: %lluMB\n", cardSize);
  tft.drawString("SD Card Size: " + strCardSize + " MB", 0, 16, 2);
  tft.drawString("Proceed file operations...", 0, 32, 2);
  listDir(SD, "/", 0);
  createDir(SD, "/mydir");
  listDir(SD, "/", 0);
  removeDir(SD, "/mydir");
  listDir(SD, "/", 2);
  writeFile(SD, "/hello.txt", "Hello ");
  appendFile(SD, "/hello.txt", "World!\n");
  readFile(SD, "/hello.txt");
  deleteFile(SD, "/foo.txt");
  renameFile(SD, "/hello.txt", "/foo.txt");
  readFile(SD, "/foo.txt");
  testFileIO(SD, "/test.txt");
  Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
  Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
}

void initDisplay(){
  tft.init();
  tft.setRotation(3);
  tft.fillScreen(TFT_TEMPAD_DARK);
  tft.setTextColor(TFT_TEMPAD_BRIGHT, TFT_TEMPAD_DARK);
  tft.setTextSize(2);
  //tft.setFreeFont(&FreeMono9pt7b); 
  tft.setFreeFont(&FreeSans12pt7b);
  tft.drawString("DISK OPERATIONS...", 0, 0, 2);

    tft.drawString("Init SD card...", 0, 16, 2);
    Serial.println("SC pin = " + String(SPI_CS_PIN));

  tft.fillScreen(TFT_TEMPAD_DARK);
  // unsigned long SDUsed = SD.usedBytes()/ (1024 * 1024);
  // unsigned long SDTotal = SD.totalBytes()/ (1024 * 1024);
  // String SDCardUsed = String(SDUsed) + "/" + String(SDTotal) + " MB";
  tft.drawString("DEVICE OK", 0, 0, 2);
  //tft.drawString("Disk " + SDCardUsed, 0, 16, 2);
  tft.drawString("Waiting input...", 0, 32, 2);

}

void tftDrawUptime(){
  t = millis();
  cs = t / 10;
  s = cs / 100;
  cs = cs % 100;
  m = s / 60;
  s = s % 60;
  h = m / 60;
  m = m % 60;
  d = h / 24;
  h = h % 24;
  msgTime = String(cs);
  if (cs < 10) { msgTime = '0' + msgTime; };
  msgTime = String(s) + '.' + msgTime;
  if (s < 10) { msgTime = '0' + msgTime; };
  msgTime = String(m) + ':' + msgTime;
  if (m < 10) { msgTime = '0' + msgTime; };
  msgTime = String(h) + ':' + msgTime;
  if (h < 10) { msgTime = '0' + msgTime; };
  if (d > 0) { msgTime = String(d) + "d " + msgTime; };
  msgTime = "UPTIME " + msgTime;
  msgPos = 160 - (8*msgTime.length());
  tft.drawString(msgTime, msgPos, 64, 2);

}

void operateSerial(){
  if (Serial.available()) {
    String teststr = Serial.readString();  //read until timeout
    teststr.trim();                        // remove any \r \n whitespace at the end of the String
    if (teststr != "") {
      Serial.println("Data: " + teststr);
      //tft.fillScreen(TFT_BLACK);
      String voidString = "                             ";
      tft.drawString(teststr + voidString, 0, 0, 2);
    };
  };

}

void drawPalette(){
  const uint16_t palette[24] = { 
    TFT_BLACK, 
    TFT_NAVY, 
    TFT_DARKGREEN, 
    TFT_DARKCYAN, 
    TFT_MAROON, 
    TFT_PURPLE, 
    TFT_OLIVE, 
    TFT_LIGHTGREY, 
    TFT_DARKGREY, 
    TFT_BLUE, 
    TFT_GREEN, 
    TFT_CYAN, 
    TFT_RED, 
    TFT_MAGENTA, 
    TFT_YELLOW, 
    TFT_WHITE, 
    TFT_ORANGE, 
    TFT_GREENYELLOW, 
    TFT_PINK, 
    TFT_BROWN, 
    TFT_GOLD, 
    TFT_SILVER, 
    TFT_SKYBLUE, 
    TFT_VIOLET
  };
  int x, y;
  for(int i=0; i<24; i++){
    x=300;
    y=i*10;
    tft.fillRect( x, y, 20, 10, palette[i]);
  };
}

void setup(){
  Serial.begin(115200);
  Serial.println("*** START ***");
  printDeviceInfo();
  Serial.println("*** Devices initialization and testing ***");
  initDisplay();

  //калибровка тачскрина
  uint16_t calibrateData[5] {152, 3484, 263, 3590,7};//эти цифры получены из процедуры tft.calibrateTouch
  // tft.calibrateTouch(calibrateData, TFT_MAGENTA, TFT_BLACK, 15);
  // Serial.println("calibrateData:");
  // for(int i=0; i<5; i++){
  //   Serial.println(String(calibrateData[i]));
  // }
  // Serial.println("");
  //tft.setTouch(calibrateData);

  //drawPalette();

  Serial.println("*** BEGIN LOOP ***");
  Serial.println("Enter data:");
}


void loop(){
  tftDrawUptime();
  operateSerial();

  //тачскрин
  uint16_t t_x = 0, t_y = 0; // To store the touch coordinates
  bool pressed = false;// = tft.getTouch(&t_x, &t_y);
  if (pressed){
    Serial.println("Touch: X=" + String(t_x) + " ; Y=" + String(t_y));
    //TFT_GREEN
    //drawPixel(int32_t x, int32_t y, uint32_t color, uint8_t alpha, uint32_t bg_color = 0x00FFFFFF);
    //tft.drawPixel(t_x, 240 - t_y, TFT_GREEN, 255);
    //tft.drawPixel(t_x, t_y, TFT_TEMPAD_MEDIUM2, 255);
    tft.fillCircle(t_x, t_y, 4, TFT_TEMPAD_MEDIUM2);
  };

  delay(10);
}