/*
 * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#pragma once

#include <stdbool.h>
#include <string.h>
#include "hal/assert.h"
#include "soc/ecdsa_reg.h"
#include "soc/hp_sys_clkrst_struct.h"
#include "soc/soc_caps.h"
#include "soc/efuse_reg.h"
#include "soc/efuse_struct.h"
#include "hal/ecdsa_types.h"
#include "hal/config.h"
#include "soc/chip_revision.h"
#include "soc/soc.h"
#ifdef __cplusplus
extern "C" {
#endif

/** Forward declaration for efuse_hal_chip_revision() */
uint32_t efuse_hal_chip_revision(void);

/**
 * @brief Memory blocks of ECDSA parameters
 */
typedef enum {
    ECDSA_PARAM_R,
    ECDSA_PARAM_S,
    ECDSA_PARAM_Z,
    ECDSA_PARAM_QAX,
    ECDSA_PARAM_QAY
} ecdsa_ll_param_t;

/**
 * @brief Interrupt types in ECDSA
 */
typedef enum {
    ECDSA_INT_CALC_DONE,
    ECDSA_INT_SHA_RELEASE,
} ecdsa_ll_intr_type_t;

/**
 * @brief Stages of ECDSA operation
 */
typedef enum {
    ECDSA_STAGE_START_CALC,
    ECDSA_STAGE_LOAD_DONE,
    ECDSA_STAGE_GET_DONE
} ecdsa_ll_stage_t;

/**
 * @brief States of ECDSA peripheral
 */
typedef enum {
    ECDSA_STATE_IDLE,
    ECDSA_STATE_LOAD,
    ECDSA_STATE_GET,
    ECDSA_STATE_BUSY
} ecdsa_ll_state_t;

/**
 * @brief Types of SHA
 */
typedef enum {
    ECDSA_SHA_224,
    ECDSA_SHA_256
} ecdsa_ll_sha_type_t;

/**
 * @brief Operation modes of SHA
 */
typedef enum {
    ECDSA_MODE_SHA_START,
    ECDSA_MODE_SHA_CONTINUE
} ecdsa_ll_sha_mode_t;

/**
 * @brief Get the state of ECDSA peripheral
 *
 * @return State of ECDSA
 */
static inline uint32_t ecdsa_ll_get_state(void)
{
    return REG_GET_FIELD(ECDSA_STATE_REG, ECDSA_BUSY);
}

/**
 * @brief Enable the bus clock for ECDSA peripheral module
 *
 * @param true to enable the module, false to disable the module
 */
static inline void ecdsa_ll_enable_bus_clock(bool enable)
{
    HP_SYS_CLKRST.peri_clk_ctrl25.reg_crypto_ecdsa_clk_en = enable;
}

/// use a macro to wrap the function, force the caller to use it in a critical section
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
#define ecdsa_ll_enable_bus_clock(...) (void)__DECLARE_RCC_ATOMIC_ENV; ecdsa_ll_enable_bus_clock(__VA_ARGS__)

/**
 * @brief Reset the ECDSA peripheral module
 */
static inline void ecdsa_ll_reset_register(void)
{
    HP_SYS_CLKRST.hp_rst_en2.reg_rst_en_ecdsa = 1;
    HP_SYS_CLKRST.hp_rst_en2.reg_rst_en_ecdsa = 0;

    // Clear reset on parent crypto, otherwise ECDSA is held in reset
    HP_SYS_CLKRST.hp_rst_en2.reg_rst_en_crypto = 0;

    while (ecdsa_ll_get_state() != ECDSA_STATE_IDLE) {
        ;
    }
}

/**
 * @brief Enable interrupt of a given type
 *
 * @param type Interrupt type
 */
static inline void ecdsa_ll_enable_intr(ecdsa_ll_intr_type_t type)
{
    switch (type) {
        case ECDSA_INT_CALC_DONE:
#if HAL_CONFIG(CHIP_SUPPORT_MIN_REV) >= 300
            REG_SET_FIELD(ECDSA_INT_ENA_REG, ECDSA_PREP_DONE_INT_ENA, 1);
#else
            REG_SET_FIELD(ECDSA_INT_ENA_REG, ECDSA_CALC_DONE_INT_ENA, 1);
#endif
            break;
        case ECDSA_INT_SHA_RELEASE:
            REG_SET_FIELD(ECDSA_INT_ENA_REG, ECDSA_SHA_RELEASE_INT_ENA, 1);
            break;
        default:
            HAL_ASSERT(false && "Unsupported interrupt type");
            break;
    }
}

