/* -*- 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/
**
*******************************************************************************
*/

#include "FossilApp.h"
#include "fossil-scm/fossil-internal.h"

const FossilAppT FossilApp_empty = FossilApp_empty_m;
FossilAppT FossilApp = FossilApp_empty_m;

void FossilApp_printf(char const * fmt, ...){
  if(FossilApp.f){
    va_list args;
    va_start(args,fmt);
    fsl_outputfv(FossilApp.f, fmt, args);
    va_end(args);
  }
}


/**
 ** Outputs a description of the global FossilApp CLI options to
 ** stdout.
 */
static void FossilApp_help_global_options();


static int FossilApp_open(){
  int rc = 0;
  fsl_cx * f = FossilApp.f;
  assert(f);
  if(FossilApp.repoDb){
    VERBOSE(("Trying to open repo db file [%s]...\n", FossilApp.repoDb));
    rc = fsl_repo_open_db( f, FossilApp.repoDb, 0 );
    if(rc) return rc;
  }
  else if(FossilApp.checkoutDir){
    fsl_buffer dir = fsl_buffer_empty;
    char const * dirName;
    rc = fsl_file_canonical_name(FossilApp.checkoutDir, &dir, 0);
    assert(!rc);
    dirName = (char const *)fsl_buffer_cstr(&dir);
    VERBOSE(("Trying to open checkout from [%s]...\n",
             dirName));
    rc = fsl_checkout_open(f, dirName, fsl_buffer_size(&dir));
    /* if(FSL_RC_NOT_FOUND==rc) rc = FSL_RC_NOT_A_CHECKOUT; */
    if(rc){
      if(!fsl_cx_err_get(f,NULL,NULL)){
        rc = fsl_cx_err_set(f, rc, "Opening of checkout under "
                            "[%s] failed with code %d (%s).",
                            dirName, rc, fsl_rc_cstr(rc));
      }
    }
    fsl_buffer_reserve(&dir, 0);
    if(rc) return rc;
  }
  if(!rc && FossilApp.verbose){
    fsl_db * dbC = fsl_cx_db_checkout(f);
    fsl_db * dbR = fsl_cx_db_repo(f);
    if(dbC){
      VERBOSE(("Checkout DB name: %s\n",
               fsl_buffer_cstr(&f->dbCkout.filename)));
    }
    if(dbR){
      VERBOSE(("Opened repo db: %s\n", fsl_buffer_cstr(&f->dbRepo.filename)));
    }
  }
  return rc;
}

void FossilApp_err_clear(){
  if(FossilApp.f){
    fsl_cx_err_clear(FossilApp.f);
  }
}

static void FossilApp_shutdown(){
  fsl_cx * f = FossilApp.f;
  int rc = 0;
  FossilApp.f = NULL;
  VERBOSE(("Finalizing fsl_cx @%p\n", (void const *)f));

  fsl_free(FossilApp.repoDb);
  fsl_free(FossilApp.userName);
  for( rc = 0; rc < FossilApp.argc; ++rc ){
    fsl_free(FossilApp.argv[rc]);
  }
  fsl_free(FossilApp.argv);
  FossilApp = FossilApp_empty;

  if(f){
    if(1 &&
       fsl_cx_db_checkout(f)){
      /* For testing/demo only: this is implicit
         when we call fsl_cx_finalize().
      */
      rc = fsl_checkout_close(f);
      assert(0==rc);
      VERBOSE(("Closed checkout/repo db(s).\n"));
    }
    fsl_cx_finalize( f );
  }
}




void FossilApp_help(){
  FossilApp_local_help();
  FossilApp_help_global_options();
}
  

