#include "flash_port.h"
#include <string.h>
#include "mx_log.h"

#undef MX_LOGD
#undef MX_LOGI
#undef MX_LOGW
#undef MX_LOGE
#define MX_LOGD FLASH_PORT_LOGD
#define MX_LOGI FLASH_PORT_LOGI
#define MX_LOGW FLASH_PORT_LOGW
#define MX_LOGE FLASH_PORT_LOGE

#define FLASH_PORT_TAG FLASH_PORT_LOG_TAG

#define FLASH_PORT_ERASE_PROBE_BYTES   8U
#define FLASH_PORT_VERIFY_CHUNK_BYTES  64U

static flash_port_ops_t s_flash_ops = {0};

void flash_port_init(const flash_port_ops_t *ops)
{
    if (ops != NULL) {
        s_flash_ops = *ops;
    }
}

static uint8_t hal_read(uint32_t addr, void *buf, uint32_t len)
{
    if (s_flash_ops.read) {
        return s_flash_ops.read(addr, buf, len/2);
    }
    return 0;
}

static uint8_t hal_write(uint32_t addr, const void *buf, uint32_t len)
{
    if (s_flash_ops.write) {
        return s_flash_ops.write(addr, buf, len);
    }
    return 0;
}

static uint8_t hal_erase(uint32_t addr, uint32_t len)
{
    if (s_flash_ops.erase) {
        return s_flash_ops.erase(addr, len);
    }
    return 0;
}

static uint8_t hal_is_erased(uint32_t addr, uint32_t len)
{
    if (s_flash_ops.is_erased) {
        return s_flash_ops.is_erased(addr, len);
    }
    return 0;
}

static uint32_t flash_port_min_u32(uint32_t a, uint32_t b)
{
    return (a < b) ? a : b;
}

static uint8_t flash_port_addr_is_erased(uint32_t addr, uint32_t bytes)
{
    uint32_t probe_bytes = flash_port_min_u32(bytes, FLASH_PORT_ERASE_PROBE_BYTES);

    if (probe_bytes == 0U) 
    {
        probe_bytes = FLASH_PORT_ERASE_PROBE_BYTES;
    }

    return hal_is_erased(addr, probe_bytes);
}

static uint8_t flash_port_erase_range(uint32_t start_addr, uint32_t bytes)
{
    if (!hal_erase(start_addr, bytes)) 
    {
        return 0;
    }

    return flash_port_addr_is_erased(start_addr, bytes);
}

static uint8_t flash_port_verify_bytes(uint32_t addr, const void *expect, uint32_t bytes)
{
    uint8_t verify_buf[FLASH_PORT_VERIFY_CHUNK_BYTES];
    const uint8_t *expect_bytes = (const uint8_t *)expect;
    uint32_t offset = 0U;

    if ((expect == NULL) && (bytes > 0U)) 
    {
        return 0;
    }

    while (offset < bytes) 
    {
        uint32_t chunk = flash_port_min_u32(bytes - offset, FLASH_PORT_VERIFY_CHUNK_BYTES);

        if (!hal_read(addr + offset, verify_buf, chunk)) {
            MX_LOGE(FLASH_PORT_TAG, "verify read failed addr=0x%08X offset=%u",
                    (unsigned int)addr, (unsigned int)offset);
            return 0;
        }

        if (memcmp(verify_buf, expect_bytes + offset, chunk) != 0) {
            MX_LOGE(FLASH_PORT_TAG, "verify failed addr=0x%08X offset=%u",
                    (unsigned int)addr, (unsigned int)offset);
            return 0;
        }

        offset += chunk;
    }

    return 1;
}

static uint8_t flash_port_load_cali_raw(app_math_cali_t *params)
{
    if (params == NULL) {
        MX_LOGE(FLASH_PORT_TAG, "load cali raw null params");
        return 0;
    }

    if (sizeof(app_math_cali_t) > FLASH_PORT_CALI_REGION_SIZE) {
        MX_LOGE(FLASH_PORT_TAG, "cali region too small: need=%u size=%u",
                (unsigned int)sizeof(app_math_cali_t), (unsigned int)FLASH_PORT_CALI_REGION_SIZE);
        return 0;
    }

    return hal_read(FLASH_PORT_CALI_REGION_ADDR, params, sizeof(app_math_cali_t));
}

