/*
** 2012 April 19
**
** 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 "sqliteInt.h"


static struct KVWrapGlobal {
  sqlite4_kvfactory xFactory;
  int nStep;                      /* Total number of successful next/prev */
  int nSeek;                      /* Total number of calls to xSeek */
} kvwg = {0};

typedef struct KVWrap KVWrap;
typedef struct KVWrapCsr KVWrapCsr;

struct KVWrap {
  KVStore base;                   /* Base class, must be first */
  KVStore *pReal;                 /* "Real" KVStore object */
};

struct KVWrapCsr {
  KVCursor base;                  /* Base class. Must be first */
  KVCursor *pReal;                /* "Real" Cursor obecjt */
};

static int kvwrapBegin(KVStore *pKVStore, int iLevel){
  int rc;
  KVWrap *p = (KVWrap *)pKVStore;
  rc = p->pReal->pStoreVfunc->xBegin(p->pReal, iLevel);
  p->base.iTransLevel = p->pReal->iTransLevel;
  return rc;
}

static int kvwrapCommitPhaseOne(KVStore *pKVStore, int iLevel){
  int rc;
  KVWrap *p = (KVWrap *)pKVStore;
  rc = p->pReal->pStoreVfunc->xCommitPhaseOne(p->pReal, iLevel);
  p->base.iTransLevel = p->pReal->iTransLevel;
  return rc;
}

static int kvwrapCommitPhaseTwo(KVStore *pKVStore, int iLevel){
  int rc;
  KVWrap *p = (KVWrap *)pKVStore;
  rc = p->pReal->pStoreVfunc->xCommitPhaseTwo(p->pReal, iLevel);
  p->base.iTransLevel = p->pReal->iTransLevel;
  return rc;
}

static int kvwrapRollback(KVStore *pKVStore, int iLevel){
  int rc;
  KVWrap *p = (KVWrap *)pKVStore;
  rc = p->pReal->pStoreVfunc->xRollback(p->pReal, iLevel);
  p->base.iTransLevel = p->pReal->iTransLevel;
  return rc;
}

static int kvwrapRevert(KVStore *pKVStore, int iLevel){
  int rc;
  KVWrap *p = (KVWrap *)pKVStore;
  rc = p->pReal->pStoreVfunc->xRevert(p->pReal, iLevel);
  p->base.iTransLevel = p->pReal->iTransLevel;
  return rc;
}

static int kvwrapReplace(
  KVStore *pKVStore,
  const KVByteArray *aKey, KVSize nKey,
  const KVByteArray *aData, KVSize nData
){
  KVWrap *p = (KVWrap *)pKVStore;
  return p->pReal->pStoreVfunc->xReplace(p->pReal, aKey, nKey, aData, nData);
}

/*
** Create a new cursor object.
*/
static int kvwrapOpenCursor(KVStore *pKVStore, KVCursor **ppKVCursor){
  int rc = SQLITE4_OK;
  KVWrap *p = (KVWrap *)pKVStore;
  KVWrapCsr *pCsr;

  pCsr = (KVWrapCsr *)sqlite4_malloc(0, sizeof(KVWrapCsr));
  if( pCsr==0 ){
    rc = SQLITE4_NOMEM;
  }else{
    memset(pCsr, 0, sizeof(KVWrapCsr));
    rc = p->pReal->pStoreVfunc->xOpenCursor(p->pReal, &pCsr->pReal);
    if( rc!=SQLITE4_OK ){
      sqlite4_free(0, pCsr);
      pCsr = 0;
    }else{
      pCsr->base.pStore = pKVStore;
      pCsr->base.pStoreVfunc = pKVStore->pStoreVfunc;
    }
  }

  *ppKVCursor = (KVCursor*)pCsr;
  return rc;
}

/*
** Reset a cursor
*/
static int kvwrapReset(KVCursor *pKVCursor){
  KVWrap *p = (KVWrap *)(pKVCursor->pStore);
  KVWrapCsr *pCsr = (KVWrapCsr *)pKVCursor;
  return p->pReal->pStoreVfunc->xReset(pCsr->pReal);
}

/*
** Destroy a cursor object
*/
static int kvwrapCloseCursor(KVCursor *pKVCursor){
  int rc;
  KVWrap *p = (KVWrap *)(pKVCursor->pStore);
  KVWrapCsr *pCsr = (KVWrapCsr *)pKVCursor;
  rc = p->pReal->pStoreVfunc->xCloseCursor(pCsr->pReal);
  sqlite4_free(0, pCsr);
  return rc;
}

/*
** Move a cursor to the next non-deleted node.
*/
static int kvwrapNextEntry(KVCursor *pKVCursor){
  int rc;
  KVWrap *p = (KVWrap *)(pKVCursor->pStore);
  KVWrapCsr *pCsr = (KVWrapCsr *)pKVCursor;
  rc = p->pReal->pStoreVfunc->xNext(pCsr->pReal);
  kvwg.nStep++;
  return rc;
}

