/* -*- 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 (most of) the fsl_xxx APIs related to handling
** configuration data from the db(s).
*/
#include "fossil-scm/fossil-internal.h"
#include <assert.h>


char const * fsl_config_table_for_role(fsl_confdb_t mode){
  switch(mode){
    case FSL_CONFDB_REPO: return "config";
    case FSL_CONFDB_CKOUT: return "vvar";
    case FSL_CONFDB_GLOBAL: return "global_config";
    default:
      assert(!"Invalid fsl_confdb_t value");
      return NULL;
  }
}

fsl_db * fsl_config_for_role(fsl_cx * f, fsl_confdb_t mode){
  switch(mode){
    case FSL_CONFDB_REPO: return fsl_cx_db_repo(f);
    case FSL_CONFDB_CKOUT: return fsl_cx_db_checkout(f);
    case FSL_CONFDB_GLOBAL: return fsl_cx_db_config(f);
    default:
      assert(!"Invalid fsl_confdb_t value");
      return NULL;
  }
}

fsl_int32_t fsl_config_get_int32( fsl_cx * f, fsl_confdb_t mode,
                                  fsl_int32_t dflt, char const * key ){
  fsl_db * db = fsl_config_for_role(f, mode);
  char const * table = fsl_config_table_for_role(mode);
  fsl_int32_t rv = dflt;
  if(db){
    fsl_stmt * st = NULL;
    fsl_db_prepare_cached(db, &st,
                          "SELECT value FROM %s WHERE name=?", table);
    if(st){
      fsl_stmt_bind_text(st, 1, key, -1, 0);
      if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){
        rv = fsl_stmt_g_int32(st, 0);
      }
      fsl_stmt_cached_yield(st);
    }
  }
  return rv;
}


fsl_int32_t fsl_get_int32_local( fsl_cx * f, fsl_int32_t dflt,
                               char const * key ){
  return fsl_config_get_int32( f, FSL_CONFDB_CKOUT, dflt, key );
}

fsl_int32_t fsl_get_int32_repo( fsl_cx * f, fsl_int32_t dflt,
                               char const * key ){
  return fsl_config_get_int32( f, FSL_CONFDB_REPO, dflt, key );
}

fsl_int32_t fsl_get_int32_global( fsl_cx * f, fsl_int32_t dflt,
                                char const * key ){
  return fsl_config_get_int32( f, FSL_CONFDB_GLOBAL, dflt, key );
}

fsl_int64_t fsl_config_get_int64( fsl_cx * f, fsl_confdb_t mode,
                                  fsl_int64_t dflt, char const * key ){
  fsl_int64_t rv = dflt;
  fsl_db * db = fsl_config_for_role(f, mode);
  char const * table = fsl_config_table_for_role(mode);
  if(db){
    fsl_stmt * st = NULL;
    fsl_db_prepare_cached(db, &st,
                          "SELECT value FROM %s WHERE name=?", table);
    if(st){
      fsl_stmt_bind_text(st, 1, key, -1, 0);
      if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){
        rv = fsl_stmt_g_int64(st, 0);
      }
      fsl_stmt_cached_yield(st);
    }
  }
  return rv;
}


fsl_int64_t fsl_get_int64_local( fsl_cx * f, fsl_int64_t dflt,
                               char const * key ){
  return fsl_config_get_int64( f, FSL_CONFDB_CKOUT, dflt, key );
}

fsl_int64_t fsl_get_int64_repo( fsl_cx * f, fsl_int64_t dflt,
                              char const * key ){
  return fsl_config_get_int64( f, FSL_CONFDB_REPO, dflt, key );
}

fsl_int64_t fsl_get_int64_global( fsl_cx * f, fsl_int64_t dflt,
                                char const * key ){
  return fsl_config_get_int64( f, FSL_CONFDB_GLOBAL, dflt, key );
}