static uint8_t flash_port_store_cali_raw(const app_math_cali_t *params)
{
    if (params == NULL) 
    {
        MX_LOGE(FLASH_PORT_TAG, "store cali raw null params");
        return 0;
    }

    if (sizeof(app_math_cali_t) > FLASH_PORT_CALI_REGION_SIZE) {
        MX_LOGE(FLASH_PORT_TAG, "cali region too small: need=%u size=%u",
                (unsigned int)sizeof(app_math_cali_t), (unsigned int)FLASH_PORT_CALI_REGION_SIZE);
        return 0;
    }

    if (!flash_port_erase_range(FLASH_PORT_CALI_REGION_ADDR, FLASH_PORT_CALI_REGION_SIZE)) {
        MX_LOGE(FLASH_PORT_TAG, "erase cali region failed");
        return 0;
    }

    if (!hal_write(FLASH_PORT_CALI_REGION_ADDR, params, sizeof(app_math_cali_t))) {
        MX_LOGE(FLASH_PORT_TAG, "write cali raw failed");
        return 0;
    }

    return flash_port_verify_bytes(FLASH_PORT_CALI_REGION_ADDR, params, sizeof(app_math_cali_t));
}

uint8_t flash_port_math_load(app_math_cali_t *params)
{
    if (!flash_port_load_cali_raw(params)) 
    {
        return 0;
    }

    if ((params->min_trigger_res_value == 0xFFFFU) &&
        (params->max_trigger_res_value == 0xFFFFU) &&
        (params->div_trigger_res_value == 0xFFFFU) &&
        (params->max_display_value == 0xFFFFU)) {
        MX_LOGW(FLASH_PORT_TAG, "math area is empty");
        return 0;
    }

    return 1;
}

uint8_t flash_port_math_save(const app_math_cali_t *params)
{
    app_math_cali_t merged = {0};

    if (params == NULL) 
    {
        MX_LOGE(FLASH_PORT_TAG, "math save null params");
        return 0;
    }

    (void)flash_port_load_cali_raw(&merged);
    merged.check_cail = params->check_cail;
    merged.min_trigger_res_value = params->min_trigger_res_value;
    merged.max_trigger_res_value = params->max_trigger_res_value;
    merged.div_trigger_res_value = params->div_trigger_res_value;
    merged.max_display_value = params->max_display_value;
    merged.sensor_data_type = params->sensor_data_type;
    return flash_port_store_cali_raw(&merged);
}

uint8_t flash_port_math_clear(void)
{
    if (!flash_port_erase_range(FLASH_PORT_CALI_REGION_ADDR, FLASH_PORT_CALI_REGION_SIZE)) {
        MX_LOGE(FLASH_PORT_TAG, "math clear erase failed");
        return 0;
    }

    return 1;
}

uint8_t flash_port_pressure_load(app_math_cali_t *params)
{
    if (!flash_port_load_cali_raw(params)) {
        return 0;
    }

    if ((params->num_points == 0xFFFFU) || (params->num_points > MAX_PRESSURE_POINTS)) {
        MX_LOGW(FLASH_PORT_TAG, "invalid pressure points=%u", (unsigned int)params->num_points);
        return 0;
    }

    return 1;
}

uint8_t flash_port_pressure_save(const app_math_cali_t *params)
{
    app_math_cali_t merged = {0};

    if ((params == NULL) || (params->num_points > MAX_PRESSURE_POINTS)) {
        MX_LOGE(FLASH_PORT_TAG, "pressure save invalid params, num_points=%u",
                (unsigned int)(params ? params->num_points : 0U));
        return 0;
    }

    (void)flash_port_load_cali_raw(&merged);
    merged.num_points = params->num_points;
    memcpy(merged.pressure_points, params->pressure_points, sizeof(merged.pressure_points));

    return flash_port_store_cali_raw(&merged);
}

uint8_t flash_port_pressure_clear(void)
{
    app_math_cali_t merged = {0};

    (void)flash_port_load_cali_raw(&merged);
    merged.num_points = 0U;
    memset(merged.pressure_points, 0, sizeof(merged.pressure_points));

    return flash_port_store_cali_raw(&merged);
}

uint8_t flash_port_map_load(uint16_t *map_matrix, uint32_t map_words)
{
    uint32_t map_bytes = map_words * sizeof(uint16_t);

    if ((map_matrix == NULL) || (map_words != FLASH_PORT_MAP_WORDS)) {
        MX_LOGE(FLASH_PORT_TAG, "map load bad arg, words=%u", (unsigned int)map_words);
        return 0;
    }

    if (map_bytes > FLASH_PORT_MAP_REGION_SIZE) {
        MX_LOGE(FLASH_PORT_TAG, "map region too small: need=%u size=%u",
                (unsigned int)map_bytes, (unsigned int)FLASH_PORT_MAP_REGION_SIZE);
        return 0;
    }

    if (flash_port_addr_is_erased(FLASH_PORT_MAP_REGION_ADDR, map_bytes)) {
        MX_LOGW(FLASH_PORT_TAG, "map area is empty");
        return 0;
    }

    return hal_read(FLASH_PORT_MAP_REGION_ADDR, map_matrix, map_bytes);
}

