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

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

#include "essl.h"
#include "essl_internal.h"

#define TIME_EXPIRED_SINCE_CORE(start, end, timeout, max) (bool)((end)>=(start)? \
         ((end)-(start)>(timeout)) :\
        ((max)-(timeout)>(start)-(end)))

#define TIME_EXPIRED_SINCE(start, end, timeout) TIME_EXPIRED_SINCE_CORE(start, end, timeout, UINT32_MAX)

#define MINUS_UNTIL_ZERO(a, b) ( ((a) > (b)) ? ((a)-(b)): 0)

#define TIME_REMAIN_CORE(start, end, timeout, max) ((end)>=(start)?\
        MINUS_UNTIL_ZERO(timeout, (end)-(start)):\
        MINUS_UNTIL_ZERO((start)-(end), (max)-(timeout)))

#define TIME_REMAIN(start, end, timeout)    TIME_REMAIN_CORE(start, end, timeout, UINT32_MAX)

#define ESSL_MIN(a, b)   ((a) < (b) ? (a) : (b))

__attribute__((unused)) static const char TAG[] = "esp_serial_slave_link";

#define _CHECK_EXECUTE_CMD(DEV, CMD, STR, ...) do{ \
    if ((DEV) == NULL) { \
        return ESP_ERR_INVALID_ARG; \
    } \
    if ((DEV)->CMD) { \
        return (DEV)->CMD((DEV)->args,##__VA_ARGS__); \
    } else { \
        ESP_LOGE(TAG, STR); \
        return ESP_ERR_NOT_SUPPORTED; \
    } } while(0)

#define CHECK_EXECUTE_CMD(DEV, CMD, ...) _CHECK_EXECUTE_CMD(DEV, CMD, #CMD" not supported for the current device.",##__VA_ARGS__)

esp_err_t essl_init(essl_handle_t handle, uint32_t wait_ms)
{
    CHECK_EXECUTE_CMD(handle, init, wait_ms);
}

esp_err_t essl_wait_for_ready(essl_handle_t handle, uint32_t wait_ms)
{
    CHECK_EXECUTE_CMD(handle, wait_for_ready, wait_ms);
}

esp_err_t essl_send_packet(essl_handle_t handle, const void *start, size_t length, uint32_t wait_ms)
{
    if (handle == NULL || start == NULL || length == 0) {
        return ESP_ERR_INVALID_ARG;
    }
    if (handle->send_packet == NULL) {
        return ESP_ERR_NOT_SUPPORTED;
    }
    esp_err_t err;
    const uint32_t timeout_ticks = pdMS_TO_TICKS(wait_ms);

    uint32_t pre = xTaskGetTickCount();
    uint32_t now;
    uint32_t remain_wait_ms = 0;

    do {
        now = xTaskGetTickCount();
        remain_wait_ms = pdTICKS_TO_MS(TIME_REMAIN(pre, now, timeout_ticks));
        err = handle->send_packet(handle->args, start, length, remain_wait_ms);
        if (err == ESP_OK) {
            break;
        } else if (err != ESP_ERR_NOT_FOUND) {
            return err;
        } // else ESP_ERR_NOT_FOUND
        //the slave is not ready, retry
    } while (remain_wait_ms > 0);
    return err;
}

esp_err_t essl_get_packet(essl_handle_t handle, void *out_data, size_t size, size_t *out_length, uint32_t wait_ms)
{
    if (handle == NULL) {
        return ESP_ERR_INVALID_ARG;
    }
    if (out_data == NULL || size == 0 || out_length == NULL) {
        return ESP_ERR_INVALID_ARG;
    }
    if (handle->get_packet == NULL || handle->update_rx_data_size == NULL || handle->get_rx_data_size == NULL) {
        return ESP_ERR_NOT_SUPPORTED;
    }
    esp_err_t err;
    const uint32_t timeout_ticks = pdMS_TO_TICKS(wait_ms);

    uint32_t pre = xTaskGetTickCount();
    uint32_t now = 3;
    uint32_t wait_remain_ms = 0;
    int data_available = handle->get_rx_data_size(handle->args);

    // if there is already enough data to read, skip the length update.
    if (data_available < size) {
        //loop until timeout, or there is at least one byte
        do {
            now = xTaskGetTickCount();
            wait_remain_ms = pdTICKS_TO_MS(TIME_REMAIN(pre, now, timeout_ticks));
            err = handle->update_rx_data_size(handle->args, wait_remain_ms);
            if (err != ESP_OK) {
                return err;
            }
            data_available = handle->get_rx_data_size(handle->args);
            if (data_available > 0) {
                break;
            }
        } while (wait_remain_ms > 0);
    }

    if (data_available == 0) {
        //the slave has no data to send
        return ESP_ERR_NOT_FOUND;
    }

    int len = ESSL_MIN(data_available, size);
    now = xTaskGetTickCount();
    wait_remain_ms = pdTICKS_TO_MS(TIME_REMAIN(pre, now, timeout_ticks));
    err = handle->get_packet(handle->args, out_data, len, wait_remain_ms);
    if (err != ESP_OK) {
        return err;
    }

    *out_length = len;
    if (len < data_available) {
        return ESP_ERR_NOT_FINISHED;
    }
    return ESP_OK;
}

