#ifndef	ACL_ZDB_INCLUDE_H
#define	ACL_ZDB_INCLUDE_H

#include "stdlib/acl_define.h"
#include "stdlib/acl_vstring.h"
#include "stdlib/acl_fhandle.h"

typedef struct ZDB ZDB;
typedef struct ZDB_KEY_HDR ZDB_KEY_HDR;
typedef struct ZDB_BLK ZDB_BLK;
typedef struct ZDB_BLK_OFF ZDB_BLK_OFF;
typedef struct ZDB_DAT_HDR ZDB_DAT_HDR;
typedef struct ZDB_KEY_STORE ZDB_KEY_STORE;
typedef struct ZDB_DAT_STORE ZDB_DAT_STORE;
typedef struct ZDB_IO ZDB_IO;

/* 偏移量的长度类型 */
typedef acl_int64 zdb_off_t;

/* 键类型定义 */
typedef acl_int64 zdb_key_t;

/* 值存储中空闲数据块链接偏移量类型 */
typedef int zdb_lnk_t;

/* 磁盘分区信息类型 */
typedef struct ZDB_DISK {
	acl_int64 limit;	/* 该分区的总数限制 */
	acl_int64 count;	/* 目前该分区已分配数量 */
	char *path;		/* 磁盘分区路径 */
	int   idisk;		/* 该磁盘分区号 */
	int   priority;		/* 权重优先级 */
	int  *dat_ifiles;	/* 当前可用的 dat_ifile 值存储文件索引号 */
	int   dat_ifiles_size;	/* dat_ifiles 的大小 */
} ZDB_DISK;

/* 调用 zdb_open() 时的 ZDB 配置对象 */
typedef struct ZDB_CFG {
	const char *key_path;	/* 键存储所在的根目录 */
	zdb_key_t key_begin;	/* 键值的起始值 */
	zdb_key_t key_limit;	/* 每个键存储文件中键的最大个数 */
	acl_int64 dat_limit;	/* 每个值存储文件中数据的最大个数 */

	int   key_cache_max;	/* 启用键存储 IO 缓存时的最大缓存数据块个数 */
	int   key_cache_timeout; /* 启用键存储 IO 缓存时每个缓存块的过期时间 */
	int   key_wback_max;	/* 启用键存储 IO 缓存时写缓存的最大缓存数据块个数 */

	int   dat_nstep;	/* 值存储中增加数据块时的步进值 */
	int   blk_dlen;		/* 值存储中数据块中数据部分的长度(等于用户数据长度) */
	int   dat_cache_max;	/* 启用值存储 IO 缓存时的最大缓存数据块个数 */
	int   dat_cache_timeout; /* 启用值存储 IO 缓存时每个缓存块的过期时间 */
	int   dat_wback_max;	/* 启用值存储 IO 缓存时写缓存的最大缓存数据块个数 */
} ZDB_CFG;

/* ZDB 结构类型 */
struct ZDB {
	/* public */

	char *dbname;		/* 数据库名 */
	char *key_path;		/* ZDB 的key存储路径 */
	unsigned int oflags;	/* 打开时的标志位 */
#define	ZDB_FLAG_LINK_BUSY	(1 << 0)  /* 将值存储中的占用数据块连接起来 */
#define	ZDB_FLAG_OPEN_LOCK	(1 << 1)  /* 以加锁模式打开存储句柄 */
#define	ZDB_FLAG_CACHE_DAT	(1 << 2)  /* 是否缓存值存储的数据 */
#define	ZDB_FLAG_CACHE_KEY	(1 << 3)  /* 是否缓存键存储的数据 */
#define	ZDB_FLAG_SLICE_KEY	(1 << 4)  /* 启用值存储时是否采用内存切片方式 */
#define	ZDB_FLAG_SLICE_DAT	(1 << 5)  /* 启用键存储时是否采用内存切片方式 */

	unsigned int status;	/* 状态位 */
#define	ZDB_STAT_KEY_NEW	(1 << 0)  /* 新键 */

	/* private */

