/****************************************************************************/
/* GETPARAM.C: command-line processing functions.                           */
/* Copyright (c) 2001 by Joshua E. Barnes, Honolulu, Hawai`i.               */
/****************************************************************************/

#include "stdinc.h"
#include "getparam.h"
#include <string.h>

/*
 * PARAM: structure encoding parameter name and value.
 */

typedef struct {
    string name;                                /* name of parameter        */
    string value;                               /* value of parameter       */
    string comment;                             /* documentation string     */
    int flags;                                  /* for various options      */
} param;

/*
 * Local routines and definitions.
 */

local int countdefaults(string *);              /* number of defaults       */
local void setprogram(param *, string);         /* set 0th parameter        */
local void copydefaults(param *, string *);     /* set default parameters   */
local void checkhelp(param *, string);          /* help processing          */
local void printitem(string, string);           /* print param and comment  */
local void setarguments(param *, string *);     /* set command parameters   */
local void reqarguments(param *);               /* check manditory args     */
local param *findparam(string, param *);        /* look up parameter value  */
local string parname(string);                   /* extract param name       */
local string parvalue(string);                  /* extract param value      */

local param *paramvec = NULL;                   /* vector of parameters     */

local string progname = NULL;                   /* program name, for errors */

/*
 * INITPARAM: initalize parameter lists and handle special requests.
 */

void initparam(string *argv, string *defv)
{
    int nparam;
    param *pvec;

    progname = argv[0];                         /* initialize program name  */
    nparam = 1 + countdefaults(defv);           /* include argv0 in count   */
    pvec = (param *) allocate(sizeof(param) * (nparam + 1));
    setprogram(pvec, argv[0]);                  /* install 0th argument     */
    copydefaults(pvec, defv);                   /* set up default values    */
    checkhelp(pvec, argv[1]);                   /* give help if requested   */
    setarguments(pvec, argv);                   /* args override defaults   */
    reqarguments(pvec);                         /* complain if args missing */
    paramvec = pvec;                            /* install parameter vector */
}

/*
 * COUNTDEFAULTS: count number of default parameters.
 */

local int countdefaults(string *defv)
{
    int ndefault;
    string *dp;

    ndefault = 0;
    for (dp = defv; *dp != NULL; dp++)          /* loop over all defaults   */
        if (**dp != ';')                        /* if not a comment         */
            ndefault++;                         /* then count one more      */
    return (ndefault);
}

/*
 * SETPROGRAM: initialize the program name as parameter "argv0".
 */

local void setprogram(param *pvec, string argv0)
{
    pvec->name = "argv0";                       /* install 0th parameter    */
    pvec->value = argv0;                        /* set name from argv[0]    */
    pvec->comment = NULL;                       /* no comment for now       */
    pvec->flags = ARGPARAM;                     /* so user can't reset it   */
}

/*
 * COPYDEFAULTS: install default parameters and comments.
 */

local void copydefaults(param *pvec, string *defv)
{
    param *pp;
    string *dp, name, value;

    pp = pvec;                                  /* start with 0th param     */
    for (dp = defv; *dp != NULL; dp++)          /* loop over the defaults   */
        if (**dp == ';') {                      /* is this is a comment?    */
            if (pp->comment != NULL)            /* already have a comment?  */
                error("copydefaults: cant append comments\n");
            pp->comment = strdup(*dp + 1);      /* store comment in field   */
        } else {                                /* else its not a comment   */
            pp++;                               /* so move onto new param   */
            name = parname(*dp);                /* extract parameter name   */
            value = parvalue(*dp);              /* and parameter value      */
            if (name == NULL || value == NULL)  /* is either one missing?   */
                error("copydefaults: bad parameter %s\n", *dp);
            pp->name = strdup(name);            /* assign parameter name    */
            pp->value = strdup(value);          /* and parameter value      */
            pp->comment = NULL;                 /* clear comment field      */
            pp->flags = DEFPARAM;               /* set source to default    */
            if (streq(pp->value, "???"))        /* is this param required?  */
                pp->flags |= REQPARAM;          /* set the required flag    */
            if (**dp == '<')                    /* an input parameter?      */
                pp->flags |= INPARAM;           /* set the input flag       */
            else if (**dp == '>')               /* or an output parameter?  */
                pp->flags |= OUTPARAM;          /* set the output flag      */
            if (name[0] == '.')                 /* a hidden parameter?      */
                pp->flags |= HIDPARAM;          /* set the hidden flag      */
        }
    pp++;                                       /* past last real param     */
    pp->name = NULL;                            /* end list of parameters   */
}

/*
 * CHECKHELP: if requested, print out help mesaages and exit.
 */

local void checkhelp(param *pvec, string argv1)
{
    param *pp;
    char buf[128];

    if (argv1 != NULL && streq(argv1, "-clue")) {
                                                /* print brief help message */
        printf("%s", pvec->value);
        for (pp = pvec+1; pp->name != NULL; pp++)
            printf(" %s=%s", pp->name, pp->value);
        printf("\n");
        exit(0);
    }
    if (argv1 != NULL && streq(argv1, "-help")) {
                                                /* print full help message  */
       printitem(pvec->value, pvec->comment);
        for (pp = pvec+1; pp->name != NULL; pp++) {
            sprintf(buf, "  %s=%s", pp->name, pp->value);
            printitem(buf, pp->comment);
        }
        exit(0);
    }
}

