/*
 * Kangaroo Punch MultiPlayer Game Server Mark II
 * Copyright (C) 2020-2021  Scott Duensing
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */


#include "db.h"
#include "array.h"

#include "sqlite3.h"


static sqlite3 *_db = NULL;


static sqlite3_stmt *dbSqlBind(char *sql, char *format, va_list args);


static sqlite3_stmt *dbSqlBind(char *sql, char *format, va_list args) {
	int32_t       r;
	int32_t       index;
	sqlite3_stmt *stmt;
	char         *buffer;

	// Bind parameters.
	r = sqlite3_prepare_v2(_db, sql, -1, &stmt, NULL);
	if (r == SQLITE_OK) {
		index = 1;
		while (*format != 0) {
			switch (*format) {
				case 'd':
					r = sqlite3_bind_double(stmt, index, va_arg(args, double));
					break;

				case 'i':
					r = sqlite3_bind_int(stmt, index, va_arg(args, int));
					break;

				case 'v':
					buffer = va_arg(args, char *);
					r = sqlite3_bind_text(stmt, index, buffer, strlen(buffer), SQLITE_STATIC);
					break;

				default:
					utilDie("db:  Unknown format option '%c'.\n", *format);
					break;
			}
			format++;
			index++;
		}
	}

	return stmt;
}


uint8_t dbExecute(char *sql, char *format, ...) {
	char         *error = NULL;
	int32_t       r;
	va_list       args;
	sqlite3_stmt *stmt  = NULL;

	if (format == NULL) {
		// No parameters, just run it.
		r = sqlite3_exec(_db, sql, NULL, NULL, &error);
	} else {
		va_start(args, format);
		stmt = dbSqlBind(sql, format, args);
		va_end(args);
		r = (sqlite3_step(stmt) == SQLITE_DONE ? SQLITE_OK : SQLITE_ERROR);
	}

	if (r == SQLITE_ERROR) logWrite("DB:  %s\n", error);

	if (stmt) sqlite3_finalize(stmt);

	return r == SQLITE_OK ? SUCCESS : FAIL;
}


uint8_t dbQueryMultiple(char ****result, char *sql, char *format, ...) {
	va_list         args;
	sqlite3_stmt   *stmt    = NULL;
	int32_t         r;
	uint8_t         cols;
	uint8_t         x;
	char         ***records = NULL;
	char          **fields  = NULL;

	if (format == NULL) {
		// No parameters, just run it.
		r = sqlite3_prepare_v2(_db, sql, -1, &stmt, NULL);
	} else {
		va_start(args, format);
		stmt = dbSqlBind(sql, format, args);
		va_end(args);
		r = SQLITE_OK;
	}

	while (r == SQLITE_OK || r == SQLITE_ROW) {
		r = sqlite3_step(stmt);
		if (r == SQLITE_ROW) {
			cols = sqlite3_data_count(stmt);
			x    = 0;
			while (cols-- > 0) {
				arrput(fields, strdup((char *)sqlite3_column_text(stmt, x++)));
			}
			arrput(records, fields);
			fields = NULL;
		}
	}

	/*
	logWrite("\n\n");
	for (int r = 0; r < arrlen(records); r++) {
		fields = records[r];
		for (int f = 0; f < arrlen(fields); f++) {
			logWrite("%s\t", fields[f]);
		}
		logWrite("\n");
	}
	logWrite("\n\n");
	*/

	sqlite3_finalize(stmt);

	*result = records;

	return (r == SQLITE_OK || r == SQLITE_DONE) ? SUCCESS : FAIL;
}



uint8_t dbQuerySingle(char rformat, void **result, char *sql, char *format, ...) {
	va_list         args;
	sqlite3_stmt   *stmt = NULL;
	int32_t         r    = 0;
	static double   d    = 0;
	static int32_t  i    = 0;
	static char    *v    = NULL;

	if (v) DEL(v);

	// If everything is NULL, we're cleaning up internal variables (which we do before this).  Just exit.
	if (rformat == 0 && result == NULL && sql == NULL && format == NULL) return SUCCESS;

	if (format == NULL) {
		// No parameters, just run it.
		r = sqlite3_prepare_v2(_db, sql, -1, &stmt, NULL);
	} else {
		va_start(args, format);
		stmt = dbSqlBind(sql, format, args);
		va_end(args);
		r = SQLITE_OK;
	}

	if (r == SQLITE_OK) {
		r = sqlite3_step(stmt);
		if (r == SQLITE_ROW) {
			switch (rformat) {
				case 'd':
					d = sqlite3_column_double(stmt, 0);
					*result = &d;
					break;

				case 'i':
					i = sqlite3_column_double(stmt, 0);
					*result = &i;
					break;

				case 'v':
					v = (char *)sqlite3_column_text(stmt, 0);
					if (v) v = strdup(v);  // Make a copy so it doesn't get clobbered.
					*result = v;
					break;

				default:
					utilDie("db:  Unknown format option '%c'.\n", rformat);
					break;
			}
		}
	}

	sqlite3_finalize(stmt);

	return (r == SQLITE_OK || r == SQLITE_DONE || r == SQLITE_ROW) ? SUCCESS : FAIL;
}


void dbResultRelease(char ****result) {
	char ***records = *result;
	char  **fields  = NULL;
	char   *value   = NULL;

	while (arrlen(records) > 0) {
		fields = records[0];
		while (arrlen(fields) > 0) {
			value = fields[0];
			DEL(value);
			arrdel(fields, 0);
		}
		arrdel(records, 0);
	}
	arrfree(records);

	result = NULL;
}


void dbShutdown(void) {
	dbQuerySingle(0, NULL, NULL, NULL);  // Release memory held by dbQuerySingle.
	sqlite3_close(_db);
	_db = NULL;
}


void dbStartup(void) {
	char    temp[OS_PATH_MAX];
	int32_t r;

	osMkDirP("CACHE");
	sprintf(temp, "CACHE%cCACHE.DB", OS_PATH_SLASH);
	r = sqlite3_open(temp, &_db);
	if (r) utilDie("Unable to open database: %s\n", sqlite3_errmsg(_db));
}
