#include <assert.h>
#include <stdarg.h>
#include "log.h"
#include "basic.h"
#include "sqlite_utils.h"

int sqlite3_utils_fetch(sqlite3_stmt *stmt, char *types, ...)
{
	va_list args;
	va_start(args, types);

	int step = sqlite3_step(stmt);
	if (step == SQLITE_DONE)
		return 1; // No more rows
	if (step != SQLITE_ROW)
		return -1; // Error occurred
	// Have a row
	for (int i = 0; types[i]; i++) {
		switch (types[i]) {

			case 'x': 
			*va_arg(args, const void**) = sqlite3_column_blob(stmt, i);
			*va_arg(args, size_t*) = sqlite3_column_bytes(stmt, i);
			break;

			case 's':
			{
				string *dst = va_arg(args, string*);
				dst->data = sqlite3_column_text(stmt, i);
				dst->size = sqlite3_column_bytes(stmt, i);;
			}
			break;

			case 'i': *va_arg(args, int*) = sqlite3_column_int(stmt, i); break;
			default: va_end(args); return -1;
		}
	}
	va_end(args);
	return 0;
}

static sqlite3_stmt *vprep(sqlite3 *handle, const char *fmt, va_list args)
{
	char   buffer[1 << 10];
	size_t copied = 0;

	char params[8]; // The size of this buffer determines the maximum 
					// number of parameters in a prepared query
	int num_params = 0;

	const char *stmt_str;
	size_t      stmt_len;

	size_t len = strlen(fmt);
	size_t cur = 0;

	while (cur < len && fmt[cur] != ':')
		cur++;

	if (cur == len) {
		stmt_str = fmt;
		stmt_len = len;
	} else {

		// The cursor refers to the first ':'

		if (cur >= sizeof(buffer)) {
			log_data(LIT("Statement text buffer is too small\n"));
			return NULL;
		}
		
		memcpy(buffer, fmt, cur);
		copied = cur;

		do {

			assert(fmt[cur] == ':');
			cur++;
			if (cur == len) {
				log_data(LIT("Missing type specifier after ':'\n"));
				return NULL;
			}
			
			char t = fmt[cur];
			if (t != 'i' && t != 's' && t != 'x') {
				log_format("Invalid type specifier '%c'\n", t);
				return NULL;
			}
			cur++;

			if (num_params == COUNTOF(params)) {
				log_format("Parameter limit reached (%d)\n", COUNTOF(params));
				return NULL;
			}
			params[num_params++] = t;

			if (copied+1 >= sizeof(buffer)) {
				log_data(LIT("Statement text buffer is too small\n"));
				return NULL;
			}
			buffer[copied++] = '?';

			size_t save = cur;

			while (cur < len && fmt[cur] != ':')
				cur++;

			size_t copying = cur - save;
			if (copied + copying >= sizeof(buffer)) {
				log_data(LIT("Statement text buffer is too small\n"));
				return NULL;
			}
			memcpy(buffer + copied, fmt + save, copying);
			copied += copying;

		} while (cur < len);

		assert(copied < sizeof(buffer));
		buffer[copied] = '\0';

		stmt_str = buffer;
		stmt_len = copied;
	}

	DEBUG("SQL: %.*s\n", (int) stmt_len, stmt_str);

	sqlite3_stmt *stmt;
	int code = sqlite3_prepare_v2(handle, stmt_str, stmt_len, &stmt, 0);
	if (code != SQLITE_OK) {
		log_format("Failed to prepare SQL statement (sqlite3: %s)\n", sqlite3_errmsg(handle));
		return NULL;
	}

	for (int i = 0; i < num_params; i++) {
		int code;
		switch (params[i]) {
			
			case 'i': 
			{
				int v = va_arg(args, int);
				DEBUG("binding param %d to int %d\n", i+1, v);
				code = sqlite3_bind_int (stmt, i+1, v); 
			}
			break;

			case 's': 
			{
				string str = va_arg(args, string);
				DEBUG("binding param %d to str %.*s\n", i+1, (int) str.size, str.data);
				code = sqlite3_bind_text(stmt, i+1, str.data, str.size, NULL); 
			}
			break;
			
			case 'x': 
			{
				void  *ptr = va_arg(args, void*);
				size_t len = va_arg(args, size_t);
				DEBUG("binding param %d to blob %p %d\n", i+1, ptr, (int) len);
				code = sqlite3_bind_blob(stmt, i+1, ptr, len, NULL); 
			}
			break;
		}
		if (code != SQLITE_OK) {
			log_format("Failed to bind parameter %d to SQL statement (sqlite3: %s)\n", i+1, sqlite3_errmsg(handle));
			sqlite3_finalize(stmt);
			return NULL;
		}
	}

	return stmt;
}

sqlite3_stmt *sqlite3_utils_prepare(sqlite3 *handle, const char *fmt, ...)
{
	sqlite3_stmt *stmt;

	va_list args;
	va_start(args, fmt);
	stmt = vprep(handle, fmt, args);
	va_end(args);

	return stmt;
}

bool sqlite3_utils_exec(sqlite3 *handle, const char *fmt, ...)
{
	sqlite3_stmt *stmt;

	va_list args;
	va_start(args, fmt);
	stmt = vprep(handle, fmt, args);
	va_end(args);

	if (stmt == NULL)
		return false;

	if (sqlite3_step(stmt) != SQLITE_DONE) {
		log_format("Failed to execute SQL statement (sqlite3: %s)\n", sqlite3_errmsg(handle));
		sqlite3_finalize(stmt);
		return false;
	}

	sqlite3_finalize(stmt);
	return true;
}

int sqlite3_utils_rows_exist(sqlite3 *handle, const char *fmt, ...)
{
	sqlite3_stmt *stmt;

	va_list args;
	va_start(args, fmt);
	stmt = vprep(handle, fmt, args);
	va_end(args);

	if (stmt == NULL)
		return -1;

	int step = sqlite3_step(stmt);
	if (step == SQLITE_DONE) {
		sqlite3_finalize(stmt);
		return 1; // No rows exist
	}

	if (step == SQLITE_ROW) {
		sqlite3_finalize(stmt);
		return 0; // Rows exist
	}

	log_format("Failed to execute SQL statement (sqlite3: %s)\n", sqlite3_errmsg(handle));
	sqlite3_finalize(stmt);
	return -1;
}
