// Font file is stored on SD card
#include <SD.h>
#include <HardwareSerial.h>

// Graphics and font library
#include <TFT_eSPI.h>
#include <SPI.h>
  
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

#include <esp_sleep.h>
#include <JPEGDecoder.h>

#define SD_CS 22 // Set GPIO 5 as chip select for SD card
#define LED_PIN 14 // Set GPIO 14 as LED output
#define HUMIDIFIER_PIN 13 // Set GPIO 13 as humidifier output
// TX (发送)：GPIO16（UART2_TXD）
// RX (接收)：GPIO17（UART2_RXD）

#define MAX_IMAGE_WIDTH 240
int16_t xpos = 0;
int16_t ypos = 0;

const char *ssid = "Xiaomi_3A40";
const char *password = "112233ww";

const char *jsonUrl = "http://monjack.cn/weatherInfo.php";
const char *postUrl = "http://192.168.131.51/postLocalData.php";
const char *postStatusUrl = "http://192.168.131.51/postStatus.php";

int dataIndex = 0;
char receivedData0[100];   // 用于存储接收到的数据
HardwareSerial serial2(2); // UART1 实例化

float local_humidity = 0.0;
float temp_local_humidity = 0.0;
float local_temperature = 0.0;
float temp_local_temperature = 0.0;

u8_t standby = 0;
u8_t reboot = 0;
u8_t humidifier = 1;
u8_t screen = 1;

bool boot = true;

String weather = "晴";
String temp_weather = "";
String temperature = "25";
String temp_temperature = "";
String humidity = "60";
String temp_humidity = "";
String wind_direction = "东北";
String temp_wind_direction = "";

String comfort_clothing = "穿短裙、短裤、短袖、短外套";

SemaphoreHandle_t xMutex;

TFT_eSPI tft = TFT_eSPI(); // Invoke library

void showTime(uint32_t msTime)
{
  // tft.setCursor(0, 0);
  // tft.setTextFont(1);
  // tft.setTextSize(2);
  // tft.setTextColor(TFT_WHITE, TFT_BLACK);
  // tft.print(F(" JPEG drawn in "));
  // tft.print(msTime);
  // tft.println(F(" ms "));
  Serial.print(F(" JPEG drawn in "));
  Serial.print(msTime);
  Serial.println(F(" ms "));
}

// ####################################################################################################
//  Draw a JPEG on the TFT, images will be cropped on the right/bottom sides if they do not fit
// ####################################################################################################
//  This function assumes xpos,ypos is a valid screen coordinate. For convenience images that do not
//  fit totally on the screen are cropped to the nearest MCU size and may leave right/bottom borders.
void jpegRender(int xpos, int ypos)
{

  // jpegInfo(); // Print information from the JPEG file (could comment this line out)

  uint16_t *pImg;
  uint16_t mcu_w = JpegDec.MCUWidth;
  uint16_t mcu_h = JpegDec.MCUHeight;
  uint32_t max_x = JpegDec.width;
  uint32_t max_y = JpegDec.height;

  bool swapBytes = tft.getSwapBytes();
  tft.setSwapBytes(true);

  // Jpeg images are draw as a set of image block (tiles) called Minimum Coding Units (MCUs)
  // Typically these MCUs are 16x16 pixel blocks
  // Determine the width and height of the right and bottom edge image blocks
  uint32_t min_w = jpg_min(mcu_w, max_x % mcu_w);
  uint32_t min_h = jpg_min(mcu_h, max_y % mcu_h);

  // save the current image block size
  uint32_t win_w = mcu_w;
  uint32_t win_h = mcu_h;

  // record the current time so we can measure how long it takes to draw an image
  uint32_t drawTime = millis();

  // save the coordinate of the right and bottom edges to assist image cropping
  // to the screen size
  max_x += xpos;
  max_y += ypos;

  // Fetch data from the file, decode and display
  while (JpegDec.read())
  {                        // While there is more data in the file
    pImg = JpegDec.pImage; // Decode a MCU (Minimum Coding Unit, typically a 8x8 or 16x16 pixel block)

    // Calculate coordinates of top left corner of current MCU
    int mcu_x = JpegDec.MCUx * mcu_w + xpos;
    int mcu_y = JpegDec.MCUy * mcu_h + ypos;

    // check if the image block size needs to be changed for the right edge
    if (mcu_x + mcu_w <= max_x)
      win_w = mcu_w;
    else
      win_w = min_w;

    // check if the image block size needs to be changed for the bottom edge
    if (mcu_y + mcu_h <= max_y)
      win_h = mcu_h;
    else
      win_h = min_h;

    // copy pixels into a contiguous block
    if (win_w != mcu_w)
    {
      uint16_t *cImg;
      int p = 0;
      cImg = pImg + win_w;
      for (int h = 1; h < win_h; h++)
      {
        p += mcu_w;
        for (int w = 0; w < win_w; w++)
        {
          *cImg = *(pImg + w + p);
          cImg++;
        }
      }
    }

    // calculate how many pixels must be drawn
    uint32_t mcu_pixels = win_w * win_h;

    // draw image MCU block only if it will fit on the screen
    if ((mcu_x + win_w) <= tft.width() && (mcu_y + win_h) <= tft.height())
      tft.pushImage(mcu_x, mcu_y, win_w, win_h, pImg);
    else if ((mcu_y + win_h) >= tft.height())
      JpegDec.abort(); // Image has run off bottom of screen so abort decoding
  }

  tft.setSwapBytes(swapBytes);

  showTime(millis() - drawTime); // These lines are for sketch testing only
}

