/*
  Teem: Tools to process and visualize scientific data and images             .
  Copyright (C) 2013, 2012, 2011, 2010, 2009  University of Chicago
  Copyright (C) 2008, 2007, 2006, 2005  Gordon Kindlmann
  Copyright (C) 2004, 2003, 2002, 2001, 2000, 1999, 1998  University of Utah

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public License
  (LGPL) as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  The terms of redistributing and/or modifying this software also
  include exceptions to the LGPL that facilitate static linking.

  This library 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.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License
  along with this library; if not, write to Free Software Foundation, Inc.,
  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include "hest.h"
#include "privateHest.h"
#include <limits.h>

const int
hestPresent = 42;

hestParm *
hestParmNew() {
  hestParm *parm;

  parm = AIR_CALLOC(1, hestParm);
  if (parm) {
    parm->verbosity = hestVerbosity;
    parm->respFileEnable = hestRespFileEnable;
    parm->elideSingleEnumType = hestElideSingleEnumType;
    parm->elideSingleOtherType = hestElideSingleOtherType;
    parm->elideSingleOtherDefault = hestElideSingleOtherDefault;
    parm->greedySingleString = hestGreedySingleString;
    parm->elideSingleNonExistFloatDefault =
      hestElideSingleNonExistFloatDefault;
    parm->elideMultipleNonExistFloatDefault =
      hestElideMultipleNonExistFloatDefault;
    parm->elideSingleEmptyStringDefault =
      hestElideSingleEmptyStringDefault;
    parm->elideMultipleEmptyStringDefault =
      hestElideMultipleEmptyStringDefault;
    parm->cleverPluralizeOtherY = hestCleverPluralizeOtherY;
    parm->columns = hestColumns;
    parm->respFileFlag = hestRespFileFlag;
    parm->respFileComment = hestRespFileComment;
    parm->varParamStopFlag = hestVarParamStopFlag;
    parm->multiFlagSep = hestMultiFlagSep;
  }
  return parm;
}

hestParm *
hestParmFree(hestParm *parm) {

  airFree(parm);
  return NULL;
}

void
_hestOptInit(hestOpt *opt) {

  opt->flag = opt->name = NULL;
  opt->type = opt->min = opt->max = 0;
  opt->valueP = NULL;
  opt->dflt = opt->info = NULL;
  opt->sawP = NULL;
  opt->enm = NULL;
  opt->CB = NULL;
  opt->sawP = NULL;
  opt->kind = opt->alloc = 0;
  opt->source = hestSourceUnknown;
}

/*
hestOpt *
hestOptNew(void) {
  hestOpt *opt;

  opt = AIR_CALLOC(1, hestOpt);
  if (opt) {
    _hestOptInit(opt);
    opt->min = 1;
  }
  return opt;
}
*/

/*
** as of Sept 2013 this returns information: the index of the
** option just added.  Returns UINT_MAX in case of error.
*/
unsigned int
hestOptAdd(hestOpt **optP,
           const char *flag, const char *name,
           int type, int min, int max,
           void *valueP, const char *dflt, const char *info, ...) {
  hestOpt *ret = NULL;
  int num;
  va_list ap;
  unsigned int retIdx;

  if (!optP)
    return UINT_MAX;

  num = *optP ? _hestNumOpts(*optP) : 0;
  if (!( ret = AIR_CALLOC(num+2, hestOpt) )) {
    return UINT_MAX;
  }
  if (num)
    memcpy(ret, *optP, num*sizeof(hestOpt));
  retIdx = AIR_UINT(num);
  ret[num].flag = airStrdup(flag);
  ret[num].name = airStrdup(name);
  ret[num].type = type;
  ret[num].min = min;
  ret[num].max = max;
  ret[num].valueP = valueP;
  ret[num].dflt = airStrdup(dflt);
  ret[num].info = airStrdup(info);
  /* initialize the things that may be set below */
  ret[num].sawP = NULL;
  ret[num].enm = NULL;
  ret[num].CB = NULL;
  /* seems to be redundant with above _hestOptInit() */
  ret[num].source = hestSourceUnknown;
  /* deal with var args */
  if (5 == _hestKind(&(ret[num]))) {
    va_start(ap, info);
    ret[num].sawP = va_arg(ap, unsigned int*);
    va_end(ap);
  }
  if (airTypeEnum == type) {
    va_start(ap, info);
    va_arg(ap, unsigned int*);  /* skip sawP */
    ret[num].enm = va_arg(ap, airEnum*);
    va_end(ap);
  }
  if (airTypeOther == type) {
    va_start(ap, info);
    va_arg(ap, unsigned int*);  /* skip sawP */
    va_arg(ap, airEnum*);       /* skip enm */
    ret[num].CB = va_arg(ap, hestCB*);
    va_end(ap);
  }
  _hestOptInit(&(ret[num+1]));
  ret[num+1].min = 1;
  if (*optP)
    free(*optP);
  *optP = ret;
  return retIdx;
}

