/*
 * slic.c — K50835F / AG1171-class SLIC control (ESP-IDF port).
 *
 * Ported from hardware/projects/slic-phone/src/slic/Ks0835SlicController.cpp
 * (Arduino) to bare ESP-IDF 5.x GPIO driver.
 *
 * Key differences from Arduino original:
 *  - Open-drain on PD achieved via GPIO_MODE_OUTPUT_OD + gpio_set_level(PD, 1)
 *  - Ring FR toggle runs as a FreeRTOS task instead of a cooperative tick()
 *  - hook_active_high is hard-coded TRUE (matches A252ConfigStore default)
 */

#include "slic.h"
#include "board_config.h"

#include "driver/gpio.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#define TAG "slic"

/* Active-high hook polarity: HIGH on SHK = off-hook.
 * Source: hardware/projects/slic-phone/src/config/A252ConfigStore.cpp:262
 *   cfg.hook_active_high = true; */
#define SLIC_SHK_OFFHOOK_LEVEL  1

static volatile bool s_ringing = false;
static TaskHandle_t  s_ring_task = NULL;

/* ── FR toggle task (ring drive at ~25 Hz) ────────────────────────────────── */

static void slic_ring_task(void *arg)
{
    (void)arg;
    bool fr_state = false;

    for (;;) {
        if (s_ringing) {
            fr_state = !fr_state;
            gpio_set_level(PLIP_SLIC_FR, fr_state ? 1 : 0);
            ESP_LOGV(TAG, "FR=%d", fr_state ? 1 : 0);
        } else {
            /* Suspended — keep FR low while idle */
            gpio_set_level(PLIP_SLIC_FR, 0);
            fr_state = false;
        }
        vTaskDelay(pdMS_TO_TICKS(20));   /* 20 ms → ~25 Hz */
    }
}

/* ── Public API ──────────────────────────────────────────────────────────── */

esp_err_t slic_init(void)
{
    /* RM: Ring Mode output, init LOW */
    gpio_config_t rm_cfg = {
        .pin_bit_mask = (1ULL << PLIP_SLIC_RM),
        .mode         = GPIO_MODE_OUTPUT,
        .pull_up_en   = GPIO_PULLUP_DISABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type    = GPIO_INTR_DISABLE,
    };
    esp_err_t ret = gpio_config(&rm_cfg);
    if (ret != ESP_OK) return ret;
    gpio_set_level(PLIP_SLIC_RM, 0);

    /* FR: Forward/Reverse output, init LOW */
    gpio_config_t fr_cfg = {
        .pin_bit_mask = (1ULL << PLIP_SLIC_FR),
        .mode         = GPIO_MODE_OUTPUT,
        .pull_up_en   = GPIO_PULLUP_DISABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type    = GPIO_INTR_DISABLE,
    };
    ret = gpio_config(&fr_cfg);
    if (ret != ESP_OK) return ret;
    gpio_set_level(PLIP_SLIC_FR, 0);

    /* SHK: Switch Hook input with pull-up (physical line has no external pull) */
    gpio_config_t shk_cfg = {
        .pin_bit_mask = (1ULL << PLIP_SLIC_SHK),
        .mode         = GPIO_MODE_INPUT,
        .pull_up_en   = GPIO_PULLUP_ENABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type    = GPIO_INTR_DISABLE,
    };
    ret = gpio_config(&shk_cfg);
    if (ret != ESP_OK) return ret;

    /* PD: Power Down — push-pull output HIGH = SLIC active (definite power-up).
     * Previous config (open-drain HIGH) left the line floating when no pull-up
     * is present on the bench, keeping the SLIC in power-down.
     * Push-pull HIGH drives the PD pin firmly high regardless of external resistors. */
    gpio_config_t pd_cfg = {
        .pin_bit_mask = (1ULL << PLIP_SLIC_PD),
        .mode         = GPIO_MODE_OUTPUT,         /* push-pull (pas OD) */
        .pull_up_en   = GPIO_PULLUP_DISABLE,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .intr_type    = GPIO_INTR_DISABLE,
    };
    ret = gpio_config(&pd_cfg);
    if (ret != ESP_OK) return ret;
    ret = gpio_set_level(PLIP_SLIC_PD, 1);   /* HIGH franc = SLIC actif */
    if (ret != ESP_OK) return ret;

    ESP_LOGI(TAG, "slic: PD GPIO%d push-pull HIGH (SLIC power-up, franc)", PLIP_SLIC_PD);
    ESP_LOGI(TAG, "slic: pins RM=%d FR=%d SHK=%d PD=%d hook_active_high=1",
             PLIP_SLIC_RM, PLIP_SLIC_FR, PLIP_SLIC_SHK, PLIP_SLIC_PD);

    /* Spawn the FR-toggle task (runs indefinitely, toggles only when s_ringing=true) */
    BaseType_t ok = xTaskCreatePinnedToCore(slic_ring_task, "slic_ring",
                                             2048, NULL, 4, &s_ring_task, 1);
    if (ok != pdPASS) {
        ESP_LOGE(TAG, "Failed to create slic_ring task");
        return ESP_ERR_NO_MEM;
    }

    int shk = gpio_get_level(PLIP_SLIC_SHK);
    ESP_LOGI(TAG, "SLIC init OK — SHK level=%d (%s)",
             shk, (shk == SLIC_SHK_OFFHOOK_LEVEL) ? "off-hook" : "on-hook");
    return ESP_OK;
}

bool slic_is_offhook(void)
{
    return gpio_get_level(PLIP_SLIC_SHK) == SLIC_SHK_OFFHOOK_LEVEL;
}

void slic_ring_start(void)
{
    if (s_ringing) return;
    ESP_LOGI(TAG, "ring start: RM=HIGH, FR toggling at 25 Hz");
    gpio_set_level(PLIP_SLIC_RM, 1);
    s_ringing = true;
}

void slic_ring_stop(void)
{
    if (!s_ringing) return;
    ESP_LOGI(TAG, "ring stop: RM=LOW, FR=LOW");
    s_ringing = false;
    gpio_set_level(PLIP_SLIC_RM, 0);
    gpio_set_level(PLIP_SLIC_FR, 0);
}

bool slic_is_ringing(void)
{
    return s_ringing;
}
