#include <stdio.h>
#include "ad5940.h"
#include "ad5941_port.h"
#include "eis.h"
#include "echem.h"
#include "ble.h"
#include "temp.h"
#include "refs.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include "esp_log.h"

#define AD5941_EXPECTED_ADIID   0x4144
static EISConfig cfg;
static EISPoint results[EIS_MAX_POINTS];
static LSVPoint lsv_results[ECHEM_MAX_POINTS];
static AmpPoint amp_results[ECHEM_MAX_POINTS];
static ClPoint  cl_results[ECHEM_MAX_POINTS];
static RefStore ref_store;

static void do_sweep(void)
{
    eis_init(&cfg);

    uint32_t n = eis_calc_num_points(&cfg);
    ble_send_sweep_start(n, cfg.freq_start_hz, cfg.freq_stop_hz);
    int got = eis_sweep(results, n, ble_send_eis_point);
    printf("Sweep complete: %d points\n", got);
    ble_send_sweep_end();
}

void app_main(void)
{
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        nvs_flash_erase();
        nvs_flash_init();
    }

    printf("EIS4: AD5941 bring-up\n");

    AD5940_MCUResourceInit(NULL);
    vTaskDelay(pdMS_TO_TICKS(50));
    AD5940_HWReset();
    vTaskDelay(pdMS_TO_TICKS(200));
    AD5940_Initialize();

    uint32_t adiid = AD5940_ReadReg(REG_AFECON_ADIID);
    printf("ADIID: 0x%04lX %s\n", adiid,
           adiid == AD5941_EXPECTED_ADIID ? "(OK)" : "(FAIL)");
    if (adiid != AD5941_EXPECTED_ADIID) return;

    eis_default_config(&cfg);
    temp_init();

    esp_log_level_set("NimBLE", ESP_LOG_WARN);
    ble_init();
    printf("Waiting for BLE connection...\n");
    ble_wait_for_connection();
    ble_send_config(&cfg);

    BleCommand cmd;
    for (;;) {
        if (ble_recv_command(&cmd, UINT32_MAX) != 0)
            continue;

        switch (cmd.type) {
        case CMD_SET_SWEEP:
            cfg.freq_start_hz    = cmd.sweep.freq_start;
            cfg.freq_stop_hz     = cmd.sweep.freq_stop;
            cfg.points_per_decade = cmd.sweep.ppd;
            eis_reconfigure(&cfg);
            printf("Sweep: %.0f-%.0f Hz, %u ppd\n",
                   cfg.freq_start_hz, cfg.freq_stop_hz, cfg.points_per_decade);
            break;

        case CMD_SET_RTIA:
            if (cmd.rtia < RTIA_COUNT) {
                cfg.rtia = cmd.rtia;
                eis_reconfigure(&cfg);
                printf("RTIA: %u\n", cfg.rtia);
            }
            break;

        case CMD_SET_RCAL:
            if (cmd.rcal < RCAL_COUNT) {
                cfg.rcal = cmd.rcal;
                eis_reconfigure(&cfg);
                printf("RCAL: %u\n", cfg.rcal);
            }
            break;

        case CMD_SET_ELECTRODE:
            if (cmd.electrode < ELEC_COUNT) {
                cfg.electrode = cmd.electrode;
                eis_reconfigure(&cfg);
                printf("Electrode: %s\n",
                       cfg.electrode == ELEC_3WIRE ? "3-wire (CE0/RE0/SE0)" : "4-wire (AIN)");
            }
            break;

        case CMD_START_SWEEP:
            printf("Config: %.0f-%.0f Hz, %u ppd, rtia=%u, rcal=%u, elec=%s\n",
                   cfg.freq_start_hz, cfg.freq_stop_hz, cfg.points_per_decade,
                   cfg.rtia, cfg.rcal,
                   cfg.electrode == ELEC_3WIRE ? "3-wire" : "4-wire");
            do_sweep();
            break;

        case CMD_GET_CONFIG:
            ble_send_config(&cfg);
            break;

        case CMD_START_LSV: {
            LSVConfig lsv_cfg;
            lsv_cfg.v_start   = cmd.lsv.v_start;
            lsv_cfg.v_stop    = cmd.lsv.v_stop;
            lsv_cfg.scan_rate = cmd.lsv.scan_rate;
            lsv_cfg.lp_rtia   = cmd.lsv.lp_rtia;
            printf("LSV: %.0f-%.0f mV, %.0f mV/s, rtia=%u\n",
                   lsv_cfg.v_start, lsv_cfg.v_stop, lsv_cfg.scan_rate, lsv_cfg.lp_rtia);

            uint32_t n = echem_lsv_calc_steps(&lsv_cfg, ECHEM_MAX_POINTS);
            ble_send_lsv_start(n, lsv_cfg.v_start, lsv_cfg.v_stop);
            int got = echem_lsv(&lsv_cfg, lsv_results, ECHEM_MAX_POINTS, ble_send_lsv_point);
            printf("LSV complete: %d points\n", got);
            ble_send_lsv_end();
            break;
        }

        case CMD_START_AMP: {
            AmpConfig amp_cfg;
            amp_cfg.v_hold      = cmd.amp.v_hold;
            amp_cfg.interval_ms = cmd.amp.interval_ms;
            amp_cfg.duration_s  = cmd.amp.duration_s;
            amp_cfg.lp_rtia     = cmd.amp.lp_rtia;
            printf("Amp: %.0f mV, %.0f ms interval, %.0f s\n",
                   amp_cfg.v_hold, amp_cfg.interval_ms, amp_cfg.duration_s);

            ble_send_amp_start(amp_cfg.v_hold);
            int got = echem_amp(&amp_cfg, amp_results, ECHEM_MAX_POINTS, ble_send_amp_point);
            printf("Amp complete: %d points\n", got);
            ble_send_amp_end();
            break;
        }

        case CMD_GET_TEMP:
            ble_send_temp(temp_get());
            break;

        case CMD_START_PH: {
            PhConfig ph_cfg;
            ph_cfg.stabilize_s = cmd.ph.stabilize_s;
            ph_cfg.temp_c = temp_get();
            printf("pH: stabilize %.0f s, temp %.1f C\n",
                   ph_cfg.stabilize_s, ph_cfg.temp_c);

            PhResult ph_result;
            echem_ph_ocp(&ph_cfg, &ph_result);
            printf("pH: OCP=%.1f mV, pH=%.2f\n",
                   ph_result.v_ocp_mv, ph_result.ph);
            ble_send_ph_result(ph_result.v_ocp_mv, ph_result.ph, ph_result.temp_c);
            break;
        }

        case CMD_START_CLEAN:
            printf("Clean: %.0f mV, %.0f s\n", cmd.clean.v_mv, cmd.clean.duration_s);
            echem_clean(cmd.clean.v_mv, cmd.clean.duration_s);
            break;

        case CMD_START_REFS:
            printf("Ref collection starting\n");
            refs_collect(&ref_store, &cfg);
            break;

        case CMD_GET_REFS:
            refs_send(&ref_store);
            break;

        case CMD_CLEAR_REFS:
            refs_clear(&ref_store);
            printf("Refs cleared\n");
            break;

        case CMD_START_CL: {
            ClConfig cl_cfg;
            cl_cfg.v_cond    = cmd.cl.v_cond;
            cl_cfg.t_cond_ms = cmd.cl.t_cond_ms;
            cl_cfg.v_free    = cmd.cl.v_free;
            cl_cfg.v_total   = cmd.cl.v_total;
            cl_cfg.t_dep_ms  = cmd.cl.t_dep_ms;
            cl_cfg.t_meas_ms = cmd.cl.t_meas_ms;
            cl_cfg.lp_rtia   = cmd.cl.lp_rtia;

            uint32_t n_per = (uint32_t)(cl_cfg.t_meas_ms / 50.0f + 0.5f);
            if (n_per < 2) n_per = 2;
            ble_send_cl_start(2 * n_per);
            ClResult cl_result;
            int got = echem_chlorine(&cl_cfg, cl_results, ECHEM_MAX_POINTS,
                                      &cl_result, ble_send_cl_point);
            printf("Cl complete: %d points, free=%.3f uA, total=%.3f uA\n",
                   got, cl_result.i_free_ua, cl_result.i_total_ua);
            ble_send_cl_result(cl_result.i_free_ua, cl_result.i_total_ua);
            ble_send_cl_end();
            break;
        }
        }
    }
}
