/*
 * tones.c — France Télécom telephone tones (dial / busy / ringback).
 *
 * Generates 440 Hz sinusoid with on/off cadence written directly to the
 * I2S speaker handle (stereo — ES8388 requires L+R pairs).
 *
 * Cadences (all at 440 Hz):
 *   DIAL     : continuous
 *   BUSY     : 0.5 s on / 0.5 s off (1 s cycle)
 *   RINGBACK : 1.5 s on / 3.5 s off (5 s cycle)
 */

#include "tones.h"
#include "audio.h"

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

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

#define TAG "tones"

typedef enum { TONE_NONE, TONE_DIAL, TONE_BUSY, TONE_RINGBACK } tone_mode_t;

static volatile tone_mode_t s_mode      = TONE_NONE;
static volatile bool        s_tone_idle = true;  /* true when tone_task is NOT in i2s_channel_write */
static TaskHandle_t         s_task      = NULL;

#define SR    16000   /* sample rate (must match audio_init) */
#define FRAME 320     /* 20 ms @ 16 kHz */

/*
 * Fill `n` stereo frames (L=R = 440 Hz sine) into buf.
 * buf must hold n * 2 int16_t (stereo interleaved).
 * ph: running phase accumulator (mono sample index).
 */
static void fill_440_stereo(int16_t *buf, int n, int *ph)
{
    for (int i = 0; i < n; i++) {
        int16_t v = (int16_t)(8000.0f * sinf(2.0f * (float)M_PI * 440.0f * (*ph) / SR));
        buf[i * 2]     = v;  /* L */
        buf[i * 2 + 1] = v;  /* R */
        (*ph)++;
    }
}

static void tone_task(void *a)
{
    (void)a;
    /* Stereo buffer: FRAME mono samples × 2 channels × 2 bytes = FRAME*4 bytes */
    int16_t buf[FRAME * 2];
    int ph = 0;
    int64_t t = 0;  /* ms elapsed in current tone state */

    for (;;) {
        tone_mode_t m = s_mode;
        if (m == TONE_NONE) {
            s_tone_idle = true;
            vTaskDelay(pdMS_TO_TICKS(20));
            t = 0;
            ph = 0;
            continue;
        }

        bool on = true;
        if (m == TONE_BUSY) {
            on = (t % 1000) < 500;          /* 500ms on / 500ms off */
        } else if (m == TONE_RINGBACK) {
            on = (t % 5000) < 1500;         /* 1.5s on / 3.5s off */
        }
        /* TONE_DIAL: always on */

        if (on) {
            fill_440_stereo(buf, FRAME, &ph);
        } else {
            memset(buf, 0, sizeof(buf));
            /* Keep phase accumulator advancing so pitch is smooth on resumption */
            ph += FRAME;
        }

        size_t written = 0;
        s_tone_idle = false;
        i2s_channel_write(audio_spk_handle(), buf, sizeof(buf), &written, pdMS_TO_TICKS(50));
        t += 20;  /* each frame = 20 ms */
    }
}

static void ensure_task(void)
{
    if (!s_task) {
        xTaskCreate(tone_task, "tones", 3072, NULL, 5, &s_task);
        ESP_LOGI(TAG, "tone_task created");
    }
}

void tones_dialtone_start(void)
{
    ESP_LOGI(TAG, "dial tone start");
    audio_pa_set(true);
    ensure_task();
    s_mode = TONE_DIAL;
}

void tones_busy_start(void)
{
    ESP_LOGI(TAG, "busy tone start");
    audio_pa_set(true);
    ensure_task();
    s_mode = TONE_BUSY;
}

void tones_ringback_start(void)
{
    ESP_LOGI(TAG, "ringback tone start");
    audio_pa_set(true);
    ensure_task();
    s_mode = TONE_RINGBACK;
}

void tones_stop(void)
{
    ESP_LOGI(TAG, "tones stop");
    s_mode = TONE_NONE;
    /* Wait until tone_task is no longer inside i2s_channel_write (I2S arbitration).
     * Guard: max 200 ms so we never block indefinitely. */
    const int max_wait_ms = 200;
    int waited_ms = 0;
    while (!s_tone_idle && waited_ms < max_wait_ms) {
        vTaskDelay(pdMS_TO_TICKS(5));
        waited_ms += 5;
    }
    if (waited_ms >= max_wait_ms) {
        ESP_LOGW(TAG, "tones_stop: timeout waiting for tone_task idle");
    }
}