// ####################################################################################################
//  Print image information to the serial port (optional)
// ####################################################################################################
//  JpegDec.decodeFile(...) or JpegDec.decodeArray(...) must be called before this info is available!
void jpegInfo()
{

  // Print information extracted from the JPEG file
  Serial.println("JPEG image info");
  Serial.println("===============");
  Serial.print("Width      :");
  Serial.println(JpegDec.width);
  Serial.print("Height     :");
  Serial.println(JpegDec.height);
  Serial.print("Components :");
  Serial.println(JpegDec.comps);
  Serial.print("MCU / row  :");
  Serial.println(JpegDec.MCUSPerRow);
  Serial.print("MCU / col  :");
  Serial.println(JpegDec.MCUSPerCol);
  Serial.print("Scan type  :");
  Serial.println(JpegDec.scanType);
  Serial.print("MCU width  :");
  Serial.println(JpegDec.MCUWidth);
  Serial.print("MCU height :");
  Serial.println(JpegDec.MCUHeight);
  Serial.println("===============");
  Serial.println("");
}

// ####################################################################################################
//  Show the execution time (optional)
// ####################################################################################################
//  WARNING: for UNO/AVR legacy reasons printing text to the screen with the Mega might not work for
//  sketch sizes greater than ~70KBytes because 16-bit address pointers are used in some libraries.

// The Due will work fine with the HX8357_Due library.
void listDir(fs::FS &fs, const char *dirname, uint8_t levels);
void serialEvent();
void ParseData(char *data);
void displayTask(void *parameter);
void fetchDataTask(void *parameters);
void parseJson(String jsonData);
void getWeatherInfo();
void postData();
void postStatus();
void parseJson2(String jsonData);
void checkStatusTask(void *parameters);
void drawSdJpeg(const char *filename, int xpos, int ypos); 

// -------------------------------------------------------------------------
// Setup
// -------------------------------------------------------------------------
void setup(void)
{
  Serial.begin(115200); // Used for messages
  serial2.begin(115200); // Used for messages
  Serial.println("UART2 initialized");

  // Connect to WiFi
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED)
  {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  Serial.println("Connected to WiFi");

  // 设置SPI频率为40MHz
  SPISettings spiSettings(40000000, MSBFIRST, SPI_MODE0);

  // Initialise the SD library before the TFT so the chip select is defined
  if (!SD.begin(SD_CS))
  {
    Serial.println("Card Mount Failed");
    return;
  }
  // Initialise the TFT after the SD card!
  tft.init();
  tft.setRotation(3);
  tft.fillScreen(TFT_BLACK);
  listDir(SD, "/", 0);
  pinMode(LED_PIN, OUTPUT); // Set LED pin as output
  pinMode(HUMIDIFIER_PIN, OUTPUT); // Set humidifier pin as output
  digitalWrite(LED_PIN, HIGH); // Turn on LED
  digitalWrite(HUMIDIFIER_PIN, HIGH); // Turn on humidifier
  Serial.println("SD and TFT initialisation done.");

  Serial.println("👌👌👌👌");
  postStatus();

  int x = 0;
  int y = 0;

  drawSdJpeg("/Photos/background.jpg", x, y); // This draws a jpeg pulled off the SD Card


  // 配置 GPIO 触发唤醒
  esp_sleep_enable_ext0_wakeup(GPIO_NUM_35, 0); // GPIO_NUM_35 为低电平唤醒

  // 创建互斥锁
  xMutex = xSemaphoreCreateMutex();

  // 创建任务
   xTaskCreate(displayTask, "Display Task", 4096, NULL, 1, NULL);
   xTaskCreate(fetchDataTask, "Fetch Data Task", 4096, NULL, 2, NULL);
   xTaskCreate(checkStatusTask, "Check Status Task", 4096, NULL, 3, NULL);
}

