// vim: set ts=4 sw=4 tw=99 noet:
//
// AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO").
// Copyright (C) The AMX Mod X Development Team.
//
// This software is licensed under the GNU General Public License, version 3 or higher.
// Additional exceptions apply. For full license details, see LICENSE.txt or visit:
//     https://alliedmods.net/amxmodx-license

//
// SQLite Module
//

#include <stdio.h>
#include "sh_list.h"
#include "sqlite_header.h"

using namespace SourceMod;

struct olddb_s
{
	IDatabase *pDatabase;
	char error[255];
	int errcode;
};

struct oldresult_s
{
	IQuery *pQuery;
	QueryInfo info;
	bool firstCall;
};

void FreeOldDb(void *ptr, unsigned int hndl)
{
	olddb_s *old = (olddb_s *)ptr;

	if (old->pDatabase)
	{
		old->pDatabase->FreeHandle();
		old->pDatabase = NULL;
	}

	delete old;
}

void FreeOldResult(void *ptr, unsigned int hndl)
{
	oldresult_s *oldres = (oldresult_s *)ptr;

	if (oldres->pQuery)
	{
		oldres->pQuery->FreeHandle();
		oldres->pQuery = NULL;
	}

	delete oldres;
}

//native Sql:dbi_connect(_host[], _user[], _pass[], _dbname[], _error[]="", _maxlength=0);
static cell AMX_NATIVE_CALL dbi_connect(AMX *amx, cell *params)
{
	int len;
	DatabaseInfo info;
	char *name = MF_GetAmxString(amx, params[4], 3, &len);
	char path[255];

	info.host = "";
	info.user = "";
	info.pass = "";
	info.database = name;

	int err;
	char error[512];
	/** if there is an older database, read there instead of the new path */
	MF_BuildPathnameR(path, sizeof(path), "%s", info.database);
	FILE *fp = fopen(path, "rb");
	if (fp)
	{
		fclose(fp);
		info.database = path;
	} else {
		MF_BuildPathnameR(path, sizeof(path), "%s/sqlite3/%s.sq3",
			MF_GetLocalInfo("amxx_datadir", "addons/amxmodx/data"),
			info.database);
		info.database = path;
	}
	IDatabase *pDatabase = g_Sqlite.Connect(&info, &err, error, sizeof(error)-1);
	if (!pDatabase)
	{
		MF_SetAmxString(amx, params[5], error, params[6]);
		return 0;
	}

	olddb_s *old = new olddb_s;
	int hndl;

	old->pDatabase = pDatabase;
	hndl = MakeHandle(old, Handle_OldDb, FreeOldDb);

	return hndl;
}

//native Result:dbi_query(Sql:_sql, _query[], {Float,_}:...);
static cell AMX_NATIVE_CALL dbi_query(AMX *amx, cell *params)
{
	olddb_s *old = (olddb_s *)GetHandle(params[1], Handle_OldDb);
	if (!old)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Invalid DBI handle %d", params[1]);
		return -1;
	}

	int len;
	char *queryString = MF_FormatAmxString(amx, params, 2, &len);

	IQuery *pQuery = old->pDatabase->PrepareQuery(queryString);
	QueryInfo info;

	old->error[0] = '\0';
	old->errcode = 0;

	if (!pQuery->Execute2(&info, old->error, 254))
	{
		old->errcode = info.errorcode;
		return -1;
	} else {
		if (info.rs && info.rs->RowCount())
		{
			oldresult_s *oldrs = new oldresult_s;
			int hndl;

			oldrs->info = info;
			oldrs->pQuery = pQuery;
			oldrs->firstCall = true;
			hndl = MakeHandle(oldrs, Handle_OldResult, FreeOldResult);
			return hndl;
		} else {
			pQuery->FreeHandle();
			return 0;
		}
	}

	/** never reach here */
	return 0;
}

