/*
** Functions for use with the sqlite library.
*/

#include <stdio.h>

#include "sqaux-base.h"

#include "sqlite.h"
#include "sqliteInt.h"	// for "struct sqlite" needed in sqaux_trigger_exists().
#include "hash.h"		// from sqlite for sqliteHashFind().
#include "os.h"

/**********************************************************************************************************************/
/* extern declarations for sqlite functions. */

extern char *sqlite_vmprintf(const char *zFormat, va_list ap);

/**********************************************************************************************************************/
/* definitions copied from sqlite source. */

typedef struct BtCursor BtCursor;
typedef struct PageOne PageOne;
typedef struct Pager Pager;
typedef struct PgHdr PgHdr;

/*
** Everything we need to know about an open database
*/
struct Btree {
  Pager *pPager;        /* The page cache */
  BtCursor *pCursor;    /* A list of all open cursors */
  PageOne *page1;       /* First page of the database */
  u8 inTrans;           /* True if a transaction is in progress */
  u8 inCkpt;            /* True if there is a checkpoint on the transaction */
  u8 readOnly;          /* True if the underlying file is readonly */
  u8 needSwab;          /* Need to byte-swapping */
};

#define N_PG_HASH 2003
/*
** A open page cache is an instance of the following structure.
*/
struct Pager {
  char *zFilename;            /* Name of the database file */
  char *zJournal;             /* Name of the journal file */
  OsFile fd, jfd;             /* File descriptors for database and journal */
  OsFile cpfd;                /* File descriptor for the checkpoint journal */
  int dbSize;                 /* Number of pages in the file */
  int origDbSize;             /* dbSize before the current change */
  int ckptSize, ckptJSize;    /* Size of database and journal at ckpt_begin() */
  int nExtra;                 /* Add this many bytes to each in-memory page */
  void (*xDestructor)(void*); /* Call this routine when freeing pages */
  int nPage;                  /* Total number of in-memory pages */
  int nRef;                   /* Number of in-memory pages with PgHdr.nRef>0 */
  int mxPage;                 /* Maximum number of pages to hold in cache */
  int nHit, nMiss, nOvfl;     /* Cache hits, missing, and LRU overflows */
  u8 journalOpen;             /* True if journal file descriptors is valid */
  u8 ckptOpen;                /* True if the checkpoint journal is open */
  u8 ckptInUse;               /* True we are in a checkpoint */
  u8 noSync;                  /* Do not sync the journal if true */
  u8 state;                   /* SQLITE_UNLOCK, _READLOCK or _WRITELOCK */
  u8 errMask;                 /* One of several kinds of errors */
  u8 tempFile;                /* zFilename is a temporary file */
  u8 readOnly;                /* True for a read-only database */
  u8 needSync;                /* True if an fsync() is needed on the journal */
  u8 dirtyFile;               /* True if database file has changed in any way */
  u8 alwaysRollback;          /* Disable dont_rollback() for all pages */
  u8 journalFormat;           /* Version number of the journal file */
  u8 *aInJournal;             /* One bit for each page in the database file */
  u8 *aInCkpt;                /* One bit for each page in the database */
  PgHdr *pFirst, *pLast;      /* List of free pages */
  PgHdr *pAll;                /* List of all pages */
  PgHdr *aHash[N_PG_HASH];    /* Hash table to map page number of PgHdr */
};


/**********************************************************************************************************************/
/* Callback functions */

/*
** Empty callback function needed to call sqlite_exec(),
**   which won't accept NULL for the callback argument.
** Return zero on success, non-zero on failure.
*/
int callback_empty(void *pArg, int nArg, char **azArg, char **azCol)
{
	return 0;
}

/* All uses of sqlite_exec() REQUIRE a callback function.
 * This function outputs the row data to stdout with tab delimiters.
 * <pArg> is an app-supplied argument passed into sqlite_exec().
 * <argc> is the number of columns of data.
 * <argv> is an array of the column data.
 * <azColName> is an array of the column names.
 */
int callback_output_tabbed(void *pArg, int argc, char **argv, char **azColName)
{
	int i;
	for (i = 0; i < argc; i++)
	{
		puts(argv[i]);
		if (i < argc-1)
			putchar('\t');
	}
	putchar('\n');

	return 0; /* okay */
}

/*
** Database pointer used by callback_query().
** TODO: this can be placed into a struct, along with a pointer for error messages,
**   and the struct can be passed as the argument passed to the callback.
**
** struct callback_query_struct {sqlite *db; const char *query; };
*/
static sqlite *callback_query_db = NULL;

