/*
 * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <esp_types.h>
#include <sys/lock.h>
#include "sdkconfig.h"
#if CONFIG_I2S_ENABLE_DEBUG_LOG
// The local log level must be defined before including esp_log.h
// Set the maximum log level for this source file
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#endif
#include "esp_log.h"
#include "esp_check.h"
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "esp_clk_tree.h"
#include "hal/hal_utils.h"
#include "hal/lp_i2s_hal.h"
#include "hal/lp_i2s_ll.h"
#include "driver/i2s_types.h"
#include "driver/lp_i2s.h"
#include "esp_private/periph_ctrl.h"
#include "esp_private/i2s_platform.h"
#include "esp_private/lp_i2s_private.h"
#include "i2s_private.h"
#include "soc/i2s_periph.h"

#define LP_I2S_MEM_ALLOC_CAPS      (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)

static const char *TAG = "LP_I2S";

static esp_err_t s_i2s_register_channel(lp_i2s_controller_t *ctlr, i2s_dir_t dir);
static inline bool s_lp_i2s_take_available_channel(lp_i2s_controller_t *ctlr, uint8_t chan_search_mask);
static void s_i2s_default_isr(void *arg);

esp_err_t lp_i2s_new_channel(const lp_i2s_chan_config_t *chan_cfg, lp_i2s_chan_handle_t *ret_tx_handle, lp_i2s_chan_handle_t *ret_rx_handle)
{
#if CONFIG_I2S_ENABLE_DEBUG_LOG
    esp_log_level_set(TAG, ESP_LOG_DEBUG);
#endif
    ESP_RETURN_ON_FALSE(chan_cfg, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
    ESP_RETURN_ON_FALSE(chan_cfg->id < SOC_LP_I2S_NUM, ESP_ERR_INVALID_ARG, TAG, "invalid LP I2S port id");
    ESP_RETURN_ON_FALSE(chan_cfg->role == I2S_ROLE_SLAVE, ESP_ERR_INVALID_ARG, TAG, "invalid argument: LP I2S not support master");
#if !LP_I2S_LL_TX_SUPPORTED
    ESP_RETURN_ON_FALSE(!ret_tx_handle, ESP_ERR_INVALID_ARG, TAG, "tx not supported");
#endif
#if !LP_I2S_LL_RX_SUPPORTED
    ESP_RETURN_ON_FALSE(!ret_rx_handle, ESP_ERR_INVALID_ARG, TAG, "rx not supported");
#endif
    ESP_RETURN_ON_FALSE(chan_cfg->threshold % 4 == 0, ESP_ERR_INVALID_ARG, TAG, "threshold must be in multiple of 4");
    ESP_RETURN_ON_FALSE(chan_cfg->threshold <= LP_I2S_LL_RX_MEM_THRESH_BYTES_MAX, ESP_ERR_INVALID_ARG, TAG, "invalid threshold");

    esp_err_t ret = ESP_OK;
    ESP_RETURN_ON_ERROR(i2s_platform_acquire_occupation(I2S_CTLR_LP, chan_cfg->id, "lp_i2s_driver"), TAG, "failed to acquire lp i2s controller");

    lp_i2s_controller_t *ctlr = heap_caps_calloc(1, sizeof(lp_i2s_controller_t), LP_I2S_MEM_ALLOC_CAPS);
    ESP_RETURN_ON_FALSE(ctlr, ESP_ERR_NO_MEM, TAG, "no mem");
    ctlr->id = chan_cfg->id;

    ESP_GOTO_ON_ERROR(esp_intr_alloc(lp_i2s_periph_signal[ctlr->id].irq, ESP_INTR_FLAG_IRAM, s_i2s_default_isr, ctlr, &ctlr->intr), err1, TAG, "allocate interrupt failed");

    uint8_t chan_search_mask = 0;
#if LP_I2S_LL_TX_SUPPORTED
    chan_search_mask |= ret_tx_handle ? I2S_DIR_TX : 0;
#endif
#if LP_I2S_LL_RX_SUPPORTED
    chan_search_mask |= ret_rx_handle ? I2S_DIR_RX : 0;
#endif

    portENTER_CRITICAL(&g_i2s.spinlock);
    g_i2s.lp_controller[chan_cfg->id] = ctlr;
    portEXIT_CRITICAL(&g_i2s.spinlock);

    bool channel_found = s_lp_i2s_take_available_channel(ctlr, chan_search_mask);
    ESP_GOTO_ON_FALSE(channel_found, ESP_ERR_NOT_FOUND, err1, TAG, "no available channel found");

    if (ret_rx_handle) {
        ESP_GOTO_ON_ERROR(s_i2s_register_channel(ctlr, I2S_DIR_RX), err1, TAG, "register lp i2s rx channel failed");
        ctlr->rx_chan->role = chan_cfg->role;
        ctlr->rx_chan->threshold = chan_cfg->threshold;
        *ret_rx_handle = ctlr->rx_chan;
        ESP_LOGD(TAG, "rx channel is registered on LP_I2S%d successfully", ctlr->id);

        ctlr->rx_chan->semphr = xSemaphoreCreateBinaryWithCaps(LP_I2S_MEM_ALLOC_CAPS);
        ESP_GOTO_ON_FALSE(ctlr->rx_chan->semphr, ESP_ERR_NO_MEM, err0, TAG, "No memory for binary semaphore");
    }

    PERIPH_RCC_ATOMIC() {
        lp_i2s_ll_enable_module_clock(chan_cfg->id, true);
        lp_i2s_ll_reset_module_clock(chan_cfg->id);
        if (chan_search_mask & I2S_DIR_RX) {
            lp_i2s_ll_enable_rx_module_clock(chan_cfg->id, true);
        }
    }
    lp_i2s_hal_init(&ctlr->hal, ctlr->id);
    lp_i2s_ll_enable_mem(chan_cfg->id, true);
    lp_i2s_ll_clk_gate_en(ctlr->hal.dev, true);
    lp_i2s_ll_rx_mem_clk_gate_en(ctlr->hal.dev, true);
    lp_i2s_ll_rx_reg_clk_gate_en(ctlr->hal.dev, true);
    lp_i2s_ll_rx_reset(ctlr->hal.dev);
    lp_i2s_ll_rx_reset_fifo(ctlr->hal.dev);
    lp_i2s_ll_rx_reset_fifo_mem(ctlr->hal.dev);

    if (ctlr->rx_chan->role == I2S_ROLE_SLAVE) {
        lp_i2s_ll_set_rx_master_slave_mode(ctlr->hal.dev, true);
    }
    lp_i2s_ll_set_rx_stop_mode(ctlr->hal.dev, LP_I2S_LL_RX_STOP_MODE_START_CLEAR);
    lp_i2s_ll_set_rx_mem_threshold(ctlr->hal.dev, chan_cfg->threshold / 4);
    lp_i2s_ll_rx_clear_interrupt_status(ctlr->hal.dev, LP_I2S_LL_EVENT_RX_MEM_THRESHOLD_INT);

    return ESP_OK;

err0:
    if (ctlr->rx_chan) {
        vSemaphoreDeleteWithCaps(ctlr->rx_chan->semphr);
        free(ctlr->rx_chan);
        ctlr->rx_chan = NULL;
    }

err1:
    /* if the controller object has no channel, find the corresponding global object and destroy it */
    if (ctlr != NULL && ctlr->rx_chan == NULL) {
        esp_intr_free(ctlr->intr);
        i2s_platform_release_occupation(I2S_CTLR_LP, chan_cfg->id);
        free(ctlr);
    }
    return ret;
}