fsl_id_t fsl_config_get_id( fsl_cx * f, fsl_confdb_t mode,
                             fsl_id_t dflt, char const * key ){
  fsl_db * db = fsl_config_for_role(f, mode);
  char const * table = fsl_config_table_for_role(mode);
  fsl_id_t rv = dflt;
  if(db){
    fsl_stmt * st = NULL;
    fsl_db_prepare_cached(db, &st,
                          "SELECT value FROM %s WHERE name=?", table);
    if(st){
      fsl_stmt_bind_text(st, 1, key, -1, 0);
      if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){
        rv = fsl_stmt_g_id(st, 0);
      }
      fsl_stmt_cached_yield(st);
    }
  }
  return rv;
}


fsl_id_t fsl_get_id_local( fsl_cx * f, fsl_id_t dflt,
                         char const * key ){
  return fsl_config_get_id( f, FSL_CONFDB_CKOUT, dflt, key );
}

fsl_id_t fsl_get_id_repo( fsl_cx * f, fsl_id_t dflt,
                        char const * key ){
  return fsl_config_get_id( f, FSL_CONFDB_REPO, dflt, key );
}

fsl_id_t fsl_get_id_global( fsl_cx * f, fsl_id_t dflt,
                        char const * key ){
  return fsl_config_get_id( f, FSL_CONFDB_GLOBAL, dflt, key );
}


fsl_double_t fsl_config_get_double( fsl_cx * f, fsl_confdb_t mode,
                                     fsl_double_t dflt, char const * key ){
  fsl_db * db = fsl_config_for_role(f, mode);
  char const * table = fsl_config_table_for_role(mode);
  fsl_double_t rv = dflt;
  if(db){
    fsl_stmt * st = NULL;
    fsl_db_prepare_cached(db, &st,
                          "SELECT value FROM %s WHERE name=?", table);
    if(st){
      fsl_stmt_bind_text(st, 1, key, -1, 0);
      if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){
        rv = fsl_stmt_g_double(st, 0);
      }
      fsl_stmt_cached_yield(st);
    }
  }
  return rv;
}

fsl_double_t fsl_get_double_local( fsl_cx * f, fsl_double_t dflt,
                                 char const * key ){
  return fsl_config_get_double( f, FSL_CONFDB_CKOUT, dflt, key );
}

fsl_double_t fsl_get_double_repo( fsl_cx * f, fsl_double_t dflt,
                                char const * key ){
  return fsl_config_get_double( f, FSL_CONFDB_REPO, dflt, key );
}

fsl_double_t fsl_get_double_global( fsl_cx * f, fsl_double_t dflt,
                                char const * key ){
  return fsl_config_get_double( f, FSL_CONFDB_GLOBAL, dflt, key );
}


char * fsl_config_get_text( fsl_cx * f, fsl_confdb_t mode,
                             char const * key, fsl_size_t * len ){
  fsl_db * db = fsl_config_for_role(f, mode);
  char const * table = fsl_config_table_for_role(mode);
  char * rv = NULL;
  if(db){
    fsl_stmt * st = NULL;
    fsl_db_prepare_cached(db, &st,
                          "SELECT value FROM %s WHERE name=?", table);
    if(st){
      fsl_stmt_bind_text(st, 1, key, -1, 0);
      if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){
        char const * s = fsl_stmt_g_text(st, 0, len);
        rv = s ? fsl_strndup( s, len ? (fsl_int_t)*len : -1) : NULL;
      }
      fsl_stmt_cached_yield(st);
    }
  }
  return rv;
}



char * fsl_get_text_local( fsl_cx * f, char const * key,
                         fsl_size_t * len ){
  return fsl_config_get_text( f, FSL_CONFDB_CKOUT, key, len );
}

char * fsl_get_text_repo( fsl_cx * f, char const * key,
                        fsl_size_t * len ){
  return fsl_config_get_text( f, FSL_CONFDB_REPO, key, len );
}

char * fsl_get_text_global( fsl_cx * f, char const * key,
                          fsl_size_t * len ){
  return fsl_config_get_text( f, FSL_CONFDB_GLOBAL, key, len );
}


