/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/*
  Copyright 2013-2021 Stephan Beal (https://wanderinghorse.net).

  Derived heavily from previous work:

  Copyright (c) 2013 D. Richard Hipp (https://www.hwaci.com/drh/)
  
  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.
  
  *****************************************************************************
  A test/demo app for working with the libfossil config db API.
*/

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

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

  puts("Views and manipulates fossil configuration data.\n");
  puts("Options:\n");
  puts("\t-g|--global Uses the global config table.\n");
  puts("\t-r|--repo Uses the repository config table.\n");
  puts("\t-c|--checkout Uses the checkout-local config table.\n");
  puts("Commands:\n");
  puts("\tls [-glob STRING] [-v]"
       "\n\tLists the config entries from the specified config. "
       "The --glob=STRING option can be used to filter by property names. "
       "The -v|--values flag enables display of the config values.\n"
       );
  puts("\tset KEY [VALUE]\n\tSets a configuration value. If the value "
       "is not provided then it is assumed to represent a boolean flag "
       "and is set to \"1\".\n");
  puts("\tunset KEY\n\tRemoves the given config key from the db.\n");
}

static struct ConfigApp {
  int  doGlobal;
  int doLocal;
  int doRepo;
  int showValues;
  const char * lsGlob;
  fsl_confdb_t mode0;
} ConfigApp = {
0,0,0,
0/*showValues*/,
NULL/*lsGlob*/,
FSL_CONFDB_GLOBAL/*mode0*/
};

static int fapp_ls(fsl_cx * f, fsl_confdb_t mode,
                   char const * nameGlob, char showVals){
  int rc;
  fsl_db * db = fsl_config_for_role(f, mode);
  fsl_stmt st = fsl_stmt_empty;
  int nameWidth = -30;
  if(!db){
    if(FSL_CONFDB_GLOBAL==mode){ /* special case: auto-open config db */
      rc = fsl_config_open(f, NULL);
      if(!rc){
        db = fsl_cx_db_config(f);
        assert(db);
      }
    }
    if(!db){
      return fcli_err_set(FSL_RC_MISUSE,
                          "Config db role #%d is not opened.",
                          mode);
    }
  }
  rc = fsl_db_prepare(db, &st, "SELECT name, value FROM %s ORDER BY name",
                      fsl_config_table_for_role(mode));
  while(FSL_RC_STEP_ROW==fsl_stmt_step(&st)){
    char const * name = fsl_stmt_g_text(&st, 0, NULL);
    if(nameGlob && !fsl_str_glob(nameGlob, name)) continue;
    if(showVals){
      f_out("%*s %s\n",
            nameWidth, name,
            fsl_stmt_g_text(&st,1,NULL));
    }else{
      f_out("%s\n", name);
    }
  }
  fsl_stmt_finalize(&st);
  return rc;
}

static int fcmd_ls(){
  fsl_cx * f = fcli_cx();
  int rc = 0;
  int didSomething = 0;

  ConfigApp.showValues = fcli_flag2("v", "values", NULL);
  fcli_flag("glob", &ConfigApp.lsGlob);

  if(fcli_has_unused_flags(0)) return fcli_error()->code;


  /* f_out("Global config db: %s\n", fsl_cx_db_file_config(f, NULL)); */
#define DUMP(MODE) \
  ++didSomething; \
  rc = fapp_ls(f, MODE, ConfigApp.lsGlob, ConfigApp.showValues)

  if(ConfigApp.doGlobal){
    DUMP(FSL_CONFDB_GLOBAL);
  }
  if(!rc && ConfigApp.doRepo){
    DUMP(FSL_CONFDB_REPO);
  }
  if(!rc && ConfigApp.doLocal){
    DUMP(FSL_CONFDB_CKOUT);
  }
#undef DUMP
  assert(didSomething);
  return rc;
}


static int fcmd_set(){
  const char * key = NULL;
  const char * val = NULL;
  fsl_cx * f = fcli_cx();
  int rc;
  key = fcli_next_arg(1);
  if(!key){
    return fcli_err_set(FSL_RC_MISUSE,"Missing property key argument.");
  }
  val = fcli_next_arg(1);
  if(fcli_has_unused_flags(0)){
    rc = fcli_error()->code;
  }else{
    FCLI_V(("Setting %s = %s\n", key, (val && *val) ? val : "1"));
    rc = fsl_config_set_text(f, ConfigApp.mode0, key,
                             (val && *val) ? val : "1");
  }
  return rc;
}

static int fcmd_unset(){
  const char * key = NULL;
  fsl_cx * f = fcli_cx();
  int rc = 0;
  int count = 0;
  if(fcli_has_unused_flags(0)) return fcli_error()->code;
  rc = fsl_config_transaction_begin(f, ConfigApp.mode0);
  if(!rc){
    while( !rc && (key = fcli_next_arg(1)) ){
      ++count;
      FCLI_V(("Unsetting: %s\n", key));
      rc = fsl_config_unset(f, ConfigApp.mode0, key);
    }
    fsl_config_transaction_end(f, ConfigApp.mode0, rc ? 1 : 0);
  }
  if(!count){
    rc = fcli_err_set(FSL_RC_MISUSE,"Missing property key argument.");
  }
  return rc;
}


static const FossilCommand ConfigCmds[] = {
{"ls", fcmd_ls},
{"set", fcmd_set},
{"unset", fcmd_unset},
{NULL,NULL}
};

static int fapp_main(fsl_cx * f){
  ConfigApp.doGlobal = fcli_flag2("g","global",NULL);
  ConfigApp.doRepo = fcli_flag2("r","repo",NULL);
  ConfigApp.doLocal = fcli_flag2("c","checkout",NULL);
  /*
    Set the main config mode for commands which use only
    one config. Priority order: -c -r -g
  */
  if(ConfigApp.doGlobal){
    int rc;
    ConfigApp.mode0 = FSL_CONFDB_GLOBAL;
    rc = fsl_config_open( f, 0 );
    if(rc) return rc;
  }
  else if(ConfigApp.doLocal) ConfigApp.mode0 = FSL_CONFDB_CKOUT;
  else if(ConfigApp.doRepo) ConfigApp.mode0 = FSL_CONFDB_REPO;
  else{
    return fcli_err_set(FSL_RC_MISUSE,
                        "No config db specified. Use one of: "
                        "-g[lobal], -r[epo], -c[heckout]");
  }
  return fcli_dispatch_commands(ConfigCmds, 0);
}

int main(int argc, char * const * argv ){
  int rc = 0;
  fcli.appHelp = fcli_local_help;
  rc = fcli_setup(argc, argv);
  if(FSL_RC_BREAK==rc) /* --help */ return 0;
  else if(rc) goto end;
  rc = fapp_main(fcli_cx());
  end:
  return fcli_end_of_main(rc);
}
