/* 
ZLib license:
Copyright (c) 2011 Dirk de la Hunt

This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.

Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/

/*
This abstracts the SQLite database a little, nothing major, just makes swapping out database engines easier.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "monkeydb.h"
#include "utils.h"

static const char *Tables[] =
{
	"CREATE TABLE category(id integer primary key, destFolder varchar(512), description varchar(64));",
	"CREATE TABLE filter(id integer primary key, description varchar(64), type integer, byteOffset integer, bytes varchar(32), categoryId integer, minFileSize integer);",
	"CREATE TABLE url(id integer primary key, messageId integer, url varchar(2048), visitedCount integer, added integer);",
	"CREATE VIRTUAL TABLE messages USING fts4(contents);",
	"CREATE TABLE message(id integer primary key, messageId integer, added integer, showCount integer, readCount integer, action varchar(32), deleted integer);",
	0
};

typedef struct CallbackCombo
{
	MonkeyDatabaseOnData  callback;
	void                 *parameters;
}
CallbackCombo;

bool MonkeyDatabase::setStatus(const char *Status)
{
	status.Duplicate(Status);
	return (status.Buffer() != 0);
}

bool MonkeyDatabase::createTables()
{
	if (!handle)
		return setStatus("Invalid database handle");

	char *zErrMsg = 0;
	bool  okay    = true;
	int   index   = 0;

	while (Tables[index])
	{
		okay = okay && (sqlite3_exec(handle, Tables[index], 0, 0, &zErrMsg) == SQLITE_OK);
		index++;
	}

	if (!okay)
	{
		setStatus("Could not create all the required database tables");
		return Destroy();
	}

	return true;
}

MonkeyDatabase::MonkeyDatabase()
{
	handle = 0;
}

MonkeyDatabase::~MonkeyDatabase()
{
	Destroy();
}

bool MonkeyDatabase::Create(const char *Path, bool CreateIfNotFound)
{
	Destroy();

	if (!Path)
		return Destroy();

	bool dbExists = Utils::FileExists(Path);

	if (dbExists || CreateIfNotFound)
	{
		if (sqlite3_open(Path, &handle) != 0)
		{
			setStatus(sqlite3_errmsg(handle));
			return Destroy();
		}
		if (!dbExists && !createTables())
			return Destroy();
	}
	else
	{
		setStatus("Database not found");
		return Destroy();
	}

	setStatus(0);
	return true;
}

bool MonkeyDatabase::Destroy()
{
	lastId = 0;

	if (handle)
	{
		sqlite3_close(handle);
		handle = 0;
	}
	/* don't free status, as it is needed outside of Create, which calls Destroy */
	return false;
}

const char* MonkeyDatabase::Status()
{
	if (status.Buffer())
		return status;
	return "";
}

int MonkeyDatabase::LastId()
{
	return lastId;
}

/* In this case, EscapeString simply replaces ' with '' */
char* MonkeyDatabase::EscapeString(const char *Message)
{
	if (!Message)
		return 0;

	int   index;
	int   length  = strlen(Message);
	char *result  = new char[length * 2 + 1]; /* worst case scenario, every character is as ' */
	int   current = 0;

	if (!result)
		return 0;

	for (index = 0; index < length; index++)
	{
		switch (Message[index])
		{
			case '\'':
				result[current++] = '\'';
				result[current++] = '\'';
				break;
			default:
				result[current++] = Message[index];
		}
	}

	result[current] = 0;
	return result;
}

int MonkeyDatabase::onData(void *Parameters, int argc, char *argv[], char *Columns[])
{
	CallbackCombo *combo = (CallbackCombo*)Parameters;

	if (combo && combo->callback)
		combo->callback(combo->parameters, argc, argv);

	return SQLITE_OK;
}

bool MonkeyDatabase::Execute(const char *Sql, MonkeyDatabaseOnData Callback/* = 0*/, void *Parameters/* = 0*/)
{
	if (!handle)
		return setStatus("Database not open");
	if (!Sql || !strlen(Sql))
		return setStatus("Empty SQL statement");

	char          *zErrMsg = 0;
	int            id      = -1;
	CallbackCombo  combo;

	combo.callback   = Callback;
	combo.parameters = Parameters;

	if (sqlite3_exec(handle, Sql, onData, Callback ? &combo : 0, &zErrMsg) == SQLITE_OK)
	{
		lastId = sqlite3_last_insert_rowid(handle);
		setStatus(0);
		return true;
	}

	if (zErrMsg)
	{
		setStatus(zErrMsg);
		sqlite3_free(zErrMsg);
	}
	else
	{
		setStatus("Could not execute database query");
	}

	return false;
}