//native Result:dbi_query2(Sql:_sql, &rows, _query[], {Float,_}:...);
static cell AMX_NATIVE_CALL dbi_query2(AMX *amx, cell *params)
{
	olddb_s *old = (olddb_s *)GetHandle(params[1], Handle_OldDb);
	if (!old)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Invalid DBI handle %d", params[1]);
		return -1;
	}

	int len;
	char *queryString = MF_FormatAmxString(amx, params, 3, &len);

	IQuery *pQuery = old->pDatabase->PrepareQuery(queryString);
	QueryInfo info;

	old->error[0] = '\0';
	old->errcode = 0;

	if (!pQuery->Execute2(&info, old->error, 254))
	{
		old->errcode = info.errorcode;
		return -1;
	} else {
		cell *addr = MF_GetAmxAddr(amx, params[2]);
		*addr = static_cast<cell>(info.affected_rows);
		if (info.rs && info.rs->RowCount())
		{
			oldresult_s *oldrs = new oldresult_s;
			int hndl;

			oldrs->info = info;
			oldrs->pQuery = pQuery;
			oldrs->firstCall = true;
			hndl = MakeHandle(oldrs, Handle_OldResult, FreeOldResult);
			return hndl;
		} else {
			pQuery->FreeHandle();
			return 0;
		}
	}

	/** never reach here */
	return 0;
}

//native dbi_nextrow(Result:_result);
static cell AMX_NATIVE_CALL dbi_nextrow(AMX *amx, cell *params)
{
	if (params[1] == 0)
	{
		return 0;
	}

	oldresult_s *oldrs = (oldresult_s *)GetHandle(params[1], Handle_OldResult);
	if (!oldrs)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Invalid DBI result handle %d", params[1]);
		return 0;
	}

	if (oldrs->firstCall)
	{
		oldrs->firstCall = false;
		return (oldrs->info.rs->IsDone() ? 0 : 1);
	} else {
		oldrs->info.rs->NextRow();
		return (oldrs->info.rs->IsDone() ? 0 : 1);
	}
}

//native dbi_field(Result:_result, _fieldnum, {Float,_}:... );
static cell AMX_NATIVE_CALL dbi_field(AMX *amx, cell *params)
{
	oldresult_s *oldrs = (oldresult_s *)GetHandle(params[1], Handle_OldResult);
	if (!oldrs)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Invalid DBI result handle %d", params[1]);
		return 0;
	}

	IResultSet *rs = oldrs->info.rs;
	IResultRow *rr = rs->GetRow();
	unsigned int num = (unsigned int)params[2] - 1;
	if (num >= rs->FieldCount())
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Invalid column %d", params[2]);
		return 0;
	}

	cell stype = params[0] / sizeof(cell);
	const char *data = rr->GetString(num);
	if (!data)
		data = "";

	switch (stype)
	{
	case 2:
		{
			return atoi(data);
			break;
		}
	case 3:
		{
			cell *destaddr = MF_GetAmxAddr(amx, params[3]);
			REAL fdata = atof(data);
			*destaddr = amx_ftoc(fdata);
			return 1;
			break;
		}
	case 4:
		{
			return MF_SetAmxString(amx, params[3], data, params[4]);
			break;
		}
	}

	/** never reach here */
	return 0;
}

//native dbi_result(Result:_result, _field[], {Float,_}:... );
static cell AMX_NATIVE_CALL dbi_result(AMX *amx, cell *params)
{
	oldresult_s *oldrs = (oldresult_s *)GetHandle(params[1], Handle_OldResult);
	if (!oldrs)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Invalid DBI result handle %d", params[1]);
		return 0;
	}

	IResultSet *rs = oldrs->info.rs;
	IResultRow *rr = rs->GetRow();
	unsigned int num;
	bool found = false;
	unsigned int fields = rs->FieldCount();
	int len;
	char *field = MF_GetAmxString(amx, params[2], 0, &len);
	for (unsigned int i=0; i<fields; i++)
	{
		if (strcmp(field, rs->FieldNumToName(i)) == 0)
		{
			num = i;
			found = true;
			break;
		}
	}

	if (!found)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Unknown column \"%s\"", field);
		return 0;
	}

	cell stype = params[0] / sizeof(cell);
	const char *data = rr->GetString(num);
	if (!data)
		data = "";

	switch (stype)
	{
	case 2:
		{
			return atoi(data);
			break;
		}
	case 3:
		{
			cell *destaddr = MF_GetAmxAddr(amx, params[3]);
			REAL fdata = atof(data);
			*destaddr = amx_ftoc(fdata);
			return 1;
			break;
		}
	case 4:
		{
			return MF_SetAmxString(amx, params[3], data, params[4]);
			break;
		}
	}

	/** never reach here */
	return 0;
}

