#ifndef SQLITE3SHX_H
#define SQLITE3SHX_H
#include "sqlite3ext.h"

#include "obj_interfaces.h"

#ifdef __cplusplus
extern "C" {
#endif

/* Convey data to, from and/or between I/O handlers and meta-commands. */
typedef struct {
  /* A semi-transient holder of arbitrary data used during operations
   * not interrupted by meta-command invocations. Any not-null pointer
   * left after a meta-command has completed is, by contract, to be
   * freeable using sqlite3_free(). It is otherwise unconstrained.
   */
  void *pvHandlerData;
  /* The user's currently open and primary DB connection */
  sqlite3 *db;
  /* The DB connection used for shell's dynamical data */
  sqlite3 *dbShell;
  /* Output stream to which shell's text output to be written */
  FILE *pCurrentOutputStream;
  /* Number of lines written during a query result output */
  int resultCount;
  /* Whether to exit as command completes.
   * 0 => no exit
   * ~0 => a non-error (0) exit
   * other => exit with process exit code other
   * For embedded shell, "exit" means "return from REPL".
   */
  int shellExit;
  /* Whether to show column names for certain output modes */
  int showHeader;
  /* Column separator character for some modes */
  char *zFieldSeparator;
  /* Row separator character for some modes (MODE_Ascii) */
  char *zRecordSeparator;
  /* Row set prefix for some modes */
  char *zRecordLead;
  /* Row set suffix for some modes */
  char *zRecordTrail;
  /* Text to represent a NULL in external data formats */
  char *zNullValue;
  /* Number of column widths presently desired or tracked */
  int  numWidths; /* known allocation count of next 2 members */
  /* The column widths last specified via .width command */
  int  *pWantWidths;
  /* The column widths last observed in query results */
  int  *pHaveWidths;
} ShellExState;

/* The shell's state, shared among meta-command implementations.
 * The ShellStateX object includes a private partition whose content
 * and usage are opaque to shell extensions compiled separately
 * from the shell.c core. (As defined here, it is wholly opaque.)
 */
typedef struct ShellStateX {
  ShellExState sxs;       /* sizeof(ShellExState) will never shrink. */
  struct ShellState *pSS; /* The offset of this member is NOT STABLE. */
} ShellStateX;

/* This function pointer has the same signature as the sqlite3_X_init()
 * function that is called as SQLite3 completes loading an extension.
 */
typedef int (*ExtensionId)
  (sqlite3 *, char **, const struct sqlite3_api_routines *);

/*****************
 * See "Shell Extensions, Programming" for purposes and usage of the following
 * interfaces supporting extended meta-commands and import and output modes.
 */

/* An object implementing below interface is registered with the
 * shell to make new or overriding meta-commands available to it.
 */
INTERFACE_BEGIN( MetaCommand );
PURE_VMETHOD(const char *, name, MetaCommand, 0,());
PURE_VMETHOD(const char *, help, MetaCommand, 1,(int more));
PURE_VMETHOD(struct {unsigned minArgs; unsigned maxArgs;},
             argsRange, MetaCommand, 0,());
PURE_VMETHOD(int, execute, MetaCommand,
             4,(ShellStateX *, char **pzErrMsg, int nArgs, char *azArgs[]));
INTERFACE_END( MetaCommand );

/* Define error codes to be returned either by a meta-command during
 * its own checking or by the dispatcher for bad argument counts.
 */
#define SHELL_INVALID_ARGS SQLITE_MISUSE
#define SHELL_FORBIDDEN_OP 0x7ffe /* Action disallowed under --safe.*/

/* An object implementing below interface is registered with the
 * shell to make new or overriding output modes available to it.
 */
INTERFACE_BEGIN( OutModeHandler );
PURE_VMETHOD(const char *, name, OutModeHandler, 0,());
PURE_VMETHOD(const char *, help, OutModeHandler, 1,(int more));
PURE_VMETHOD(int, openResultsOutStream, OutModeHandler,
             5,( ShellExState *pSES, char **pzErr,
                 int numArgs, char *azArgs[], const char * zName ));
PURE_VMETHOD(int, prependResultsOut, OutModeHandler,
             3,( ShellExState *pSES, char **pzErr, sqlite3_stmt *pStmt ));
PURE_VMETHOD(int, rowResultsOut, OutModeHandler,
             3,( ShellExState *pSES, char **pzErr, sqlite3_stmt *pStmt ));
PURE_VMETHOD(int, appendResultsOut, OutModeHandler,
             3,( ShellExState *pSES, char **pzErr, sqlite3_stmt *pStmt ));
PURE_VMETHOD(void, closeResultsOutStream, OutModeHandler,
             2,( ShellExState *pSES, char **pzErr ));
INTERFACE_END( OutModeHandlerVtable );

/* An object implementing below interface is registered with the
 * shell to make new or overriding data importers available to it.
 */
INTERFACE_BEGIN( ImportHandler );
PURE_VMETHOD(const char *, name, ImportHandler, 0,());
PURE_VMETHOD(const char *, help, ImportHandler, 1,( int more ));
PURE_VMETHOD(int,  openDataInStream, ImportHandler,
             5,( ShellExState *pSES, char **pzErr,
                 int numArgs, char *azArgs[], const char * zName ));
PURE_VMETHOD(int, prepareDataInput, ImportHandler,
             3,( ShellExState *pSES, char **pzErr, sqlite3_stmt * *ppStmt ));
PURE_VMETHOD(int, rowDataInput, ImportHandler,
             3,( ShellExState *pSES, char **pzErr, sqlite3_stmt *pStmt ));
PURE_VMETHOD(int, finishDataInput, ImportHandler,
             3,( ShellExState *pSES, char **pzErr, sqlite3_stmt *pStmt ));
PURE_VMETHOD(void, closeDataInStream, ImportHandler,
             2,( ShellExState *pSES, char **pzErr ));
INTERFACE_END( ImportHandlerVtable );

typedef struct {
  int helperCount;
  union ExtHelp {
    struct {
      void (*failIfSafeMode)(ShellStateX *p, const char *zErrMsg, ...);
    } named ;
    void (*nameless[2])(); /* Same as named but anonymous plus a sentinel. */
  } helpers;
} ExtensionHelpers;

#define SHELLEXT_VALIDITY_MARK "ExtensibleShell"

typedef struct ShellExtensionLink {
  char validityMark[16]; /* Preset to contain "ExtensibleShell\x00" */
  char *zErrMsg;         /* Extension puts error message here if any. */
  int sizeOfThis;           /* sizeof(struct ShellExtensionLink) */
  const char *shellVersion; /* Preset to "3.??.??\x00" or similar */

  /* An init "out" parameter, used as the loaded extension ID. Unless
   * this is set within sqlite3_X_init() prior to register*() calls,
   * the extension cannot be unloaded. 
   */
  ExtensionId eid;

  /* Another init "out" parameter, a destructor for extension overall.
   * Set to 0 on input and may be left so if no destructor is needed.
   */
  void (*extensionDestruct)(void *);

  /* Various shell extension helpers and feature registration functions
   */
  ExtensionHelpers * pExtHelp;

  union ShellExtensionAPI {
    struct ShExtAPI {
      /* Register a meta-command */
      int (*registerMetaCommand)(ExtensionId eid, MetaCommand *pMC);
      /* Register an output data display (or other disposition) mode */
      int (*registerOutMode)(ExtensionId eid, OutModeHandler *pOMH);
      /* Register an import variation from (various sources) for .import */
      int (*registerImporter)(ExtensionId eid, ImportHandler *pIH);
      /* Preset to 0 at extension load, a sentinel for expansion */
      void (*pExtra)(void); 
    } named;
    void (*pFunctions[4])(); /* 0-terminated sequence of function pointers */
  } api;
} ShellExtensionLink;

/* Test whether a char ** references a ShellExtensionLink instance's
 * validityMark, and if so return the instance's address, else return 0.
 * This macro may be used by a shell extension's sqlite3_X_init() function
 * to obtain a pointer to the ShellExtensionLink struct, derived from the
 * error message pointer (pzErrMsg) passed as the 2nd argument. This enables
 * the extension to incorporate its features into a running shell process.
 */
#define EXTENSION_LINKAGE_PTR(pzem) ( \
  pzem != 0 && *pzem != 0 && strcmp(*pzem, SHELLEXT_VALIDITY_MARK) == 0 \
  && *pzem == (char *)pzem \
  + offsetof(ShellExtensionLink, validityMark) \
  - offsetof(ShellExtensionLink, zErrMsg) ) \
  ? (ShellExtensionLink *) \
    ((char *)pzem-offsetof(ShellExtensionLink,zErrMsg)) \
  : 0