/**
 * @brief Disable interrupt of a given type
 *
 * @param type Interrupt type
 */
static inline void ecdsa_ll_disable_intr(ecdsa_ll_intr_type_t type)
{
    switch (type) {
        case ECDSA_INT_CALC_DONE:
#if HAL_CONFIG(CHIP_SUPPORT_MIN_REV) >= 300
            REG_SET_FIELD(ECDSA_INT_ENA_REG, ECDSA_PREP_DONE_INT_ENA, 0);
#else
            REG_SET_FIELD(ECDSA_INT_ENA_REG, ECDSA_CALC_DONE_INT_ENA, 0);
#endif
            break;
        case ECDSA_INT_SHA_RELEASE:
            REG_SET_FIELD(ECDSA_INT_ENA_REG, ECDSA_SHA_RELEASE_INT_ENA, 0);
            break;
        default:
            HAL_ASSERT(false && "Unsupported interrupt type");
            break;
    }
}

/**
 * @brief Clear interrupt of a given type
 *
 * @param type Interrupt type
 */
static inline void ecdsa_ll_clear_intr(ecdsa_ll_intr_type_t type)
{
    switch (type) {
        case ECDSA_INT_CALC_DONE:
#if HAL_CONFIG(CHIP_SUPPORT_MIN_REV) >= 300
            REG_SET_FIELD(ECDSA_INT_ENA_REG, ECDSA_PREP_DONE_INT_CLR, 1);
#else
            REG_SET_FIELD(ECDSA_INT_CLR_REG, ECDSA_CALC_DONE_INT_CLR, 1);
#endif
            break;
        case ECDSA_INT_SHA_RELEASE:
            REG_SET_FIELD(ECDSA_INT_CLR_REG, ECDSA_SHA_RELEASE_INT_CLR, 1);
            break;
        default:
            HAL_ASSERT(false && "Unsupported interrupt type");
            break;
    }
}

/**
 * @brief Set working mode of ECDSA
 *
 * @param mode Mode of operation
 */
static inline void ecdsa_ll_set_mode(ecdsa_mode_t mode)
{
    switch (mode) {
        case ECDSA_MODE_SIGN_VERIFY:
            REG_SET_FIELD(ECDSA_CONF_REG, ECDSA_WORK_MODE, 0);
            break;
        case ECDSA_MODE_SIGN_GEN:
            REG_SET_FIELD(ECDSA_CONF_REG, ECDSA_WORK_MODE, 1);
            break;
        case ECDSA_MODE_EXPORT_PUBKEY:
            REG_SET_FIELD(ECDSA_CONF_REG, ECDSA_WORK_MODE, 2);
            break;
        default:
            HAL_ASSERT(false && "Unsupported mode");
            break;
    }
}

/**
 * @brief Set curve for ECDSA operation
 *
 * @param curve ECDSA curve
 */
static inline void ecdsa_ll_set_curve(ecdsa_curve_t curve)
{
    switch (curve) {
        case ECDSA_CURVE_SECP192R1:
        case ECDSA_CURVE_SECP256R1:
        case ECDSA_CURVE_SECP384R1:
        case ECDSA_CURVE_SM2:
            REG_SET_FIELD(ECDSA_CONF_REG, ECDSA_ECC_CURVE, curve);
            break;
        default:
            HAL_ASSERT(false && "Unsupported curve");
            return;
    }
}

/**
 * @brief Set the source of `Z` (SHA message)
 *
 * @param mode Mode of SHA generation
 */
static inline void ecdsa_ll_set_z_mode(ecdsa_ll_sha_mode_t mode)
{
    switch (mode) {
        case ECDSA_Z_USE_SHA_PERI:
            REG_CLR_BIT(ECDSA_CONF_REG, ECDSA_SOFTWARE_SET_Z);
            break;
        case ECDSA_Z_USER_PROVIDED:
            REG_SET_BIT(ECDSA_CONF_REG, ECDSA_SOFTWARE_SET_Z);
            break;
        default:
            HAL_ASSERT(false && "Unsupported curve");
            break;
    }
}

/**
 * @brief Set the signature generation type of ECDSA operation
 *
 * @param type Type of the ECDSA signature
 */
