#include "lvgl.h"
#include "pins_config.h"
#include "AXS15231B.h"
#include "ui-util.h"
#include "WiFi.h"
#include "sntp.h"
#include "time.h"
#include <Arduino.h>
#include <XPowersLib.h>
#include <HTTPClient.h>
#include "freertos/semphr.h"
#include "DFRobot_mmWave_Radar.h"
#include <HardwareSerial.h>

#include "Thread.h"
#include "ThreadController.h"

// Serial
#define BAUD 115200

// Sensor pins
#define RX2_PIN TOUCH_IICSCL
#define TX2_PIN TOUCH_IICSDA

// Sensor configuration
#define PRESENCE_DISTANCE 2 // in m
#define DELAY_STATUS_CHECK 1000 // delay (in ms) between two checks of the presence
#define SLEEP_DELAY 30000 // delay (in ms) until sleep mode
#define POWER_DELAY 100000 // delay (in ms) until turning off the screen

HardwareSerial mySerial(1);
DFRobot_mmWave_Radar sensor(&mySerial);

SemaphoreHandle_t xSemaphore = NULL;
PowersSY6970      PMU;

// Sensor variables
uint16_t state = 1; // 0 = presence detected, 1 = no presence detected for the past SLEEP_DELAY ms, 2 = no presence detected for the past POWER_DELAY ms
unsigned long lastMotionDetected = 0; // time when the last motion was detected

// Time variables
static uint32_t last_tick;
struct tm       timeinfo;
uint32_t        cycleInterval = 0;

// Display variables
static lv_disp_draw_buf_t draw_buf;
static lv_color_t        *buf;
static lv_color_t        *buf1;

// REST API Home assistant request
static String BASE_URL_HA = "http://192.168.1.2:8123/api/services";
static String HA_TOKEN = "abc";

ThreadController controller;
Thread thread1 = Thread();
Thread thread2 = Thread();

void setup() {
    xSemaphore = xSemaphoreCreateBinary();
    xSemaphoreGive(xSemaphore);

    initSerial();
    initializeWifi();
    initializeTime();
    initializeUI();
    initializeThreading();
}

void loop() {
    controller.run();

    if (transfer_num <= 0 && lcd_PushColors_len <= 0)
        lv_timer_handler();

    if (transfer_num <= 1 && lcd_PushColors_len > 0) {
        lcd_PushColors(0, 0, 0, 0, NULL);
    }

    static int           flag_bl = 0;
    static unsigned long cnt     = 0;

    cnt++;
    if (cnt >= 100) {
        if (flag_bl == 0) {
            pinMode(TFT_BL, OUTPUT);
            digitalWrite(TFT_BL, HIGH);
            flag_bl = 1;
            lv_delay_ms(500);
            ui_begin();
            lv_msg_send(MSG_NEW_VOLT, "");
        }
    }
}

void initSerial() {
    // Start the primary serial communication (USB Monitor)
    Serial.begin(BAUD);
    unsigned long startAttemptTime = millis();
    while (!Serial && millis() - startAttemptTime < 2000) {
      delay(100);
    }
    Serial.println("Serial Monitor Initialized.");

    pinMode(PIN_BAT_VOLT, ANALOG);

    // Screen
    pinMode(TOUCH_RES, OUTPUT);
    digitalWrite(TOUCH_RES, HIGH);
    delay(2);
    digitalWrite(TOUCH_RES, LOW);
    delay(10);
    digitalWrite(TOUCH_RES, HIGH);
    delay(2);

    // Start HMMD Sensor
    mySerial.begin(BAUD, SERIAL_8N1, RX2_PIN, TX2_PIN);  //RX,TX
    Serial.println("Sensor Initialized on RX:" + String(RX2_PIN) + ", TX:" + String(TX2_PIN));
    sensor.factoryReset();    //Restore to the factory settings 
    sensor.DetRangeCfg(0, PRESENCE_DISTANCE);    //The detection range is as far as 9m
    sensor.OutputLatency(0, 0);
}

void initializeWifi() {
    WiFi.persistent(true);
    WiFi.mode(WIFI_STA);
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    Serial.println("Connecting to WiFi...");
    int connectTime = millis();
    while(WiFi.status() != WL_CONNECTED) {
        delay(200);
        Serial.print(".");
        if (millis() - connectTime >= WIFI_CONNECT_WAIT_MAX) {
            ESP.restart();
        }
    }

    Serial.println();
    Serial.println("Connection succesful!");
    Serial.printf("SSID: %s\r\n", WiFi.SSID().c_str());
    String IP = WiFi.localIP().toString();
    Serial.printf("IP address: %s\r\n", IP.c_str());
}

void initializeTime() {
    Serial.println("Initializing time...");
    configTime(GMT_OFFSET_SEC, DAY_LIGHT_OFFSET_SEC, NTP_SERVER1, NTP_SERVER2);
}

