#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

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

#include "pilot.h"
#include "adc.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

#define NUM_PILOT_SAMPLES       100
#define MAX_SAMPLE_ATTEMPTS     1000
#define PILOT_EXTREME_PERCENT   10  // 15% superior e inferior

static const char *TAG = "pilot";
static pilot_voltage_cache_t last_voltage = {0, 0};

static inline uint16_t adc_to_mv(uint16_t x) {
    return (uint16_t)(((uint32_t)(x) * 3300U) / 4095U);
}

void pilot_init(void)
{
    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,
        .clk_cfg = LEDC_AUTO_CLK
    };
    ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));

    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));
    ESP_ERROR_CHECK(ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, 0));

    ESP_ERROR_CHECK(ledc_fade_func_install(0));

    adc_oneshot_chan_cfg_t config = {
        .bitwidth = ADC_BITWIDTH_DEFAULT,
        .atten = ADC_ATTEN_DB_12,
    };

    ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, board_config.pilot_adc_channel, &config));
}

void pilot_set_level(bool level)
{
    ESP_LOGI(TAG, "Set level %d", level);
    ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, level ? 1 : 0);
}

void pilot_set_amps(uint16_t amps)
{
    ESP_LOGI(TAG, "Set amps %d", amps);

    if (amps < 60 || amps > 800) {
        ESP_LOGE(TAG, "Invalid ampere value: %d A*10", amps);
        return;
    }

    uint32_t duty;
    if (amps <= 510) {
        duty = (PILOT_PWM_MAX_DUTY * amps) / 600;
    } else {
        duty = ((PILOT_PWM_MAX_DUTY * amps) / 2500) + (64 * (PILOT_PWM_MAX_DUTY / 100));
    }

    if (duty > PILOT_PWM_MAX_DUTY)
        duty = PILOT_PWM_MAX_DUTY;

    ESP_LOGI(TAG, "Set amp %dA*10 -> duty %lu/%d", amps, duty, PILOT_PWM_MAX_DUTY);
    ledc_set_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, duty);
    ledc_update_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL);
}

static int compare_u16(const void *a, const void *b) {
    return (*(uint16_t *)a - *(uint16_t *)b);
}

static uint16_t select_low_median_qsort(uint16_t *src, int n, int percent) {
    int k = (n * percent) / 100;
    if (k == 0) k = 1;

    uint16_t *copy = alloca(n * sizeof(uint16_t));
    memcpy(copy, src, n * sizeof(uint16_t));

    qsort(copy, n, sizeof(uint16_t), compare_u16);
    return copy[k / 2];
}

static uint16_t select_high_median_qsort(uint16_t *src, int n, int percent) {
    int k = (n * percent) / 100;
    if (k == 0) k = 1;

    uint16_t *copy = alloca(n * sizeof(uint16_t));
    memcpy(copy, src, n * sizeof(uint16_t));

    qsort(copy, n, sizeof(uint16_t), compare_u16);
    return copy[n - k + (k / 2)];
}

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, attempts = 0;
    uint16_t sample;

    while (collected < NUM_PILOT_SAMPLES && attempts < MAX_SAMPLE_ATTEMPTS) {
        if (adc_oneshot_read(adc_handle, board_config.pilot_adc_channel, &sample) == ESP_OK) {
            samples[collected++] = sample;
            esp_rom_delay_us(10);
        } 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;
    }

    uint16_t high_raw = select_high_median_qsort(samples, collected, PILOT_EXTREME_PERCENT);
    uint16_t low_raw  = select_low_median_qsort(samples, collected, PILOT_EXTREME_PERCENT);

    int high_mv = 0;
    int low_mv = 0;

    if (adc_cali_raw_to_voltage(adc_cali_handle, high_raw, &high_mv) != ESP_OK ||
        adc_cali_raw_to_voltage(adc_cali_handle, low_raw, &low_mv) != ESP_OK) {
        ESP_LOGW(TAG, "ADC calibration failed");
        *up_voltage = PILOT_VOLTAGE_1;
        *down_voltage_n12 = false;
        return;
    }

    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;

    *down_voltage_n12 = (low_mv <= board_config.pilot_down_threshold_n12);

    ESP_LOGD(TAG, "Final: up_voltage=%d, down_voltage_n12=%d", *up_voltage, *down_voltage_n12);
}

bool pilot_get_state(void)
{
    pilot_voltage_t voltage;
    bool is_n12v;

    pilot_measure(&voltage, &is_n12v);

    // Considera que "estado alto" significa pelo menos 12V (standby ou pronto)
    return voltage == PILOT_VOLTAGE_12;
}
