/*
 * Copyright (c) 2020-2023, Armink, <armink.ztl@gmail.com>
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @file
 * @brief Public definition.
 */

#ifndef _FDB_DEF_H_
#define _FDB_DEF_H_

#ifdef __cplusplus
extern "C" {
#endif

/* software version number */
#define FDB_SW_VERSION     "2.0.0"
#define FDB_SW_VERSION_NUM 0x20000

/* the KV max name length must less then it */
#ifndef FDB_KV_NAME_MAX
#define FDB_KV_NAME_MAX 64
#endif

/* the KV cache table size, it will improve KV search speed when using cache */
#ifndef FDB_KV_CACHE_TABLE_SIZE
#define FDB_KV_CACHE_TABLE_SIZE 64
#endif

/* the sector cache table size, it will improve KV save speed when using cache
 */
#ifndef FDB_SECTOR_CACHE_TABLE_SIZE
#define FDB_SECTOR_CACHE_TABLE_SIZE 4
#endif

#if (FDB_KV_CACHE_TABLE_SIZE > 0) && (FDB_SECTOR_CACHE_TABLE_SIZE > 0)
#define FDB_KV_USING_CACHE
#endif

#if defined(FDB_USING_FILE_LIBC_MODE) || defined(FDB_USING_FILE_POSIX_MODE)
#define FDB_USING_FILE_MODE
#endif

#ifndef FDB_WRITE_GRAN
#define FDB_WRITE_GRAN 1
#endif

#include "string.h"

static inline char *fdb_base_name(char *path)
{
    char *offset = path + strlen(path);

    while (--offset > path) {
        if (*offset == '/') {
            return offset + 1;
        }
    }

    return path;
}

/* log function. default FDB_PRINT macro is printf() */
#ifndef FDB_PRINT
#define FDB_PRINT(...) printf(__VA_ARGS__)
#endif
#define FDB_LOG_PREFIX1() FDB_PRINT("[FlashDB]" FDB_LOG_TAG)
#define FDB_LOG_PREFIX2() FDB_PRINT(" ")
#define FDB_LOG_PREFIX()                                                                                               \
    FDB_LOG_PREFIX1();                                                                                                 \
    FDB_LOG_PREFIX2()
#ifdef FDB_DEBUG_ENABLE
#define FDB_DEBUG(...)                                                                                                 \
    FDB_LOG_PREFIX();                                                                                                  \
    FDB_PRINT("(%s:%d) ", fdb_base_name(__FILE__), __LINE__);                                                          \
    FDB_PRINT(__VA_ARGS__)
#else
#define FDB_DEBUG(...)
#endif
/* routine print function. Must be implement by user. */
#define FDB_INFO(...)                                                                                                  \
    FDB_LOG_PREFIX();                                                                                                  \
    FDB_PRINT(__VA_ARGS__)
/* assert for developer. */
#define FDB_ASSERT(EXPR)                                                                                               \
    if (!(EXPR)) {                                                                                                     \
        FDB_INFO("(%s) has assert failed at %s.\n", #EXPR, __func__);                                                  \
        while (1)                                                                                                      \
            ;                                                                                                          \
    }

#define FDB_KVDB_CTRL_SET_SEC_SIZE                                                                                     \
    0x00 /**< set sector size control command, this change MUST before                                                 \
            database initialization */
#define FDB_KVDB_CTRL_GET_SEC_SIZE                                                                                     \
    0x01                              /**< get sector size control command                                             \
                                       */
#define FDB_KVDB_CTRL_SET_LOCK   0x02 /**< set lock function control command */
#define FDB_KVDB_CTRL_SET_UNLOCK 0x03 /**< set unlock function control command */
#define FDB_KVDB_CTRL_SET_FILE_MODE                                                                                    \
    0x09 /**< set file mode control command, this change MUST before database                                          \
            initialization */
#define FDB_KVDB_CTRL_SET_MAX_SIZE                                                                                     \
    0x0A /**< set database max size in file mode control command, this change                                          \
            MUST before database initialization */
#define FDB_KVDB_CTRL_SET_NOT_FORMAT                                                                                   \
    0x0B /**< set database NOT format mode control command, this change MUST                                           \
            before database initialization */

#define FDB_TSDB_CTRL_SET_SEC_SIZE                                                                                     \
    0x00 /**< set sector size control command, this change MUST before                                                 \
            database initialization */
#define FDB_TSDB_CTRL_GET_SEC_SIZE                                                                                     \
    0x01                              /**< get sector size control command                                             \
                                       */
#define FDB_TSDB_CTRL_SET_LOCK   0x02 /**< set lock function control command */
#define FDB_TSDB_CTRL_SET_UNLOCK 0x03 /**< set unlock function control command */
#define FDB_TSDB_CTRL_SET_ROLLOVER                                                                                     \
    0x04                                 /**< set rollover control command, this change MUST after database            \
                                            initialization */
#define FDB_TSDB_CTRL_GET_ROLLOVER  0x05 /**< get rollover control command */
#define FDB_TSDB_CTRL_GET_LAST_TIME 0x06 /**< get last save time control command */
#define FDB_TSDB_CTRL_SET_FILE_MODE                                                                                    \
    0x09 /**< set file mode control command, this change MUST before database                                          \
            initialization */
#define FDB_TSDB_CTRL_SET_MAX_SIZE                                                                                     \
    0x0A /**< set database max size in file mode control command, this change                                          \
            MUST before database initialization */
#define FDB_TSDB_CTRL_SET_NOT_FORMAT                                                                                   \
    0x0B /**< set database NOT formatable mode control command, this change                                            \
            MUST before database initialization */

#ifdef FDB_USING_TIMESTAMP_64BIT
typedef int64_t fdb_time_t;
#else
typedef int32_t fdb_time_t;
#endif /* FDB_USING_TIMESTAMP_64BIT */

typedef fdb_time_t (*fdb_get_time)(void);

struct fdb_default_kv_node {
    char *key;
    void *value;
    size_t value_len;
};

struct fdb_default_kv {
    struct fdb_default_kv_node *kvs;
    size_t num;
};

/* error code */
typedef enum {
    FDB_NO_ERR,
    FDB_ERASE_ERR,
    FDB_READ_ERR,
    FDB_WRITE_ERR,
    FDB_PART_NOT_FOUND,
    FDB_KV_NAME_ERR,
    FDB_KV_NAME_EXIST,
    FDB_SAVED_FULL,
    FDB_INIT_FAILED,
} fdb_err_t;

enum fdb_kv_status {
    FDB_KV_UNUSED,
    FDB_KV_PRE_WRITE,
    FDB_KV_WRITE,
    FDB_KV_PRE_DELETE,
    FDB_KV_DELETED,
    FDB_KV_ERR_HDR,
#define FDB_KV_STATUS_NUM 6
};
typedef enum fdb_kv_status fdb_kv_status_t;

enum fdb_tsl_status {
    FDB_TSL_UNUSED,
    FDB_TSL_PRE_WRITE,
    FDB_TSL_WRITE,
    FDB_TSL_USER_STATUS1,
    FDB_TSL_DELETED,
    FDB_TSL_USER_STATUS2,
#define FDB_TSL_STATUS_NUM 6
};
typedef enum fdb_tsl_status fdb_tsl_status_t;

/* key-value node object */
struct fdb_kv {
    fdb_kv_status_t status;     /**< node status, @see fdb_kv_status_t */
    bool crc_is_ok;             /**< node CRC32 check is OK */
    uint8_t name_len;           /**< name length */
    uint32_t magic;             /**< magic word(`K`, `V`, `4`, `0`) */
    uint32_t len;               /**< node total length (header + name + value), must align by
                                   FDB_WRITE_GRAN */
    uint32_t value_len;         /**< value length */
    char name[FDB_KV_NAME_MAX]; /**< name */
    struct {
        uint32_t start; /**< node start address */
        uint32_t value; /**< value start address */
    } addr;
};
typedef struct fdb_kv *fdb_kv_t;

struct fdb_kv_iterator {
    struct fdb_kv curr_kv;       /**< Current KV we get from the iterator */
    uint32_t iterated_cnt;       /**< How many KVs have we iterated already */
    size_t iterated_obj_bytes;   /**< Total storage size of KVs we have iterated. */
    size_t iterated_value_bytes; /**< Total value size of KVs we have iterated. */
    uint32_t sector_addr;        /**< Current sector address we're iterating. DO NOT
                                    touch it. */
    uint32_t traversed_len;      /**< Traversed sector total length. */
};
typedef struct fdb_kv_iterator *fdb_kv_iterator_t;

/* time series log node object */
struct fdb_tsl {
    fdb_tsl_status_t status; /**< node status, @see fdb_log_status_t */
    fdb_time_t time;         /**< node timestamp */
    uint32_t log_len;        /**< log length, must align by FDB_WRITE_GRAN */
    struct {
        uint32_t index; /**< node index address */
        uint32_t log;   /**< log data address */
    } addr;
};
typedef struct fdb_tsl *fdb_tsl_t;
typedef bool (*fdb_tsl_cb)(fdb_tsl_t tsl, void *arg);

typedef enum {
    FDB_DB_TYPE_KV,
    FDB_DB_TYPE_TS,
} fdb_db_type;

/* the flash sector store status */
enum fdb_sector_store_status {
    FDB_SECTOR_STORE_UNUSED,
    FDB_SECTOR_STORE_EMPTY,
    FDB_SECTOR_STORE_USING,
    FDB_SECTOR_STORE_FULL,
#define FDB_SECTOR_STORE_STATUS_NUM 4
};
typedef enum fdb_sector_store_status fdb_sector_store_status_t;

/* the flash sector dirty status */
enum fdb_sector_dirty_status {
    FDB_SECTOR_DIRTY_UNUSED,
    FDB_SECTOR_DIRTY_FALSE,
    FDB_SECTOR_DIRTY_TRUE,
    FDB_SECTOR_DIRTY_GC,
#define FDB_SECTOR_DIRTY_STATUS_NUM 4
};
typedef enum fdb_sector_dirty_status fdb_sector_dirty_status_t;

/* KVDB section information */
struct kvdb_sec_info {
    bool check_ok; /**< sector header check is OK */
    struct {
        fdb_sector_store_status_t store; /**< sector store status @see fdb_sector_store_status_t */
        fdb_sector_dirty_status_t dirty; /**< sector dirty status @see sector_dirty_status_t */
    } status;
    uint32_t addr;     /**< sector start address */
    uint32_t magic;    /**< magic word(`E`, `F`, `4`, `0`) */
    uint32_t combined; /**< the combined next sector number, 0xFFFFFFFF: not
                          combined */
    size_t remain;     /**< remain size */
    uint32_t empty_kv; /**< the next empty KV node start address */
};
typedef struct kvdb_sec_info *kv_sec_info_t;

/* TSDB section information */
struct tsdb_sec_info {
    bool check_ok;                     /**< sector header check is OK */
    fdb_sector_store_status_t status;  /**< sector store status @see fdb_sector_store_status_t */
    uint32_t addr;                     /**< sector start address */
    uint32_t magic;                    /**< magic word(`T`, `S`, `L`, `0`) */
    fdb_time_t start_time;             /**< the first start node's timestamp, 0xFFFFFFFF: unused */
    fdb_time_t end_time;               /**< the last end node's timestamp, 0xFFFFFFFF: unused */
    uint32_t end_idx;                  /**< the last end node's index, 0xFFFFFFFF: unused */
    fdb_tsl_status_t end_info_stat[2]; /**< the last end node's info status */
    size_t remain;                     /**< remain size */
    uint32_t empty_idx;                /**< the next empty node index address */
    uint32_t empty_data;               /**< the next empty node's data end address */
};
typedef struct tsdb_sec_info *tsdb_sec_info_t;

struct kv_cache_node {
    uint16_t name_crc; /**< KV name's CRC32 low 16bit value */
    uint16_t active;   /**< KV node access active degree */
    uint32_t addr;     /**< KV node address */
};
typedef struct kv_cache_node *kv_cache_node_t;

struct sector_cache_node {
    uint32_t addr;       /**< sector start address */
    uint32_t empty_addr; /**< sector empty address */
};
typedef struct sector_cache_node *sector_cache_node_t;

/* database structure */
typedef struct fdb_db *fdb_db_t;
struct fdb_db {
    const char *name; /**< database name */
    fdb_db_type type; /**< database type */
    union {
#ifdef FDB_USING_FAL_MODE
        const struct fal_partition *part; /**< flash partition for saving database */
#endif
#ifdef FDB_USING_FILE_MODE
        const char *dir; /**< directory path for saving database */
#endif
    } storage;
    uint32_t sec_size;    /**< flash section size. It's a multiple of block size */
    uint32_t max_size;    /**< database max size. It's a multiple of section size */
    uint32_t oldest_addr; /**< the oldest sector start address */
    bool init_ok;         /**< initialized successfully */
    bool file_mode;       /**< is file mode, default is false */
    bool not_formatable;  /**< is can NOT be formated mode, default is false */
#ifdef FDB_USING_FILE_MODE
#if defined(FDB_USING_FILE_POSIX_MODE)
    int cur_file; /**< current file object */
#elif defined(FDB_USING_FILE_LIBC_MODE)
    FILE *cur_file; /**< current file object */
#endif
    uint32_t cur_sec; /**< current operate sector address  */
#endif
    void (*lock)(fdb_db_t db);   /**< lock the database operate */
    void (*unlock)(fdb_db_t db); /**< unlock the database operate */

    void *user_data;
};

/* KVDB structure */
struct fdb_kvdb {
    struct fdb_db parent;              /**< inherit from fdb_db */
    struct fdb_default_kv default_kvs; /**< default KV */
    bool gc_request;                   /**< request a GC check */
    bool in_recovery_check;            /**< is in recovery check status when first reboot */
    struct fdb_kv cur_kv;
    struct kvdb_sec_info cur_sector;
    bool last_is_complete_del;

#ifdef FDB_KV_USING_CACHE
    /* KV cache table */
    struct kv_cache_node kv_cache_table[FDB_KV_CACHE_TABLE_SIZE];
    /* sector cache table, it caching the sector info which status is current
     * using */
    struct sector_cache_node sector_cache_table[FDB_SECTOR_CACHE_TABLE_SIZE];
#endif /* FDB_KV_USING_CACHE */

#ifdef FDB_KV_AUTO_UPDATE
    uint32_t ver_num; /**< setting version number for update */
#endif

    void *user_data;
};
typedef struct fdb_kvdb *fdb_kvdb_t;

/* TSDB structure */
struct fdb_tsdb {
    struct fdb_db parent;         /**< inherit from fdb_db */
    struct tsdb_sec_info cur_sec; /**< current using sector */
    fdb_time_t last_time;         /**< last TSL timestamp */
    fdb_get_time get_time;        /**< the current timestamp get function */
    size_t max_len;               /**< the maximum length of each log */
    bool rollover;                /**< the oldest data will rollover by newest data, default is
                                     true */

    void *user_data;
};
typedef struct fdb_tsdb *fdb_tsdb_t;

/* blob structure */
struct fdb_blob {
    void *buf;   /**< blob data buffer */
    size_t size; /**< blob data buffer size */
    struct {
        uint32_t meta_addr; /**< saved KV or TSL index address */
        uint32_t addr;      /**< blob data saved address */
        size_t len;         /**< blob data saved length */
    } saved;
};
typedef struct fdb_blob *fdb_blob_t;

#ifdef __cplusplus
}
#endif

#endif /* _FDB_DEF_H_ */