/* String used with SQLite "Pointer Passing Interfaces" as a type marker. 
 * That API subset is used by the shell to pass its extension API to the
 * sqlite3_X_init() function of extensions, via the DB parameter.
 */
#define SHELLEXT_API_POINTERS "shellext_api_pointers"

/* Pre-write a function to retrieve a ShellExtensionLink pointer from the
 * shell's DB. This is an alternative to use of the EXTENSION_LINKAGE_PTR
 * macro above. It takes some more code, replicated across extensions.
 */
#define DEFINE_SHDB_TO_SHEXT_API(func_name) \
 static ShellExtensionLink * func_name(sqlite3 * db){ \
  ShellExtensionLink *rv = 0; sqlite3_stmt *pStmt = 0; \
  if( SQLITE_OK==sqlite3_prepare(db,"SELECT shext_pointer(0)",-1,&pStmt,0) \
      && SQLITE_ROW == sqlite3_step(pStmt) ) \
    rv = (ShellExtensionLink *)sqlite3_value_pointer \
     (sqlite3_column_value(pStmt, 0), SHELLEXT_API_POINTERS); \
  sqlite3_finalize(pStmt); return rv; \
 }

#ifdef __cplusplus
} // extern "C"
#endif

#endif /* !defined(SQLITE3SHX_H) */