esp_err_t lp_i2s_channel_enable(lp_i2s_chan_handle_t chan)
{
    ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
    ESP_RETURN_ON_FALSE(chan->state < I2S_CHAN_STATE_RUNNING, ESP_ERR_INVALID_STATE, TAG, "the channel is in enabled state already");

    lp_i2s_evt_data_t evt_data = {};
    if (chan->cbs.on_request_new_trans) {
        chan->cbs.on_request_new_trans(chan, &evt_data, chan->user_data);
        ESP_RETURN_ON_FALSE(evt_data.trans.buffer, ESP_ERR_INVALID_STATE, TAG, "no transaction buffer");
        ESP_RETURN_ON_FALSE(((evt_data.trans.buflen % LP_I2S_LL_RX_MEM_POP_BYTES) == 0), ESP_ERR_INVALID_STATE, TAG, "invalid transaction buflen, not aligned to %d", LP_I2S_LL_RX_MEM_POP_BYTES);
        ESP_LOGD(TAG, "evt_data.trans.buffer: %p, evt_data.trans.buflen: %d", evt_data.trans.buffer, evt_data.trans.buflen);
        chan->trans = evt_data.trans;
    }

    chan->state = I2S_CHAN_STATE_RUNNING;
    portENTER_CRITICAL(&g_i2s.spinlock);
    lp_i2s_ll_rx_enable_interrupt(chan->ctlr->hal.dev, LP_I2S_LL_EVENT_RX_MEM_THRESHOLD_INT, true);
    lp_i2s_ll_rx_start(chan->ctlr->hal.dev);
    portEXIT_CRITICAL(&g_i2s.spinlock);

    return ESP_OK;
}