int fsl_config_get_buffer( fsl_cx * f, fsl_confdb_t mode,
                            char const * key, fsl_buffer * b ){
  fsl_db * db = fsl_config_for_role(f, mode);
  char const * table = fsl_config_table_for_role(mode);
  int rc = FSL_RC_NOT_FOUND;
  if(db){
    fsl_stmt * st = NULL;
    rc = fsl_db_prepare_cached(db, &st,
                               "SELECT value FROM %s WHERE name=?", table);
    if(!rc){
      fsl_stmt_bind_text(st, 1, key, -1, 0);
      if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){
        fsl_size_t len = 0;
        char const * s = fsl_stmt_g_text(st, 0, &len);
        rc = s ? fsl_buffer_append(b, s, len) : 0;
      }else{
        rc = FSL_RC_NOT_FOUND;
      }
      fsl_stmt_cached_yield(st);
    }
  }
  return rc;
}

char fsl_config_get_bool( fsl_cx * f,
                          fsl_confdb_t mode,
                          char dflt,
                          char const * key ){
  int rc;
  char rv = dflt;
  fsl_stmt * st = NULL;
  char const * table = fsl_config_table_for_role(mode);
  fsl_db * db;
  if(!f || !key || !*key) return dflt;
  db = fsl_config_for_role(f, mode);
  if(!db) return dflt;
  rc = fsl_db_prepare_cached(db, &st,
                             "SELECT value FROM %s WHERE name=?",
                             /* Normally we wouldn't use %s in a
                                cached statement, but we're only
                                expecting 3 values for table here
                                (config, vvar, and global_config) and
                                each one will only be cached once.
                             */
                             table);
  if(!rc){
    fsl_stmt_bind_text(st, 1, key, -1, 0);
    if(FSL_RC_STEP_ROW==fsl_stmt_step(st)){
      char const * col = fsl_stmt_g_text(st, 1, NULL);
      rv = col ? fsl_str_bool(col) : dflt /* 0? */;
    }
    fsl_stmt_cached_yield(st);
  }
  return rv;
}

int fsl_config_set_text( fsl_cx * f, fsl_confdb_t mode,
                         char const * key, char const * val ){
  char const * table = fsl_config_table_for_role(mode);
  fsl_db * db = fsl_config_for_role(f,mode);
  if(!db || !key) return FSL_RC_MISUSE;
  else if(!*key) return FSL_RC_RANGE;
  return fsl_db_exec(db,
                     "REPLACE INTO %s(name,value) VALUES(%Q,%Q)",
                     table, key, val);
}

int fsl_config_set_blob( fsl_cx * f, fsl_confdb_t mode,
                         char const * key, fsl_buffer const * val ){
  char const * table = fsl_config_table_for_role(mode);
  fsl_db * db = fsl_config_for_role(f,mode);
  if(!db || !key) return FSL_RC_MISUSE;
  else if(!*key) return FSL_RC_RANGE;
  else{
    fsl_stmt st = fsl_stmt_empty;
    int rc = fsl_db_prepare(db,&st,
                            "REPLACE INTO %s(name,value) VALUES(%Q,?)",
                            table, key);
    if(!rc){
      rc = fsl_stmt_bind_blob(&st, 1, val->mem,
                              (fsl_int_t)val->used, 0);
      if(!rc){
        rc = fsl_stmt_step(&st);
        rc = (FSL_RC_STEP_DONE == rc)
          ? 0 : rc;
      }
      fsl_stmt_finalize(&st);
    }
    return rc;
  }
}
  
int fsl_config_set_int32( fsl_cx * f, fsl_confdb_t mode,
                          char const * key, fsl_int32_t val ){
  char const * table = fsl_config_table_for_role(mode);
  fsl_db * db = fsl_config_for_role(f,mode);
  if(!db || !key) return FSL_RC_MISUSE;
  else if(!*key) return FSL_RC_RANGE;
  return fsl_db_exec(db,
                     "REPLACE INTO %s(name,value) "
                     "VALUES(%Q,%"FSL_INT32_T_PFMT")",
                     table, key, (fsl_int32_t)val);
}