uint8_t flash_port_map_save(const uint16_t *map_matrix, uint32_t map_words)
{
    uint32_t map_bytes = map_words * sizeof(uint16_t);

    if ((map_matrix == NULL) || (map_words != FLASH_PORT_MAP_WORDS)) {
        MX_LOGE(FLASH_PORT_TAG, "map save bad arg, words=%u", (unsigned int)map_words);
        return 0;
    }

    if (map_bytes > FLASH_PORT_MAP_REGION_SIZE) {
        MX_LOGE(FLASH_PORT_TAG, "map region too small: need=%u size=%u",
                (unsigned int)map_bytes, (unsigned int)FLASH_PORT_MAP_REGION_SIZE);
        return 0;
    }

    if (!flash_port_erase_range(FLASH_PORT_MAP_REGION_ADDR, FLASH_PORT_MAP_REGION_SIZE)) {
        MX_LOGE(FLASH_PORT_TAG, "map erase failed");
        return 0;
    }

    if (!hal_write(FLASH_PORT_MAP_REGION_ADDR, map_matrix, map_bytes)) {
        MX_LOGE(FLASH_PORT_TAG, "map write failed");
        return 0;
    }

    return flash_port_verify_bytes(FLASH_PORT_MAP_REGION_ADDR, map_matrix, map_bytes);
}

uint8_t flash_port_map_clear(void)
{
    if (!flash_port_erase_range(FLASH_PORT_MAP_REGION_ADDR, FLASH_PORT_MAP_REGION_SIZE)) {
        MX_LOGE(FLASH_PORT_TAG, "map clear erase failed");
        return 0;
    }

    return 1;
}

uint8_t flash_port_creep_load(app_creep_params *params)
{
    uint32_t raw_data[2];

    if (params == NULL) {
        return 0;
    }

    if (!hal_read(FLASH_PORT_CREEP_REGION_ADDR, raw_data, sizeof(raw_data))) {
        return 0;
    }

    if ((raw_data[0] == 0xFFFFFFFFUL) && (raw_data[1] == 0xFFFFFFFFUL)) {
        return 0;
    }

    params->creep_strength = (uint8_t)raw_data[0];
    params->creep_level = (uint8_t)raw_data[1];
    return 1;
}

uint8_t flash_port_creep_save(const app_creep_params *params)
{
    uint32_t raw_data[2];

    if (params == NULL) {
        return 0;
    }

    raw_data[0] = (uint32_t)params->creep_strength;
    raw_data[1] = (uint32_t)params->creep_level;

    if (!flash_port_erase_range(FLASH_PORT_CREEP_REGION_ADDR, FLASH_PORT_CREEP_REGION_SIZE)) {
        return 0;
    }

    if (!hal_write(FLASH_PORT_CREEP_REGION_ADDR, raw_data, sizeof(raw_data))) {
        return 0;
    }

    return flash_port_verify_bytes(FLASH_PORT_CREEP_REGION_ADDR, raw_data, sizeof(raw_data));
}

uint8_t flash_port_creep_clear(void)
{
    if (!flash_port_erase_range(FLASH_PORT_CREEP_REGION_ADDR, FLASH_PORT_CREEP_REGION_SIZE)) {
        return 0;
    }
    return 1;
}

uint8_t flash_port_device_info_load(app_dev_info *info)
{
    if (info == NULL) {
        return 0;
    }

    if (flash_port_addr_is_erased(FLASH_PORT_DEVICE_INFO_REGION_ADDR, sizeof(*info))) {
        return 0;
    }

    return hal_read(FLASH_PORT_DEVICE_INFO_REGION_ADDR, info, sizeof(*info));
}

uint8_t flash_port_device_info_save(const app_dev_info *info)
{
    if (info == NULL) {
        return 0;
    }

    if (!flash_port_erase_range(FLASH_PORT_DEVICE_INFO_REGION_ADDR, FLASH_PORT_DEVICE_INFO_REGION_SIZE)) {
        return 0;
    }

    if (!hal_write(FLASH_PORT_DEVICE_INFO_REGION_ADDR, info, sizeof(*info))) {
        return 0;
    }

    return flash_port_verify_bytes(FLASH_PORT_DEVICE_INFO_REGION_ADDR, info, sizeof(*info));
}