// -------------------------------------------------------------------------
// Main loop
// -------------------------------------------------------------------------
void loop()
{
  
}

void fetchDataTask(void *parameters){
  while (true)
  {
    getWeatherInfo();

    postData();

    vTaskDelay(500 / portTICK_PERIOD_MS); // 每0.5秒获取一次JSON数据
  }
}

void getWeatherInfo(){
  if (WiFi.status() == WL_CONNECTED)
  {
    HTTPClient http;

    // 配置HTTP请求
    http.begin(jsonUrl);

    // 发送HTTP GET请求
    int httpCode = http.GET();

    if (httpCode > 0)
    {
      // 如果请求成功
      if (httpCode == HTTP_CODE_OK)
      {
        String payload = http.getString(); // 获取响应字符串
        Serial.println("Received JSON data:");
        Serial.println(payload);

        // 解析JSON数据
        parseJson(payload);
      }
    }
    else
    {
      // 如果请求失败
      Serial.print("Error on sending GET: ");
      Serial.println(http.errorToString(httpCode).c_str());
    }

    http.end(); // 结束HTTP请求
  }
  else
  {
    Serial.println("Error in WiFi connection");
  }
}

void postStatus()
{
  if (WiFi.status() == WL_CONNECTED)
  {
    HTTPClient http;
      // 配置HTTP请求
      http.begin(postStatusUrl);
      // 设置HTTP头部
      http.addHeader("Content-Type", "application/json");
      StaticJsonDocument<100> doc;
      doc["standby"] = standby;
      doc["reboot"] = reboot;
      doc["humidifier"] = humidifier;
      doc["screen"] = screen;

      // 序列化JSON数据
      String jsonPayload;
      serializeJson(doc, jsonPayload);
      Serial.print("JSON Payload: ");
      Serial.println(jsonPayload);

      // 发送HTTP POST请求
      int httpCode = http.POST(jsonPayload);
      if (httpCode > 0)
      {
        // 如果请求成功
        if (httpCode == HTTP_CODE_OK)
        {
          String payload = http.getString(); // 获取响应字符串
          Serial.println("poststatus状态信息：");
          Serial.println(payload);
          // 解析JSON数据
          parseJson2(payload);
        }
        else
        {
          Serial.print("Unexpected HTTP code: ");
          Serial.println(httpCode);
        }
      }
      else
      {
        // 如果请求失败
        Serial.print("Error on sending POST: ");
        Serial.println(http.errorToString(httpCode).c_str());
      }

      http.end(); // 结束HTTP请求
  }
  else
  {
    Serial.println("WiFi is not connected.");
  }
}

void postData()
{
  if (WiFi.status() == WL_CONNECTED)
  {
    HTTPClient http;
    // 配置HTTP请求
    http.begin(postUrl);

    // 设置HTTP头部
    http.addHeader("Content-Type", "application/json");

    StaticJsonDocument<200> doc;
    doc["Humidity"] = local_humidity;
    doc["Temperature"] = local_temperature;

    // 序列化JSON数据
    String jsonPayload;
    serializeJson(doc, jsonPayload);

    Serial.print("JSON Payload: ");
    Serial.println(jsonPayload);

    // 发送HTTP POST请求
    int httpCode = http.POST(jsonPayload);
    if (httpCode > 0)
    {
      // 如果请求成功
      if (httpCode == HTTP_CODE_OK)
      {
        String payload = http.getString(); // 获取响应字符串
        Serial.println("状态信息：");
        Serial.println(payload);
        // 解析JSON数据
        parseJson2(payload);

      }
    }
    else
    {
      // 如果请求失败
      Serial.print("Error on sending POST: ");
      Serial.println(http.errorToString(httpCode).c_str());
    }

    http.end(); // 结束HTTP请求
  }
  else
  {
    Serial.println("Error in WiFi connection");
  }
}

