#include <stdio.h>
#include <string.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s_std.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "esp_codec_dev.h"
#include "esp_codec_dev_defaults.h"
#include "esp_codec_dev_vol.h"
#include "usb_device_uac.h"
#include "dummy_codec.h"

#define APP_I2S_PORT 0
#define APP_I2S_DMA_DESC_NUM 8
#define APP_I2S_DMA_FRAME_NUM 96
#define APP_I2S_MCLK_IO 16
#define APP_I2S_BCK_IO 21
#define APP_I2S_WS_IO 22
#define APP_I2S_DO_IO 20
#define APP_I2S_DI_IO 11
#define APP_CODEC_VOLUME 60

static const char *TAG = "sample_app";

static i2s_chan_handle_t i2s_tx_handle = NULL;
static i2s_chan_handle_t i2s_rx_handle = NULL;
static esp_codec_dev_handle_t codec_handle = NULL;

static void i2s_driver_init(void)
{
    i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(APP_I2S_PORT, I2S_ROLE_MASTER);
    chan_cfg.auto_clear = true;
    chan_cfg.dma_desc_num = APP_I2S_DMA_DESC_NUM;
    chan_cfg.dma_frame_num = APP_I2S_DMA_FRAME_NUM;
    ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &i2s_tx_handle, &i2s_rx_handle));

    i2s_std_config_t std_cfg = {
        .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(CONFIG_UAC_SAMPLE_RATE),
        .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(CONFIG_UAC_BYTES_PER_SAMPLE * 8,
                                                       I2S_SLOT_MODE_STEREO),
        .gpio_cfg = {
            .mclk = APP_I2S_MCLK_IO,
            .bclk = APP_I2S_BCK_IO,
            .ws = APP_I2S_WS_IO,
            .dout = APP_I2S_DO_IO,
            .din = APP_I2S_DI_IO,
            .invert_flags = {
                .mclk_inv = false,
                .bclk_inv = false,
                .ws_inv = false,
            },
        },
    };

    ESP_ERROR_CHECK(i2s_channel_init_std_mode(i2s_rx_handle, &std_cfg));
    ESP_ERROR_CHECK(i2s_channel_init_std_mode(i2s_tx_handle, &std_cfg));
    ESP_ERROR_CHECK(i2s_channel_enable(i2s_rx_handle));
    ESP_ERROR_CHECK(i2s_channel_enable(i2s_tx_handle));
}

static void codec_init(void)
{
    audio_codec_i2s_cfg_t i2s_cfg = {
        .port = APP_I2S_PORT,
        .rx_handle = i2s_rx_handle,
        .tx_handle = i2s_tx_handle,
        .clk_src = 0,
    };
    const audio_codec_data_if_t *data_if = audio_codec_new_i2s_data(&i2s_cfg);
    if (!data_if) {
        ESP_LOGE(TAG, "failed to create i2s data interface");
        return;
    }

    const audio_codec_gpio_if_t *gpio_if = audio_codec_new_gpio();
    if (!gpio_if) {
        ESP_LOGE(TAG, "failed to create gpio interface");
        return;
    }

    dummy_codec_cfg_t dummy_cfg = {
        .gpio_if = gpio_if,
        .pa_pin = -1,
        .pa_reverted = false,
    };
    const audio_codec_if_t *dummy_if = dummy_codec_new(&dummy_cfg);
    if (!dummy_if) {
        ESP_LOGE(TAG, "failed to create dummy codec");
        return;
    }

    esp_codec_dev_cfg_t dev_cfg = {
        .codec_if = dummy_if,
        .data_if = data_if,
        .dev_type = ESP_CODEC_DEV_TYPE_OUT,
    };
    codec_handle = esp_codec_dev_new(&dev_cfg);
    if (!codec_handle) {
        ESP_LOGE(TAG, "failed to create codec device");
        return;
    }

    esp_codec_dev_sample_info_t sample_cfg = {
        .bits_per_sample = CONFIG_UAC_BYTES_PER_SAMPLE * 8,
        .channel = CONFIG_UAC_SPEAKER_CHANNEL_NUM,
        .channel_mask = (1U << CONFIG_UAC_SPEAKER_CHANNEL_NUM) - 1,
        .sample_rate = CONFIG_UAC_SAMPLE_RATE,
    };

    if (esp_codec_dev_open(codec_handle, &sample_cfg) != ESP_CODEC_DEV_OK) {
        ESP_LOGE(TAG, "failed to open codec device");
        return;
    }

    if (esp_codec_dev_set_out_vol(codec_handle, APP_CODEC_VOLUME) != ESP_CODEC_DEV_OK) {
        ESP_LOGE(TAG, "failed to configure codec volume");
    }

    ESP_LOGI(TAG, "dummy codec and I2S data interface initialized");
}

static esp_err_t usb_uac_device_output_cb(uint8_t *buf, size_t len, void *arg)
{
    esp_err_t ret = esp_codec_dev_write(codec_handle, buf, len);
    if (ret != ESP_CODEC_DEV_OK) {
        ESP_LOGE(TAG, "failed to write audio data to codec (%s)", esp_err_to_name(ret));
        return ret;
    }

    return ESP_OK;
}

static void usb_uac_device_set_mute_cb(uint32_t mute, void *arg)
{
    esp_err_t ret = esp_codec_dev_set_out_mute(codec_handle, !!mute);
    if (ret != ESP_CODEC_DEV_OK) {
        ESP_LOGE(TAG, "failed to set mute state (%s)", esp_err_to_name(ret));
    }
}

static void usb_uac_device_set_volume_cb(uint32_t volume, void *arg)
{
    esp_err_t ret = esp_codec_dev_set_out_vol(codec_handle, volume);
    if (ret != ESP_CODEC_DEV_OK) {
        ESP_LOGE(TAG, "failed to set output volume (%s)", esp_err_to_name(ret));
    }
}

static void usb_uac_device_init(void)
{
    uac_device_config_t config = {
        .skip_tinyusb_init = false,
        .output_cb = usb_uac_device_output_cb,
        .input_cb = NULL,
        .set_mute_cb = usb_uac_device_set_mute_cb,
        .set_volume_cb = usb_uac_device_set_volume_cb,
        .cb_ctx = NULL,
    };

    ESP_ERROR_CHECK(uac_device_init(&config));
}

void app_main(void)
{
    i2s_driver_init();
    codec_init();
    usb_uac_device_init();

    while (1) {
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}
