/*
** Wrapper around SQlite3 amalgamation file for mORMot use
**
** Please download and put sqlite3.c in amalgamation/ sub-folder
** from https://sqlite.org/download.html
** then follow the amalgamation/ReadMe.md instructions
*/

/*
** Define conditionals / extensions specially tuned for mORMot
**
** See also https://www.sqlite.org/compile.html#recommended_compile_time_options
*/

#define SQLITE_DEFAULT_MEMSTATUS 0
// don't need any debug here, and don't even define sqlite3_status()

// assuming multi-thread safety is made by caller - in our framework, there is
// only one thread using the database connection at the same time, but there could
// be multiple database connection at the same time
// * 0 = single-thread = all mutexes disabled - seems unsafe
// * 1 = serialized = all calls serialized - seems overkill
// * 2 = multi-thread = thread-safe by connection - fine for our purpose
// - note that we keep 1=serialized at compile time (to allow all modes)
// but SQLITE_CONFIG_MULTITHREAD is set in TSqlite3Library.BeforeInitialization
// and rely on TSqlDataBase to do explicit Lock/LockJson/UnLock calls 
#if SQLITE_NO_THREAD
# define SQLITE_THREADSAFE 0
#else  
# define SQLITE_THREADSAFE 1
#endif

#define SQLITE_OMIT_SHARED_CACHE 1
// no need of shared cache in a threadsafe calling model

#define SQLITE_OMIT_AUTOINIT 1
// sqlite3_initialize() is done in unit initialization -> no AUTOINIT

#define SQLITE_OMIT_DEPRECATED 1
// spare some code size


#define SQLITE_LIKE_DOESNT_MATCH_BLOBS 1
// historical function, never used

#define SQLITE_ENABLE_FTS3 1
#define SQLITE_ENABLE_FTS3_PARENTHESIS 1
#define SQLITE_ENABLE_FTS4 1
#define SQLITE_ENABLE_FTS5 1
// enable all FTS engines https://www.sqlite.org/fts3.html https://www.sqlite.org/fts5.html

#define SQLITE_ENABLE_JSON1 1
// enable JSON https://www.sqlite.org/json1.html

#define SQLITE_MAX_EXPR_DEPTH 0
// no SQL depth limit, since we trust the input and expect the best performance

#define SQLITE_ENABLE_DESERIALIZE
// enable sqlite3_serialize() and sqlite3_deserialize()

#define SQLITE_ENABLE_RTREE 1
// enable RTREE https://sqlite.org/rtree.html

#define SQLITE_ENABLE_GEOPOLY 1
// enable GeoJSON over RTREE https://sqlite.org/geopoly.html 

#define SQLITE_ENABLE_REGEXP 1
// enable the compact https://www.sqlite.org/src/file?name=ext/misc/regexp.c
// - can be overloaded with any other implementation

#define SQLITE_ENABLE_RBU 1
// enable "Resumable Bulk Update" (or OTA) https://www.sqlite.org/rbu.html

#define SQLITE_ENABLE_SESSION 1
#define SQLITE_ENABLE_PREUPDATE_HOOK 1
// enable Sessions https://sqlite.org/sessionintro.html

#define SQLITE_ENABLE_NORMALIZE 1
// enable all https://sqlite.org/c3ref/expanded_sql.html functions

#define YYTRACKMAXSTACKDEPTH 1
// enable SQLITE_STATUS_PARSER_STACK support

#define SQLITE_ENABLE_COLUMN_METADATA 1
//enable column_database_name, column_table_name and column_origin_name support

#define SQLITE_ENABLE_STMT_SCANSTATUS 1
// enable stmt_scanstatus and stmt_scanstatus_reset support

#define SQLITE_ENABLE_SNAPSHOT 1
// support the sqlite3_snapshot object

#define SQLITE_ENABLE_UNLOCK_NOTIFY 1
// enable sqlite3_unlock_notify

/*
** Disabled conditionals / extensions
*/

// #define SQLITE_ENABLE_ICU
// disabled because induces a huge dependency - use WIN32NOCASE (which calls
// ICU on POSIX) or even better the UNICODENOCASE as available in mORMot 2

// #define SQLITE_ENABLE_STAT4
// adds additional logic to the ANALYZE command and to the Query Planner


/*
** Define function for extra initilization
**
** The extra initialization function registers an extension function
** which will be automatically executed for each new database connection.
*/

#define SQLITE_EXTRA_INIT sqlite3mc_initialize
#define SQLITE_EXTRA_SHUTDOWN sqlite3mc_terminate

/*
** Compile the official SQLite3 amalgamation file
*/

#if defined(__BORLANDC__)
#define __STDC__ 1
#endif

#if defined(__BORLANDC__)
#undef __STDC__
#endif

#include "sqlite3patched.c"
// to be downloaded from https://sqlite.org/download.html
// then execute ./patch.sh to apply Codec patches

/*
** Handle Database Ciphering
** adapted from https://github.com/utelle/SQLite3MultipleCiphers patches
** wxWindows Library Licence, Version 3.1
*/

/*
** Define the Codec types as needed by codecext.c
**
*/

#define WX_PAGER_MJ_PGNO(x) ((PENDING_BYTE/(x))+1)
// ATTENTION: Macro similar to that in pager.c

#define KEYLENGTH 304
// match maximum possible AESContextSize, with 8 bytes alignment

