/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
   Copyright (c) 2013 D. Richard Hipp
  
   This program is free software; you can redistribute it and/or
   modify it under the terms of the Simplified BSD License (also
   known as the "2-Clause License" or "FreeBSD License".)
  
   This program is distributed in the hope that it will be useful,
   but without any warranty; without even the implied warranty of
   merchantability or fitness for a particular purpose.
  
   Author contact information:
     drh@hwaci.com
     http://www.hwaci.com/drh/
  
  *****************************************************************************
   This file implements a basic SQL query tool using a libfossil-extended
   command set (new SQL-side functions for working with fossil data).
*/

#include "fossil-scm/fossil-cli.h" /* Fossil App mini-framework */
/* #include "fossil-scm/fossil-internal.h" */

static void fcli_local_help(){
  printf("Usage:\n\t%s [options] "
         "-e|-E SQL_CODE|FILE [-e SQL_CODE|FILE ...]\n\n", fcli.appName);

  puts("Executes SQL against fossil repo/checkout databases. "
       "When executing \"fetching\" queries (SELECT and friends) "
       "the results are dumped to the fcli output "
       "channel. Options:\n");

  puts("\t-e SQL_CODE|FILENAME runs the given SQL (or file contents) as "
       "a SELECT-style query, preparing it and stepping through it as long as "
       "it returns results. ONLY THE FIRST SQL statement in each -e string/file "
       "is processed!\n");

  puts("\t-E SQL_CODE|FILENAME runs the given SQL via the \"multi-exec\" "
       "API, meaning that it can be used to run an arbitrary amount "
       "of SQL, but that any SELECT results from the SQL are ignored "
       "(not output). "
       "Use this to feed it, e.g., CREATE TABLE and similar code.\n");

  puts("\t--dry-run|-n forces a rollback of the implicit transaction "
       "at completion.\n");

  puts("\t--no-header|-h disables the display of the "
       "column headers for fetching queries.\n");

  puts("\t--no-transaction|-t disables automatic creation of a transaction. "
       "Needed when ATTACH is used in script files.\n");

  puts("\t--separator|-s=STRING sets the separator string for "
       "result columns. Default=TAB. Use \\t and \\n for "
       "TAB and NEWLINE, respectively.\n");

}

static struct {
  char showHeaders;
  char * separator;
} QueryApp = {
1/*showHeaders*/,
NULL/*separator*/
};

static int fsl_stmt_each_f_row( fsl_stmt * stmt, void * state ){
  int i;
  char const * sep = QueryApp.separator
    ? QueryApp.separator : "\t";
  if('\\'==*sep){
    /* Translate client-provided \t and \n */
    switch(*(sep+1)){
      case 't': sep = "\t"; break;
      case 'n': sep = "\n"; break;
    }
  }
  if((1==stmt->rowCount) && QueryApp.showHeaders){
    for( i = 0; i < stmt->colCount; ++i){
      f_out("%s%s", fsl_stmt_col_name(stmt, i),
            (i<(stmt->colCount-1)) ? sep : "\n");
    }
  }
  for( i = 0; i < stmt->colCount; ++i){
    char const * col = fsl_stmt_g_text(stmt, i, NULL);
    f_out("%s%s", col ? col : "NULL",
          (i<(stmt->colCount-1)) ? sep : "\n");
  }
  return 0;
}

int main(int argc, char * const * argv ){
  int rc = 0;
  char * fname = NULL;
  char * sql = NULL;
  fsl_cx * f;
  fsl_db * db;
  char qCount = 0;
  fsl_buffer scriptBuf = fsl_buffer_empty;
  char noTransaction = 0;
  int dashE = 0;
  fcli.appHelp = fcli_local_help;
  rc = fcli_setup(argc, argv);
  if(FSL_RC_BREAK==rc) /* --help */ return 0;
  else if(rc) goto end;
  /* Set up/validate args... */
  f = fcli.f;
  db = fsl_cx_db(f);
  if(!db){
    rc = fcli_err_set(FSL_RC_NOT_A_REPO,
                      "Requires an opened database. See --help.");
    goto end;
  }
  noTransaction = fcli_flag2("t", "no-transaction", NULL);
  QueryApp.showHeaders = !fcli_flag2("h", "no-header", NULL);
  fcli_flag2("s", "separator", &QueryApp.separator);
  if(!fcli.fDryRun){
    fcli.fDryRun = fcli_flag("n", NULL);
  }

  rc = noTransaction ? 0 : fsl_db_transaction_begin(db);
  for( ; !rc && (fcli_flag("e", &sql)
                 || (dashE = fcli_flag("E",&sql)));
       dashE = 0){
    /* FIXME: add a flag to force statement mode for a file input, or else
       we cannot run SELECT queries via files.

       e.g. use -E SCRIPT to force multi-exec mode
    */
    fsl_stmt st = fsl_stmt_empty;
    fsl_buffer_reset(&scriptBuf);
    if(0==fsl_file_access(sql,0)){
      rc = fsl_buffer_fill_from_filename(&scriptBuf, sql);
      if(rc){
        fcli_err_set(rc, "Error %d (%s) loading SQL from file [%s]",
                     rc, fsl_rc_cstr(rc), sql);
        fsl_free(sql);
        sql = NULL;
        goto end;
      }
      fsl_free(sql);
      sql = (char *)scriptBuf.mem;
      scriptBuf = fsl_buffer_empty /* xfer ownership to sql */;
    }
    ++qCount;
    if(dashE){
      rc = fsl_db_exec_multi(db, "%z", sql);
    }else{
      rc = fsl_db_prepare(db, &st, "%z", sql);
      if(!rc){
        if(st.colCount){ /* SELECT-style query */
          rc = fsl_stmt_each( &st, fsl_stmt_each_f_row, NULL );
        }else{
          rc = fsl_stmt_step(&st);
          if(FSL_RC_STEP_ROW==rc || FSL_RC_STEP_DONE==rc) rc = 0;
        }
      }
      fsl_stmt_finalize(&st);
    }
    if(rc) fsl_cx_uplift_db_error(f, db);
    sql = 0 /* "%z" fsl_free()'d it already */;
  }
  if(db->beginCount>0){
    assert(!noTransaction);
    if(rc || fcli.fDryRun){
      FCLI_V(("Rolling back transaction.\n"));
      fsl_db_transaction_rollback(db);
    }else{
      FCLI_V(("Committing transaction.\n"));
      rc = fsl_db_transaction_commit(db);
      if(rc) fsl_cx_uplift_db_error(f, db);
    }
  }
  if(!qCount){
    fcli_help();
    rc = FSL_RC_MISUSE;
    goto end;
  }
  
  end:
  fsl_buffer_clear(&scriptBuf);
  fsl_free(QueryApp.separator);
  fsl_free(fname);
  return fcli_end_of_main(rc);
}
