/**
 * @file app_log.c
 * @author LokLiang
 * @brief app_log.h 模块源码
 * @version 0.1
 * @date 2023-11-242
 *
 * @copyright Copyright (c) 2023
 *
 */

#include "app_log.h"
#include "nvs.h"
#include "utils/crc.h"
#include "os/os.h"

#undef CONFIG_SYS_LOG_LEVEL
#define CONFIG_SYS_LOG_LEVEL SYS_LOG_LEVEL_INF
#define SYS_LOG_DOMAIN "APLOG"
#define CONS_ABORT()
#include "sys_log.h"

#define _NVS_KEY_MAIN "log-main"

/* 数据区 -------------------------------------------------------------------------------------- */

extern nvs_handle g_nvs_hdl;

static struct
{
    os_time_t flight_time_start;
} s_cm;

/* nvs 接口 ------------------------------------------------------------------------------------ */

static int _read_key(const char *key, void *out_value, size_t tar_length, uint32_t *crc_base);
static int _set_crc(const void *struct_base, size_t struct_length, uint32_t *crc_base);
static int _do_factory_set(log_app_t *log);

/**
 * @brief 读取指定的数据，并按给出的特性进行校验，返回校验结果
 *
 * @param key               在 nvs 的存储块的键值
 * @param out_value[out]    输出内存
 * @param tar_length        输出的内存预期的长度
 * @param crc_base[io]      CRC 校验值所在内存块的地址（绝对值）。输入：校验值，输出：新的正确校验值
 * @retval 0 正确读取了已存储数据
 * @retval 1 nvs 模块可用， CRC 校验值已更新
 * @retval -1 nvs 模块不可用
 */
static int _read_key(const char *key, void *out_value, size_t tar_length, uint32_t *crc_base)
{
    size_t required_size = 0;
    esp_err_t err = ESP_OK;

    /* 在确认已存储的数据长度前，仅获取长度以免数据溢出 */
    err = nvs_get_blob(g_nvs_hdl, key, NULL, &required_size);
    if (err != ESP_OK)
    {
        SYS_LOG_WRN("failed to read prfsdata data length, err:%d", err);
        return -1;
    }
    if (required_size != tar_length)
    {
        SYS_LOG_WRN("invalid prfsdata length, length:%d, err:%d", required_size, err);
        return -1;
    }

    /* 读出已存储的数据 */
    err = nvs_get_blob(g_nvs_hdl, key, out_value, &required_size);
    if (err != ESP_OK)
    {
        SYS_LOG_ERR("failed to read prfsdata data, err:%d", err);
        return -1;
    }

    /* 设置并校验 CRC */
    if (_set_crc(out_value, tar_length, crc_base))
    {
        SYS_LOG_WRN("read frs failed, incorrect crc and update");
        return 1;
    }

    return 0;
}

/**
 * @brief 校验并更新结构体中 CRC 的值。
 * 这将校验整个结构（包含 CRC 本身），因此 CRC 自身的初始值为 0，然后更新 CRC 的结果到结构中
 *
 * @param struct_base   待分析和设置的内存块
 * @param struct_length 待分析和设置的内存块的实际长度
 * @param crc_base[io]  CRC 校验值所在内存块的地址（绝对值）
 * @retval 0 校验正确
 * @retval 1 校验错误，并且设置了正确的校验值
 */
static int _set_crc(const void *struct_base, size_t struct_length, uint32_t *crc_base)
{
    // 校验读出来的数据的正确性
    uint32_t crc_read = *crc_base;
    *crc_base = 0;
    *crc_base = crc32_calc(0, struct_base, struct_length);
    return (crc_read != *crc_base);
}

static int _do_factory_set(log_app_t *log)
{
    memset(log, 0, sizeof(*log));

    log->flight_data.crc16 = crc16_calc(NULL, 0);

    return app_log_do_save(log);
}

/**
 * @brief 初始化
 */
void app_log_init(void)
{
    SYS_ASSERT(g_nvs_hdl != 0, "");
}

/**
 * @brief 使内存的数据恢复到默认设置
 *
 * @retval 0 操作成功
 * @retval -1 操作失败
 */