/*
** Set the database to use with callback_query().
*/
void callback_query_set_db(sqlite *db)
{
	callback_query_db = db;
}

/*
** Get the database to use with callback_query().
*/
sqlite *callback_query_get_db()
{
	return callback_query_db;
}

#define CALLBACK_QUERY_MAX	(20*1024)

/*
** Callback function called to perform a query using the result row data.
** This acts on a static database pointer.
** <pQuery> is a query string passed into the original sqlite_exec() call.
** It will contain %s fields which will be replaced by the row data.
** The number of %s occurrances must match the number of columns in the result.
** NULL result strings will be inserted as "NULL" in the query string.
** Return zero on success, non-zero on failure.
*/
static int callback_query(void *pArg, int nArg, char **azArg, char **azCol)
{
	char buf[CALLBACK_QUERY_MAX];
	int argvlen = 0, userlen = 0;
	const char *pQuery = (const char*)pArg;
	const char *s = pQuery;
	char *b = buf;
	int i;
	int n = 0; // number of args copied over so far.
	int rcs;

	assert(pArg && azArg);

	for (i = 0; i < nArg; i++)
		if (azArg[i])
			argvlen += strlen(azArg[i]);
	userlen = pArg? strlen((char*)pArg): 0;
	assert(argvlen + userlen < CALLBACK_QUERY_MAX);

	while (s && *s)
	{
		if (s[0] == '%' && s[1] == 's')
		{
			const char *arg = (n < nArg)? azArg[n]: NULL;
			int len;

			/* Insert the next argument. */
			assert(n < nArg);

			if (arg == NULL)
				arg = "NULL";

			len = strlen(arg);
			strncpy(b, arg, len);

			b += len;
			s += 2;
			n++;
		}
		else
		{
			/* Copy the character over. */
			*b++ = *s++;
		}
	}

	/* Terminate the buffer string. */
	*b = '\0';

	/* Now do the query using the target database. */
	rcs = sqlite_exec(callback_query_db, buf, callback_empty, NULL, NULL);

	return (rcs != SQLITE_OK);
}


/**********************************************************************************************************************/
/* Utility functions */

/*
** Get the name of an open sqlite database.
*/
const char *sqaux_get_filename(sqlite *db)
{
	Btree *pBe;
	Pager *pPager;
	const char *zFilename;

	if (!db)
		return NULL;

	/*
	** The following code depends on the order of members in
	**   structs sqlite, Btree, Pager. The filename we want is
	**   actually the first member in struct sqlite.
	*/
//	pBe = (struct Btree*)db;
	pBe = db->pBe;
//	pPager = (struct Pager*)pBe;
	pPager = pBe->pPager;;
//	zFilename = (char*)pPager;
	zFilename = pPager->zFilename;
	return zFilename;
}

/*
** Open another connection to an open sqlite database.
** <mode> is currently unused by sqlite_open().
** <pzErrMsg> points to a string pointer, and can be NULL.
*/
sqlite *sqaux_dup(sqlite *db, int mode, char **pzErrMsg)
{
	const char *zFilename;

	if (!db)
		return NULL;

	zFilename = sqaux_get_filename(db);
	if (!zFilename)
		return NULL;

	return sqlite_open(zFilename, mode, pzErrMsg);
}

/*
** Compute the maximum size required by sqlite_encode_binary().
** This doesn't include the NUL byte which terminates the string.
*/
int sqaux_encode_maxsize(int datasize)
{
	int nEncMax = (256*datasize + 1262)/253;
	return nEncMax;
}

/*
** Retrieve the first data string returned by a query.
** Retrieve NULL if the query had no results.
** The string must be freed by sqaux_free_string().
** Returns an integer error value.
*/
int sqaux_get_string(
  sqlite *db,           /* An open database */
  const char *sql,      /* SQL to be executed */
  char **resultp,       /* Result written to a char* that this points to */
						/* This argument must be non-null. */
  char **errmsg         /* Error msg written here */
)
{
	int rcs;
	int nrow, ncol;
	char **table;

	assert(resultp);
	if (resultp == NULL)
	{
		return SQLITE_ERROR;
	}

	*resultp = NULL;
	
	rcs = sqlite_get_table(db, sql, &table, &nrow, &ncol, errmsg);
	if (rcs == SQLITE_OK && nrow > 0 && ncol > 0)
	{
		// Get the first data string in the returned table.
		// The first <ncol> array entries are column names.
		const char *str = table[ncol];

		// Make a copy of the string.
		// TODO: remove the string from the table data and substitute NULL in the table data.
		// This avoids the need to duplicate the string.
		*resultp = sqliteStrDup(str);
	}
	// Free the results table.
	sqlite_free_table(table);

	return rcs;
}

