// components/evse/evse_pilot.c
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

#include "driver/ledc.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_rom_sys.h"

#include "evse_pilot.h"
#include "adc121s021_dma.h"
#include "board_config.h"

#define PILOT_PWM_TIMER       LEDC_TIMER_0
#define PILOT_PWM_CHANNEL     LEDC_CHANNEL_0
#define PILOT_PWM_SPEED_MODE  LEDC_LOW_SPEED_MODE
#define PILOT_PWM_DUTY_RES    LEDC_TIMER_10_BIT
#define PILOT_PWM_MAX_DUTY    1023

// --- Configuração de amostragem do Pilot ---
#define NUM_PILOT_SAMPLES     100
#define MAX_SAMPLE_ATTEMPTS   1000

#define PILOT_SAMPLE_DELAY_US 10

// Percentagem para descartar extremos superior/inferior (ruído)
#define PILOT_EXTREME_PERCENT 10 // 10% superior e inferior

// ADC referência
#define ADC121_VREF_MV 3300
#define ADC121_MAX     4095

static const char *TAG = "evse_pilot";

typedef enum {
    PILOT_MODE_DC_HIGH = 0, // +12V (nível alto)
    PILOT_MODE_DC_LOW,      // nível baixo / pilot desligado (dependente do hardware)
    PILOT_MODE_PWM          // PWM ativo
} pilot_mode_t;

static pilot_mode_t s_mode = PILOT_MODE_DC_LOW;
static uint32_t     last_pwm_duty = 0;

// ---------------------
// Helpers internos
// ---------------------
static int adc_raw_to_mv(uint16_t raw)
{
    return (int)((raw * ADC121_VREF_MV) / ADC121_MAX);
}

static int compare_uint16(const void *a, const void *b)
{
    uint16_t va = *(const uint16_t *)a;
    uint16_t vb = *(const uint16_t *)b;
    if (va < vb) return -1;
    if (va > vb) return 1;
    return 0;
}

// ---------------------
// Inicialização PWM + ADC
// ---------------------
void pilot_init(void)
{
    // Configura timer do PWM do Pilot (1 kHz)
    ledc_timer_config_t ledc_timer = {
        .speed_mode       = PILOT_PWM_SPEED_MODE,
        .timer_num        = PILOT_PWM_TIMER,
        .duty_resolution  = PILOT_PWM_DUTY_RES,
        .freq_hz          = 1000, // 1 kHz (IEC 61851)
        .clk_cfg          = LEDC_AUTO_CLK
    };
    ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));

    // Canal do PWM no pino configurado em board_config
    ledc_channel_config_t ledc_channel = {
        .speed_mode = PILOT_PWM_SPEED_MODE,
        .channel    = PILOT_PWM_CHANNEL,
        .timer_sel  = PILOT_PWM_TIMER,
        .intr_type  = LEDC_INTR_DISABLE,
        .gpio_num   = board_config.pilot_pwm_gpio,
        .duty       = 0,
        .hpoint     = 0
    };
    ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));

    // Garante que começa parado e em idle baixo (pilot off)
    ESP_ERROR_CHECK(ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, 0));
    s_mode = PILOT_MODE_DC_LOW;
    last_pwm_duty = 0;

    // Inicializa driver do ADC121S021
    adc121s021_dma_init();
}

// ---------------------
// Controlo do modo do Pilot
// ---------------------
void pilot_set_level(bool high)
{
    pilot_mode_t target = high ? PILOT_MODE_DC_HIGH : PILOT_MODE_DC_LOW;

    // Se já estiver no modo DC desejado e sem PWM ativo, ignora
    if (s_mode == target && last_pwm_duty == 0) {
        return;
    }

    ESP_LOGI(TAG, "Pilot set DC level: %s", high ? "HIGH(+12V)" : "LOW/OFF");

    // Para PWM e fixa o nível idle do GPIO
    ESP_ERROR_CHECK(ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, high ? 1 : 0));

    s_mode = target;
    last_pwm_duty = 0;
}

