/*
** 2022-08-27
**
** 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 "sqlite3recover.h"
#include "sqliteInt.h"

#include <tcl.h>
#include <assert.h>

#ifndef SQLITE_OMIT_VIRTUALTABLE

typedef struct TestRecover TestRecover;
struct TestRecover {
  sqlite3_recover *p;
  Tcl_Interp *interp;
  Tcl_Obj *pScript;
};

static int xSqlCallback(void *pSqlArg, const char *zSql){
  TestRecover *p = (TestRecover*)pSqlArg;
  Tcl_Obj *pEval = 0;
  int res = 0;

  pEval = Tcl_DuplicateObj(p->pScript);
  Tcl_IncrRefCount(pEval);

  res = Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewStringObj(zSql, -1));
  if( res==TCL_OK ){
    res = Tcl_EvalObjEx(p->interp, pEval, 0);
  }

  Tcl_DecrRefCount(pEval);
  if( res ){
    Tcl_BackgroundError(p->interp);
    return TCL_ERROR;
  }else{
    Tcl_Obj *pObj = Tcl_GetObjResult(p->interp);
    if( Tcl_GetCharLength(pObj)==0 ){
      res = 0;
    }else if( Tcl_GetIntFromObj(p->interp, pObj, &res) ){
      Tcl_BackgroundError(p->interp);
      return TCL_ERROR;
    }
  }
  return res;
}

static int getDbPointer(Tcl_Interp *interp, Tcl_Obj *pObj, sqlite3 **pDb){
  Tcl_CmdInfo info;
  if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(pObj), &info) ){
    Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(pObj), 0);
    return TCL_ERROR;
  }
  *pDb = *(sqlite3 **)info.objClientData;
  return TCL_OK;
}

/*
** Implementation of the command created by [sqlite3_recover_init]:
**
**     $cmd config OP ARG
**     $cmd run
**     $cmd errmsg
**     $cmd errcode
**     $cmd finalize
*/
static int testRecoverCmd(
  void *clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  static struct RecoverSub {
    const char *zSub;
    int nArg;
    const char *zMsg;
  } aSub[] = {
    { "config",    2, "ARG"         }, /* 0 */
    { "run",      0, ""             }, /* 1 */
    { "errmsg",    0, ""            }, /* 2 */
    { "errcode",   0, ""            }, /* 3 */
    { "finish",  0, ""              }, /* 4 */
    { "step",  0, ""                }, /* 5 */
    { 0 }
  };
  int rc = TCL_OK;
  int iSub = 0;
  TestRecover *pTest = (TestRecover*)clientData;

  if( objc<2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
    return TCL_ERROR;
  }
  rc = Tcl_GetIndexFromObjStruct(interp, 
      objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
  );
  if( rc!=TCL_OK ) return rc;
  if( (objc-2)!=aSub[iSub].nArg ){
    Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
    return TCL_ERROR;
  }

  switch( iSub ){
    case 0:  assert( sqlite3_stricmp("config", aSub[iSub].zSub)==0 ); {
      const char *aOp[] = {
        "testdb",          /* 0 */
        "lostandfound",    /* 1 */
        "freelistcorrupt", /* 2 */
        "rowids",          /* 3 */
        "slowindexes",     /* 4 */
        "invalid",         /* 5 */
        0
      };
      int iOp = 0;
      int res = 0;
      if( Tcl_GetIndexFromObj(interp, objv[2], aOp, "option", 0, &iOp) ){
        return TCL_ERROR;
      }
      switch( iOp ){
        case 0:
          res = sqlite3_recover_config(pTest->p, 
              789, (void*)Tcl_GetString(objv[3]) /* MAGIC NUMBER! */
          );
          break;
        case 1: {
          const char *zStr = Tcl_GetString(objv[3]);
          res = sqlite3_recover_config(pTest->p, 
              SQLITE_RECOVER_LOST_AND_FOUND, (void*)(zStr[0] ? zStr : 0)
          );
          break;
        }
        case 2: {
          int iVal = 0;
          if( Tcl_GetBooleanFromObj(interp, objv[3], &iVal) ) return TCL_ERROR;
          res = sqlite3_recover_config(pTest->p, 
              SQLITE_RECOVER_FREELIST_CORRUPT, (void*)&iVal
          );
          break;
        }
        case 3: {
          int iVal = 0;
          if( Tcl_GetBooleanFromObj(interp, objv[3], &iVal) ) return TCL_ERROR;
          res = sqlite3_recover_config(pTest->p, 
              SQLITE_RECOVER_ROWIDS, (void*)&iVal
          );
          break;
        }
        case 4: {
          int iVal = 0;
          if( Tcl_GetBooleanFromObj(interp, objv[3], &iVal) ) return TCL_ERROR;
          res = sqlite3_recover_config(pTest->p, 
              SQLITE_RECOVER_SLOWINDEXES, (void*)&iVal
          );
          break;
        }
        case 5: {
          res = sqlite3_recover_config(pTest->p, 12345, 0);
          break;
        }
      }
      Tcl_SetObjResult(interp, Tcl_NewIntObj(res));
      break;
    }
    case 1:  assert( sqlite3_stricmp("run", aSub[iSub].zSub)==0 ); {
      int res = sqlite3_recover_run(pTest->p);
      Tcl_SetObjResult(interp, Tcl_NewIntObj(res));
      break;
    }
    case 2:  assert( sqlite3_stricmp("errmsg", aSub[iSub].zSub)==0 ); {
      const char *zErr = sqlite3_recover_errmsg(pTest->p);
      Tcl_SetObjResult(interp, Tcl_NewStringObj(zErr, -1));
      break;
    }
    case 3:  assert( sqlite3_stricmp("errcode", aSub[iSub].zSub)==0 ); {
      int errCode = sqlite3_recover_errcode(pTest->p);
      Tcl_SetObjResult(interp, Tcl_NewIntObj(errCode));
      break;
    }
    case 4:  assert( sqlite3_stricmp("finish", aSub[iSub].zSub)==0 ); {
      int res = sqlite3_recover_errcode(pTest->p);
      int res2;
      if( res!=SQLITE_OK ){
        const char *zErr = sqlite3_recover_errmsg(pTest->p);
        Tcl_SetObjResult(interp, Tcl_NewStringObj(zErr, -1));
      }
      res2 = sqlite3_recover_finish(pTest->p);
      assert( res2==res );
      if( res ) return TCL_ERROR;
      break;
    }
    case 5:  assert( sqlite3_stricmp("step", aSub[iSub].zSub)==0 ); {
      int res = sqlite3_recover_step(pTest->p);
      Tcl_SetObjResult(interp, Tcl_NewIntObj(res));
      break;
    }
  }

  return TCL_OK;
}