int fsl_config_set_int64( fsl_cx * f, fsl_confdb_t mode,
                          char const * key, fsl_int64_t val ){
  char const * table = fsl_config_table_for_role(mode);
  fsl_db * db = fsl_config_for_role(f,mode);
  if(!db || !key) return FSL_RC_MISUSE;
  else if(!*key) return FSL_RC_RANGE;
  return fsl_db_exec(db,
                     "REPLACE INTO %s(name,value) "
                     "VALUES(%Q,%"FSL_INT64_T_PFMT")",
                     table, key, (fsl_int64_t)val);
}

int fsl_config_set_double( fsl_cx * f, fsl_confdb_t mode,
                           char const * key, fsl_double_t val ){
  char const * table = fsl_config_table_for_role(mode);
  fsl_db * db = fsl_config_for_role(f,mode);
  if(!db || !key) return FSL_RC_MISUSE;
  else if(!*key) return FSL_RC_RANGE;
  return fsl_db_exec(db,
                     "REPLACE INTO %s(name,value) "
                     "VALUES(%Q,%"FSL_DOUBLE_T_PFMT")",
                     table, key, (fsl_double_t)val);
}

int fsl_config_set_bool( fsl_cx * f, fsl_confdb_t mode,
                         char const * key, char val ){
  char const * table = fsl_config_table_for_role(mode);
  fsl_db * db = fsl_config_for_role(f,mode);
  if(!db || !key) return FSL_RC_MISUSE;
  else if(!*key) return FSL_RC_RANGE;
  return fsl_db_exec(db,
                     "REPLACE INTO %s(name,value) "
                     "VALUES(%Q,%d)",
                     table, key, val ? 1 : 0);
}

/*
  TODO: the infrastructure from v1's configure.c and db.c which deals
  with the config db and the list of known/allowed settings.
 */
static const struct {
  const char *zName;   /* Name of the configuration set */
  int groupMask;       /* Mask for that configuration set */
  const char *zHelp;   /* What it does */

} fslConfigGroups[] = {
  { "/email",        FSL_CONFIGSET_ADDR,  "Concealed email addresses in tickets" },
  { "/project",      FSL_CONFIGSET_PROJ,  "Project name and description"         },
  { "/skin",         FSL_CONFIGSET_SKIN | FSL_CONFIGSET_CSS,
                                      "Web interface appearance settings"    },
  { "/css",          FSL_CONFIGSET_CSS,   "Style sheet"                          },
  { "/shun",         FSL_CONFIGSET_SHUN,  "List of shunned artifacts"            },
  { "/ticket",       FSL_CONFIGSET_TKT,   "Ticket setup",                        },
  { "/user",         FSL_CONFIGSET_USER,  "Users and privilege settings"         },
  { "/xfer",         FSL_CONFIGSET_XFER,  "Transfer setup",                      },
  { "/all",          FSL_CONFIGSET_ALL,   "All of the above"                     },
  {NULL, 0, NULL}
};

