/*
** Library of user functions for use with sqlite.
** This implements user variables with user functions.
** These are useful in complex queries and TRIGGERs.
**
** Set a variable:
**   SELECT FnSetVar('varname', 'value');
** This creates the variable if it didn't exist.
**
** Remove a variable:
**   SELECT FnSetVar('varname', NULL);
**
** Get a variable's value:
**   SELECT FnGetVar('varname');
** The value NULL will be returned if the variable isn't stored.
** It is not possible to store the value NULL for a variable.
**
** 2002-11-26	James P. Lyon
** 2002-11-28	finished FnGet/SetVar() implementation
** 2002-12-01	implemented hashing of init'd db connections
**
** The author disclaims copyright to this source code.
** In place of a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
 */

#include <assert.h>
#include <string.h>

#include "sqlite.h"
#include "sqliteInt.h"

#include "sqaux-base.h"

/*
** If true, variables will be stored in a temporary table.
** If false, variables will be stored in a permanent table,
**   and will be stored when the database is saved.
*/
#define USERVARS_TEMP 1

/*
** The name of the table to store user variables in.
** It is safe for this table name to contain spaces, single quotes,...
*/
#define USERVARS_TABL "Variables"

/************************************************************************/

/* forwards declaration(s) */
int register_user_var_fns(sqlite *db);

/*
** Hash table stores open sqlite* database connections which have
**   called UserVarsInit(). Used as a fast set implementation.
*/
static Hash UserVarsConnectedHash;
static int bUserVarsConnectedHashInit = 0;

/*
** Has this database connection initialized the UserVars fns?
** Returns 1 for true, 0 for false.
*/
int UserVarsGetInit(sqlite* db)
{
	assert(bUserVarsConnectedHashInit);

	return (int)sqliteHashFind(&UserVarsConnectedHash, db, 0);
}

/*
** Has this database connection initialized the UserVars fns?
*/
static void UserVarsSetInit(sqlite* db, int nInit)
{
	assert(bUserVarsConnectedHashInit);

	if (nInit)
	{
		assert(!UserVarsGetInit(db));
		sqliteHashInsert(&UserVarsConnectedHash, db, 0, (void*)1L);
	}
	else
	{
		assert(UserVarsGetInit(db));
		/* There is no mechanism for removing a hash entry, so just store a 0. */
		sqliteHashInsert(&UserVarsConnectedHash, db, 0, (void*)0L);
	}
}

/*
** Initialize use of user variables for this database connection.
** Returns SQLITE_OK if successful, nonzero on error.
*/
int UserVarsInit(sqlite *db)
{
	const char *pszQuery = 
	"CREATE"
#if USERVARS_TEMP
	" TEMPORARY"
#endif
	" TABLE '%q' ("
	" Name  STRING PRIMARY KEY,"
	" Value STRING NOT NULL); "
	;
	/* TODO: add TRIGGERs for INSERT and UPDATE
	**   to remove entries when a NULL Value is assigned to them?
	** Perhaps add ON CONFLICT handler to CREATE TABLE?
	** Perhaps remove Value's NOT NULL to avoid conflict with TRIGGERs?
	*/
/*
	const char *pszTrigger = 
	===
	;
*/
	char *zErrMsg;
	int rcs;

	if (!bUserVarsConnectedHashInit)
	{
		/* Initialize the hash table for use. */
		sqliteHashInit(&UserVarsConnectedHash, SQLITE_HASH_POINTER, 0);
	}

	rcs = sqlite_exec_printf(db, pszQuery, callback_empty, NULL, &zErrMsg,
		USERVARS_TABL);
	if (rcs != SQLITE_OK)
	{
		assert(0);
		sqliteFree(zErrMsg);
	}
	else
	{
		register_user_var_fns(db);
		UserVarsSetInit(db, 1);
	}

	return rcs;
}

/*
** Clear all stored user variables.
** The user variable functions are still safe to use after this call.
*/
int UserVarsDeinit(sqlite *db)
{
	const char *pszQuery = "DROP TABLE '%q'; ";
	/* TODO: drop a TRIGGER if there is one,
	or will triggers on the table be dropped automatically? */
	char *zErrMsg;
	int rcs;

	assert(bUserVarsConnectedHashInit);

	rcs = sqlite_exec_printf(db, pszQuery, callback_empty, NULL, &zErrMsg,
		USERVARS_TABL);
	if (rcs != SQLITE_OK)
	{
		assert(0);
		sqliteFree(zErrMsg);
	}
	else
	{
		UserVarsSetInit(db, 0);
	}

	return rcs;
}