	zdb_key_t key_begin;	/* 所有键存储的起始值 */
	zdb_key_t key_limit;	/* 每个键存储中键的个数的最大值 */
	int   key_cache_max;	/* 启用键存储 IO 缓存时的最大缓存数据块个数 */
	int   key_cache_timeout; /* 启用键存储 IO 缓存时每个缓存块的过期时间 */
	int   key_wback_max;	/* 启用键存储 IO 缓存时写缓存的最大缓存数据块个数 */

	acl_int64 dat_limit;	/* 值存储中值对象存储个数限制 */
	int   blk_dlen;		/* 值存储中数据块中每个基础块单元长度 */
	int   dat_nstep;	/* 值存储中每次增加数据块的个数 */
	int   dat_cache_max;	/* 启用值存储 IO 缓存时的最大缓存数据块个数 */
	int   dat_cache_timeout; /* 启用值存储 IO 缓存时每个缓存块的过期时间 */
	int   dat_wback_max;	/* 启用值存储 IO 缓存时写缓存的最大缓存数据块个数 */

	ZDB_DISK *dat_disks;	/* 值存储磁盘分区数组 */

	int   (*key_get)(ZDB*, zdb_key_t, ZDB_BLK_OFF*);
	int   (*key_set) (ZDB*, zdb_key_t, const ZDB_BLK_OFF*);

	ZDB_BLK *(*dat_get)(ZDB*, const ZDB_BLK_OFF*, zdb_key_t*, size_t*);
	int   (*dat_add)(ZDB*, zdb_key_t, const void*, int);
	int   (*dat_update)(ZDB*, zdb_key_t, const ZDB_BLK_OFF*,
			const void*, size_t);

	/* private */

	/* 以下为临时变量 */

	ACL_VSTRING *path_tmp;	/* 临时用的存储文件的全路径, 主要为了参数传递 */
	int   blk_count_tmp;	/* 临时用的块数, 主要为了参数传递 */
	int   inode_tmp;	/* 临时用的相对路径号，主要为了参数传递 */
};

/* xxx: 为了保证跨平台性，以下结构定义都是4字节对齐的 */

#ifdef	ACL_SUNOS5
#pragma pack(4)
#else
#pragma pack(push, 4)
#endif

/* 值存储头 */
struct ZDB_DAT_HDR {
	acl_int64  limit;	/* 值存储中值对象存储个数限制 */
	acl_int64  size;	/* 当前值存储文件已经分配的对象存储的个数 */
	acl_int64  count;	/* 当前值存储中的总数 */
	acl_int64  reserv1;	/* 保留字段 */
	acl_int64  reserv2;	/* 保留字段 */
	acl_int64  reserv3;	/* 保留字段 */
	acl_int64  reserv4;	/* 保留字段 */
	int   nstep;		/* 每次增加时值存储文件大小时的步进值大小 */
	int   blk_hdr_dlen;	/* 值存储中每块数据的头部长度: ZDB_BLK_HDR.sizeof */
	int   blk_dlen;		/* 值存储中数据块中每个基础块单元长度 */
	int   blk_count;	/* 该值存储中的数据块中基础块个数 */

	/* 第一个空闲块的位置的真实具体位置的计算公式为:
	 * zdb_off_t off_head = ZDB_DAT_HDR.sizeof +
	 *     ZDB_DAT_HDR.ihead_idle * (ZDB_DAT_HDR.blk_hdr_dlen +
	 *         ZDB_DAT_HDR.blk_dlen * ZDB_DAT_HDR.blk_count);
	 */
	zdb_lnk_t ihead_idle;

	/* 第一个数据块的位置的真实具体位置的计算公式为:
	 * zdb_off_t off_head = ZDB_DAT_HDR.sizeof +
	 *     ZDB_DAT_HDR.ihead_busy * (ZDB_DAT_HDR.blk_hdr_dlen +
	 *         ZDB_DAT_HDR.blk_dlen * ZDB_DAT_HDR.blk_count);
	 */
	zdb_lnk_t ihead_busy;
	zdb_lnk_t itail_busy;
	int   dummy;		/* 保证8字节对齐 */
};