/*
** The following is a list of settings that we are willing to
** transfer.
**
** Setting names that begin with an alphabetic characters refer to
** single entries in the CONFIG table.  Setting names that begin with
** "@" are for special processing.
*/
static struct {
  const char *zName;   /* Name of the configuration parameter */
  int groupMask;       /* Which config groups is it part of */
} fslConfigXfer[] = {
  { "css",                    FSL_CONFIGSET_CSS  },

  { "header",                 FSL_CONFIGSET_SKIN },
  { "footer",                 FSL_CONFIGSET_SKIN },
  { "logo-mimetype",          FSL_CONFIGSET_SKIN },
  { "logo-image",             FSL_CONFIGSET_SKIN },
  { "background-mimetype",    FSL_CONFIGSET_SKIN },
  { "background-image",       FSL_CONFIGSET_SKIN },
  { "index-page",             FSL_CONFIGSET_SKIN },
  { "timeline-block-markup",  FSL_CONFIGSET_SKIN },
  { "timeline-max-comment",   FSL_CONFIGSET_SKIN },
  { "timeline-plaintext",     FSL_CONFIGSET_SKIN },
  { "adunit",                 FSL_CONFIGSET_SKIN },
  { "adunit-omit-if-admin",   FSL_CONFIGSET_SKIN },
  { "adunit-omit-if-user",    FSL_CONFIGSET_SKIN },

  { "th1-setup",              FSL_CONFIGSET_ALL },

  { "tcl",                    FSL_CONFIGSET_SKIN|FSL_CONFIGSET_TKT|FSL_CONFIGSET_XFER },
  { "tcl-setup",              FSL_CONFIGSET_SKIN|FSL_CONFIGSET_TKT|FSL_CONFIGSET_XFER },

  { "project-name",           FSL_CONFIGSET_PROJ },
  { "project-description",    FSL_CONFIGSET_PROJ },
  { "manifest",               FSL_CONFIGSET_PROJ },
  { "binary-glob",            FSL_CONFIGSET_PROJ },
  { "clean-glob",             FSL_CONFIGSET_PROJ },
  { "ignore-glob",            FSL_CONFIGSET_PROJ },
  { "keep-glob",              FSL_CONFIGSET_PROJ },
  { "crnl-glob",              FSL_CONFIGSET_PROJ },
  { "encoding-glob",          FSL_CONFIGSET_PROJ },
  { "empty-dirs",             FSL_CONFIGSET_PROJ },
  { "allow-symlinks",         FSL_CONFIGSET_PROJ },

  { "ticket-table",           FSL_CONFIGSET_TKT  },
  { "ticket-common",          FSL_CONFIGSET_TKT  },
  { "ticket-change",          FSL_CONFIGSET_TKT  },
  { "ticket-newpage",         FSL_CONFIGSET_TKT  },
  { "ticket-viewpage",        FSL_CONFIGSET_TKT  },
  { "ticket-editpage",        FSL_CONFIGSET_TKT  },
  { "ticket-reportlist",      FSL_CONFIGSET_TKT  },
  { "ticket-report-template", FSL_CONFIGSET_TKT  },
  { "ticket-key-template",    FSL_CONFIGSET_TKT  },
  { "ticket-title-expr",      FSL_CONFIGSET_TKT  },
  { "ticket-closed-expr",     FSL_CONFIGSET_TKT  },
  { "@reportfmt",             FSL_CONFIGSET_TKT  },

  { "@user",                  FSL_CONFIGSET_USER },

  { "@concealed",             FSL_CONFIGSET_ADDR },

  { "@shun",                  FSL_CONFIGSET_SHUN },

  { "xfer-common-script",     FSL_CONFIGSET_XFER },
  { "xfer-push-script",       FSL_CONFIGSET_XFER },
};

/*
** Return a pointer to a string that contains the RHS of an IN
** operator that will select CONFIG table names that are part of the
** configuration that matches iMatch. The returned string must
** eventually be fsl_free()'d.
*/
char *fsl_config_inop_rhs(int iMask){
  fsl_buffer x = fsl_buffer_empty;
  const char *zSep = "";
  const int n = sizeof(fslConfigXfer)/sizeof(fslConfigXfer[0]);
  int i;
  int rc = fsl_buffer_append(&x, "(", 1);
  for(i=0; !rc && (i<n); i++){
    if( (fslConfigXfer[i].groupMask & iMask)==0 ) continue;
    if( fslConfigXfer[i].zName[0]=='@' ) continue;
    rc = fsl_buffer_appendf(&x, "%s'%s'", zSep, fslConfigXfer[i].zName);
    zSep = ",";
  }
  if(!rc){
    rc = fsl_buffer_append(&x, ")", 1);
  }
  if(rc){
    fsl_buffer_clear(&x);
    assert(!x.mem);
  }else{
    fsl_buffer_resize(&x, x.used);
  }
  return (char *)x.mem;
}

  
struct fsl_config_ctrl {
  /** Name of the setting */
  char const *name;
  /** Internal variable name used by db_set() */
  char const *var;
  /** Historical (HTML UI). Width of display.  0 for boolean
      values.
  */
  int width;
  /** Is this setting versionable? */
  int versionable;
  /** Default value */
  char const *def;
};
typedef struct fsl_config_ctrl fsl_config_ctrl;

