#include "stdio.h"
#include "string.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "driver/uart.h"
#include "esp_timer.h"
#include "esp_ota_ops.h"
#include "zh_espnow.h"
#include "zh_config.h"

#define ZH_UART_TASK_PRIORITY 6
#define ZH_UART_STACK_SIZE 2048
#define ZH_UART_BUFF_SIZE 1024
#define ZH_UART_QUEUE_SIZE 10
#define ZH_UART_NUM 0

static uint8_t s_sensor_type = HAST_NONE;
static uint8_t s_open_status = HAONOFT_NONE;
static uint8_t s_battery_status = HAONOFT_NONE;

static uint8_t s_gateway_mac[6] = {0};
static bool s_gateway_is_available = false;

static const esp_partition_t *s_update_partition = NULL;
static esp_ota_handle_t s_update_handle = 0;
static uint16_t s_ota_message_part_number = 0;

static QueueHandle_t s_zh_uart_queue = {0};

static const char s_initial_message[] = {0x55, 0xAA, 0x00, 0x01, 0x00, 0x00, 0x00};
static const char s_connected_message[] = {0x55, 0xAA, 0x00, 0x02, 0x00, 0x01, 0x04, 0x06};
static const char s_setting_message[] = {0x55, 0xAA, 0x00, 0x03, 0x00, 0x00, 0x02};
static const char s_confirmation_message[] = {0x55, 0xAA, 0x00, 0x08, 0x00, 0x01, 0x00, 0x08};

static void s_zh_uart_processing_task(void *pvParameter);

static void s_zh_load_config(void);
static void s_zh_save_config(void);
static void s_zh_load_status(void);
static void s_zh_save_status(void);

static void s_zh_send_sensor_attributes_message(void);
static void s_zh_send_sensor_config_message(void);
static void s_zh_send_sensor_status_message(void);

static void s_zh_espnow_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);

void app_main(void)
{
#if CONFIG_SENSOR_TYPE_WINDOW
    s_sensor_type = HAST_WINDOW;
#elif CONFIG_SENSOR_TYPE_DOOR
    s_sensor_type = HAST_DOOR;
#endif
    const esp_partition_t *running = esp_ota_get_running_partition();
    esp_ota_img_states_t ota_state = {0};
    esp_ota_get_state_partition(running, &ota_state);
    nvs_flash_init();
    esp_netif_init();
    esp_event_loop_create_default();
    s_zh_load_config();
    s_zh_load_status();
    wifi_init_config_t wifi_init_config = WIFI_INIT_CONFIG_DEFAULT();
    esp_wifi_init(&wifi_init_config);
    esp_wifi_set_mode(WIFI_MODE_STA);
    esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_11B);
    zh_espnow_init_config_t zh_espnow_init_config = ZH_ESPNOW_INIT_CONFIG_DEFAULT();
    zh_espnow_init(&zh_espnow_init_config);
    esp_event_handler_instance_register(ZH_ESPNOW, ESP_EVENT_ANY_ID, &s_zh_espnow_event_handler, NULL, NULL);
    xTaskCreatePinnedToCore(&s_zh_uart_processing_task, "s_zh_uart_processing_tack", ZH_UART_STACK_SIZE, NULL, ZH_UART_TASK_PRIORITY, NULL, tskNO_AFFINITY);
    s_zh_send_sensor_config_message();
    s_zh_send_sensor_attributes_message();
    if (ota_state == ESP_OTA_IMG_PENDING_VERIFY)
    {
        vTaskDelay(100 / portTICK_PERIOD_MS);
        esp_ota_mark_app_valid_cancel_rollback();
    }
}