esp_err_t essl_get_tx_buffer_num(essl_handle_t handle, uint32_t *out_tx_num, uint32_t wait_ms)
{
    if (handle == NULL || out_tx_num == NULL) {
        return ESP_ERR_INVALID_ARG;
    }
    if (handle->update_tx_buffer_num == NULL || handle->get_tx_buffer_num == NULL) {
        return ESP_ERR_NOT_SUPPORTED;
    }

    esp_err_t err = handle->update_tx_buffer_num(handle->args, wait_ms);
    if (err != ESP_OK) {
        return err;
    }

    *out_tx_num = handle->get_tx_buffer_num(handle->args);
    return ESP_OK;
}

esp_err_t essl_get_rx_data_size(essl_handle_t handle, uint32_t *out_rx_size, uint32_t wait_ms)
{
    if (handle == NULL || out_rx_size == NULL) {
        return ESP_ERR_INVALID_ARG;
    }
    if (handle->update_rx_data_size == NULL || handle->get_rx_data_size == NULL) {
        return ESP_ERR_NOT_SUPPORTED;
    }

    esp_err_t err = handle->update_rx_data_size(handle->args, wait_ms);
    if (err != ESP_OK) {
        return err;
    }

    *out_rx_size = handle->get_rx_data_size(handle->args);
    return ESP_OK;
}

esp_err_t essl_write_reg(essl_handle_t handle, uint8_t addr, uint8_t value, uint8_t *value_o, uint32_t wait_ms)
{
    CHECK_EXECUTE_CMD(handle, write_reg, addr, value, value_o, wait_ms);
}

esp_err_t essl_read_reg(essl_handle_t handle, uint8_t add, uint8_t *value_o, uint32_t wait_ms)
{
    CHECK_EXECUTE_CMD(handle, read_reg, add, value_o, wait_ms);
}

esp_err_t essl_wait_int(essl_handle_t handle, TickType_t wait_ms)
{
    CHECK_EXECUTE_CMD(handle, wait_int, wait_ms);
}

esp_err_t essl_reset_cnt(essl_handle_t handle)
{
    if (handle == NULL) {
        return ESP_ERR_INVALID_ARG;
    }
    if (handle->reset_cnt == NULL) {
        return ESP_ERR_NOT_SUPPORTED;
    }
    handle->reset_cnt(handle->args);
    return ESP_OK;
}

esp_err_t essl_clear_intr(essl_handle_t handle, uint32_t intr_mask, uint32_t wait_ms)
{
    CHECK_EXECUTE_CMD(handle, clear_intr, intr_mask, wait_ms);
}

esp_err_t essl_get_intr(essl_handle_t handle, uint32_t *intr_raw, uint32_t *intr_st, uint32_t wait_ms)
{
    if (intr_raw == NULL && intr_st == NULL) {
        return ESP_ERR_INVALID_ARG;
    }
    CHECK_EXECUTE_CMD(handle, get_intr, intr_raw, intr_st, wait_ms);
}

esp_err_t essl_set_intr_ena(essl_handle_t handle, uint32_t ena_mask, uint32_t wait_ms)
{
    CHECK_EXECUTE_CMD(handle, set_intr_ena, ena_mask, wait_ms);
}

esp_err_t essl_get_intr_ena(essl_handle_t handle, uint32_t *ena_mask_o, uint32_t wait_ms)
{
    if (ena_mask_o == NULL) {
        return ESP_ERR_INVALID_ARG;
    }
    CHECK_EXECUTE_CMD(handle, get_intr_ena, ena_mask_o, wait_ms);
}

esp_err_t essl_send_slave_intr(essl_handle_t handle, uint32_t intr_mask, uint32_t wait_ms)
{
    CHECK_EXECUTE_CMD(handle, send_slave_intr, intr_mask, wait_ms);
}