/*
** sqlite3_recover_init DB DBNAME URI
*/
static int test_sqlite3_recover_init(
  void *clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  static int iTestRecoverCmd = 1;

  TestRecover *pNew = 0;
  sqlite3 *db = 0;
  const char *zDb = 0;
  const char *zUri = 0;
  char zCmd[128];
  int bSql = clientData ? 1 : 0;

  if( objc!=4 ){
    const char *zErr = (bSql ? "DB DBNAME SCRIPT" : "DB DBNAME URI");
    Tcl_WrongNumArgs(interp, 1, objv, zErr);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, objv[1], &db) ) return TCL_ERROR;
  zDb = Tcl_GetString(objv[2]);
  if( zDb[0]=='\0' ) zDb = 0;

  pNew = ckalloc(sizeof(TestRecover));
  if( bSql==0 ){
    zUri = Tcl_GetString(objv[3]);
    pNew->p = sqlite3_recover_init(db, zDb, zUri);
  }else{
    pNew->interp = interp;
    pNew->pScript = objv[3];
    Tcl_IncrRefCount(pNew->pScript);
    pNew->p = sqlite3_recover_init_sql(db, zDb, xSqlCallback, (void*)pNew);
  }

  sprintf(zCmd, "sqlite_recover%d", iTestRecoverCmd++);
  Tcl_CreateObjCommand(interp, zCmd, testRecoverCmd, (void*)pNew, 0);

  Tcl_SetObjResult(interp, Tcl_NewStringObj(zCmd, -1));
  return TCL_OK;
}

/*
** Declaration for public API function in file dbdata.c. This may be called
** with NULL as the final two arguments to register the sqlite_dbptr and
** sqlite_dbdata virtual tables with a database handle.
*/
#ifdef _WIN32
__declspec(dllexport)
#endif
int sqlite3_dbdata_init(sqlite3*, char**, const sqlite3_api_routines*);

/*
** sqlite3_recover_init DB DBNAME URI
*/
static int test_sqlite3_dbdata_init(
  void *clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite3 *db = 0;

  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "DB");
    return TCL_ERROR;
  }
  if( getDbPointer(interp, objv[1], &db) ) return TCL_ERROR;
  sqlite3_dbdata_init(db, 0, 0);

  Tcl_ResetResult(interp);
  return TCL_OK;
}

#endif /* SQLITE_OMIT_VIRTUALTABLE */

int TestRecover_Init(Tcl_Interp *interp){
#ifndef SQLITE_OMIT_VIRTUALTABLE
  struct Cmd {
    const char *zCmd;
    Tcl_ObjCmdProc *xProc;
    void *pArg;
  } aCmd[] = {
    { "sqlite3_recover_init", test_sqlite3_recover_init, 0 },
    { "sqlite3_recover_init_sql", test_sqlite3_recover_init, (void*)1 },
    { "sqlite3_dbdata_init", test_sqlite3_dbdata_init, (void*)1 },
  };
  int i;

  for(i=0; i<sizeof(aCmd)/sizeof(struct Cmd); i++){
    struct Cmd *p = &aCmd[i];
    Tcl_CreateObjCommand(interp, p->zCmd, p->xProc, p->pArg, 0);
  }
#endif
  return TCL_OK;
}