static int FossilApp_process_args( int argc, char * const * argv ){
  int i;
  fsl_buffer avb = fsl_buffer_empty;
  int rc = 0;
  char * cp;
  FossilApp.appName = argv[0];
  FossilApp.argc = 0;
  rc = fsl_buffer_reserve(&avb, 10 * sizeof(char*));
  if(rc) return rc;
  FossilApp.argv = (char **)avb.mem;
  for( i = 1; i < argc; ++i ){
    char const * arg = argv[i];
    if('-'==*arg){
      char const * flag = arg+1;
      while('-'==*flag) ++flag;
#define FLAG(F) if(0==strcmp(F,flag))
      FLAG("help") {
        FossilApp.helpRequested = 1;
        continue;
      }
      FLAG("?") {
        FossilApp.helpRequested = 1;
        continue;
      }
      FLAG("V") {
        FossilApp.verbose = 1;
        continue;
      }
      FLAG("verbose") {
        FossilApp.verbose = 1;
        continue;
      }
      FLAG("C"){ /* Disable auto-open of checkout */
        FossilApp.checkoutDir = NULL;
        continue;
      }
      FLAG("S") {
        FossilApp.traceSql = 1;
        continue;
      }
#undef FLAG
      /* else fall through */
    }
    cp = fsl_strdup(arg);
    if(!cp) return FSL_RC_OOM;
    if(avb.capacity < (FossilApp.argc * sizeof(char*))){
      rc = fsl_buffer_reserve(&avb, avb.capacity * 2);
      if(rc){
        fsl_free(cp);
        return rc;
      }
      FossilApp.argv = (char **)avb.mem;
    }
    FossilApp.argv[FossilApp.argc++] = cp;
  }

  /** Now pull out our flags. We do this
      in a second step so that we can use
      the FossilApp_cli_flag() parsing.
  */
  if( FossilApp_cli_flag("no-checkout", NULL) ){
    FossilApp.checkoutDir = NULL;
  }
  if(FossilApp.traceSql<0){
    FossilApp.traceSql = FossilApp_cli_flag("trace-sql", NULL);
  }else{
    /* Just make sure it's not left around in the args list. */
    FossilApp_cli_flag("trace-sql", NULL);
  }
  FossilApp_cli_flag2("R","repo", &FossilApp.repoDb);
  if(!FossilApp_cli_flag2("user", "U", &FossilApp.userName)){
    FossilApp.userName = fsl_guess_user_name();
  }

  if(FossilApp.helpRequested){
    FossilApp_help();
    rc = FSL_RC_BREAK;
  }
  
  return rc;
}

char FossilApp_cli_flag(char const * opt, char ** value){
  int i = 0;
  int remove = 0;
  char rc = 0;
  fsl_size_t optLen = fsl_strlen(opt);
  for( ; i < FossilApp.argc; ++i ){
    char const * arg = FossilApp.argv[i];
    char const * x;
    char const * vp = NULL;
    if('-' != *arg) continue;
    rc = 0;
    x = arg+1;
    if('-' == *x) { ++x;}
    if(0 != fsl_strncmp(x, opt, optLen)) continue;
    if(!value){
      if(x[optLen]) continue /* not exact match */;
      /* Treat this as a boolean. */
      rc = 1;
      ++remove;
      break;
    }else{
      /* -FLAG VALUE or -FLAG=VALUE */
      if(x[optLen] == '='){
        rc = 1;
        vp = x+optLen+1;
        ++remove;
      }
      else if(x[optLen]) continue /* not an exact match */;
      else if(i<(FossilApp.argc-1)){
        rc = 1;
        vp = FossilApp.argv[i+1];
        ++remove;
      }
      else{
        rc = 0;
        --remove /* leave --opt in the args */;
      }
      if(rc) *value = fsl_strdup(vp);
      break;
    }
  }
  if(remove>0){
    int x;
    for( x = 0; x < remove; ++x ){
      fsl_free(FossilApp.argv[i+x]);
      FossilApp.argv[i+x] = NULL;
    }
    for( ; i < FossilApp.argc; ++i ){
      FossilApp.argv[i] = FossilApp.argv[i+remove];
    }
    FossilApp.argc -= remove;
    FossilApp.argv[i] = NULL;
  }
  return rc;
}