int app_log_factory_set(void)
{
    log_app_t *log = os_malloc(sizeof(log_app_t));
    if (log == NULL)
    {
        SYS_LOG_WRN("malloc fail");
        return -1;
    }

    int ret = _do_factory_set(log);

    os_free(log);

    return ret;
}

/**
 * @brief 立即执行保存 配置数据 到 nvs 的动作
 *
 * @param delay_ms 0: 立即保存， > 0 多少毫秒后执行保存
 * @return int @ref esp_err_t
 */
int app_log_do_save(log_app_t *log)
{
    log->version = APP_LOG_DATA_VER;
    _set_crc(log, sizeof(*log), &log->crc32);
    esp_err_t err = nvs_set_blob(g_nvs_hdl, _NVS_KEY_MAIN, log, sizeof(*log));
    if (err != ESP_OK)
    {
        SYS_LOG_WRN("nvs_set_blob() err: %d", err);
    }
    return err;
}

/* 读接口 -------------------------------------------------------------------------------------- */

/**
 * @brief 读取日志信息
 * 将从 nvs 读取已保存的数据到内存 log_app_t 中。
 * 当出现以下情况时，配置数据被恢复到默认值：
 * nvs 未初始化或操作失败；
 * 使用 app_log_factory_set() 立即恢复到默认值；
 * 结构体的长度 log_app_t 发生改变；
 * APP_CONFIG_DATA_VER 定义的值发生改变；
 *
 * @param out_log[out] 用于保存日志信息的内存
 * @retval 0 成功读取已有记录
 * @retval 1 已恢复默认设置
 * @retval -1 nvs 操作失败
 */
int app_log_read(log_app_t *out_log)
{
    int ret = _read_key(_NVS_KEY_MAIN, out_log, sizeof(*out_log), &out_log->crc32);
    if (ret)
    {
        goto use_default_prfs;
    }
    if (out_log->version != APP_LOG_DATA_VER)
    {
        ret = 1;
        SYS_LOG_WRN("Upgrade to the latest version: %u ==> %u", out_log->version, APP_LOG_DATA_VER);
        goto use_default_prfs;
    }

    return ret;

use_default_prfs:
    _do_factory_set(out_log);
    return ret;
}

/* 写接口 -------------------------------------------------------------------------------------- */

/**
 * @brief 开始计时
 *
 */
void app_log_flight_time_start(void)
{
    log_app_t *log = os_malloc(sizeof(log_app_t));
    if (log == NULL)
    {
        SYS_LOG_WRN("malloc fail");
        return;
    }

    if (log->flight_data.flight_number < __ARRAY_SIZE(log->flight_data.datas))
    {
        s_cm.flight_time_start = os_get_sys_time();
    }
    else
    {
        s_cm.flight_time_start = 0;
        SYS_LOG_INF("The maximum number of groups: %d", __ARRAY_SIZE(log->flight_data.datas));
    }

    os_free(log);
}

/**
 * @brief 结束计时，并保存这个记录
 *
 */
void app_log_flight_time_stop(void)
{
    if (s_cm.flight_time_start == 0)
    {
        SYS_LOG_WRN("never exec app_log_flight_time_start()");
        return;
    }

    log_app_t *log = os_malloc(sizeof(log_app_t));
    if (log == NULL)
    {
        SYS_LOG_WRN("malloc fail");
        return;
    }

    os_time_t time_cost_sec = (os_get_sys_time() - s_cm.flight_time_start) / 1000;
    if (time_cost_sec > 10)
    {
        log->flight_data.datas[log->flight_data.flight_number++] = time_cost_sec;
        log->flight_data.crc16 = crc16_calc(log->flight_data.datas, sizeof(log->flight_data.datas[0]) * log->flight_data.flight_number);
        app_log_do_save(log);
    }
    s_cm.flight_time_start = 0;

    os_free(log);
}

/**
 * @brief 清除全部计时记录
 *
 */
void app_log_flight_time_clr(void)
{
    log_app_t *log = os_malloc(sizeof(log_app_t));
    if (log == NULL)
    {
        SYS_LOG_WRN("malloc fail");
        return;
    }

    memset(&log->flight_data, 0, sizeof(log->flight_data));
    app_log_do_save(log);

    os_free(log);
}