/*
** Move a cursor to the previous non-deleted node.
*/
static int kvwrapPrevEntry(KVCursor *pKVCursor){
  int rc;
  KVWrap *p = (KVWrap *)(pKVCursor->pStore);
  KVWrapCsr *pCsr = (KVWrapCsr *)pKVCursor;
  rc = p->pReal->pStoreVfunc->xPrev(pCsr->pReal);
  kvwg.nStep++;
  return rc;
}

/*
** Seek a cursor.
*/
static int kvwrapSeek(
  KVCursor *pKVCursor, 
  const KVByteArray *aKey,
  KVSize nKey,
  int dir
){
  KVWrap *p = (KVWrap *)(pKVCursor->pStore);
  KVWrapCsr *pCsr = (KVWrapCsr *)pKVCursor;

  /* If aKey[0]==0, this is a seek to retrieve meta-data. Don't count this. */
  if( aKey[0] ) kvwg.nSeek++;

  return p->pReal->pStoreVfunc->xSeek(pCsr->pReal, aKey, nKey, dir);
}

/*
** Delete the entry that the cursor is pointing to.
**
** Though the entry is "deleted", it still continues to exist as a
** phantom.  Subsequent xNext or xPrev calls will work, as will
** calls to xKey and xData, thought the result from xKey and xData
** are undefined.
*/
static int kvwrapDelete(KVCursor *pKVCursor){
  KVWrap *p = (KVWrap *)(pKVCursor->pStore);
  KVWrapCsr *pCsr = (KVWrapCsr *)pKVCursor;
  return p->pReal->pStoreVfunc->xDelete(pCsr->pReal);
}

/*
** Return the key of the node the cursor is pointing to.
*/
static int kvwrapKey(
  KVCursor *pKVCursor,         /* The cursor whose key is desired */
  const KVByteArray **paKey,   /* Make this point to the key */
  KVSize *pN                   /* Make this point to the size of the key */
){
  KVWrap *p = (KVWrap *)(pKVCursor->pStore);
  KVWrapCsr *pCsr = (KVWrapCsr *)pKVCursor;
  return p->pReal->pStoreVfunc->xKey(pCsr->pReal, paKey, pN);
}

/*
** Return the data of the node the cursor is pointing to.
*/
static int kvwrapData(
  KVCursor *pKVCursor,         /* The cursor from which to take the data */
  KVSize ofst,                 /* Offset into the data to begin reading */
  KVSize n,                    /* Number of bytes requested */
  const KVByteArray **paData,  /* Pointer to the data written here */
  KVSize *pNData               /* Number of bytes delivered */
){
  KVWrap *p = (KVWrap *)(pKVCursor->pStore);
  KVWrapCsr *pCsr = (KVWrapCsr *)pKVCursor;
  return p->pReal->pStoreVfunc->xData(pCsr->pReal, ofst, n, paData, pNData);
}

/*
** Destructor for the entire in-memory storage tree.
*/
static int kvwrapClose(KVStore *pKVStore){
  int rc;
  KVWrap *p = (KVWrap *)pKVStore;
  rc = p->pReal->pStoreVfunc->xClose(p->pReal);
  sqlite4_free(0, p);
  return rc;
}

/*
** Invoke the xControl() method of the underlying KVStore object.
*/
static int kvwrapControl(KVStore *pKVStore, int op, void *pArg){
  KVWrap *p = (KVWrap *)pKVStore;
  return p->pReal->pStoreVfunc->xControl(p->pReal, op, pArg);
}

static int kvwrapGetMeta(KVStore *pKVStore, unsigned int *piVal){
  KVWrap *p = (KVWrap *)pKVStore;
  return p->pReal->pStoreVfunc->xGetMeta(p->pReal, piVal);
}

static int kvwrapPutMeta(KVStore *pKVStore, unsigned int iVal){
  KVWrap *p = (KVWrap *)pKVStore;
  return p->pReal->pStoreVfunc->xPutMeta(p->pReal, iVal);
}

static int kvwrapGetMethod(
  sqlite4_kvstore *pKVStore, 
  const char *zMethod, 
  void **ppArg,
  void (**pxFunc)(sqlite4_context *, int, sqlite4_value **),
  void (**pxDestroy)(void *)
){
  KVWrap *p = (KVWrap *)pKVStore;
  return p->pReal->pStoreVfunc->xGetMethod(
      p->pReal, zMethod, ppArg, pxFunc, pxDestroy
  );
}