void
_hestOptFree(hestOpt *opt) {

  opt->flag = (char *)airFree(opt->flag);
  opt->name = (char *)airFree(opt->name);
  opt->dflt = (char *)airFree(opt->dflt);
  opt->info = (char *)airFree(opt->info);
  return;
}

hestOpt *
hestOptFree(hestOpt *opt) {
  int op, num;

  if (!opt)
    return NULL;

  num = _hestNumOpts(opt);
  if (opt[num].min) {
    /* we only try to free this if it looks like something we allocated */
    for (op=0; op<num; op++) {
      _hestOptFree(opt+op);
    }
    free(opt);
  }
  return NULL;
}

int
hestOptCheck(hestOpt *opt, char **errP) {
  char *err, me[]="hestOptCheck";
  hestParm *parm;
  int big;

  big = _hestErrStrlen(opt, 0, NULL);
  if (!( err = AIR_CALLOC(big, char) )) {
    fprintf(stderr, "%s PANIC: couldn't allocate error message "
            "buffer (size %d)\n", me, big);
    if (errP)
      *errP = NULL;
    return 1;
  }
  parm = hestParmNew();
  if (_hestPanic(opt, err, parm)) {
    /* problems */
    if (errP) {
      /* they did give a pointer address; they'll free it */
      *errP = err;
    }
    else {
      /* they didn't give a pointer address; their loss */
      free(err);
    }
    hestParmFree(parm);
    return 1;
  }
  /* else, no problems */
  if (errP)
    *errP = NULL;
  free(err);
  hestParmFree(parm);
  return 0;
}


/*
** _hestIdent()
**
** how to identify an option in error and usage messages
*/
char *
_hestIdent(char *ident, hestOpt *opt, const hestParm *parm, int brief) {
  char copy[AIR_STRLEN_HUGE], *sep;

  if (opt->flag && (sep = strchr(opt->flag, parm->multiFlagSep))) {
    strcpy(copy, opt->flag);
    sep = strchr(copy, parm->multiFlagSep);
    *sep = '\0';
    if (brief)
      sprintf(ident, "-%s%c--%s option", copy, parm->multiFlagSep, sep+1);
    else
      sprintf(ident, "-%s option", copy);
  }
  else {
    sprintf(ident, "%s%s%s option",
            opt->flag ? "\"-"      : "<",
            opt->flag ? opt->flag : opt->name,
            opt->flag ? "\""       : ">");
  }
  return ident;
}

int
_hestMax(int max) {

  if (-1 == max) {
    max = INT_MAX;
  }
  return max;
}

int
_hestKind(const hestOpt *opt) {
  int max;

  max = _hestMax(opt->max);
  if (!( (int)opt->min <= max )) {    /* HEY scrutinize casts */
    /* invalid */
    return -1;
  }

  if (0 == opt->min && 0 == max) {
    /* flag */
    return 1;
  }

  if (1 == opt->min && 1 == max) {
    /* single fixed parameter */
    return 2;
  }

  if (2 <= opt->min && 2 <= max && (int)opt->min == max) {  /* HEY scrutinize casts */
    /* multiple fixed parameters */
    return 3;
  }

  if (0 == opt->min && 1 == max) {
    /* single optional parameter */
    return 4;
  }

  /* else multiple variable parameters */
  return 5;
}

void
_hestPrintArgv(int argc, char **argv) {
  int a;

  printf("argc=%d : ", argc);
  for (a=0; a<argc; a++) {
    printf("%s ", argv[a]);
  }
  printf("\n");
}