/* 键存储的头 */
struct ZDB_KEY_HDR {
	zdb_key_t  key_limit;	/* 该键存储的最大容量 */
	zdb_key_t  key_count;	/* 当前所有键的总数 */
	zdb_key_t  key_begin;	/* 键的起始值 */
	acl_int64  reserv1;	/* 保留字段 */
	acl_int64  reserv2;	/* 保留字段 */
	acl_int64  reserv3;	/* 保留字段 */
	acl_int64  reserv4;	/* 保留字段 */
};

/* 块数据的头结构类型 */
typedef struct ZDB_BLK_HDR {
	zdb_key_t  key;		/* 对应于键存储中的键 */

	/* 标识本数据块的索引位置, 校验用, 同时保证了 8 字节对齐 */
	zdb_lnk_t  blk_ilnk;

	/* 由 inext_idle 将空闲数据块连接成一个单向链表, 若此值为 -1 则表示
	 * 非空闲块, 后一个空闲块位置, 后一个空闲块的真实具体位置的计算公式为:
	 * zdb_off_t off_next = ZDB_BLK_HDR.sizeof +
	 *     ZDB_BLK_HDR.inext_idle * (ZDB_BLK_HDR.blk_hdr_dlen +
	 *         ZDB_DAT_HDR.blk_dlen * ZDB_DAT_HDR.blk_count);
	 */
	zdb_lnk_t  inext_idle;

#ifdef	ZDB_LINK_BUSY
	/* 由 inext_busy/iprev_busy 将占用数据块连接成一个双向链表, 若此值为 -1
	 * 则表示非占用块, 后一个占用块位置, 后一个占用块的真实具体位置的计算公式为:
	 * zdb_off_t off_next = ZDB_BLK_HDR.sizeof +
	 *     ZDB_BLK_HDR.inext_busy * (ZDB_BLK_HDR.blk_hdr_dlen +
	 *         ZDB_DAT_HDR.blk_dlen * ZDB_DAT_HDR.blk_count);
	 * zdb_off_t off_prev = ZDB_BLK_HDR.sizeof +
	 *     ZDB_BLK_HDR.inext_prev * (ZDB_BLK_HDR.blk_hdr_dlen +
	 *         ZDB_DAT_HDR.blk_dlen * ZDB_DAT_HDR.blk_count);
	 */
	zdb_lnk_t  inext_busy;
	zdb_lnk_t  iprev_busy;
#endif
} ZDB_BLK_HDR;

/* 键存储中存储的值存储的偏移量类型 */
struct ZDB_BLK_OFF {
	zdb_off_t offset;	/* 偏移量信息 */
	int   inode;		/* 路径信息 */
};

/* 块数据结构类型 */
struct ZDB_BLK {
	ZDB_BLK_HDR hdr;
	char  dat[1];		/* 值存储中每块数据的体数据 */
};

#ifdef	ACL_SUNOS5
#pragma pack(0)  /* 以下取消4字节对齐限制 */
#else
#pragma pack(pop)  /* 以下取消4字节对齐限制 */
#endif

/* 通用存储句柄结构 */
typedef struct ZDB_STORE {
	ACL_FHANDLE fhandle;	/* 文件句柄 */
	ZDB *db;		/* 引用对象 */
	ZDB_IO *io;		/* IO 句柄对象 */
	int   cache_max;	/* 启用存储 IO 缓存时的最大缓存数据块个数 */
	int   cache_timeout;	/* 启用存储 IO 缓存时每个缓存块的过期时间 */
	int   wback_max;	/* 写缓存中数据块的限制 */

	unsigned int flag;	/* 标志位 */
#define	STORE_FLAG_KEY		(1 << 0)  /* 表示是键存储 */
#define	STORE_FLAG_DAT		(1 << 1)  /* 表示是值存储 */
#define	STORE_FLAG_IO_SLICE	(1 << 2)  /* 是否启用 ZDB_IO 的内存切片分配方式 */

	/* for acl_iterator */

	/* 取迭代器头函数 */
	const void *(*iter_head)(ACL_ITER*, struct ZDB_STORE*);
	/* 取迭代器下一个函数 */
	const void *(*iter_next)(ACL_ITER*, struct ZDB_STORE*);
} ZDB_STORE;

