#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <Windows.h>//用到了ShellExecute

#include "sqlite3/sqlite3.h"

/*
说明:
	传给sqlite3_exec的回调函数,用来显示查询结果
	对每一条查询结果调用一次该回调函数
参数:
	pv:由sqlite3_exec传递的初始化参数
	argc:表头的列数
	col:表头的名字数组指针
	argv:表头的数据数组指针
返回值:
	1:中断查找
	0:继续列举查询到的数据
+-----------------------------------+
|  id  |  pic   |  data(16进制数据)  |
|-----------------------------------|
|   1  |  a.jpg |      00 00 00 ... |
|-----------------------------------|
|   2  |  b.jpg |     XX XX XX      |
+-----------------------------------+
对第一行:
	argc=3 即 [0]...[2]
	argv[0]="1",argv[1]="a.jpg",argv[2]="000000..."
	col[0]="id",col[1]="pic",col[2]="data"
*/
int callback(void* pv, int argc, char** argv, char** col)
{
	printf("id:%s -> pic:%s\n", argv[0], argv[1]);
	return 0;//continue search
}

int main(void)
{
	sqlite3* pDB = NULL;//先要为被打开的数据库建立一个sqlite3型的变量 
	sqlite3_stmt* pStmt = NULL;//sqlite3内部定义的一种"SQL语句"
	char* szMsg = NULL;//传递给sqlite3_*,sqlite3_*为其分配内存空间装载错误消息,调用sqlite3_free释放
	char buffer[1024];//我用来写SQL语句等其它临时使用的缓冲区
	char* file_buffer = NULL;//用来保存JPG的二进制数据
	FILE* fp = NULL;//文件指针
	int pic_id;//后来在显示查询时用来索引图片ID
	size_t file_size;//读取本地的文件的大小 或 从数据库读取的二进制数据的大小
	WIN32_FIND_DATA fd;//用来查找的结构体,C库可用_finddata_t,_finfirst,_findnext
	HANDLE hFile = INVALID_HANDLE_VALUE;//Win32API FindFirstFile传回的句柄
	int ret;//保存返回值
	/*函数:sqlite3_open(char* db, sqlite3* pDB)
	**参数:db:数据库文件路径,都要使用UTF-8编码(或UTF-16)
	**     pDB:sqlite3* 型的数据库指针
	**返回值:打开的状态:SQLITE_OK 成功		
	*/
	ret = sqlite3_open("./test.db", &pDB);
	if(ret != SQLITE_OK) {
		fprintf(stderr, "open error.\n");
		return 1;
	}
	//SQL创建表的语句,"integer primary key":主键,唯一,递增; "text":变长文本; "blob":二进制数据类型(都是变长的)
	_snprintf(buffer, sizeof(buffer), "create table if not exists testdb (id integer primary key, pic text, data blob);");
	//执行一条语句:sqlite3_exec(sqlite3* pDB, char* SQL, 回调函数, 初始化参数, char** ppErrMsg);
	//回调函数主要针对查询, 这里就不需要了
	if(sqlite3_exec(pDB, buffer, NULL, NULL, &szMsg) != SQLITE_OK) {
		fprintf(stderr, "exec error:%s\n", szMsg);
		sqlite3_free(szMsg);//释放内部分配的空间
		sqlite3_close(pDB);//来用关闭打开的数据库
		return 1;
	}
	printf("scanning pics...\n");
	hFile = FindFirstFile("*.jpg", &fd);//查找所有的JPG文件
	if(hFile != INVALID_HANDLE_VALUE) {
		do 
		{
			printf("add %s...\n", fd.cFileName);
			//SQL的插入语句:"insert into 表名 (需要的表头的名字[, ...]) values (对应的值[,...]);"
			//对于主键不需要插入, 自动会增加的; 字符串用 单引号 括起来
			//对于二进制数据:可以利用sqlite3提供的准备语句完成:即使用 '?' 临时代替值(也可以使用其它符号,参考文档. 因为二进制数据太多), 后面利用 sqlite3_bind_* 绑定相关的数据
			//准备语句 sqlite3_prepare(sqlite3* pDB, char* SQL, int LenOfSQL, sqlite3_stmt* pStmt, char** pzTail)
			//LenOfSQL可以传-1,表示到第一个'\0'为止, 最后一个参数不太清楚作用, 记得是指向未使用的SQL, 因为可以指定SQL语句的长度的原因, 我一般不使用
			//返回SQLITE_OK表示成功
			_snprintf(buffer, sizeof(buffer), "insert into testdb (pic,data) values (\'%s\',?);", fd.cFileName);
			if(sqlite3_prepare(pDB, buffer, -1, &pStmt, NULL) != SQLITE_OK) {
				fprintf(stderr, "sqlite3_prepare error\n");
				continue;
			}
			//C库读取文件到buffer空间
			fp = fopen(fd.cFileName, "rb");
			if(!fp) continue;
			fseek(fp, 0L, SEEK_END);
			file_size = ftell(fp);
			rewind(fp);
			file_buffer = (char*)malloc(file_size);
			if(!file_buffer) {
				fclose(fp);
				continue;
			}
			fread(file_buffer, 1, file_size, fp);
			fclose(fp);
			//这里就是绑定前面的'?'数据的语句, 因为是data blob, 所以这里使用 *_blob 函数
			//sqlite3_bind_blob(sqlite3_stmt* pStmt, int which, void* pv, size_t size, XXX)
			//pStmt前面准备的"语句". which:第几个未设定数据的"变量"(前面的'?'), 第一个是1, 依次++
			//返回SQLITE_OK示成功. 最后一个参数不清楚, 未使用
			if(sqlite3_bind_blob(pStmt, 1, file_buffer, file_size, NULL)!=SQLITE_OK) {
				fprintf(stderr, "sqlite3_bind_blod error:%s", sqlite3_errmsg(pDB));
			}
			//现在是真正地完成SQL语句的操作:初始化语句->绑定语句->执行语句
			//这里如返回SQLITE_DONE表示完成, 返回值应根据语句选择. SQLITE_ROW 表示查询有了至少有了一条结果
			if(sqlite3_step(pStmt) !=SQLITE_DONE) {
				fprintf(stderr, "sqlite3_step error:%s", sqlite3_errmsg(pDB));
			}
			free(file_buffer);
			//用来析构前面的操作(准备语句)所分配的空间. 一定要析构掉, 不然空间不会释放(不管是否执行成功)
			sqlite3_finalize(pStmt);
		}while(FindNextFile(hFile, &fd));//继续查找一下个 JPG 文件
		printf("\n");
	}
	else
	{
		fprintf(stderr, "no more files found\n");
		sqlite3_close(pDB);
		return 1;
	}
	FindClose(hFile);
	//SQL的查找语句:select 表头名[,表头名] from 表名 [order by 表头名 desc|asc];
	//像查找之类的就不需要 准备->绑定->执行 这样复杂了, 直接一个sqlite3_exec 就可以完成
	//查询当然是需要回调的
	_snprintf(buffer, sizeof(buffer), "select id,pic from testdb order by id asc;");
	if(sqlite3_exec(pDB, buffer, callback, NULL, &szMsg) != SQLITE_OK) {
		fprintf(stderr, "sqlite3_exec error:%s\n", szMsg);
		sqlite3_close(pDB);
		return 1;
	}
	printf("\n");
	printf("Input pic's id to open(q for quit):");
	while(scanf("%d", &pic_id) == 1) {
		_snprintf(buffer, sizeof(buffer), "select * from testdb where id=%d", pic_id);
		//由于是select * , 且我这个表里有二进制数据, 所以这里用准备语句优于直接执行语句
		sqlite3_prepare(pDB, buffer, -1, &pStmt, NULL);
		//SQLITE_ROW表示查询有结果
		//可以使用while语句显示所有数据, 直到sqlite3_step 返回 SQLITE_DONE 表示没有更多的数据为止
		//我这里只有至多只有一条, 因为id是唯一的
		if(sqlite3_step(pStmt) == SQLITE_ROW) {
			//sqlite3_column_bytes 返回表头某一项的大小(字节), 最左边的是0, 在这里:id为0,pic是1,data是2
			//pStmt, 准备语句指针
			file_size = sqlite3_column_bytes(pStmt, 2); //left most is 0->schema:id,test,blob
			//sqlite3_column_text(sqlite3_stmt* pStmt, int iCol);
			//iCol:返回哪一项的文本(最好不要把数据类型乱用)
			//最左边的iCol是0, pic这里就是1, pic是text文本类型
			_snprintf(buffer, sizeof(buffer), "new_%s", sqlite3_column_text(pStmt, 1));
			//把读取到的数据保存为新文件
			fp = fopen(buffer, "wb");
			if(!fp) {
				sqlite3_finalize(pStmt);
				continue;
			}
			//sqlite3_column_blob 返回表头某一项的二进制数据指针, 最左边的是0, 这里data是2
			fwrite(sqlite3_column_blob(pStmt, 2), 1, file_size, fp);
			fclose(fp);
			//打开该文件
			ShellExecute(NULL, "open", buffer, NULL, NULL, SW_SHOWNORMAL);
		}
		else//sqlite3_step 不是返回 SQLITE_ROW 表示没有数据
		{
			printf("no such pic whose id is:%d\n", pic_id);
		}
		//析构掉
		sqlite3_finalize(pStmt);
		printf("\nInput pic's id to open(q for quit):");
	}
	//结束, 关闭, 退出
	sqlite3_close(pDB);
	return 0;
}