/*
** _hestWhichFlag()
**
** given a string in "flag" (with the hypen prefix) finds which of
** the flags in the given array of options matches that.  Returns
** the index of the matching option, or -1 if there is no match,
** but returns -2 if the flag is the end-of-variable-parameter
** marker (according to parm->varParamStopFlag)
*/
int
_hestWhichFlag(hestOpt *opt, char *flag, const hestParm *parm) {
  char buff[AIR_STRLEN_HUGE], copy[AIR_STRLEN_HUGE], *sep;
  int op, numOpts;

  numOpts = _hestNumOpts(opt);
  if (parm->verbosity)
    printf("_hestWhichFlag: flag = %s, numOpts = %d\n", flag, numOpts);
  for (op=0; op<numOpts; op++) {
    if (parm->verbosity)
      printf("_hestWhichFlag: op = %d\n", op);
    if (!opt[op].flag)
      continue;
    if (strchr(opt[op].flag, parm->multiFlagSep) ) {
      strcpy(copy, opt[op].flag);
      sep = strchr(copy, parm->multiFlagSep);
      *sep = '\0';
      /* first try the short version */
      sprintf(buff, "-%s", copy);
      if (!strcmp(flag, buff))
        return op;
      /* then try the long version */
      sprintf(buff, "--%s", sep+1);
      if (!strcmp(flag, buff))
        return op;
    }
    else {
      /* flag has only the short version */
      sprintf(buff, "-%s", opt[op].flag);
      if (!strcmp(flag, buff))
        return op;
    }
  }
  if (parm->verbosity)
    printf("_hestWhichFlag: numOpts = %d\n", numOpts);
  if (parm->varParamStopFlag) {
    sprintf(buff, "-%c", parm->varParamStopFlag);
    if (parm->verbosity)
      printf("_hestWhichFlag: flag = %s, buff = %s\n", flag, buff);
    if (!strcmp(flag, buff))
      return -2;
  }
  if (parm->verbosity)
    printf("_hestWhichFlag: numOpts = %d\n", numOpts);
  return -1;
}


/*
** _hestCase()
**
** helps figure out logic of interpreting parameters and defaults
** for kind 4 and kind 5 options.
*/
int
_hestCase(hestOpt *opt, int *udflt, unsigned int *nprm, int *appr, int op) {

  if (opt[op].flag && !appr[op]) {
    return 0;
  }
  else if ( (4 == opt[op].kind && udflt[op]) ||
            (5 == opt[op].kind && !nprm[op]) ) {
    return 1;
  }
  else {
    return 2;
  }
}

/*
** _hestExtract()
**
** takes "pnum" parameters, starting at "base", out of the
** given argv, and puts them into a string WHICH THIS FUNCTION
** ALLOCATES, and also adjusts the argc value given as "*argcP".
*/
char *
_hestExtract(int *argcP, char **argv, unsigned int base, unsigned int pnum) {
  unsigned int len, pidx;
  char *ret;

  if (!pnum)
    return NULL;

  len = 0;
  for (pidx=0; pidx<pnum; pidx++) {
    if (base+pidx==AIR_UINT(*argcP)) {
      return NULL;
    }
    len += AIR_UINT(strlen(argv[base+pidx]));
    if (strstr(argv[base+pidx], " ")) {
      len += 2;
    }
  }
  len += pnum;
  ret = AIR_CALLOC(len, char);
  strcpy(ret, "");
  for (pidx=0; pidx<pnum; pidx++) {
    /* if a single element of argv has spaces in it, someone went
       to the trouble of putting it in quotes, and we perpetuate
       the favor by quoting it when we concatenate all the argv
       elements together, so that airParseStrS will recover it as a
       single string again */
    if (strstr(argv[base+pidx], " ")) {
      strcat(ret, "\"");
    }
    /* HEY: if there is a '\"' character in this string, quoted or
       not, its going to totally confuse later parsing */
    strcat(ret, argv[base+pidx]);
    if (strstr(argv[base+pidx], " ")) {
      strcat(ret, "\"");
    }
    if (pidx < pnum-1)
      strcat(ret, " ");
  }
  for (pidx=base+pnum; pidx<=AIR_UINT(*argcP); pidx++) {
    argv[pidx-pnum] = argv[pidx];
  }
  *argcP -= pnum;
  return ret;
}

int
_hestNumOpts(const hestOpt *opt) {
  int num = 0;

  while (opt[num].flag || opt[num].name || opt[num].type) {
    num++;
  }
  return num;
}