//解析天气情况
void parseJson(String jsonData){
  // 定义JSON文档的大小，根据实际情况调整
  const size_t capacity = JSON_OBJECT_SIZE(2) + 300;
  StaticJsonDocument<capacity> doc;

  // 解析JSON数据
  DeserializationError error = deserializeJson(doc, jsonData);
  if (error)
  {
    Serial.print("deserializeJson() failed: ");
    Serial.println(error.c_str());
    return;
  }

  // 获取JSON中的值
  String newHumidity = doc["lives"][0]["humidity"] | "0.0";
  String newTemperature = doc["lives"][0]["temperature"] | "0.0";
  String newWeather = doc["lives"][0]["weather"] | "晴";
  String newWindDirection = doc["lives"][0]["winddirection"] | "东北";
  if(newTemperature.toInt()<4){
    comfort_clothing = "棉衣或羽绒服";
  }else if(newTemperature.toInt()<10){
    comfort_clothing = "厚外套";
  }else if(newTemperature.toInt()<20){
    comfort_clothing = "薄外套、长袖";
  }else{
    comfort_clothing = "短外套、短裤、短裙";
  }

  Serial.print("newHumidity: ");
  Serial.println(newHumidity);
  Serial.print("newTemperature: ");
  Serial.println(newTemperature);
  Serial.print("Weather: ");
  Serial.println(newWeather);
  Serial.print("WindDirection: ");
  Serial.println(newWindDirection);
  // 获取互斥锁
  if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE)
  {
    humidity = newHumidity;
    temperature = newTemperature;
    weather = newWeather;
    wind_direction = newWindDirection;
    // 释放互斥锁
    xSemaphoreGive(xMutex);
  }
}

//解析状态信息
void parseJson2(String jsonData){
  // 定义JSON文档的大小，根据实际情况调整
  const size_t capacity = JSON_OBJECT_SIZE(2) + 300;
  StaticJsonDocument<capacity> doc;

  // 解析JSON数据
  DeserializationError error = deserializeJson(doc, jsonData);
  
  if (error)
  {
    Serial.print("deserializeJson() failed: ");
    Serial.println(error.c_str());
    return;
  }

  // 获取JSON中的值
  u8_t newStandby = doc["standby"].as<u8_t>();
  u8_t newReboot = doc["reboot"].as<u8_t>();
  u8_t newHumidifier = doc["humidifier"].as<u8_t>();
  u8_t newScreen = doc["screen"].as<u8_t>();

  Serial.print("😊😊screen::😊😊");
  Serial.println(newScreen);
  Serial.print("😊😊standby::😊😊");
  Serial.println(newStandby);
  Serial.print("😊😊reboot::😊😊");
  Serial.println(newReboot);
  Serial.print("😊😊humidifier::😊😊");
  Serial.println(newHumidifier);

  // 获取互斥锁
  if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE)
  {
    standby = newStandby;
    reboot = newReboot;
    humidifier = newHumidifier;
    screen = newScreen;
    // 释放互斥锁
    xSemaphoreGive(xMutex);
  }

} 

void displayTask(void *parameter)
{
  while (true)
  {
      // Check for incoming serial data
      serialEvent();

      // Name of font file (library adds leading / and .vlw)
      String fileName = "MiSan20Chinese";

      if(boot){
        tft.loadFont(fileName, SD);
        tft.setTextColor(0x5ACB, TFT_WHITE, true);
        tft.setCursor(30, 135);
        tft.print("病房空气指标");
        boot = false;
        tft.loadFont("BigFonts45", SD);
        tft.setCursor(50, 160);
        tft.print("良好");
      }
      tft.loadFont(fileName, SD); // Use font stored on SD
      if (temp_local_temperature != local_temperature)
      {
        tft.loadFont("MiddleNumFonts25", SD);
        tft.setTextColor(0x5ACB, TFT_WHITE, true);
        tft.fillRect(220, 195, 70, 20, TFT_WHITE); // Clear screen
        tft.setCursor(220, 195);
        tft.print(local_temperature);
        tft.println("℃");
        temp_local_temperature = local_temperature;
      }
      if (temp_local_humidity != local_humidity)
      {
        tft.loadFont("MiddleNumFonts25",SD);
        tft.setTextColor(0x5ACB, TFT_WHITE, true);
        tft.fillRect(220, 140, 70, 20, TFT_WHITE); // Clear screen
        tft.setCursor(220, 140);
        tft.print(local_humidity);
        tft.println("%");

        temp_local_humidity = local_humidity;
      }
      tft.setTextColor(TFT_WHITE, TFT_BLACK);
      if(temp_weather != weather)
      {
        tft.loadFont("BigFonts45", SD);
        tft.setTextColor(TFT_WHITE, 0x5873FF, true);
        drawSdJpeg("/Photos/cloudy.jpg",23,20);
        tft.setCursor(112, 20);
        tft.print(weather);
        temp_weather = weather;
      }
      if(temp_temperature != temperature){
        tft.loadFont(fileName, SD);
        tft.setTextColor(TFT_WHITE, 0x5873FF,true);
        tft.setCursor(240, 20);
        tft.print(temperature);
        tft.print("℃");
        temp_temperature = temperature;
      }
      if(temp_humidity != humidity){
        tft.setTextColor(TFT_WHITE, 0x5873FF,true);
        tft.setCursor(240, 60);
        tft.print(humidity);
        tft.print("%");
        temp_humidity = humidity;
      }
      // if(temp_wind_direction != wind_direction){
      //   tft.fillRect(80,130,60,20,TFT_BLACK);
      //   tft.print("     ");
      //   tft.print(wind_direction);
      //   tft.print("风");
      //   temp_wind_direction = wind_direction;
      // }
    
    vTaskDelay(1000 / portTICK_PERIOD_MS); // 每1秒刷新一次屏幕
  }
} 