/* 键存储 */
struct ZDB_KEY_STORE {
	ZDB_STORE store;
	ZDB_KEY_HDR hdr;	/* 键存储头 */
};


/* 值存储 */
struct ZDB_DAT_STORE {
	ZDB_STORE store;
	ZDB_DAT_HDR hdr;	/* 值存储头 */
};

#ifdef	__cplusplus
extern "C" {
#endif

/*-------------------------  in zdb_test.c  ----------------------------------*/

/**
 * zdb 测试函数
 */
ACL_API void zdb_test(const char *cmd);

/*----------------------------   in zdb.c   ----------------------------------*/

/* in zdb.c */

/**
 * 程序开始运行时需要初始化 zdb
 */
ACL_API void zdb_init(void);

/**
 * 程序退出前需要释放 zdb 内部一些资源
 */
ACL_API void zdb_end(void);

/**
 * 更新ZDB相关信息至磁盘
 * @param db {ZDB*}
 */
ACL_API void zdb_sync(ZDB *db);

/**
 * 打开或创建一个 ZDB 数据库
 * @param dbname {const char*} ZDB 数据库名称
 * @param oflags {unsigned int} 打开 ZDB 库时的标志位
 * @param cfg {const ZDB_CFG*} 打开 ZDB 时的配置对象
 * @return {ZDB*} ZDB 数据库句柄
 */
ACL_API ZDB *zdb_open(const char *dbname, unsigned int oflags, const ZDB_CFG *cfg);

/**
 * 关闭 ZDB 数据库句柄
 * @param db {ZDB*} ZDB 数据库句柄
 */
ACL_API void zdb_close(ZDB *db);

/**
 * 在 ZDB 数据库中查找相应键值的数据
 * @param db {ZDB*} ZDB 数据库句柄
 * @param key {zdb_key_t} 键值
 * @param size {size_t*} 若此指针非空且查询结果也非空则存储查询结果的数据长度,
 *  即是 ZDB_BLK.dat 中存储数据的长度
 * @param blk_off_buf {ZDB_BLK_OFF*} 若非空则存储所查值的偏移位置索引号
 * @return {ZDB_BLK*} NULL: 未找到; !NULL: ZDB_BLK->dat 为用户数据的地址,
 *  其中 ZDB_BLK 对象可以用 acl_myfree()/1 进行释放
 * 注意:
 *  当 size 指针地址非空时，调用者的类型必须是 size_t 类型而非 int 类型，因为在
 *  64位机上 size_t 为8个字节长度，而 int 为4个字节长度，内部在对 size 地址赋值
 *  时，编译器会自动按 size_t 的真实空间长度赋值，如果调用者的 size 为 int 类型，
 *  则就会出现空间越办现象，从而会使整个程序出现莫名其妙的问题，并且用 valgrind
 *  也查不出这个错误来!
 */
ACL_API ZDB_BLK *zdb_lookup(ZDB *db, zdb_key_t key, size_t *size, ZDB_BLK_OFF *blk_off_buf);

/**
 * 从 ZDB_BLK 中取得用户数据
 * @param b {ZDB_BLK*}
 * @return {void*}
 */
#define	zdb_blk_data(b)	((b)->dat)

/**
 * 释放由 zdb_lookup()/3 返回的数据空间
 * @param b {ZDB_BLK*}
 */
#define	zdb_blk_free(b)	acl_myfree((b))

/**
 * 添加或更新 ZDB 数据库中的数据
 * @param db {ZDB*} ZDB 数据库句柄
 * @param key {zdb_key_t} 键值
 * @param blk_off_saved {const ZDB_BLK_OFF*} 上次调用 zdb_lookup()/4 时的返回结果,
 *  从而通过重复利用查询结果来提高效率, 如果在调用 zdb_lookup()/4 时返回结果为空,
 *  则调用 zdb_update()/5 时必须将此值置 NULL
 * @param dat {const void*} 键 key 所对应的数据地址
 * @param len {size_t} dat 数据长度
 * @return {int} 0: 未更新或添加; -1: 出错; 1: 成功
 */
ACL_API int zdb_update(ZDB *db, zdb_key_t key, const ZDB_BLK_OFF *blk_off_saved,
	const void *dat, size_t len);

/*--------------------------------- in zdb_key.c -----------------------------*/

/**
 * 同步键存储头至磁盘
 * @param store {ZDB_KEY_STORE*} 值存储句柄
 * @return {int} -1: error, > 0 ok
 */
ACL_API int key_store_header_sync(ZDB_KEY_STORE *store);

/**
 * 根据键值打开键存储
 * @param db {ZDB*}
 * @param key {zdb_key_t} 键值
 * @return {ZDB_KEY_STORE*} !NULL: ok; NULL: error
 */
ACL_API ZDB_KEY_STORE *zdb_key_store_open(ZDB *db, zdb_key_t key);

/**
 * 根据文件名打开键存储
 * @param db {ZDB*}
 * @param filepath {const char*} 文件名
 * @return {ZDB_KEY_STORE*} !NULL: ok; NULL: error
 */
ACL_API ZDB_KEY_STORE *zdb_key_store_open2(ZDB *db, const char *filepath);

/**
 * 关闭键存储
 * @param store {ZDB_KEY_STORE*} 键存储句柄
 */
ACL_API void zdb_key_store_close(ZDB_KEY_STORE *store);

/**
 * 设置键存储中键位置的值位置值
 * @param db {ZDB*}
 * @param key {zdb_key_t}
 * @param blk_off {const ZDB_BLK_OFF*}
 * @return {int} 0: ok; -1: error
 */
ACL_API int zdb_key_set(ZDB *db, zdb_key_t key, const ZDB_BLK_OFF *blk_off);

/**
 * 根据键值从键存储中取得该键所对应的数据索引位置
 * @param db {ZDB*}
 * @param key {zdb_key_t} 键值
 * @param blk_off {ZDB_BLK_OFF*} 存储结果的对象
 * @return {int} 1: 表示查到, 0: 表示未查到, -1: 表示出错
 */
ACL_API int zdb_key_get(ZDB *db, zdb_key_t key, ZDB_BLK_OFF *blk_off);

/**
 * 查询键存储头的状态
 * @param db {ZDB*}
 * @param filepath {const char*} 键存储文件全路径
 * @param key_hdr {ZDB_KEY_HDR*} 用来存放键存储头信息的内存地址
 * @return {int} 0: ok; -1: error
 */
ACL_API int zdb_key_status(ZDB *db, const char *filepath, ZDB_KEY_HDR *key_hdr);

/**
 * 遍历键存储中的键的状态
 * @param db {ZDB*}
 * @param filepath {const char*} 键存储文件全路径
 * @param key_hdr {ZDB_KEY_HDR*} 若非空则用来存放键存储头信息的内存地址
 * @return {int} 0: ok; -1: error
 */
ACL_API int zdb_key_check3(ZDB *db, const char *filepath, ZDB_KEY_HDR *key_hdr);
ACL_API int zdb_key_check(ZDB_KEY_STORE *store, ZDB_KEY_HDR *key_hdr);

/**
 * 初始化键存储
 * @param db {ZDB*}
 * @param key_begin {zdb_key_t} 起始键值
 * @param key_end {zdb_key_t} 结束键值
 * @return {int} 0: ok; -1: error
 */
ACL_API int zdb_key_init(ZDB *db, zdb_key_t key_begin, zdb_key_t key_end);
/*--------------------------------- in zdb_dat.c -----------------------------*/

/**
 * 同步值存储头至磁盘
 * @param store {ZDB_DAT_STORE*} 值存储句柄
 * @return {int} -1: error, > 0 ok
 */
ACL_API int dat_store_header_sync(ZDB_DAT_STORE *store);

/**
 * 关闭值存储
 * @param store {ZDB_DAT_STORE*} 值存储句柄
 */
ACL_API void zdb_dat_store_close(ZDB_DAT_STORE *store);

/**
 * 打开或创建值存储
 * @param filepath {const char*} 值存储文件全路径
 * @return {ZDB_DAT_STORE*} !NULL: ok; NULL: error
 */
ACL_API ZDB_DAT_STORE *zdb_dat_store_open(ZDB *db, const char *filepath);

/**
 * 从值存储中取得对应的数据
 * @param db {ZDB*}
 * @param blk_off {const ZDB_BLK_OFF*}
 * @param key {zdb_key_t*} 存储键的值
 * @param size {size_t*} 存储数据的长度
 * @return {void*} 数据地址，若找到则不为空，找不到或出错则为空, 若不为空,
 *  则需要用 zdb_blk_free()/1 进行释放
 * 注意:
 *  当 size 指针地址非空时，调用者的类型必须是 size_t 类型而非 int 类型，因为在
 *  64位机上 size_t 为8个字节长度，而 int 为4个字节长度，内部在对 size 地址赋值
 *  时，编译器会自动按 size_t 的真实空间长度赋值，如果调用者的 size 为 int 类型，
 *  则就会出现空间越办现象，从而会使整个程序出现莫名其妙的问题，并且用 valgrind
 *  也查不出这个错误来!
 */
ACL_API ZDB_BLK *zdb_dat_get(ZDB *db, const ZDB_BLK_OFF *blk_off,
	zdb_key_t *key, size_t *size);

/**
 * 向值存储中添加新数据
 * @param db {ZDB*}
 * @param key {zdb_key_t} 键值
 * @param dat {const void*} 数据地址
 * @param len {size_t} dat 数据长度
 * @return {int} 0: 数据没有更新; 1: 数据更新; -1: 出错
 */
ACL_API int zdb_dat_add(ZDB *db, zdb_key_t key, const void *dat, int len);

/**
 * 更新值存储中的数据信息
 * @param db {ZDB*}
 * @param key {zdb_key_t} 键
 * @param blk_off {const ZDB_BLK_OFF*} 存储于键存储中相应值的位置信息
 * @param dat {const void*} 数据地址
 * @param len {size_t} dat 数据长度
 * @return {int} 0: 数据没有更新; 1: 数据更新; -1: 出错
 */
ACL_API int zdb_dat_update(ZDB *db, zdb_key_t key, const ZDB_BLK_OFF *blk_off,
	const void *dat, size_t len);

/**
 * 读取值存储的头信息
 * @param db {ZDB*}
 * @param filepath {const char*} 值存储文件名
 * @param dat_hdr {ZDB_DAT_HDR*} 存储结果
 * @retur {int} 0: ok; -1: error
 */
ACL_API int zdb_dat_stat(ZDB *db, const char *filepath, ZDB_DAT_HDR *dat_hdr);

/**
 * 检查值存储中的数据块的正确性
 * @param db {ZDB*}
 * @param filepath {const char*} 值存储文件名
 * @param dat_hdr {ZDB_DAT_HDR*} 若非空则存储值存储头信息
 * @return {int} 0: ok; -1: error
 */
ACL_API int zdb_dat_check3(ZDB *db, const char *filepath, ZDB_DAT_HDR *dat_hdr);
ACL_API int zdb_dat_check(ZDB_DAT_STORE *store, ZDB_DAT_HDR *dat_hdr);

/*--------------------   in zdb_key_walk.c -----------------------------------*/
ACL_API int zdb_key_walk(ZDB *db, int (*walk_fn)(ZDB_KEY_STORE *store));

/*--------------------   in zdb_dat_walk.c -----------------------------------*/
ACL_API int zdb_dat_walk(ZDB *db, int (*walk_fn)(ZDB_DAT_STORE *store));

/*--------------------   in zdb_key_iter.c -----------------------------------*/

/*--------------------   in zdb_dat_iter.c -----------------------------------*/
/**
 * 设置值存储的迭代器
 * @param store {ZDB_DAT_STORE*}
 * @param read_data {int} 是否需要读取数据块中的数据部分, !0: 表示读取数据块中的
 *  数据部分; 0: 仅读取数据块中的头
 */
ACL_API void zdb_dat_iter_set(ZDB_DAT_STORE *store, int read_data);

#ifdef	__cplusplus
}
#endif

#endif
