#include <stdio.h>
#include <string.h>
#include "driver/uart.h"
#include "driver/gpio.h"
#include "esp_system.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/adc.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_crc.h"
#include "esp_log.h"

// Paramètres du port série
#define UART_NUM       UART_NUM_0
#define TXD_PIN        GPIO_NUM_25
#define RXD_PIN        GPIO_NUM_26
#define BUF_SIZE       1024
#define BAUD_RATE      115200

// Définition de la broche du bouton
#define BUTTON23_PIN     23
#define BUTTON22_PIN     22
#define BUTTON21_PIN     21
#define BUTTON19_PIN     19
#define BUTTON18_PIN     18
#define BUTTON05_PIN     5
#define BUTTON17_PIN     17
#define BUTTON16_PIN     16
#define BUTTON04_PIN     4
#define BUTTON00_PIN     0
#define LED_COMM_PIN     12
#define LED_1_PIN        14
#define LED_2_PIN        27
#define LED_3_PIN        26

// Joystick 1
#define JOYSTICK1_X_PIN ADC1_CHANNEL_4  // GPIO32
#define JOYSTICK1_Y_PIN ADC1_CHANNEL_5  // GPIO33
#define JOYSTICK1_SW_PIN 25             // GPIO25

// Joystick 2
#define JOYSTICK2_X_PIN ADC1_CHANNEL_6  // GPIO34
#define JOYSTICK2_Y_PIN ADC1_CHANNEL_7  // GPIO35
#define JOYSTICK2_SW_PIN 26             // GPIO26

