/**
 * Audio Test - Record & Playback
 *
 * Records from the ES8311 microphone via I2S and plays it back through
 * the FM8002E speaker amplifier. Uses the Tactility kernel I2S controller API.
 *
 * UI: Two buttons - Record and Play
 *   Record: holds recording for up to ~3 seconds (16kHz 16-bit mono ≈ 96KB)
 *   Play:   plays back the last recorded buffer
 */

#include <tt_app.h>
#include <tt_lvgl.h>
#include <tt_lvgl_toolbar.h>

#include <tactility/device.h>
#include <tactility/drivers/i2s_controller.h>

#include <string.h>
#include <stdlib.h>

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

#define TAG "AudioTest"

/* ─── Audio params ─── */
#define SAMPLE_RATE      16000
#define BITS_PER_SAMPLE  16
#define RECORD_SECONDS   3

/* 16-bit mono → 2 bytes per sample */
#define AUDIO_BUF_SIZE   (SAMPLE_RATE * (BITS_PER_SAMPLE / 8) * RECORD_SECONDS)

/* Chunk size for read/write calls (512 samples = 1024 bytes at 16-bit) */
#define CHUNK_SAMPLES    512
#define CHUNK_BYTES      (CHUNK_SAMPLES * (BITS_PER_SAMPLE / 8))

/* ─── App state ─── */
typedef enum {
    STATE_IDLE,
    STATE_RECORDING,
    STATE_PLAYING
} AudioState;

typedef struct {
    struct Device* i2s_dev;
    uint8_t*       audio_buf;
    size_t         recorded_bytes;
    AudioState     state;
    lv_obj_t*      btn_record;
    lv_obj_t*      btn_play;
    lv_obj_t*      lbl_status;
    lv_obj_t*      bar_progress;
    TaskHandle_t   task_handle;
} AppCtx;

/* ─── Forward decls ─── */
static void record_task(void* arg);
static void play_task(void* arg);
static void update_ui(AppCtx* ctx);

/* ─── Callbacks ─── */
static void on_record_click(lv_event_t* e) {
    AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
    if (ctx->state != STATE_IDLE) return;
    if (ctx->i2s_dev == NULL) return;

    ctx->state = STATE_RECORDING;
    ctx->recorded_bytes = 0;
    update_ui(ctx);

    xTaskCreate(record_task, "audio_rec", 4096, ctx, 5, &ctx->task_handle);
}

static void on_play_click(lv_event_t* e) {
    AppCtx* ctx = (AppCtx*)lv_event_get_user_data(e);
    if (ctx->state != STATE_IDLE) return;
    if (ctx->recorded_bytes == 0) return;
    if (ctx->i2s_dev == NULL) return;

    ctx->state = STATE_PLAYING;
    update_ui(ctx);

    xTaskCreate(play_task, "audio_play", 4096, ctx, 5, &ctx->task_handle);
}

/* ─── Update UI labels/buttons from any context ─── */
static void update_ui(AppCtx* ctx) {
    if (ctx->lbl_status == NULL) return;

    switch (ctx->state) {
        case STATE_RECORDING:
            lv_label_set_text(ctx->lbl_status, "🔴 Recording...");
            lv_obj_add_state(ctx->btn_record, LV_STATE_DISABLED);
            lv_obj_add_state(ctx->btn_play, LV_STATE_DISABLED);
            break;
        case STATE_PLAYING:
            lv_label_set_text(ctx->lbl_status, "🔊 Playing...");
            lv_obj_add_state(ctx->btn_record, LV_STATE_DISABLED);
            lv_obj_add_state(ctx->btn_play, LV_STATE_DISABLED);
            break;
        case STATE_IDLE:
        default:
            if (ctx->recorded_bytes > 0) {
                char buf[64];
                snprintf(buf, sizeof(buf), "Ready (%u bytes recorded)", (unsigned)ctx->recorded_bytes);
                lv_label_set_text(ctx->lbl_status, buf);
            } else {
                lv_label_set_text(ctx->lbl_status, "Press Record to capture audio");
            }
            lv_obj_clear_state(ctx->btn_record, LV_STATE_DISABLED);
            if (ctx->recorded_bytes > 0) {
                lv_obj_clear_state(ctx->btn_play, LV_STATE_DISABLED);
            } else {
                lv_obj_add_state(ctx->btn_play, LV_STATE_DISABLED);
            }
            break;
    }
}