esp_err_t lp_i2s_channel_read(lp_i2s_chan_handle_t chan, lp_i2s_trans_t *trans, uint32_t timeout_ms)
{
    ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
    ESP_RETURN_ON_FALSE(chan->state == I2S_CHAN_STATE_RUNNING, ESP_ERR_INVALID_STATE, TAG, "the channel isn't enabled");
    ESP_RETURN_ON_FALSE(!chan->cbs.on_request_new_trans, ESP_ERR_INVALID_STATE, TAG, "on_request_new_trans registered, no use of this read API");

    TickType_t ticks_to_wait = timeout_ms / portTICK_PERIOD_MS;
    if (timeout_ms == LP_I2S_MAX_DELAY) {
        ticks_to_wait = portMAX_DELAY;
    }

    BaseType_t r = xSemaphoreTake(chan->semphr, ticks_to_wait);
    if (r != pdTRUE) {
        ESP_LOGW(TAG, "lp_i2s read API, new data receiving timeout");
        return ESP_ERR_TIMEOUT;
    }

    size_t len = MIN(lp_i2s_ll_get_rx_mem_fifo_cnt(chan->ctlr->hal.dev), trans->buflen);
    lp_i2s_ll_read_buffer(chan->ctlr->hal.dev, trans->buffer, len);
    trans->received_size = len;
    portENTER_CRITICAL(&g_i2s.spinlock);
    lp_i2s_ll_rx_clear_interrupt_status(chan->ctlr->hal.dev, LP_I2S_LL_EVENT_RX_MEM_THRESHOLD_INT);
    lp_i2s_ll_rx_enable_interrupt(chan->ctlr->hal.dev, LP_I2S_LL_EVENT_RX_MEM_THRESHOLD_INT, true);
    portEXIT_CRITICAL(&g_i2s.spinlock);

    return ESP_OK;
}

esp_err_t lp_i2s_channel_read_until_bytes(lp_i2s_chan_handle_t chan, lp_i2s_trans_t *trans)
{
    size_t received_size = 0;
    size_t recv_len = trans->buflen;
    lp_i2s_trans_t t = {};
    while (1) {
        t.buffer = trans->buffer + received_size;
        t.buflen = MIN(chan->threshold, recv_len);
        ESP_RETURN_ON_ERROR(lp_i2s_channel_read(chan, &t, LP_I2S_MAX_DELAY), TAG, "failed to do lp i2s read");
        received_size += t.received_size;
        recv_len -= t.received_size;
        ESP_LOGD(TAG, "received_size: %d, recv_len: %d, t.received_size: %d", received_size, recv_len, t.received_size);
        if (recv_len == 0) {
            break;
        }
    }
    trans->received_size = received_size;

    return ESP_OK;
}

esp_err_t lp_i2s_channel_disable(lp_i2s_chan_handle_t chan)
{
    ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
    ESP_RETURN_ON_FALSE(chan->state  > I2S_CHAN_STATE_READY, ESP_ERR_INVALID_STATE, TAG, "the channel is disabled already");
    chan->state = I2S_CHAN_STATE_READY;

    portENTER_CRITICAL(&g_i2s.spinlock);
    lp_i2s_ll_rx_enable_interrupt(chan->ctlr->hal.dev, LP_I2S_LL_EVENT_RX_MEM_THRESHOLD_INT, false);
    lp_i2s_ll_rx_stop(chan->ctlr->hal.dev);
    portEXIT_CRITICAL(&g_i2s.spinlock);

    return ESP_OK;
}

esp_err_t lp_i2s_del_channel(lp_i2s_chan_handle_t chan)
{
    ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
    ESP_RETURN_ON_FALSE(chan->state < I2S_CHAN_STATE_RUNNING, ESP_ERR_INVALID_STATE, TAG, "the channel can't be deleted unless it is disabled");

    int id = chan->ctlr->id;
    portENTER_CRITICAL(&g_i2s.spinlock);
    if (chan->dir == I2S_DIR_RX) {
        g_i2s.lp_controller[chan->ctlr->id]->rx_chan = NULL;
    }

    g_i2s.lp_controller[id] = NULL;
    portEXIT_CRITICAL(&g_i2s.spinlock);

    ESP_RETURN_ON_ERROR(esp_intr_free(chan->ctlr->intr), TAG, "failed to free intr");
    vSemaphoreDeleteWithCaps(chan->semphr);
    ESP_RETURN_ON_ERROR(i2s_platform_release_occupation(I2S_CTLR_LP, id), TAG, "failed to release lp i2s controller");
    free(chan->ctlr);
    free(chan);

    return ESP_OK;
}

#ifndef __cplusplus
/* To make sure the i2s_event_callbacks_t is same size as i2s_event_callbacks_internal_t */
_Static_assert(sizeof(lp_i2s_evt_cbs_t) == sizeof(lp_i2s_evt_cbs_internal_t), "Invalid size of lp_i2s_evt_cbs_t structure");
#endif