void checkStatusTask(void *parameters){
  while (true)
  {
    if(screen == 1){
      digitalWrite(LED_PIN, HIGH); // Turn on LED
    }else{
      digitalWrite(LED_PIN, LOW); // Turn off LED
    }
    if(reboot == 1){
      if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE){
        reboot = 0;
        xSemaphoreGive(xMutex);
      }
      postStatus();
      delay(1000);
      ESP.restart();
    }
    if(standby == 1){
      Serial.println("进入待机模式");
      delay(1000);
      esp_light_sleep_start(); // 进入低功耗模式
    }
    if(humidifier == 1){
      digitalWrite(HUMIDIFIER_PIN, HIGH); // Turn on humidifier
    }else{
      digitalWrite(HUMIDIFIER_PIN, LOW); // Turn off humidifier
    }
    vTaskDelay(500 / portTICK_PERIOD_MS); // 每0.秒检查一次数据
  }
}

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 serialEvent()
{
  // 检查是否有数据传入
  while (serial2.available())
  {
    // 从串口读取一个字符
    char incomingByte = serial2.read();

    // 如果接收到换行符或最大长度限制，则显示数据
    if (incomingByte == '\n' || dataIndex >= 100)
    {
      receivedData0[dataIndex] = '\0'; // 结束字符串
      ParseData(receivedData0);        // 显示数据
      // Serial.println(receivedData0);
      dataIndex = 0; // 重置接收指针
    }
    else
    {
      receivedData0[dataIndex++] = incomingByte; // 将字符存入缓冲区
    }
  }
}

void ParseData(char *data) // 解析数据
{
  char *token = strtok(data, ":");
  while (token != NULL)
  {
    char *value = strtok(NULL, "C\r\n");
    if (value != NULL)
    {
      if (strcmp(token, "Humidity") == 0)
      {
        local_humidity = atof(value);
        Serial.print("Humidity: ");
        Serial.println(local_humidity);
      }
      else if (strcmp(token, "Temperature") == 0)
      {
        local_temperature = atof(value);
        Serial.print("Temperature: ");
        Serial.println(local_temperature);
      }
    }
    token = strtok(NULL, ":");
  }
}

void drawSdJpeg(const char *filename, int xpos, int ypos)
{

  // Open the named file (the Jpeg decoder library will close it)
  File jpegFile = SD.open(filename, FILE_READ); // or, file handle reference for SD library

  if (!jpegFile)
  {
    Serial.print("ERROR: File \"");
    Serial.print(filename);
    Serial.println("\" not found!");
    return;
  }

  Serial.println("===========================");
  Serial.print("Drawing file: ");
  Serial.println(filename);
  Serial.println("===========================");

  // Use one of the following methods to initialise the decoder:
  bool decoded = JpegDec.decodeSdFile(jpegFile); // Pass the SD file handle to the decoder,
  // bool decoded = JpegDec.decodeSdFile(filename);  // or pass the filename (String or character array)

  if (decoded)
  {
    // print information about the image to the serial port
    jpegInfo();
    // render the image onto the screen at given coordinates
    jpegRender(xpos, ypos);
  }
  else
  {
    Serial.println("Jpeg file format not supported!");
  }
}