//native dbi_num_rows(Result:_result);
static cell AMX_NATIVE_CALL dbi_num_rows(AMX *amx, cell *params)
{
	oldresult_s *oldrs = (oldresult_s *)GetHandle(params[1], Handle_OldResult);
	if (!oldrs)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Invalid DBI result handle %d", params[1]);
		return 0;
	}

	return oldrs->info.rs->RowCount();
}

//native dbi_free_result(&Result:result);
static cell AMX_NATIVE_CALL dbi_free_result(AMX *amx, cell *params)
{
	cell *_r = MF_GetAmxAddr(amx, params[1]);
	cell num = *_r;

	if (!num)
	{
		return 1;
	}

	oldresult_s *oldrs = (oldresult_s *)GetHandle(num, Handle_OldResult);
	if (!oldrs)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Invalid DBI result handle %d", num);
		return 0;
	}

	FreeHandle(num);

	*_r = 0;

	return 1;
}

//native dbi_close(&Sql:_sql);
static cell AMX_NATIVE_CALL dbi_close(AMX *amx, cell *params)
{
	cell *_r = MF_GetAmxAddr(amx, params[1]);
	cell num = *_r;
	olddb_s *old = (olddb_s *)GetHandle(num, Handle_OldDb);
	if (!old)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Invalid DBI handle %d", num);
		return 0;
	}

	FreeHandle(num);

	*_r = 0;

	return 1;
}

//native dbi_error(Sql:_sql, _error[], _len);
static cell AMX_NATIVE_CALL dbi_error(AMX *amx, cell *params)
{
	olddb_s *old = (olddb_s *)GetHandle(params[1], Handle_OldDb);
	if (!old)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Invalid DBI handle %d", params[1]);
		return -1;
	}

	MF_SetAmxString(amx, params[2], old->error, params[3]);
	return old->errcode;
}

//native dbi_type(_type[], _len);
static cell AMX_NATIVE_CALL dbi_type(AMX *amx, cell *params)
{
	return MF_SetAmxString(amx, params[1], g_Sqlite.NameString(), params[2]);
}

//native dbi_num_fields(Result:result);
static cell AMX_NATIVE_CALL dbi_num_fields(AMX *amx, cell *params)
{
	oldresult_s *oldrs = (oldresult_s *)GetHandle(params[1], Handle_OldResult);
	if (!oldrs)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Invalid DBI result handle %d", params[1]);
		return 0;
	}

	return oldrs->info.rs->FieldCount();
}

//native dbi_field_name(Result:result, field, name[], maxLength);
static cell AMX_NATIVE_CALL dbi_field_name(AMX *amx, cell *params)
{
	oldresult_s *oldrs = (oldresult_s *)GetHandle(params[1], Handle_OldResult);
	if (!oldrs)
	{
		MF_LogError(amx, AMX_ERR_NATIVE, "Invalid DBI result handle %d", params[1]);
		return 0;
	}

	const char *name = oldrs->info.rs->FieldNumToName(static_cast<unsigned int>(params[2]-1));
	if (!name)
		return 0;

	MF_SetAmxString(amx, params[3], name, params[4]);

	return 1;
}

AMX_NATIVE_INFO g_OldCompatNatives[] =
{
	{ "dbi_connect",		dbi_connect },
	{ "dbi_query",			dbi_query },
	{ "dbi_query2",			dbi_query2 },
	{ "dbi_field",			dbi_field },
	{ "dbi_nextrow",		dbi_nextrow },
	{ "dbi_close",			dbi_close },
	{ "dbi_error",			dbi_error },
	{ "dbi_type",			dbi_type },
	{ "dbi_free_result",	dbi_free_result },
	{ "dbi_num_rows",		dbi_num_rows },
	{ "dbi_result",			dbi_result },
	{ "dbi_num_fields",		dbi_num_fields },
	{ "dbi_field_name",		dbi_field_name },

	{ NULL, NULL }
};