esp_err_t lp_i2s_register_event_callbacks(lp_i2s_chan_handle_t handle, const lp_i2s_evt_cbs_t *cbs, void *user_data)
{
    ESP_RETURN_ON_FALSE(handle && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
    ESP_RETURN_ON_FALSE(handle->state < I2S_CHAN_STATE_RUNNING, ESP_ERR_INVALID_STATE, TAG, "the channel is in enabled state already");

    handle->cbs.on_thresh_met = cbs->on_thresh_met;
    handle->cbs.on_request_new_trans = cbs->on_request_new_trans;
    handle->user_data = user_data;

    return ESP_OK;
}

static inline bool s_lp_i2s_take_available_channel(lp_i2s_controller_t *ctlr, uint8_t chan_search_mask)
{
    bool is_available = false;

    portENTER_CRITICAL(&g_i2s.spinlock);
    if (!(chan_search_mask & ctlr->chan_occupancy)) {
        ctlr->chan_occupancy |= chan_search_mask;
        is_available = true;
    }
    portEXIT_CRITICAL(&g_i2s.spinlock);
    return is_available;
}

static esp_err_t s_i2s_register_channel(lp_i2s_controller_t *ctlr, i2s_dir_t dir)
{
    esp_err_t ret = ESP_OK;

    lp_i2s_chan_handle_t new_chan = (lp_i2s_chan_handle_t)heap_caps_calloc(1, sizeof(struct lp_i2s_channel_obj_t), LP_I2S_MEM_ALLOC_CAPS);
    ESP_RETURN_ON_FALSE(new_chan, ESP_ERR_NO_MEM, TAG, "No memory for new channel");
    new_chan->mode = I2S_COMM_MODE_NONE;
    new_chan->role = I2S_ROLE_MASTER;
    new_chan->dir = dir;
    new_chan->state = I2S_CHAN_STATE_REGISTER;
    new_chan->ctlr = ctlr;

    if (dir == I2S_DIR_RX) {
        ctlr->rx_chan = new_chan;
    }

    return ret;
}

/*---------------------------------------------------------------
                      INTR
---------------------------------------------------------------*/
static void IRAM_ATTR s_i2s_default_isr(void *arg)
{
    lp_i2s_controller_t *ctlr = (lp_i2s_controller_t *)arg;
    bool need_yield = false;
    BaseType_t high_task_woken = pdFALSE;
    ESP_DRAM_LOGD(TAG, "in isr, rx_mem_fifo_cnt: %d bytes", lp_i2s_ll_get_rx_mem_fifo_cnt(ctlr->hal.dev));

    if (ctlr->rx_chan->cbs.on_request_new_trans) {
        size_t len = MIN(lp_i2s_ll_get_rx_mem_fifo_cnt(ctlr->hal.dev), ctlr->rx_chan->trans.buflen);
        ESP_DRAM_LOGD(TAG, "len: %d", len);
        lp_i2s_ll_read_buffer(ctlr->hal.dev, ctlr->rx_chan->trans.buffer, len);
        lp_i2s_ll_rx_clear_interrupt_status(ctlr->hal.dev, LP_I2S_LL_EVENT_RX_MEM_THRESHOLD_INT);
        ctlr->rx_chan->trans.received_size = len;

        lp_i2s_evt_data_t edata = {
            .trans = ctlr->rx_chan->trans,
        };
        if (ctlr->rx_chan->cbs.on_thresh_met) {
            need_yield |= ctlr->rx_chan->cbs.on_thresh_met(ctlr->rx_chan, &edata, ctlr->rx_chan->user_data);
        }

        assert(ctlr->rx_chan->cbs.on_request_new_trans);
        lp_i2s_evt_data_t new_edata = {};
        ctlr->rx_chan->cbs.on_request_new_trans(ctlr->rx_chan, &new_edata, ctlr->rx_chan->user_data);
        memcpy(&ctlr->rx_chan->trans, &new_edata.trans, sizeof(lp_i2s_trans_t));

    } else {
        portENTER_CRITICAL_ISR(&g_i2s.spinlock);
        lp_i2s_ll_rx_enable_interrupt(ctlr->hal.dev, LP_I2S_LL_EVENT_RX_MEM_THRESHOLD_INT, false);
        portEXIT_CRITICAL_ISR(&g_i2s.spinlock);
        xSemaphoreGiveFromISR(ctlr->rx_chan->semphr, &high_task_woken);
    }

    need_yield |= high_task_woken == pdTRUE;
    if (need_yield) {
        portYIELD_FROM_ISR();
    }
}

/*---------------------------------------------------------------
                      HELPERS
---------------------------------------------------------------*/
lp_i2s_soc_handle_t lp_i2s_get_soc_handle(lp_i2s_chan_handle_t chan)
{
    if (!chan) {
        return NULL;
    }

    return chan->ctlr->hal.dev;
}