/* ─── Record task ─── */
static void record_task(void* arg) {
    AppCtx* ctx = (AppCtx*)arg;
    size_t total = 0;
    size_t bytes_read = 0;

    /* Configure I2S for recording */
    struct I2sConfig cfg = {
        .communication_format = I2S_FORMAT_STAND_I2S,
        .sample_rate = SAMPLE_RATE,
        .bits_per_sample = BITS_PER_SAMPLE,
        .channel_left = 0,
        .channel_right = I2S_CHANNEL_NONE
    };

    device_lock(ctx->i2s_dev);
    error_t err = i2s_controller_set_config(ctx->i2s_dev, &cfg);
    device_unlock(ctx->i2s_dev);

    if (err != ERROR_NONE) {
        ESP_LOGE(TAG, "Failed to set I2S config for recording: %d", err);
        tt_lvgl_lock(portMAX_DELAY);
        ctx->state = STATE_IDLE;
        update_ui(ctx);
        tt_lvgl_unlock();
        vTaskDelete(NULL);
        return;
    }

    ESP_LOGI(TAG, "Recording started (max %d bytes)", AUDIO_BUF_SIZE);

    while (total < AUDIO_BUF_SIZE && ctx->state == STATE_RECORDING) {
        size_t remaining = AUDIO_BUF_SIZE - total;
        size_t to_read = (remaining < CHUNK_BYTES) ? remaining : CHUNK_BYTES;

        err = i2s_controller_read(ctx->i2s_dev,
                                  ctx->audio_buf + total,
                                  to_read,
                                  &bytes_read,
                                  pdMS_TO_TICKS(1000));
        if (err != ERROR_NONE) {
            ESP_LOGE(TAG, "I2S read error: %d", err);
            break;
        }
        total += bytes_read;

        /* Update progress bar */
        int pct = (int)((total * 100) / AUDIO_BUF_SIZE);
        tt_lvgl_lock(portMAX_DELAY);
        lv_bar_set_value(ctx->bar_progress, pct, LV_ANIM_OFF);
        tt_lvgl_unlock();
    }

    ctx->recorded_bytes = total;
    ctx->state = STATE_IDLE;
    ctx->task_handle = NULL;

    ESP_LOGI(TAG, "Recording complete: %u bytes", (unsigned)total);

    tt_lvgl_lock(portMAX_DELAY);
    lv_bar_set_value(ctx->bar_progress, 0, LV_ANIM_OFF);
    update_ui(ctx);
    tt_lvgl_unlock();
    vTaskDelete(NULL);
}

/* ─── Play task ─── */
static void play_task(void* arg) {
    AppCtx* ctx = (AppCtx*)arg;
    size_t total = 0;
    size_t bytes_written = 0;

    /* Configure I2S for playback */
    struct I2sConfig cfg = {
        .communication_format = I2S_FORMAT_STAND_I2S,
        .sample_rate = SAMPLE_RATE,
        .bits_per_sample = BITS_PER_SAMPLE,
        .channel_left = 0,
        .channel_right = I2S_CHANNEL_NONE
    };

    device_lock(ctx->i2s_dev);
    error_t err = i2s_controller_set_config(ctx->i2s_dev, &cfg);
    device_unlock(ctx->i2s_dev);

    if (err != ERROR_NONE) {
        ESP_LOGE(TAG, "Failed to set I2S config for playback: %d", err);
        tt_lvgl_lock(portMAX_DELAY);
        ctx->state = STATE_IDLE;
        update_ui(ctx);
        tt_lvgl_unlock();
        vTaskDelete(NULL);
        return;
    }

    ESP_LOGI(TAG, "Playback started (%u bytes)", (unsigned)ctx->recorded_bytes);

    while (total < ctx->recorded_bytes && ctx->state == STATE_PLAYING) {
        size_t remaining = ctx->recorded_bytes - total;
        size_t to_write = (remaining < CHUNK_BYTES) ? remaining : CHUNK_BYTES;

        err = i2s_controller_write(ctx->i2s_dev,
                                   ctx->audio_buf + total,
                                   to_write,
                                   &bytes_written,
                                   pdMS_TO_TICKS(1000));
        if (err != ERROR_NONE) {
            ESP_LOGE(TAG, "I2S write error: %d", err);
            break;
        }
        total += bytes_written;

        /* Update progress bar */
        int pct = (int)((total * 100) / ctx->recorded_bytes);
        tt_lvgl_lock(portMAX_DELAY);
        lv_bar_set_value(ctx->bar_progress, pct, LV_ANIM_OFF);
        tt_lvgl_unlock();
    }

    ctx->state = STATE_IDLE;
    ctx->task_handle = NULL;

    ESP_LOGI(TAG, "Playback complete");

    tt_lvgl_lock(portMAX_DELAY);
    lv_bar_set_value(ctx->bar_progress, 0, LV_ANIM_OFF);
    update_ui(ctx);
    tt_lvgl_unlock();
    vTaskDelete(NULL);
}