// We embed two SynCrypto's TAES objects in the Codec struct
typedef struct _Codec
{
  /* Defined if this DB is encrypted */
  int           m_isEncrypted;
  /* Read cipher */ 
  int           m_hasReadKey;
  unsigned char m_readKey[KEYLENGTH];
  /* Write cipher */ 
  int           m_hasWriteKey;
  unsigned char m_writeKey[KEYLENGTH];
  /* Pointers to DB and its B-trees */
  sqlite3*      m_db; 
  Btree*        m_bt; 
  BtShared*     m_btShared; 
  /* Temporary memory buffer used during AES process */
  unsigned char m_page[SQLITE_MAX_PAGE_SIZE + 24];
} Codec;

static int CodecInit(Codec* codec)
{
  int rc = SQLITE_OK;
  if (codec != NULL)
  {
    codec->m_isEncrypted = 0;
    codec->m_hasReadKey = 0;
    codec->m_hasWriteKey = 0;
    codec->m_db = 0;
    codec->m_bt = 0;
    codec->m_btShared = 0;
  }
  else
  {
    rc = SQLITE_NOMEM;
  }
  return rc;
}

static void CodecCopyKey(Codec* codec, int read2write)
{
  if (read2write)
  {
    memcpy(&codec->m_writeKey, &codec->m_readKey, KEYLENGTH);
  }
  else
  {
    memcpy(&codec->m_readKey, &codec->m_writeKey, KEYLENGTH);
  }
}

static int CodecCopyCipher(Codec* codec, Codec* other)
{
  codec->m_isEncrypted = other->m_isEncrypted;
  codec->m_hasReadKey  = other->m_hasReadKey;
  codec->m_hasWriteKey = other->m_hasWriteKey;
  memcpy(&codec->m_readKey, &other->m_readKey, KEYLENGTH);
  memcpy(&codec->m_writeKey, &other->m_writeKey, KEYLENGTH);
  return SQLITE_OK;
}

// implemented in pascal using SynCrypto optimized AES functions
extern void CodecGenerateReadKey(Codec* codec, char* userPassword, int passwordLength);
extern void CodecGenerateWriteKey(Codec* codec, char* userPassword, int passwordLength);
extern int CodecEncrypt(Codec* codec, int page, unsigned char* data, int len, int useWriteKey);
extern int CodecDecrypt(Codec* codec, int page, unsigned char* data, int len);
extern int CodecTerm(Codec* codec);

// used by SynSQlite3Static to retrieve the PAES members from a given codec

unsigned char* CodecGetReadKey(Codec* codec)
{
  return codec->m_readKey;
}

unsigned char* CodecGetWriteKey(Codec* codec)
{
  return codec->m_writeKey;
}

static void CodecSetIsEncrypted(Codec* codec, int isEncrypted)
{
  codec->m_isEncrypted = isEncrypted;
}

static void CodecSetHasReadKey(Codec* codec, int hasReadKey)
{
  codec->m_hasReadKey = hasReadKey;
}

static void CodecSetHasWriteKey(Codec* codec, int hasWriteKey)
{
  codec->m_hasWriteKey = hasWriteKey;
}

static int CodecIsEncrypted(Codec* codec)
{
  return codec->m_isEncrypted;
}

static int CodecHasReadKey(Codec* codec)
{
  return codec->m_hasReadKey;
}

static int CodecHasWriteKey(Codec* codec)
{
  return codec->m_hasWriteKey;
}

static void CodecSetDb(Codec* codec, sqlite3* db)
{
  codec->m_db = db;
}

static void CodecSetBtree(Codec* codec, Btree* bt)
{
  codec->m_bt = bt;
  codec->m_btShared = bt->pBt;
}

static Btree* CodecGetBtree(Codec* codec)
{
  return codec->m_bt;
}

static BtShared* CodecGetBtShared(Codec* codec)
{
  return codec->m_btShared;
}

static unsigned char* CodecGetPageBuffer(Codec* codec)
{
  return &codec->m_page[4];
}

#include "codecext.c"

/*
** REGEXP
*/
#ifdef SQLITE_ENABLE_REGEXP
/* Prototype for initialization function of REGEXP extension */
#ifdef _WIN32
__declspec(dllexport)
#endif
int sqlite3_regexp_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi);
#include "regexp.c"
#endif 

/*
** Multi cipher VFS
*/

SQLITE_API const char* sqlite3mc_vfs_name();
SQLITE_API void sqlite3mc_vfs_terminate();
SQLITE_API int sqlite3mc_vfs_initialize(sqlite3_vfs* vfsDefault, int makeDefault);

#include "sqlite3mc_vfs.c"

int
sqlite3mc_initialize(const char* arg)
{
  int rc = SQLITE_OK;
  sqlite3_vfs* vfsDefault;

  /*
  ** Initialize and register MultiCipher VFS as default VFS
  ** if it isn't already registered
  */
  if (sqlite3_vfs_find(sqlite3mc_vfs_name()) == NULL)
  {
    vfsDefault = sqlite3_vfs_find("unix-excl");
    /* WAL requires unix-excl so we force it as default on posix */
    if (vfsDefault == NULL)
    {
      vfsDefault = sqlite3_vfs_find(NULL);
    }
    rc = sqlite3mc_vfs_initialize(vfsDefault, 1);
  }

  /*
  ** Can be overloaded later with any other REGEXP engine
  */
  #ifdef SQLITE_ENABLE_REGEXP
    if (rc == SQLITE_OK)
    {
      rc = sqlite3_auto_extension((void(*)(void)) sqlite3_regexp_init);
    }
  #endif  
  
  return rc;
}

void
sqlite3mc_terminate(void)
{
  sqlite3mc_vfs_terminate();
}