static int newFileStorage(
  sqlite4_env *pEnv,
  KVStore **ppKVStore,
  const char *zName,
  unsigned openFlags
){

  /* Virtual methods for the new factory */
  static const KVStoreMethods kvwrapMethods = {
    1,
    sizeof(KVStoreMethods),
    kvwrapReplace,
    kvwrapOpenCursor,
    kvwrapSeek,
    kvwrapNextEntry,
    kvwrapPrevEntry,
    kvwrapDelete,
    kvwrapKey,
    kvwrapData,
    kvwrapReset,
    kvwrapCloseCursor,
    kvwrapBegin,
    kvwrapCommitPhaseOne,
    kvwrapCommitPhaseTwo,
    kvwrapRollback,
    kvwrapRevert,
    kvwrapClose,
    kvwrapControl,
    kvwrapGetMeta,
    kvwrapPutMeta,
    kvwrapGetMethod
  };

  KVWrap *pNew;
  int rc = SQLITE4_OK;

  pNew = (KVWrap *)sqlite4_malloc(0, sizeof(KVWrap));
  if( pNew==0 ){
    rc = SQLITE4_NOMEM;
  }else{
    memset(pNew, 0, sizeof(KVWrap));
    pNew->base.pStoreVfunc = &kvwrapMethods;
    rc = kvwg.xFactory(pEnv, &pNew->pReal, zName, openFlags);
    if( rc!=SQLITE4_OK ){
      sqlite4_free(0, pNew);
      pNew = 0;
    }
  }

  *ppKVStore = (KVStore*)pNew;
  return rc;
}

static int kvwrap_install_cmd(Tcl_Interp *interp, int objc, Tcl_Obj **objv){
  const char *z = "main";

  if( objc!=2 && objc!=3 ){
    Tcl_WrongNumArgs(interp, 2, objv, "?FACTORY?");
    return TCL_ERROR;
  }
  if( objc==3 ){
    z = Tcl_GetString(objv[2]);
  }

  if( kvwg.xFactory==0 ){
    sqlite4_env_config(0, SQLITE4_ENVCONFIG_KVSTORE_GET,z,&kvwg.xFactory);
    if( kvwg.xFactory==0 ){
      Tcl_AppendResult(interp, "no such factory: ", z, 0);
      return TCL_ERROR;
    }
    sqlite4_env_config(0, SQLITE4_ENVCONFIG_KVSTORE_PUSH,"main",newFileStorage);
  }
  return TCL_OK;
}

static int kvwrap_uninstall_cmd(Tcl_Interp *interp, int objc, Tcl_Obj **objv){
  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 2, objv, "");
    return TCL_ERROR;
  }

  if( kvwg.xFactory ){
    sqlite4_env_config(0, SQLITE4_ENVCONFIG_KVSTORE_POP,"main", &kvwg.xFactory);
    kvwg.xFactory = 0;
  }
  return TCL_OK;
}

static int kvwrap_seek_cmd(Tcl_Interp *interp, int objc, Tcl_Obj **objv){
  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 2, objv, "");
    return TCL_ERROR;
  }

  Tcl_SetObjResult(interp, Tcl_NewIntObj(kvwg.nSeek));
  return TCL_OK;
}

static int kvwrap_step_cmd(Tcl_Interp *interp, int objc, Tcl_Obj **objv){
  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 2, objv, "");
    return TCL_ERROR;
  }

  Tcl_SetObjResult(interp, Tcl_NewIntObj(kvwg.nStep));
  return TCL_OK;
}

static int kvwrap_reset_cmd(Tcl_Interp *interp, int objc, Tcl_Obj **objv){
  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 2, objv, "");
    return TCL_ERROR;
  }

  kvwg.nStep = 0;
  kvwg.nSeek = 0;

  Tcl_ResetResult(interp);
  return TCL_OK;
}


/*
** TCLCMD:    kvwrap SUB-COMMAND
*/
static int kvwrap_command(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  struct Subcmd {
    const char *zCmd;
    int (*xCmd)(Tcl_Interp *, int, Tcl_Obj **);
  } aSub[] = {
    { "install",   kvwrap_install_cmd },
    { "step",      kvwrap_step_cmd },
    { "seek",      kvwrap_seek_cmd },
    { "reset",     kvwrap_reset_cmd },
    { "uninstall", kvwrap_uninstall_cmd },
  };
  int iSub;
  int rc;

  rc = Tcl_GetIndexFromObjStruct(
      interp, objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
  );
  if( rc==TCL_OK ){
    rc = aSub[iSub].xCmd(interp, objc, (Tcl_Obj **)objv); 
  }

  return rc;
}

/*
** TCLCMD:    kv_default DEFAULT
*/
static int kv_default_cmd(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_kvfactory factory = 0;
  const char *zDflt;

  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "DEFAULT");
    return TCL_ERROR;
  }

  zDflt = Tcl_GetString(objv[1]);

  sqlite4_env_config(0, SQLITE4_ENVCONFIG_KVSTORE_GET, zDflt, &factory);
  if( factory==0 ){
    Tcl_ResetResult(interp);
    Tcl_AppendResult(interp, "no such factory: ", zDflt, 0);
    return TCL_ERROR;
  }
  sqlite4_env_config(0, SQLITE4_ENVCONFIG_KVSTORE_PUSH, "main", factory);
  return TCL_OK;
}

/*
** Register the TCL commands defined above with the TCL interpreter.
**
** This routine should be the only externally visible symbol in this
** source code file.
*/
int Sqliteteststorage2_Init(Tcl_Interp *interp){
  Tcl_CreateObjCommand(interp, "kvwrap", kvwrap_command, 0, 0);
  Tcl_CreateObjCommand(interp, "kv_default", kv_default_cmd, 0, 0);
  return TCL_OK;
}