char FossilApp_cli_flag2(char const * shortOpt,
                         char const * longOpt,
                         char ** value){
  char const rc = FossilApp_cli_flag(shortOpt, value);
  return (rc || !longOpt) ? rc : FossilApp_cli_flag(longOpt, value);
}


/**
 ** Should be called early on in main(), passed the arguments passed
 ** to main(). Returns 0 on success.  Sets up the FossilApp instance
 ** and opens a checkout in the current dir by default. It returns
 ** FSL_RC_BREAK if argument processing finds either of the (--help, -?)
 ** flags, and sets FossilApp.helpRequested to a true value.
 */
int FossilApp_setup(int argc, char * const * argv ){
  fsl_cx * f = NULL;
  int rc = 0;
  fsl_init_param init = fsl_init_param_empty;
  atexit(FossilApp_shutdown);
  rc = FossilApp_process_args(argc, argv);
  if(rc) return rc;
  init.output = fsl_outputer_FILE;
  init.output.state.state = stdout;
  init.config.traceSql = FossilApp.traceSql;
  rc = fsl_cx_init( &f, &init );
  FossilApp.f = f;
  VERBOSE(("Initialized fsl_cx @%p\n", (void const *)f));
  if(!rc && (FossilApp.checkoutDir || FossilApp.repoDb)){
    rc = FossilApp_open();
    if(!FossilApp.repoDb
       && FossilApp.checkoutDir
       && (FSL_RC_NOT_FOUND == rc)) rc = 0;
  }
  return rc;
}


void FossilApp_help_global_options(){

  puts("\nGlobal FossilApp options and features:\n");
  puts("\t--help|-? triggers the app-specific help.\n");
  puts("\t--no-checkout|-C supresses the automatic attempt "
       "to open a checkout under the current directory. "
       "This is implied by -R, to avoid opening a "
       "mismatched checkout/repo combination.\n");
  puts("\t--repo|-R=DBFILE specifies a repository "
       "db to open.\n");
  puts("\t--trace-sql|-S enables SQL tracing.\n");
  printf("\t--user|-U=name [default=%s] sets the default Fossil "
         "user for operations which need one.\n\n", FossilApp.userName);
  puts("\t--verbose|-V enables verbose mode (extra output).\n");
  puts("\nAll flags may be preceeded by - or -- and "
       "flags taking values may be in the form -FLAG=VALUE "
       "or -FLAG VALUE. Flags not taking values are "
       "treated as booleans.\n");
}

int FossilApp_err_report2(char clear, char const * file, int line){
  int errRc = 0;
  if(FossilApp.f){
    char const * msg = NULL;
    errRc = fsl_cx_err_get( FossilApp.f, &msg, NULL );
    if(errRc || msg){
      fprintf(stderr, "%s %s:%d: Fossil error: code #%d (%s): %s\n",
              FossilApp.appName,
              file, line, errRc, fsl_rc_cstr(errRc), msg);
    }
    if(clear) FossilApp_err_clear();
  }
  return errRc;
}


char * FossilApp_next_arg(char take){
  char * rc = (FossilApp.argc>0) ? FossilApp.argv[0] : NULL;
  if(rc && take){
    int i;
    --FossilApp.argc;
    for(i = 0; i < FossilApp.argc; ++i){
      FossilApp.argv[i] = FossilApp.argv[i+1];
    }
    FossilApp.argv[FossilApp.argc] = NULL;
  }
  return rc;
}

char FossilApp_has_unused_flags(char outputError){
  int i;
  for( i = 0; i < FossilApp.argc; ++i ){
    char const * arg = FossilApp.argv[i];
    if('-'==*arg){
      fsl_cx_err_set(FossilApp.f, FSL_RC_MISUSE,
                     "Unhandled/unknown flag: %s",
                     arg);
      if(outputError){
        FossilApp_err_report(0);
      }
      return 1;
    }
  }
  return 0;
}