local void printitem(string item, string comment)
{
    if (comment == NULL)
        printf("%s\n", item);
    else
        if (strlen(item) < 32)
            printf("%-32s  %s\n", item, comment);
        else
            printf("%s\n\t\t\t\t  %s\n", item, comment);
}

/*
 * SETARGUMENTS: override defaults with commandline arguments.
 */

local void setarguments(param *pvec, string *argv)
{
    bool scanpos;
    param *pp;
    string *ap, name;

    scanpos = TRUE;                             /* start scan by position   */
    pp = pvec;                                  /* start at 0th param       */
    for (ap = argv + 1; *ap != NULL; ap++) {    /* loop over command args   */
        name = parname(*ap);                    /* get param name, if any   */
        scanpos = scanpos && (name == NULL);    /* see how to match args    */
        if (scanpos) {                          /* matching by position?    */
            pp++;                               /* then move to next param  */
            if (pp->name == NULL)               /* make sure it exists      */
                error("%s: too many arguments\n", progname);
            pp->value = strdup(*ap);            /* OK, set new param value  */
        } else {                                /* else matching by name?   */
            if (name == NULL)                   /* make sure name was given */
                error("%s: nameless arg %s\n", progname, *ap);
            pp = findparam(name, pvec);         /* look for named param     */
            if (pp == NULL)                     /* make sure param exists   */
                error("%s: parameter %s unknown\n", progname, name);
            if (pp->flags & ARGPARAM)           /* must not already be set  */
                error("%s: parameter %s duplicated\n", progname, name);
            pp->value = strdup(parvalue(*ap));  /* OK, set new param value  */
        }
        pp->flags = (pp->flags & ~DEFPARAM) | ARGPARAM;
                                                /* switch source flag       */
    }
}

/*
 * REQARGUMENTS: print out short message on use.
 */

local void reqarguments(param *pvec)
{
    bool needarg;
    param *pp;

    needarg = FALSE;                            /* see if any args left out */
    for (pp = pvec+1; pp->name != NULL; pp++)   /* scan list of parameters  */
        if ((pp->flags & REQPARAM) &&           /* a required parameter?    */
              (pp->flags & DEFPARAM))           /* and still defaulted?     */
            needarg = TRUE;                     /* note missing args exist  */
    if (needarg) {                              /* list required arguments  */
        eprintf("Usage: %s", progname);
        for (pp = pvec+1; pp->name != NULL; pp++)
            if ((pp->flags & REQPARAM))
                eprintf(" %s=???", pp->name);
        error("%s: required arguments missing\n", progname);
    }
}

/*
 * GETPARAM: return value of parameter.  As a special case, requests for
 * "argv0" can be handled before paramvec has been set, to allow error
 * handling during the initialization process.
 */

string getparam(string name)
{
    param *par;

    if (paramvec == NULL)
        if (streq(name, "argv0") && progname != NULL)
            return (progname);
        else
            error("getparam: called before initparam\n");
    par = findparam(name, paramvec);
    if (par == NULL)
        error("getparam in %s: parameter %s unknown\n", progname, name);
    return (par->value);
}

/*
 * GETPARAMSTAT: return parameter flags, or zero if no such parameter
 * (note that all defined parameters have at least one bit set).
 */

int getparamstat(string name)
{
    param *par;

    par = findparam(name, paramvec);
    return (par != NULL ? par->flags : 0);
}

/*
 * GETIPARAM, GETDPARAM, GETBPARAM: get int, double, or bool parameter.
 */

int getiparam(string name)
{
    int val;
    string end;

    val = strtol(getparam(name), &end, 0);
    if (*end == 'k' || *end == 'K')
        return (1024 * val);
    if (*end == 'm' || *end == 'M')
        return (1024 * 1024 * val);
    return (val);
}

double getdparam(string name)
{
    return (atof(getparam(name)));              /* convert value to double  */
}

bool getbparam(string name)
{
    char *val;

    val = getparam(name);                       /* obtain value of param    */
    if (strchr("tTyY1", *val) != NULL)          /* is value true?           */
        return (TRUE);
    if (strchr("fFnN0", *val) != NULL)          /* is value false?          */
        return (FALSE);
    error("getbparam: %s=%s not bool\n", name, val);
    return (FALSE);                             /* keep compiler happy...   */
}

/*
 * FINDPARAM: look for named parameter in list of parameters.
 */

local param *findparam(string name, param *pvec)
{
    param *pp;

    for (pp = pvec; pp->name != NULL; pp++)
        if (streq(name, pp->name))
            return (pp);
    return (NULL);
}

/*
 * PARNAME: extract name from name=value string.
 * W A R N I N G :  returns ptr to static storage.
 */

local string parname(string arg)
{
    char *ap, *ep;
    static char namebuf[64];

    ap = (char *) arg;
    if (*ap == '<' || *ap == '>')
        ap++;
    strncpy(namebuf, ap, 63);
    namebuf[63] = NULL;
    ep = strchr(namebuf, '=');
    if (ep == NULL)                             /* not of form name=value?  */
        return (NULL);
    *ep = NULL;
    return (namebuf);
}

/*
 * PARVALUE: extract value from name=value string.
 */

local string parvalue(string arg)
{
    char *ep;

    ep = strchr(arg, '=');
    if (ep == NULL)
        return (NULL);
    return (ep + 1);
}