static struct fsl_config_ctrl const fslConfigCtrl[] = {
  { "access-log",    0,                0, 0, "off"                 },
  { "allow-symlinks",0,                0, 1, "off"                 },
  { "auto-captcha",  "autocaptcha",    0, 0, "on"                  },
  { "auto-hyperlink",0,                0, 0, "on",                 },
  { "auto-shun",     0,                0, 0, "on"                  },
  { "autosync",      0,                0, 0, "on"                  },
  { "binary-glob",   0,               40, 1, ""                    },
  { "clearsign",     0,                0, 0, "off"                 },
#if defined(_WIN32) || defined(__CYGWIN__) || defined(__DARWIN__) || defined(__APPLE__)
  { "case-sensitive",0,                0, 0, "off"                 },
#else
  { "case-sensitive",0,                0, 0, "on"                  },
#endif
  { "clean-glob",    0,               40, 1, ""                    },
  { "crnl-glob",     0,               40, 1, ""                    },
  { "default-perms", 0,               16, 0, "u"                   },
  { "diff-binary",   0,                0, 0, "on"                  },
  { "diff-command",  0,               40, 0, ""                    },
  { "dont-push",     0,                0, 0, "off"                 },
  { "editor",        0,               32, 0, ""                    },
  { "empty-dirs",    0,               40, 1, ""                    },
  { "encoding-glob",  0,              40, 1, ""                    },
  { "gdiff-command", 0,               40, 0, "gdiff"               },
  { "gmerge-command",0,               40, 0, ""                    },
  { "http-port",     0,               16, 0, "8080"                },
  { "https-login",   0,                0, 0, "off"                 },
  { "ignore-glob",   0,               40, 1, ""                    },
  { "keep-glob",     0,               40, 1, ""                    },
  { "localauth",     0,                0, 0, "off"                 },
  { "main-branch",   0,               40, 0, "trunk"               },
  { "manifest",      0,                0, 1, "off"                 },
  { "max-upload",    0,               25, 0, "250000"              },
  { "mtime-changes", 0,                0, 0, "on"                  },
  { "pgp-command",   0,               40, 0, "gpg --clearsign -o " },
  { "proxy",         0,               32, 0, "off"                 },
  { "relative-paths",0,                0, 0, "on"                  },
  { "repo-cksum",    0,                0, 0, "on"                  },
  { "self-register", 0,                0, 0, "off"                 },
  { "ssh-command",   0,               40, 0, ""                    },
  { "ssl-ca-location",0,              40, 0, ""                    },
  { "ssl-identity",  0,               40, 0, ""                    },
  { "tcl",           0,                0, 0, "off"                 },
  { "tcl-setup",     0,               40, 0, ""                    },
  { "th1-setup",     0,               40, 0, ""                    },
  { "web-browser",   0,               32, 0, ""                    },
  { "white-foreground", 0,             0, 0, "off"                 },
  { 0,0,0,0,0 }
};

/*
** Return a pointer to a string that contains the RHS of an IN operator
** that will select CONFIG table names that are in the list of control
** settings.
*/
char *fsl_db_setting_inop_rhs(){
  fsl_buffer x = fsl_buffer_empty;
  const char *zSep = "";
  fsl_config_ctrl const * ct = &fslConfigCtrl[0];
  int rc = fsl_buffer_append(&x, "(", 1);
  for( ; !rc && ct && ct->name; ++ct){
    rc = fsl_buffer_appendf(&x, "%s'%s'", zSep, ct->name);
    zSep = ",";
  }
  if(!rc){
    rc = fsl_buffer_append(&x, ")", 1);
  }
  if(rc){
    fsl_buffer_clear(&x);
    assert(!x.mem);
  }else{
    fsl_buffer_resize(&x, x.used);
  }
  return (char *)x.mem;
}