/*
** Version of sqaux_get_string() that takes vprintf-style arguments.
*/
int sqaux_get_string_vprintf(
  sqlite *db,           /* An open database */
  const char *sqlfmt,   /* printf-style format string for the SQL */
  char **resultp,       /* Result written to a char* that this points to. */
						/* This argument must be non-null. */
  char **errmsg,        /* Error msg written here */
  va_list ap            /* Arguments to the format string. */
)
{
  char *zSql;
  int rc;

  zSql = sqlite_vmprintf(sqlfmt, ap);
  rc = sqaux_get_string(db, zSql, resultp, errmsg);
  sqliteFree(zSql);
  return rc;
}

/*
** Version of sqaux_get_string() that takes printf-style arguments.
*/
int sqaux_get_string_printf(
  sqlite *db,           /* An open database */
  const char *sqlfmt,   /* printf-style format string for the SQL */
  char **resultp,       /* Result written to a char* that this points to. */
						/* This argument must be non-null. */
  char **errmsg,        /* Error msg written here */
  ...					/* Arguments to the format string. */
)
{
  va_list ap;
  int rc;

  va_start(ap, errmsg);
  rc = sqaux_get_string_vprintf(db, sqlfmt, resultp, errmsg, ap);
  va_end(ap);
  return rc;
}

/*
** Free a string returned by sqaux_get_string().
*/
void sqaux_free_string(char *str)
{
	sqliteFree(str);
}

/*
** Retrieves the first data string returned by a query.
** Converts the string to an integer and 'returns' it.
** If no string is obtained, the original long is
**   left unchanged, but no error value is returned
**   unless the original query had an error.
** Returns an integer error value.
*/
int sqaux_get_long(
  sqlite *db,            /* An open database */
  const char *sql,       /* SQL to be executed */
  long *resultp,         /* Long integer result */
  char **errmsg          /* Error msg written here */
)
{
	char *string = NULL;

	int rcs = sqaux_get_string(db, sql, &string, errmsg);

	if (string && resultp)
		*resultp = atol(string);

	sqaux_free_string(string);

	return rcs;
}

/*
** Find out if a table/index/view/... exists in the database.
** This finds both normal and temporary features.
** <type> is the type of a feature as it exists in SQLITE_[TEMP_]MASTER:
**   "index", "table", "view".
**   This will NOT check if a TRIGGER exists -- this isn't stored in SQLITE_MASTER.
** <name> is the name of a feature as it exists in SQLITE_[TEMP_]MASTER.
*/
BOOL sqaux_feature_exists(sqlite *db, const char *type, const char *name)
{
	char *string = NULL;
	int rcs;
	BOOL bFound;
const char *pszQuery = 
"SELECT name"
" FROM SQLITE_MASTER"
" WHERE type=%Q"
" AND   name=%Q"
" UNION"
" SELECT name"
" FROM SQLITE_TEMP_MASTER"
" WHERE type=%Q"
" AND   name=%Q;"
;

	rcs = sqaux_get_string_printf(db, pszQuery, &string, NULL,
		type, name, type, name);

	assert(rcs == SQLITE_OK);
	bFound = (rcs == SQLITE_OK && string != NULL);
	sqaux_free_string(string);
	return bFound;
}

/*
** Find out if a [temporary] table exists in the database.
*/
BOOL sqaux_table_exists(sqlite *db, const char *name)
{
	return sqaux_feature_exists(db, "table", name);
}

/*
** Find out if a trigger is registered with the database.
** Triggers aren't stored in the database -- they are runtime only.
*/ 
BOOL sqaux_trigger_exists(sqlite *db, const char *trigger)
{
	BOOL bFound;

/*	ASSERT_OR_RETURN_FALSE(db && trigger && *trigger); */
	assert(db && trigger && *trigger);
	if (!db || !trigger || !*trigger)
		return FALSE;

	/* See sqliteCreateTrigger() in "trigger.c". */

	bFound = (sqliteHashFind(&(db->trigHash), trigger, strlen(trigger)+1) != NULL);
	return bFound;
}