void pilot_set_amps(uint16_t amps)
{
    if (amps < 6 || amps > 80)
    {
        ESP_LOGE(TAG, "Invalid ampere value: %d A (valid: 6–80 A)", amps);
        return;
    }

    uint32_t duty_percent;
    if (amps <= 51)
    {
        duty_percent = (amps * 10) / 6;
    }
    else
    {
        duty_percent = (amps * 10) / 25 + 64;
    }
    if (duty_percent > 100) duty_percent = 100;

    uint32_t duty = (PILOT_PWM_MAX_DUTY * duty_percent) / 100;

    // Se já estiver em PWM com o mesmo duty, ignora
    if (s_mode == PILOT_MODE_PWM && last_pwm_duty == duty) {
        return;
    }

    s_mode = PILOT_MODE_PWM;
    last_pwm_duty = duty;

    ESP_LOGI(TAG, "Pilot set PWM: %d A → %d/%d (≈ %d%% duty)",
             amps, (int)duty, PILOT_PWM_MAX_DUTY, (int)duty_percent);

    ESP_ERROR_CHECK(ledc_set_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, duty));
    ESP_ERROR_CHECK(ledc_update_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL));
}

bool pilot_get_state(void)
{
    // "Alto" significa DC +12V (estado A). PWM não conta como DC high.
    return (s_mode == PILOT_MODE_DC_HIGH);
}

// ---------------------
// Medição do sinal de Pilot (PWM 1 kHz J1772)
// ---------------------
void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12)
{
    ESP_LOGD(TAG, "pilot_measure");

    uint16_t samples[NUM_PILOT_SAMPLES];
    int collected = 0;
    int attempts  = 0;

    while (collected < NUM_PILOT_SAMPLES && attempts < MAX_SAMPLE_ATTEMPTS)
    {
        uint16_t adc_sample;

        if (adc121s021_dma_get_sample(&adc_sample))
        {
            samples[collected++] = adc_sample;
            esp_rom_delay_us(PILOT_SAMPLE_DELAY_US);
        }
        else
        {
            esp_rom_delay_us(100);
            attempts++;
        }
    }

    if (collected < NUM_PILOT_SAMPLES)
    {
        ESP_LOGW(TAG, "Timeout on sample read (%d/%d)", collected, NUM_PILOT_SAMPLES);
        *up_voltage = PILOT_VOLTAGE_1;
        *down_voltage_n12 = false;
        return;
    }

    // Ordena as amostras para eliminar extremos (ruído/espúrios)
    qsort(samples, collected, sizeof(uint16_t), compare_uint16);

    int k = (collected * PILOT_EXTREME_PERCENT) / 100;
    if (k < 2) k = 2; // garante margem mínima

    // descarta k/2 em cada lado (aprox. 10% total, mantendo simetria)
    int low_index  = k / 2;
    int high_index = collected - 1 - (k / 2);

    if (low_index < 0) low_index = 0;
    if (high_index >= collected) high_index = collected - 1;
    if (high_index <= low_index) high_index = low_index;

    uint16_t low_raw  = samples[low_index];
    uint16_t high_raw = samples[high_index];

    int high_mv = adc_raw_to_mv(high_raw);
    int low_mv  = adc_raw_to_mv(low_raw);

    // Determina o nível positivo (+12, +9, +6, +3 ou <3 V)
    if (high_mv >= board_config.pilot_down_threshold_12)
    {
        *up_voltage = PILOT_VOLTAGE_12;
    }
    else if (high_mv >= board_config.pilot_down_threshold_9)
    {
        *up_voltage = PILOT_VOLTAGE_9;
    }
    else if (high_mv >= board_config.pilot_down_threshold_6)
    {
        *up_voltage = PILOT_VOLTAGE_6;
    }
    else if (high_mv >= board_config.pilot_down_threshold_3)
    {
        *up_voltage = PILOT_VOLTAGE_3;
    }
    else
    {
        *up_voltage = PILOT_VOLTAGE_1;
    }

    // Verifica se o nível negativo atinge -12 V (diodo presente, C/D válidos)
    *down_voltage_n12 = (low_mv <= board_config.pilot_down_threshold_n12);

    ESP_LOGD(TAG, "Final: up_voltage=%d, down_voltage_n12=%d (high=%d mV, low=%d mV)",
             *up_voltage, *down_voltage_n12, high_mv, low_mv);
}