/*
** Get the stored value of a [string] user variable.
** Returns NULL if the no variable of that name is stored.
** Returns NULL if the variable name is NULL. 
**
** argv[0] variable name
*/
void FnGetVar(sqlite_func *pfn, int argc, const char **argv)
{
	sqlite *db;

	// Retrieve a pointer to the database.
	db = (sqlite*)sqlite_user_data(pfn);
	assert(db);

	/* Validate arguments */
	assert(argc == 1 && argv);

	/* Verify that user variable handling is initialized. */
	assert(UserVarsGetInit(db));

	/* Handle NULL correctly. */
	if (argv[0] == NULL)
	{
		sqlite_set_result_string(pfn, NULL, 0);
	}
	else
	{
		int rc;
		char *zErrMsg;
		char *zResult;
		const char *sqlFormat = "SELECT Value FROM '%q' WHERE Name=%Q";

		/* Retrieve the value for the variable name. */
		rc = sqaux_get_string_printf(db, sqlFormat, &zResult, &zErrMsg,
			USERVARS_TABL, argv[0]);
		if (rc != SQLITE_OK)
		{
			sqliteFree(zErrMsg);
			sqlite_set_result_error(pfn, "FnGetVar(): query failed.", -1);
		}
		else
		{
			sqlite_set_result_string(pfn, zResult, -1);
			sqaux_free_string(zResult);
		}
	}
}

/*
** Set the stored value of a [string] user variable.
** If the value is NULL, the user variable is removed.
** This function 'returns' an empty string.
**
** argv[0] variable name
** argv[1] string value
*/
void FnSetVar(sqlite_func *pfn, int argc, const char **argv)
{
	sqlite *db;
	int rcs;
	char *zErrMsg;
	const char *query = "INSERT OR REPLACE INTO '%q' VALUES(%Q, %Q);";

	/* Validate arguments */
	assert(argc == 2 && argv);

	/* Retrieve a pointer to the database. */
	db = (sqlite*)sqlite_user_data(pfn);
	assert(db);

	/* Verify that user variable handling is initialized. */
	assert(UserVarsGetInit(db));

	rcs = sqlite_exec_printf(db, query, callback_empty, NULL, &zErrMsg,
		USERVARS_TABL, argv[0], argv[1]);
	if (rcs != SQLITE_OK)
	{
		assert(0);
		sqlite_set_result_error(pfn, zErrMsg, -1);
		sqliteFree(zErrMsg);
	}
	else
	{
		/* Return an empty string. */
		sqlite_set_result_string(pfn, "", 0);
	}
}

/*
** Functions to register.
** <fntype> can be either SQLITE_TEXT or SQLITE_NUMERIC,
**   and reflects the return value only.
** The registered function name is the name used in sqlite.
**   It can be different from the actual function name.
*/
struct user_fn_data
{
	const char *fnname;
	int fnargc;
	int fntype;
	void (*fn)(sqlite_func*, int, const char**);
}
user_fns[] = 
{
	"FnGetVar",		1, SQLITE_TEXT,			FnGetVar,
	"FnSetVar",		2, SQLITE_NUMERIC,		FnSetVar,
};
const int num_user_fns = sizeof(user_fns)/sizeof(user_fns[0]);

/*
** Register the user functions with sqlite.
** They must be registered for every open connection.
** This function is safe to call repeatedly.
** Return nonzero on failure.
*/
int register_user_var_fns(sqlite *db)
{
	int i;

	assert(db);
	if (!db) return SQLITE_ERROR;

	for (i = 0; i < num_user_fns; i++)
	{
		/*
		** Register each function. This will overwrite a function
		**   previously registered using the same name and arguments.
		** We pass in the pointer to the sqlite database as its user data.
		** This is retrieved with sqlite_user_data().
		*/
		int rc = sqlite_create_function(db, user_fns[i].fnname, user_fns[i].fnargc, user_fns[i].fn, (void*)db);
		if (rc != SQLITE_OK)
		{
			/*
			** TODO: add error message or logging.
			*/
			return SQLITE_ERROR;
		}

		/*
		** Function types must be registered separately.
		** This API function was added after sqlite_create_function().
		*/
		sqlite_function_type(db, user_fns[i].fnname, user_fns[i].fntype);
	}

	return SQLITE_OK;
}

/************************************************************************/