// Initialiser l'UART
void init_uart() {
    // Configuration du port série UART0
    uart_config_t uart_config = {
        .baud_rate = BAUD_RATE,
        .data_bits = UART_DATA_8_BITS,           // 8 bits de données
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE
    };
    uart_param_config(UART_NUM, &uart_config);
    uart_set_pin(UART_NUM, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
    uart_driver_install(UART_NUM, BUF_SIZE, 0, 0, NULL, 0);
}

// Initialiser les leds et entrées boutons
void init_gpio() {
    gpio_set_direction(LED_COMM_PIN, GPIO_MODE_OUTPUT);
    gpio_set_level(LED_COMM_PIN, 0);  // LED éteinte au démarrage
    gpio_set_direction(LED_1_PIN, GPIO_MODE_OUTPUT);
    gpio_set_level(LED_1_PIN, 0);  // LED éteinte au démarrage
    gpio_set_direction(LED_2_PIN, GPIO_MODE_OUTPUT);
    gpio_set_level(LED_2_PIN, 0);  // LED éteinte au démarrage
    gpio_set_direction(LED_3_PIN, GPIO_MODE_OUTPUT);
    gpio_set_level(LED_3_PIN, 0);  // LED éteinte au démarrage


    // Configuration du GPIO pour le bouton avec résistance pull-up interne
    gpio_set_direction(BUTTON23_PIN, GPIO_MODE_INPUT);  // Définir le bouton comme entrée
    gpio_set_direction(BUTTON22_PIN, GPIO_MODE_INPUT);  // Définir le bouton comme entrée
    gpio_set_direction(BUTTON21_PIN, GPIO_MODE_INPUT);  // Définir le bouton comme entrée
    gpio_set_direction(BUTTON19_PIN, GPIO_MODE_INPUT);  // Définir le bouton comme entrée
    gpio_set_direction(BUTTON18_PIN, GPIO_MODE_INPUT);  // Définir le bouton comme entrée
    gpio_set_direction(BUTTON05_PIN, GPIO_MODE_INPUT);  // Définir le bouton comme entrée
    gpio_set_direction(BUTTON17_PIN, GPIO_MODE_INPUT);  // Définir le bouton comme entrée
    gpio_set_direction(BUTTON16_PIN, GPIO_MODE_INPUT);  // Définir le bouton comme entrée
    gpio_set_direction(BUTTON04_PIN, GPIO_MODE_INPUT);  // Définir le bouton comme entrée
    gpio_set_direction(BUTTON00_PIN, GPIO_MODE_INPUT);  // Définir le bouton comme entrée
    gpio_pullup_en(BUTTON23_PIN);                       // Activer la résistance pull-up
    gpio_pullup_en(BUTTON22_PIN);                       // Activer la résistance pull-up
    gpio_pullup_en(BUTTON21_PIN);                       // Activer la résistance pull-up
    gpio_pullup_en(BUTTON19_PIN);                       // Activer la résistance pull-up
    gpio_pullup_en(BUTTON18_PIN);                       // Activer la résistance pull-up
    gpio_pullup_en(BUTTON05_PIN);                       // Activer la résistance pull-up
    gpio_pullup_en(BUTTON17_PIN);                       // Activer la résistance pull-up
    gpio_pullup_en(BUTTON16_PIN);                       // Activer la résistance pull-up
    gpio_pullup_en(BUTTON04_PIN);                       // Activer la résistance pull-up
    gpio_pullup_en(BUTTON00_PIN);                       // Activer la résistance pull-up    
}

// Initialiser le convertisseur analogique numérique
adc_oneshot_unit_handle_t init_adc() {
    adc_oneshot_unit_handle_t adc_handle;
      // Configuration de l'ADC
    adc_oneshot_unit_init_cfg_t init_config = {
        .unit_id = ADC_UNIT_1
    };
    adc_oneshot_new_unit(&init_config, &adc_handle);

    // Configuration des canaux ADC
    adc_oneshot_chan_cfg_t chan_config = {
        .bitwidth = ADC_BITWIDTH_DEFAULT,
        .atten = ADC_ATTEN_DB_11
    };
    adc_oneshot_config_channel(adc_handle, JOYSTICK1_X_PIN, &chan_config);
    adc_oneshot_config_channel(adc_handle, JOYSTICK1_Y_PIN, &chan_config);
    adc_oneshot_config_channel(adc_handle, JOYSTICK2_X_PIN, &chan_config);
    adc_oneshot_config_channel(adc_handle, JOYSTICK2_Y_PIN, &chan_config);

    // // Configuration des broches des boutons
    // gpio_pad_select_gpio(JOYSTICK1_SW_PIN);
    // gpio_set_direction(JOYSTICK1_SW_PIN, GPIO_MODE_INPUT);
    // gpio_pullup_en(JOYSTICK1_SW_PIN);

    // gpio_pad_select_gpio(JOYSTICK2_SW_PIN);
    // gpio_set_direction(JOYSTICK2_SW_PIN, GPIO_MODE_INPUT);
    // gpio_pullup_en(JOYSTICK2_SW_PIN);
    return adc_handle;
}

// Fonction pour calculer le CRC32 et ajouter le CRC à la trame
size_t create_frame_with_crc(adc_oneshot_unit_handle_t adc_handle, uint8_t *frame_out) {
    // Lire l'état des boutons (0 si appuyé, 1 si relâché)
    int button23_state = gpio_get_level(BUTTON23_PIN);
    int button22_state = gpio_get_level(BUTTON22_PIN);
    int button21_state = gpio_get_level(BUTTON21_PIN);
    int button19_state = gpio_get_level(BUTTON19_PIN);
    int button18_state = gpio_get_level(BUTTON18_PIN);
    int button05_state = gpio_get_level(BUTTON05_PIN);
    int button17_state = gpio_get_level(BUTTON17_PIN);
    int button16_state = gpio_get_level(BUTTON16_PIN);
    int button04_state = gpio_get_level(BUTTON04_PIN);
    int button00_state = gpio_get_level(BUTTON00_PIN);
    
    // Lire l'état des joystick
    int buttonJ1A_state;
    adc_oneshot_read(adc_handle, JOYSTICK1_X_PIN, &buttonJ1A_state);
    int buttonJ1R_state;
    adc_oneshot_read(adc_handle, JOYSTICK1_Y_PIN, &buttonJ1R_state);
    int buttonJ2A_state;
    adc_oneshot_read(adc_handle, JOYSTICK2_X_PIN, &buttonJ2A_state);
    int buttonJ2R_state;
    adc_oneshot_read(adc_handle, JOYSTICK2_Y_PIN, &buttonJ2R_state);
    

    // Création de la trame à envoyer
    char message[128];
    int message_length = snprintf(message, sizeof(message),
                          "\nT%s%s%s%s%s%s%s%s%s%s%.4d%.4d%.4d%.4d",
                          button23_state == 0 ? "1" : "0", // pinces int + bloque planche
                          button22_state == 0 ? "1" : "0", // pinces ext
                          button21_state == 0 ? "1" : "0", // pousse planche
                          button19_state == 0 ? "1" : "0", // N0
                          button18_state == 0 ? "1" : "0", // N1
                          button05_state == 0 ? "1" : "0", // N2
                          button17_state == 0 ? "1" : "0", // N3
                          button16_state == 0 ? "1" : "0", // Tempo Pami
                          button04_state == 0 ? "1" : "0", // Increment points
                          button00_state == 0 ? "1" : "0", // Ouverture affiche
                          buttonJ1A_state,
                          buttonJ1R_state,
                          buttonJ2A_state,
                          buttonJ2R_state);

    // Calculer le CRC32
    memcpy(frame_out, message, message_length);
    uint32_t crc = esp_crc32_le(0, (const uint8_t *)message, message_length);

    // Ajouter le CRC à la fin de la trame (little-endian)
    frame_out[message_length] = (uint8_t)(crc & 0xFF);
    frame_out[message_length + 1] = (uint8_t)((crc >> 8) & 0xFF);
    frame_out[message_length + 2] = (uint8_t)((crc >> 16) & 0xFF);
    frame_out[message_length + 3] = (uint8_t)((crc >> 24) & 0xFF);

    // Retourner la longueur totale de la trame (données + CRC)
    return message_length + 4;
}

// Fonction pour vérifier le CRC32 d'une trame
bool verify_crc32(const uint8_t *frame, size_t length) {
    if (length < 4) {
        // La longueur doit être au moins 4 octets (pour contenir un CRC32)
        return false;
    }

    // Extraire le CRC32 reçu (les 4 derniers octets de la trame)
    uint32_t received_crc = (uint32_t)frame[length - 4] |
                            ((uint32_t)frame[length - 3] << 8) |
                            ((uint32_t)frame[length - 2] << 16) |
                            ((uint32_t)frame[length - 1] << 24);

    // Calculer le CRC32 des données, sans inclure les 4 derniers octets
    uint32_t calculated_crc = esp_crc32_le(0, frame, length - 4);

    // Comparer le CRC reçu avec le CRC calculé
    return (calculated_crc == received_crc);
}

// Fonction pour lire la trame et allumer la LED si elle commence par "R"
void check_uart_data() {
    uint8_t data[BUF_SIZE];
    int length = uart_read_bytes(UART_NUM, data, BUF_SIZE, 20 / portTICK_PERIOD_MS);

    if (length > 0) {
        ESP_LOGI("UART", "Données reçues : %.*s", length, data);
        if (verify_crc32(data, length))
        {
          // Vérifier si la trame commence par 'R'
          if (data[0] == 'R') {
              ESP_LOGI("UART", "Trame valide reçue, allumer la LED.");
              gpio_set_level(LED_COMM_PIN, 1);  // Allumer la LED
          } else {
              gpio_set_level(LED_COMM_PIN, 0);  // Éteindre la LED
          }
          // LED 1
          if (data[1] == '1') {
              ESP_LOGI("UART", "Data[1]=1");
              gpio_set_level(LED_1_PIN, 1);  // Allumer la LED
          } else {
              ESP_LOGI("UART", "Data[1]=0");
              gpio_set_level(LED_1_PIN, 0);  // Éteindre la LED
          }
          // LED 2
          if (data[2] == '1') {
              ESP_LOGI("UART", "Data[2]=1");
              gpio_set_level(LED_2_PIN, 1);  // Allumer la LED
          } else {
              ESP_LOGI("UART", "Data[2]=0");
              gpio_set_level(LED_2_PIN, 0);  // Éteindre la LED
          }
          // LED 3
          if (data[3] == '1') {
              ESP_LOGI("UART", "Data[3]=1");
              gpio_set_level(LED_3_PIN, 1);  // Allumer la LED
          } else {
              ESP_LOGI("UART", "Data[3]=0");
              gpio_set_level(LED_3_PIN, 0);  // Éteindre la LED
          }
        } else {
          ESP_LOGI("UART", "CRC trame invalide");
          gpio_set_level(LED_COMM_PIN, 0);  // Éteindre la LED
        }
    } else {
      gpio_set_level(LED_COMM_PIN, 0);  // Éteindre la LED
    }
}

void app_main(void)
{
    init_uart();
    init_gpio();
    adc_oneshot_unit_handle_t adc_handle = init_adc();

    // Envoi de trames en continu
    while (1) {
        // Buffer pour la trame avec CRC
        uint8_t frame[128];
        // Créer la trame avec CRC
        size_t frame_length = create_frame_with_crc(adc_handle, frame);

        // Envoi de la trame sur le port série
        uart_write_bytes(UART_NUM, (const char *)frame, frame_length);

        // Attente de 10 milliseconde avant la prochaine trame
        vTaskDelay(10 / portTICK_PERIOD_MS);

        // Recevoir la réponse préfixée par 'R'
        check_uart_data();
    }
}