#if 0 // doesn't work in 2.4.x
/*
** Find the version of a sqlite database from the file.
** This is done without calling sqlite_open(), so an older version
**   database won't automatically be converted to a newer version.
**   When sqlite_open() is called on a database with file_format <= 2,
**   it is automatically upgraded to 3 without prompting.
** This function was adapted from sqlite_open().
** <zFilename> is the full name of the file to open.
** <pfile_format> is a pointer to return the file_format in.
** The various file formats are as follows:
**   file_format==-1   Set on error by this function
**   file_format== 1   Version 2.1.0.
**   file_format== 2   Version 2.2.0. Add support for INTEGER PRIMARY KEY.
**   file_format== 3   Version 2.6.0. Fix empty-string index bug.
**   file_format== 4   Version 2.7.0. Add support for separate numeric and text datatypes.
** Returns SQLITE_OK on success, nonzero on error.
*/
int sqaux_get_file_format(const char *zFilename, int *pfile_format)
{
	sqlite *db;
	int mode = 0; /* unused for now. */
	int rc;

	assert(zFilename && pfile_format);
	if (!zFilename || !pfile_format)
		return SQLITE_ERROR;

	*pfile_format = -1; // init to error value.

	/* Allocate an sqlite struct to use. It must be allocated
	** dynamically, since it will be freed by sqlite_close().
	*/
	db = (sqlite *)sqliteMalloc(sizeof(sqlite));
	if (db == NULL)
		return SQLITE_NOMEM;

	/* Allocate the sqlite data structure */
	sqliteHashInit(&db->tblHash, SQLITE_HASH_STRING, 0);
	sqliteHashInit(&db->idxHash, SQLITE_HASH_STRING, 0);
	sqliteHashInit(&db->trigHash, SQLITE_HASH_STRING, 0);
	sqliteHashInit(&db->aFunc, SQLITE_HASH_STRING, 1);
	sqliteHashInit(&db->aFKey, SQLITE_HASH_STRING, 1);
	db->onError = OE_Default;
	db->priorNewRowid = 0;
	db->magic = SQLITE_MAGIC_BUSY;

	/* Open the backend database driver */
	rc = sqliteBtreeOpen(zFilename, mode, MAX_PAGES, &db->pBe);
	if( rc!=SQLITE_OK )
		return SQLITE_IOERR;

	/* Attempt to read the schema */
#if 0
	sqliteRegisterBuiltinFunctions(db); // TODO: can this be removed???
#endif
	rc = sqliteInit(db, NULL);
	if (rc == SQLITE_OK)
	{
		db->magic = SQLITE_MAGIC_OPEN;
		*pfile_format = db->file_format;
	}

	/* Now close the database and free its sqlite struct.
	** This is the reason we must allocate the db struct
	**  dynamically instead of declaring it locally.
	*/
	sqlite_close(db);

	return rc;
}
#endif

/*
** Backup the sqlite values which are used by sqlite_exec()
** and overwrite with values that will allow a recursive call.
** sqaux_end_recurse() must be called afterwards.
** <pmagic> holds the sqlite internal data backed up.
** These are related to helpers sqliteSafetyOn/Off().
** Returns 0 on success, non-zero on error.
*/
int sqaux_begin_recurse(sqlite *db, int *pmagic)
{
	assert(db); if (!db) return SQLITE_ERROR;
	assert(pmagic); if (!pmagic) return SQLITE_ERROR;

	/* TODO: check if the sqlitedb is properly initialized,... */

	/* Back up the 'magic' state */
	*pmagic = db->magic;

	if (db->magic == SQLITE_MAGIC_BUSY || db->magic == SQLITE_MAGIC_OPEN)
	{
		db->magic = SQLITE_MAGIC_OPEN;
		return SQLITE_OK;
	}
	else
	{
		return SQLITE_ERROR;
	}
}

/*
** Restore the sqlite values which are used by sqlite_exec()
** with the data saved by sqaux_begin_recurse().
** <pmagic> holds the sqlite internal data backed up.
** These are related to helpers sqliteSafetyOn/Off().
** Returns 0 on success, non-zero on error.
*/
int sqaux_end_recurse(sqlite *db, int *pmagic)
{
	assert(db); if (!db) return SQLITE_ERROR;
	assert(pmagic); if (!pmagic) return SQLITE_ERROR;

	/* TODO: check if the sqlitedb is properly initialized,... */

	/* Back up the 'magic' state */
	db->magic = *pmagic;

	return SQLITE_OK;
}


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