static inline void ecdsa_ll_set_k_type(ecdsa_sign_type_t type)
{
    switch (type) {
        case ECDSA_K_TYPE_TRNG:
            REG_CLR_BIT(ECDSA_CONF_REG, ECDSA_DETERMINISTIC_K);
            break;
        case ECDSA_K_TYPE_DETERMINISITIC:
            REG_SET_BIT(ECDSA_CONF_REG, ECDSA_DETERMINISTIC_K);
            break;
        default:
            HAL_ASSERT(false && "Unsupported K type");
            break;
    }
}

/**
 * @brief Set the loop number value that is used for deterministic derivation of K
 *
 * @param loop_number Loop number for deterministic K
 */
static inline void ecdsa_ll_set_deterministic_loop(uint16_t loop_number)
{
#if HAL_CONFIG(CHIP_SUPPORT_MIN_REV) < 300
    REG_SET_FIELD(ECDSA_CONF_REG, ECDSA_DETERMINISTIC_LOOP, loop_number);
#endif
}

/**
 * @brief Set the stage of ECDSA operation
 *
 * @param stage Stage of operation
 */
static inline void ecdsa_ll_set_stage(ecdsa_ll_stage_t stage)
{
    switch (stage) {
        case ECDSA_STAGE_START_CALC:
            REG_SET_BIT(ECDSA_START_REG, ECDSA_START);
            break;
        case ECDSA_STAGE_LOAD_DONE:
            REG_SET_BIT(ECDSA_START_REG, ECDSA_LOAD_DONE);
            break;
        case ECDSA_STAGE_GET_DONE:
            REG_SET_BIT(ECDSA_START_REG, ECDSA_GET_DONE);
            break;
        default:
            HAL_ASSERT(false && "Unsupported state");
            break;
    }
}

/**
 * @brief Set the SHA type
 *
 * @param type Type of SHA
 */
static inline void ecdsa_ll_sha_set_type(ecdsa_ll_sha_type_t type)
{
    switch (type) {
        case ECDSA_SHA_224:
            REG_SET_FIELD(ECDSA_SHA_MODE_REG, ECDSA_SHA_MODE, 1);
            break;
        case ECDSA_SHA_256:
            REG_SET_FIELD(ECDSA_SHA_MODE_REG, ECDSA_SHA_MODE, 2);
            break;
        default:
            HAL_ASSERT(false && "Unsupported type");
            break;
    }
}

/**
 * @brief Set the SHA operation mode
 *
 * @param mode Mode of SHA operation
 */
static inline void ecdsa_ll_sha_set_mode(ecdsa_ll_sha_mode_t mode)
{
    switch (mode) {
        case ECDSA_MODE_SHA_START:
            REG_SET_BIT(ECDSA_SHA_START_REG, ECDSA_SHA_START);
            break;
        case ECDSA_MODE_SHA_CONTINUE:
            REG_SET_BIT(ECDSA_SHA_CONTINUE_REG, ECDSA_SHA_CONTINUE);
            break;
        default:
            HAL_ASSERT(false && "Unsupported type");
            break;
    }
}

/**
 * @brief Check if SHA is busy
 *
 * @return - true, if SHA is busy
 *         - false, if SHA is IDLE
 */
static inline bool ecdsa_ll_sha_is_busy(void)
{
    return REG_GET_BIT(ECDSA_SHA_BUSY_REG, ECDSA_SHA_BUSY);
}

/**
 * @brief Write the ECDSA parameter
 *
 * @param param Parameter to be written
 * @param buf   Buffer containing data
 * @param len   Length of buffer
 */
static inline void ecdsa_ll_write_param(ecdsa_ll_param_t param, const uint8_t *buf, uint16_t len)
{
    uint32_t reg;
    uint32_t word;
    switch (param) {
        case ECDSA_PARAM_R:
            reg = ECDSA_R_MEM;
            break;
        case ECDSA_PARAM_S:
            reg = ECDSA_S_MEM;
            break;
        case ECDSA_PARAM_Z:
            reg = ECDSA_Z_MEM;
            break;
        case ECDSA_PARAM_QAX:
            reg = ECDSA_QAX_MEM;
            break;
        case ECDSA_PARAM_QAY:
            reg = ECDSA_QAY_MEM;
            break;
        default:
            HAL_ASSERT(false && "Invalid parameter");
            return;
    }

    for (int i = 0; i < len; i += 4) {
        memcpy(&word, buf + i, 4);
        REG_WRITE(reg + i, word);
    }
}