static void s_zh_uart_processing_task(void *pvParameter)
{
    uart_config_t uart_config = {
        .baud_rate = 9600,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_DEFAULT,
    };
    uart_driver_install(ZH_UART_NUM, ZH_UART_BUFF_SIZE, ZH_UART_BUFF_SIZE, ZH_UART_QUEUE_SIZE, &s_zh_uart_queue, 0);
    uart_param_config(ZH_UART_NUM, &uart_config);
    uart_set_pin(ZH_UART_NUM, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
    uint8_t *data = (uint8_t *)calloc(1, ZH_UART_BUFF_SIZE);
    uart_event_t event = {0};
    uart_write_bytes(ZH_UART_NUM, &s_initial_message, sizeof(s_initial_message));
    for (;;)
    {
        if (xQueueReceive(s_zh_uart_queue, (void *)&event, (TickType_t)portMAX_DELAY))
        {
            switch (event.type)
            {
            case UART_DATA:
                uart_read_bytes(ZH_UART_NUM, data, event.size, portMAX_DELAY);
                if (data[0] == 0x55 && data[3] == 0x01) // MCU system information.
                {
                    uart_write_bytes(ZH_UART_NUM, &s_connected_message, sizeof(s_connected_message));
                }
                if (data[0] == 0x55 && data[3] == 0x03) // Message for switching to setting mode.
                {
                    uart_write_bytes(ZH_UART_NUM, &s_setting_message, sizeof(s_setting_message));
                }
                if (data[0] == 0x55 && data[3] == 0x08) // Sensor status message.
                {
                    if (data[7] == 0x01) // Battery status.
                    {
                        if (data[17] == 0x02 || data[17] == 0x01)
                        {
                            s_battery_status = HAONOFT_HIGH;
                        }
                        else
                        {
                            s_battery_status = HAONOFT_LOW;
                        }
                        s_zh_save_status();
                        s_zh_send_sensor_status_message();
                        uart_write_bytes(ZH_UART_NUM, &s_confirmation_message, sizeof(s_confirmation_message));
                    }
                    if (data[7] == 0x02) // Sensor position.
                    {
                        if (data[17] == 0x01)
                        {
                            s_open_status = HAONOFT_OPEN;
                        }
                        else
                        {
                            s_open_status = HAONOFT_CLOSE;
                        }
                        s_zh_save_status();
                        s_zh_send_sensor_status_message();
                        uart_write_bytes(ZH_UART_NUM, &s_confirmation_message, sizeof(s_confirmation_message));
                    }
                }
                break;
            default:
                break;
            }
        }
    }
    free(data);
    vTaskDelete(NULL);
}

static void s_zh_load_config(void)
{
    nvs_handle_t nvs_handle = 0;
    nvs_open("config", NVS_READWRITE, &nvs_handle);
    uint8_t config_is_present = 0;
    if (nvs_get_u8(nvs_handle, "present", &config_is_present) == ESP_ERR_NVS_NOT_FOUND)
    {
        nvs_set_u8(nvs_handle, "present", 0xFE);
        nvs_close(nvs_handle);
        s_zh_save_config();
        return;
    }
    nvs_get_u8(nvs_handle, "sensor_type", &s_sensor_type);
    nvs_close(nvs_handle);
}

static void s_zh_save_config(void)
{
    nvs_handle_t nvs_handle = 0;
    nvs_open("config", NVS_READWRITE, &nvs_handle);
    nvs_set_u8(nvs_handle, "sensor_type", s_sensor_type);
    nvs_close(nvs_handle);
}

static void s_zh_load_status(void)
{
    nvs_handle_t nvs_handle = 0;
    nvs_open("status", NVS_READWRITE, &nvs_handle);
    uint8_t status_is_present = 0;
    if (nvs_get_u8(nvs_handle, "present", &status_is_present) == ESP_ERR_NVS_NOT_FOUND)
    {
        nvs_set_u8(nvs_handle, "present", 0xFE);
        nvs_close(nvs_handle);
        s_zh_save_status();
        return;
    }
    nvs_get_u8(nvs_handle, "open_state", &s_open_status);
    nvs_get_u8(nvs_handle, "battery_state", &s_battery_status);
    nvs_close(nvs_handle);
}

static void s_zh_save_status(void)
{
    nvs_handle_t nvs_handle = 0;
    nvs_open("status", NVS_READWRITE, &nvs_handle);
    nvs_set_u8(nvs_handle, "open_state", s_open_status);
    nvs_set_u8(nvs_handle, "battery_state", s_battery_status);
    nvs_close(nvs_handle);
}

static void s_zh_send_sensor_attributes_message(void)
{
    const esp_app_desc_t *app_info = esp_app_get_description();
    zh_attributes_message_t attributes_message = {0};
    attributes_message.chip_type = HACHT_ESP32;
    strcpy(attributes_message.flash_size, CONFIG_ESPTOOLPY_FLASHSIZE);
    attributes_message.cpu_frequency = CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ;
    attributes_message.reset_reason = (uint8_t)esp_reset_reason();
    strcpy(attributes_message.app_name, app_info->project_name);
    strcpy(attributes_message.app_version, app_info->version);
    zh_espnow_data_t data = {0};
    data.device_type = ZHDT_BINARY_SENSOR;
    data.payload_type = ZHPT_ATTRIBUTES;
    attributes_message.heap_size = esp_get_free_heap_size();
    attributes_message.min_heap_size = esp_get_minimum_free_heap_size();
    attributes_message.uptime = esp_timer_get_time() / 1000000;
    data.payload_data = (zh_payload_data_t)attributes_message;
    zh_espnow_send(NULL, (uint8_t *)&data, sizeof(zh_espnow_data_t));
}

static void s_zh_send_sensor_config_message(void)
{
    zh_binary_sensor_config_message_t binary_sensor_config_message = {0};
    binary_sensor_config_message.unique_id = 1;
    binary_sensor_config_message.binary_sensor_device_class = (s_sensor_type == HAST_WINDOW) ? HABSDC_WINDOW : HABSDC_DOOR;
    binary_sensor_config_message.payload_on = HAONOFT_OPEN;
    binary_sensor_config_message.payload_off = HAONOFT_CLOSE;
    binary_sensor_config_message.enabled_by_default = true;
    binary_sensor_config_message.force_update = true;
    binary_sensor_config_message.qos = 2;
    binary_sensor_config_message.retain = true;
    zh_config_message_t config_message = {0};
    config_message = (zh_config_message_t)binary_sensor_config_message;
    zh_espnow_data_t data = {0};
    data.device_type = ZHDT_BINARY_SENSOR;
    data.payload_type = ZHPT_CONFIG;
    data.payload_data = (zh_payload_data_t)config_message;
    zh_espnow_send(NULL, (uint8_t *)&data, sizeof(zh_espnow_data_t));
    binary_sensor_config_message.unique_id = 2;
    binary_sensor_config_message.binary_sensor_device_class = HABSDC_BATTERY;
    binary_sensor_config_message.payload_on = HAONOFT_LOW;
    binary_sensor_config_message.payload_off = HAONOFT_HIGH;
    config_message = (zh_config_message_t)binary_sensor_config_message;
    data.payload_data = (zh_payload_data_t)config_message;
    zh_espnow_send(NULL, (uint8_t *)&data, sizeof(zh_espnow_data_t));
}

static void s_zh_send_sensor_status_message(void)
{
    zh_binary_sensor_status_message_t binary_sensor_status_message = {0};
    binary_sensor_status_message.sensor_type = s_sensor_type;
    binary_sensor_status_message.open = s_open_status;
    binary_sensor_status_message.battery = s_battery_status;
    zh_status_message_t status_message = {0};
    status_message = (zh_status_message_t)binary_sensor_status_message;
    zh_espnow_data_t data = {0};
    data.device_type = ZHDT_BINARY_SENSOR;
    data.payload_type = ZHPT_STATE;
    data.payload_data = (zh_payload_data_t)status_message;
    zh_espnow_send(NULL, (uint8_t *)&data, sizeof(zh_espnow_data_t));
}

static void s_zh_espnow_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
    const esp_app_desc_t *app_info = esp_app_get_description();
    zh_espnow_data_t data_in = {0};
    zh_espnow_data_t data_out = {0};
    zh_espnow_ota_message_t espnow_ota_message = {0};
    data_out.device_type = ZHDT_BINARY_SENSOR;
    espnow_ota_message.chip_type = HACHT_ESP32;
    data_out.payload_data = (zh_payload_data_t)espnow_ota_message;
    switch (event_id)
    {
    case ZH_ESPNOW_ON_RECV_EVENT:;
        zh_espnow_event_on_recv_t *recv_data = event_data;
        if (recv_data->data_len != sizeof(zh_espnow_data_t))
        {
            goto ZH_ESPNOW_EVENT_HANDLER_EXIT;
        }
        memcpy(&data_in, recv_data->data, recv_data->data_len);
        switch (data_in.device_type)
        {
        case ZHDT_GATEWAY:
            if (s_gateway_is_available == false)
            {
                s_gateway_is_available = true;
                memcpy(s_gateway_mac, recv_data->mac_addr, 6);
            }
            switch (data_in.payload_type)
            {
            case ZHPT_UPDATE:
                s_update_partition = esp_ota_get_next_update_partition(NULL);
                strcpy(espnow_ota_message.app_name, app_info->project_name);
                strcpy(espnow_ota_message.app_version, app_info->version);
                data_out.payload_type = ZHPT_UPDATE;
                data_out.payload_data = (zh_payload_data_t)espnow_ota_message;
                zh_espnow_send(s_gateway_mac, (uint8_t *)&data_out, sizeof(zh_espnow_data_t));
                break;
            case ZHPT_UPDATE_BEGIN:
                esp_ota_begin(s_update_partition, OTA_SIZE_UNKNOWN, &s_update_handle);
                s_ota_message_part_number = 1;
                break;
            case ZHPT_UPDATE_PROGRESS:
                if (s_ota_message_part_number == data_in.payload_data.espnow_ota_message.part)
                {
                    ++s_ota_message_part_number;
                    esp_ota_write(s_update_handle, (const void *)data_in.payload_data.espnow_ota_message.data, data_in.payload_data.espnow_ota_message.data_len);
                }
                data_out.payload_type = ZHPT_UPDATE_PROGRESS;
                zh_espnow_send(s_gateway_mac, (uint8_t *)&data_out, sizeof(zh_espnow_data_t));
                break;
            case ZHPT_UPDATE_ERROR:
                esp_ota_end(s_update_handle);
                break;
            case ZHPT_UPDATE_END:
                if (esp_ota_end(s_update_handle) != ESP_OK)
                {
                    data_out.payload_type = ZHPT_UPDATE_FAIL;
                    zh_espnow_send(s_gateway_mac, (uint8_t *)&data_out, sizeof(zh_espnow_data_t));
                    break;
                }
                esp_ota_set_boot_partition(s_update_partition);
                data_out.payload_type = ZHPT_UPDATE_SUCCESS;
                zh_espnow_send(s_gateway_mac, (uint8_t *)&data_out, sizeof(zh_espnow_data_t));
                vTaskDelay(1000 / portTICK_PERIOD_MS);
                esp_restart();
                break;
            default:
                break;
            }
            break;
        default:
            break;
        }
    ZH_ESPNOW_EVENT_HANDLER_EXIT:
        free(recv_data->data);
        break;
    case ZH_ESPNOW_ON_SEND_EVENT:
        break;
    default:
        break;
    }
}