/* ─── App lifecycle ─── */
static AppCtx g_ctx;

static void onShowApp(AppHandle app, void* data, lv_obj_t* parent) {
    memset(&g_ctx, 0, sizeof(g_ctx));

    /* Allocate audio buffer */
    g_ctx.audio_buf = (uint8_t*)malloc(AUDIO_BUF_SIZE);
    if (g_ctx.audio_buf == NULL) {
        ESP_LOGE(TAG, "Failed to allocate audio buffer (%d bytes)", AUDIO_BUF_SIZE);
    }

    /* Find I2S device */
    g_ctx.i2s_dev = device_find_by_name("i2s0");
    if (g_ctx.i2s_dev == NULL) {
        ESP_LOGE(TAG, "I2S device 'i2s0' not found!");
    } else {
        ESP_LOGI(TAG, "Found I2S device: %s", g_ctx.i2s_dev->name);
    }

    /* ─── UI ─── */
    lv_obj_t* toolbar = tt_lvgl_toolbar_create_for_app(parent, app);
    lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);

    /* Status label */
    g_ctx.lbl_status = lv_label_create(parent);
    lv_label_set_text(g_ctx.lbl_status, "Initializing...");
    lv_obj_set_width(g_ctx.lbl_status, lv_pct(90));
    lv_label_set_long_mode(g_ctx.lbl_status, LV_LABEL_LONG_WRAP);
    lv_obj_set_style_text_align(g_ctx.lbl_status, LV_TEXT_ALIGN_CENTER, 0);
    lv_obj_align(g_ctx.lbl_status, LV_ALIGN_TOP_MID, 0, 50);

    /* Progress bar */
    g_ctx.bar_progress = lv_bar_create(parent);
    lv_obj_set_size(g_ctx.bar_progress, lv_pct(80), 10);
    lv_bar_set_range(g_ctx.bar_progress, 0, 100);
    lv_bar_set_value(g_ctx.bar_progress, 0, LV_ANIM_OFF);
    lv_obj_align(g_ctx.bar_progress, LV_ALIGN_CENTER, 0, -10);

    /* Button container */
    lv_obj_t* btn_container = lv_obj_create(parent);
    lv_obj_remove_style_all(btn_container);
    lv_obj_set_size(btn_container, lv_pct(90), 50);
    lv_obj_set_flex_flow(btn_container, LV_FLEX_FLOW_ROW);
    lv_obj_set_flex_align(btn_container, LV_FLEX_ALIGN_SPACE_EVENLY, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
    lv_obj_align(btn_container, LV_ALIGN_CENTER, 0, 30);

    /* Record button */
    g_ctx.btn_record = lv_btn_create(btn_container);
    lv_obj_set_size(g_ctx.btn_record, 100, 40);
    lv_obj_t* lbl_rec = lv_label_create(g_ctx.btn_record);
    lv_label_set_text(lbl_rec, LV_SYMBOL_AUDIO " Record");
    lv_obj_center(lbl_rec);
    lv_obj_add_event_cb(g_ctx.btn_record, on_record_click, LV_EVENT_CLICKED, &g_ctx);

    /* Play button */
    g_ctx.btn_play = lv_btn_create(btn_container);
    lv_obj_set_size(g_ctx.btn_play, 100, 40);
    lv_obj_t* lbl_play = lv_label_create(g_ctx.btn_play);
    lv_label_set_text(lbl_play, LV_SYMBOL_PLAY " Play");
    lv_obj_center(lbl_play);
    lv_obj_add_event_cb(g_ctx.btn_play, on_play_click, LV_EVENT_CLICKED, &g_ctx);

    /* Check initialization status */
    if (g_ctx.i2s_dev == NULL) {
        lv_label_set_text(g_ctx.lbl_status, "ERROR: I2S device not found");
        lv_obj_add_state(g_ctx.btn_record, LV_STATE_DISABLED);
        lv_obj_add_state(g_ctx.btn_play, LV_STATE_DISABLED);
    } else if (g_ctx.audio_buf == NULL) {
        lv_label_set_text(g_ctx.lbl_status, "ERROR: Out of memory");
        lv_obj_add_state(g_ctx.btn_record, LV_STATE_DISABLED);
        lv_obj_add_state(g_ctx.btn_play, LV_STATE_DISABLED);
    } else {
        g_ctx.state = STATE_IDLE;
        update_ui(&g_ctx);
    }
}

int main(int argc, char* argv[]) {
    tt_app_register((AppRegistration) {
        .onShow = onShowApp
    });
    return 0;
}