/**
 * @brief Read the ECDSA parameter
 *
 * @param param Parameter to be read
 * @param buf   Buffer where the data will be written
 * @param len   Length of buffer
 */
static inline void ecdsa_ll_read_param(ecdsa_ll_param_t param, uint8_t *buf, uint16_t len)
{
    uint32_t reg;
    switch (param) {
        case ECDSA_PARAM_R:
            reg = ECDSA_R_MEM;
            break;
        case ECDSA_PARAM_S:
            reg = ECDSA_S_MEM;
            break;
        case ECDSA_PARAM_Z:
            reg = ECDSA_Z_MEM;
            break;
        case ECDSA_PARAM_QAX:
            reg = ECDSA_QAX_MEM;
            break;
        case ECDSA_PARAM_QAY:
            reg = ECDSA_QAY_MEM;
            break;
        default:
            HAL_ASSERT(false && "Invalid parameter");
            return;
    }

    memcpy(buf, (void *)reg, len);
}

/**
 * @brief Check if the ECDSA operation is successful
 *
 * @return - 1, if ECDSA operation succeeds
 *         - 0, otherwise
 */
static inline int ecdsa_ll_get_operation_result(void)
{
    return REG_GET_BIT(ECDSA_RESULT_REG, ECDSA_OPERATION_RESULT);
}

/**
 * @brief Check if the k value is greater than the curve order.
 *
 * @return 0, k value is not greater than the curve order. In this case, the k value is the set k value.
 * @return 1, k value is greater than than the curve order. In this case, the k value is the set (k mod n).
 */
static inline int ecdsa_ll_check_k_value(void)
{
#if HAL_CONFIG(CHIP_SUPPORT_MIN_REV) < 300
    return REG_GET_BIT(ECDSA_RESULT_REG, ECDSA_K_VALUE_WARNING);
#else
    return 0;
#endif
}

/**
 * @brief Check if the ECDSA deterministic mode is supported
 */
static inline bool ecdsa_ll_is_deterministic_mode_supported(void)
{
    return true;
}

/**
 * @brief Set the ECDSA key block in eFuse
 *
 * @param curve    ECDSA curve type
 * @param efuse_blk eFuse block number
 */
__attribute__((always_inline)) static inline void ecdsa_ll_set_ecdsa_key_blk(ecdsa_curve_t curve, int efuse_blk)
{
#ifdef EFUSE_CFG_ECDSA_BLK
    (void) curve;
    EFUSE.conf.cfg_ecdsa_blk = efuse_blk;
#else
    uint8_t efuse_blk_low = 0;
    uint8_t efuse_blk_high = 0;
    switch (curve) {
        case ECDSA_CURVE_SECP192R1:
            EFUSE.ecdsa.cfg_ecdsa_p192_blk = efuse_blk;
            break;
        case ECDSA_CURVE_SECP256R1:
            EFUSE.ecdsa.cfg_ecdsa_p256_blk = efuse_blk;
            break;
        case ECDSA_CURVE_SECP384R1:
            // ECDSA-p384 uses two efuse blocks to store the key. These two blocks are stored in a single integer
            // where the least significant 4 bits store the low key block number and the next 4 more significant bits store the high key block number.
            HAL_ECDSA_EXTRACT_KEY_BLOCKS(efuse_blk, efuse_blk_high, efuse_blk_low);
            EFUSE.ecdsa.cfg_ecdsa_p384_h_blk = efuse_blk_high;
            EFUSE.ecdsa.cfg_ecdsa_p384_l_blk = efuse_blk_low;
            break;
        default:
            HAL_ASSERT(false && "Unsupported curve");
            break;
    }
#endif
}

/**
 * @brief Check if the ECDSA peripheral uses MPI module's memory
 */
static inline bool ecdsa_ll_is_mpi_required(void)
{
    return true;
}

/**
 * @brief Check if the ECDSA peripheral is supported on this chip revision
 * For ESP32-P4, ECDSA is only supported on eco5+ (major 3, minor 0+)
 */
static inline bool ecdsa_ll_is_supported(void)
{
    return ESP_CHIP_REV_ABOVE(efuse_hal_chip_revision(), 300);
}

#ifdef __cplusplus
}
#endif
