/* src/rsdb_sqlite.h
 *   Contains the code for the sqlite database backend.
 *
 * Copyright (C) 2003-2006 Lee Hardy <leeh@leeh.co.uk>
 * Copyright (C) 2003-2006 ircd-ratbox development team
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * 1.Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * 2.Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * 3.The name of the author may not be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
#include "stdinc.h"
#include "rsdb.h"

#include <sqlite3.h>

struct sqlite3 *rb_bandb;

rsdb_error_cb *error_cb;

static void
mlog(const char *errstr, ...)
{
	if(error_cb != NULL)
	{
		char buf[256];
		va_list ap;
		va_start(ap, errstr);
		vsnprintf(buf, sizeof(buf), errstr, ap);
		va_end(ap);
		error_cb(buf);
	}
	else
		exit(1);
}

int
rsdb_init(rsdb_error_cb * ecb)
{
	const char *bandb_dbpath_env;
	char dbpath[PATH_MAX];
	char errbuf[128];
	error_cb = ecb;

	/* try a path from the environment first, useful for basedir overrides */
	bandb_dbpath_env = getenv("BANDB_DBPATH");

	if(bandb_dbpath_env != NULL)
		rb_strlcpy(dbpath, bandb_dbpath_env, sizeof(dbpath));
	else
		rb_strlcpy(dbpath, DBPATH, sizeof(dbpath));

	if(sqlite3_open(dbpath, &rb_bandb) != SQLITE_OK)
	{
		snprintf(errbuf, sizeof(errbuf), "Unable to open sqlite database: %s",
			    sqlite3_errmsg(rb_bandb));
		mlog(errbuf);
		return -1;
	}
	if(access(dbpath, W_OK))
	{
		snprintf(errbuf, sizeof(errbuf),  "Unable to open sqlite database for write: %s", strerror(errno));
		mlog(errbuf);
		return -1;
	}
	return 0;
}

void
rsdb_shutdown(void)
{
	if(rb_bandb)
		sqlite3_close(rb_bandb);
}

const char *
rsdb_quote(const char *src)
{
	static char buf[BUFSIZE * 4];
	char *p = buf;

	/* cheap and dirty length check.. */
	if(strlen(src) >= (sizeof(buf) / 2))
		return NULL;

	while(*src)
	{
		if(*src == '\'')
			*p++ = '\'';

		*p++ = *src++;
	}

	*p = '\0';
	return buf;
}

static int
rsdb_callback_func(void *cbfunc, int argc, char **argv, char **colnames)
{
	rsdb_callback cb = (rsdb_callback)((uintptr_t)cbfunc);
	(cb) (argc, (const char **)(void *)argv);
	return 0;
}

void
rsdb_exec(rsdb_callback cb, const char *format, ...)
{
	static char buf[BUFSIZE * 4];
	va_list args;
	char *errmsg;
	unsigned int i;
	int j;

	va_start(args, format);
	i = rs_vsnprintf(buf, sizeof(buf), format, args);
	va_end(args);

	if(i >= sizeof(buf))
	{
		mlog("fatal error: length problem with compiling sql");
	}

	if((i = sqlite3_exec(rb_bandb, buf, (cb ? rsdb_callback_func : NULL), (void *)((uintptr_t)cb), &errmsg)))
	{
		switch (i)
		{
		case SQLITE_BUSY:
			for(j = 0; j < 5; j++)
			{
				rb_sleep(0, 500000);
				if(!sqlite3_exec
				   (rb_bandb, buf, (cb ? rsdb_callback_func : NULL), (void *)((uintptr_t)cb), &errmsg))
					return;
			}

			/* failed, fall through to default */
			mlog("fatal error: problem with db file: %s", errmsg);
			break;

		default:
			mlog("fatal error: problem with db file: %s", errmsg);
			break;
		}
	}
}

void
rsdb_exec_fetch(struct rsdb_table *table, const char *format, ...)
{
	static char buf[BUFSIZE * 4];
	va_list args;
	char *errmsg;
	char **data;
	int pos;
	unsigned int retval;
	int i, j;

	va_start(args, format);
	retval = rs_vsnprintf(buf, sizeof(buf), format, args);
	va_end(args);

	if(retval >= sizeof(buf))
	{
		mlog("fatal error: length problem with compiling sql");
	}

	if((retval =
	    sqlite3_get_table(rb_bandb, buf, &data, &table->row_count, &table->col_count, &errmsg)))
	{
		int success = 0;

		switch (retval)
		{
		case SQLITE_BUSY:
			for(i = 0; i < 5; i++)
			{
				rb_sleep(0, 500000);
				if(!sqlite3_get_table
				   (rb_bandb, buf, &data, &table->row_count, &table->col_count,
				    &errmsg))
				{
					success++;
					break;
				}
			}

			if(success)
				break;

			mlog("fatal error: problem with db file: %s", errmsg);
			break;

		default:
			mlog("fatal error: problem with db file: %s", errmsg);
			break;
		}
	}

	/* we need to be able to free data afterward */
	table->arg = data;

	if(table->row_count == 0)
	{
		table->row = NULL;
		return;
	}

	/* sqlite puts the column names as the first row */
	pos = table->col_count;
	table->row = rb_malloc(sizeof(char **) * table->row_count);
	for(i = 0; i < table->row_count; i++)
	{
		table->row[i] = rb_malloc(sizeof(char *) * table->col_count);

		for(j = 0; j < table->col_count; j++)
		{
			table->row[i][j] = data[pos++];
		}
	}
}

void
rsdb_exec_fetch_end(struct rsdb_table *table)
{
	int i;

	for(i = 0; i < table->row_count; i++)
	{
		rb_free(table->row[i]);
	}
	rb_free(table->row);

	sqlite3_free_table((char **)table->arg);
}

void
rsdb_transaction(rsdb_transtype type)
{
	if(type == RSDB_TRANS_START)
		rsdb_exec(NULL, "BEGIN TRANSACTION");
	else if(type == RSDB_TRANS_END)
		rsdb_exec(NULL, "COMMIT TRANSACTION");
}