void initializeUI() {
    Serial.println("Initializing UI....");
    axs15231_init();
    lv_init();
    size_t buffer_size =
        sizeof(lv_color_t) * EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES;
    buf = (lv_color_t *)ps_malloc(buffer_size);
    if (buf == NULL) {
        while (1) {
            Serial.println("buf NULL");
            delay(500);
        }
    }

    buf1 = (lv_color_t *)ps_malloc(buffer_size);
    if (buf1 == NULL) {
        while (1) {
            Serial.println("buf NULL");
            delay(500);
        }
    }

    lv_disp_draw_buf_init(&draw_buf, buf, buf1, buffer_size);
    /*Initialize the display*/
    static lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    /*Change the following line to your display resolution*/
    disp_drv.hor_res      = EXAMPLE_LCD_H_RES;
    disp_drv.ver_res      = EXAMPLE_LCD_V_RES;
    disp_drv.flush_cb     = my_disp_flush;
    disp_drv.draw_buf     = &draw_buf;
    disp_drv.sw_rotate    = 1;          // If you turn on software rotation, Do not update or replace LVGL
    disp_drv.rotated      = LV_DISP_ROT_90;
    disp_drv.full_refresh = 1;          // full_refresh must be 1
    lv_disp_drv_register(&disp_drv);

    static lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.type    = LV_INDEV_TYPE_POINTER;
    lv_indev_drv_register(&indev_drv);

    Serial.println("UI initialized");
}

void initializeThreading() {
    thread1.onRun(printLocalTime);
    thread1.setInterval(100);

    thread2.onRun(readAndProcessSensorLines);
    thread2.setInterval(1000);

    controller.add(&thread1);
    controller.add(&thread2);
}

void printLocalTime() {
    if (millis() - last_tick > 100) {
        struct tm timeInfo;
        if(!getLocalTime(&timeInfo)){
            Serial.println("Failed to obtain time");
        } else {
            lv_msg_send(MSG_NEW_HOUR, &timeInfo.tm_hour);
            lv_msg_send(MSG_NEW_MIN, &timeInfo.tm_min);
            lv_msg_send(MSG_NEW_SEC, &timeInfo.tm_sec);
        }
        last_tick = millis();
    }
}

void readAndProcessSensorLines() {
    int currentStatus = sensor.readPresenceDetection();

    if (currentStatus == 1) {
        lastMotionDetected = millis();
    }

    //Serial.printf("Sensor: %d\n", currentStatus);

    if (currentStatus == 1 && state != 0) {
        state = 0;
        Serial.println("Motion detected, turning screen on.");
        lv_msg_send(MSG_NEW_VOLT, "0");
        //restApiAction(0);
    } else if (currentStatus == 0 && state == 1 && (millis() - lastMotionDetected >= POWER_DELAY)) {
        state = 2;
        Serial.printf("No motion detected for %d ms, turning screen off.\n", POWER_DELAY);
        lv_msg_send(MSG_NEW_VOLT, "2");
        //restApiAction(2);
    } else if (currentStatus == 0 && state == 0 && (millis() - lastMotionDetected >= SLEEP_DELAY)) {
        state = 1;
        Serial.printf("No motion detected for %d ms, turning screen screensaver on.\n", SLEEP_DELAY);
        lv_msg_send(MSG_NEW_VOLT, "1");
        //restApiAction(1);
    }
}

// action: 0 => Screen on, => 1 Screen off, => 2 Power off
void restApiAction(int action) {
    WiFiClient client;
    HTTPClient http;
    int httpResponseCode;
    String url;
    
    if (WiFi.status() != WL_CONNECTED){
        Serial.println("WiFi Disconnected. No request sent");
    }

    switch (action) {
        case 0: // Screen on
            Serial.println("Sending Screen ON Request");
            url = BASE_URL_HA + "/webostv/command";
            http.begin(client, url);
            http.addHeader("Authorization", "Bearer " + HA_TOKEN);
            http.addHeader("Content-Type", "application/json");
            httpResponseCode = http.POST("{\"entity_id\":\"media_player.lg_webos_smart_tv\",\"command\":\"com.webos.service.tvpower/power/turnOnScreen\"}");
            break;
        case 1: // Screen off
            Serial.println("Sending Screen OFF Request");
            url = BASE_URL_HA + "/webostv/command";
            http.begin(client, url);
            http.addHeader("Authorization", "Bearer " + HA_TOKEN);
            http.addHeader("Content-Type", "application/json");
            httpResponseCode = http.POST("{\"entity_id\":\"media_player.lg_webos_smart_tv\",\"command\":\"com.webos.service.tvpower/power/turnOffScreen\"}");
            break;
        case 2: // Power off
            Serial.println("Sending Power Off Request");
            url = BASE_URL_HA + "/webostv/command";
            http.begin(client, url);
            http.addHeader("Authorization", "Bearer " + HA_TOKEN);
            http.addHeader("Content-Type", "application/json");
            httpResponseCode = http.POST("{\"entity_id\":\"media_player.lg_webos_smart_tv\",\"command\":\"com.webos.service.tvpower/power/powerOffScreen\"}");
            break;
    }
    
    Serial.print("HTTP Response code: ");
    Serial.println(httpResponseCode);
    
    http.end();
}