/*
   <p>Extended <i>getopt</i> procedure plus associated <i>getarg</i>
   procedure.</p>

   <kbd>
   <b>environ - C programming support functions</b><br>
   Copyright (C) 2005  Martin C Gregorie
   
   This program is free software; you can redistribute it and/or
   modify it under the terms of the Lesser GNU General Public 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.  See the
   GNU General Public License for more details.
  
   You should have received a copy of the Lesser GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   </kbd>
  
   <p>Contact Martin Gregorie at <i>martin@gregorie.org</i></p>
*/

#include <stdio.h>
#include <string.h>
#include "environ.h"

/*
    Constants that are not referenced in the executable portion
    of the code ... their sole purpose is to initialise global
    variables.
*/

#define BADCH        (int)'?'
#define ERRFD        2
#define MAYBESEP     (int)'\0'
#define NEEDSEP      (int)':'
#define START        "-"

/*    Other constant definitions */

#define EMSG        ""
#define PARMSTART   (int)'='
#define FOUND       1
#define NOTHING     0
#define MAXUSED     52
#define HUGEVAL     9999
#define QUOTES      "'\""

#ifndef EOF
#    define EOF (-1)
#endif    /* ! EOF */

#ifndef NULL
#    define NULL    (char *)0
#endif    /* ! NULL */

/*    
    Here are all the global variables that control getopt().
    They are accessable by the caller who may change the 
    standard defaults shown below.
*/

char *optstart = START;    /* list of characters that start options */
int  optbad = BADCH;       /* character returned on error */
int  opterr = 1;           /* if true, output error message */
int  opterrfd = ERRFD;     /* file descriptor for error text */
int  optind = 1;           /* index into parent argv vector */
int  optmaybe = MAYBESEP;  /* flag for optional argument */
int  optneed = NEEDSEP;    /* flag for mandatory argument */
int  optcase = 1;          /* 1=case sensitive options; 0=caseless */

/*
    The following global contains NULL or a pointer to the 
    option value (after getopt()) or the argument that has been 
    found (getarg()).
*/
char *optarg;        /* argument associated with option */

/*
    Internal globals with externally visible names. 
    This follows the standard Unix getopt()
*/
int  optchar = 0;    /* flag character for the current option argument */
int  optopt;         /* option character checked by last getopt() call */

/*    
    Private global variables used by 'getopt' and 'getarg'
    for intercommunication.
*/ 

int  _used = 0;            /* pointer into _usedarg array */
int  _usedarg[MAXUSED];    /* list of indices of command params 
                              used as arguments of options */

/*
    Private global used to keep the option scan pointer tidy.

    The option scanner points here when there's no 
    current option argument.
*/
char *_emsgstr = EMSG;

/*    
    General support procedures
    --------------------------
    
    _tell
    -----
    Conditionally output an error message.
    
*/
void _tell(char *s)
{
    char    option;
    char*   name;
    
    if (opterr && opterrfd >= 0)
    {
        option = (char)optopt;
        name = (char*)progname();
        write(opterrfd, name, strlen(name));
        write(opterrfd, (s), strlen(s));
        write(opterrfd, &option, 1);
        write(opterrfd, "\n", 1);
    }
}

/*    
    _sindex 
    -------
    Used to determine whether a given character is in 
    a list. It returns a pointer to the character in 
    'string' if it matches 'ch' and NULL otherwise.
*/
char *_sindex(const char *string, int ch)
{
    if (string)
        return (index(string, ch));
    return(NULL);
}

/*
    <b>getopt</b> - extended version.
    <dl>
    <dt>Original version:</dt>
        <dd>Keith Bostic</dd>
        
    <dt>Extended version:</dt>
        <dd>Lloyd Zusman<br>
        Masterbyte Software<br>
        Los Gatos, CA</dd>
        
    <dt>Adapted to OS9/68K standard:</dt>
        <dd>Martin Gregorie<br>    
        Harlow,<br>
        U.K.</dd>
    </dl>
    <b>getopt</b> is used to parse options on the command
    line. The extension to OS/9 standards adds these facilities:
    <ol>
    <li>Arguments and options may be freely mixed on the command line.
    <li>Argument options must meet one of the following standards:
         <ul> 
            <li>-xval  
            <li>-x=val  
            <li>-x= val
         </ul>
    </li>
    <li>The entire option or just the value may be enclosed in quotes</li>
    <li>Options not taking values can be concatenated</li>
    <li>Added the optcase global;
         <ul>
            <li>1=case sensitive
            <li>0=ignore case.
         </ul>
        Defaults to case sensitive.</li>
    <li>Causes <i>getarg()</i> to ignore parameters that have already
        been processed as options and/or option values</li>
    <li>Returns:
         <ul>
            <li>NULL for non-options (skips other arguments)
            <li><b>optchar</b> for valid options
            <li>EOF for end of command line (all options processed)
         </ul>
         </li>
    </ol>

    <b>getopt</b> is called repeatedly with the same parameters to process
    all the arguments on the command line.
    Each call returns NULL if the command line argument is not an option:
    it should be called again to skip this argument. It returns EOF when all
    arguments have been processed. It returns each option character as
    it is found: if the option has an associated value then <b>optarg</b>
    points to a string containing the option value.

    After <b>getopt</b> has completely processed the command line
    <i>getarg()</i> should be used to retrieve the other argument(s).
    
    <b>NOTE:</b>
    This version can ONLY be compiled under Linux, as the <i>_tell()</i>
    function uses <i>writeln()</i> to force a newline onto the terminal; 
    <i>writeln()</i> is not a standard UNIX function.

    <b>See also:</b> <i>getarg(), argval()</i>
*/
int getopt(int nargc, char * const nargv[], const char *ostr)
{
    static char    *place = NULL;    /* option letter processing */
    register char  *oli;             /* option letter list index */
    register char  *osi = NULL;      /* option start list index */
    int            expect = 0;       /* set when '=' found */
    char           *qti;             /* vars for dequoting args */
    int            qlen;
    char           *quotes = QUOTES;
    
    /* Various checks for the end of the command line */

    if (!nargv)
        return(EOF);
    if (optind >= nargc)    /* all args processed */
        return(EOF);
    if (!nargv[optind])     /* null arg found */
        return(EOF);
        
    if (!place)              /* place always points to a string */
        place = _emsgstr;    /* even if it is null */ 
        
    /* 
        initialise the used arguments list if the scan
        is starting from the beginning of the command
        parameters
    */
    
    if (optind <= 1)
    {
        for ( _used = 0; _used < MAXUSED; _used++) 
            _usedarg[_used] = HUGEVAL;
        _used = 0;
    }
        
    /*    Update scanning pointer; we need to start a new argument */

    if (!*place)
    {
        place = nargv[optind];
        if (!place 
            || optind >= nargc 
            || !*place)
            return(EOF);
        if (osi = _sindex(optstart, *place))
            optchar = (int)*osi;
        if (osi == NULL)        /* not an option argument */
        {
            place = _emsgstr;
            optind++;
            return(0);
        }
        place++;
            
        /* 
            Two adjacent, identical flag characters were found
            at the start of an option argument. They are
            treated as an unconditional end of option scan.
            This may make trouble for subsequent use of getarg()
            if it is followed by non-options starting with a
            flag character, which getarg() will skip. 
            
            You have been warned!
            
            This action is disabled by putting the start
            character in the valid options list and processing
            it as an option in its own right.
        */
        if (*place == place[-1])
        {
            ++optind;
            place = _emsgstr;
            return(EOF);
        }
    }
    
    /*
        If the option is a separator or the option isn't in the list,
        we've got an error.
    */
    optopt = (int)*place++;
    if (!optcase)
        if (optopt>='A' && optopt<='Z')
            optopt += 'a'-'A';
    oli = _sindex(ostr, optopt);
    if (optopt == optneed || optopt == optmaybe || !oli)
    {
        /*
            If we're at the end of the current argument,
            increment the argument index.
        */
        if (!*place)
            ++optind;
        _tell(": illegal option - ");    /* bye bye */
        return (optbad);
    }

    /*
        If there is no argument indicator, then we don't even
        try to return an argument.
    */
    if (!*++oli || (*oli != optneed && *oli != optmaybe))
    {
        /*
            If we're at the end of the current argument,
            increment the argument index.
        */
        if (!*place)
            ++optind;
        optarg = NULL;
    }
    else
    {
        /*
            If we're here theres an argument indicator. 
            It's handled differently depending on whether 
            it's a mandatory or an optional argument
    
            There are several possibilities:
            1   the argument directly follows the option
            2   this is the last char in the param string,
                which indicates:
                a    either a missing mandatory arg (error!)
                b    an absent optional arg (ok)
            3   '=' directly follows the option in which 
                case the arg may:
                a    directly follow the '=' (ok)
                b    form the next param (ok) This case is 
                     allowed for MS-DOS compatibility
                c    be missing (error!)
        */
        if (*place == PARMSTART)
        {
            expect = *place;    /* case 3 recognised and */
            place++;            /* noted as such for later */
        }

        if (*place)
            optarg = place;     /* cases 1 and 3a */
        else
        {                       /* cases 2a, 2b, 3b, 3c */
            if (!expect)
            {
                if (*oli == optneed)
                {
                    place = _emsgstr;    /* case 2a */
                    optind++;
                    _tell(": option requires an argument - ");
                    return (optbad);
                }
                else
                    optarg = NULL;    /* case 2b */
            }
            else
            {
                optind++;
                if (nargc <= optind || !nargv[optind])
                {
                    _tell(": option requires an argument - "); /* case 3c */
                    return (optbad);
                }
                else
                {
                    /*    case 3b
                    
                        separated argument. Record it in _usedarg to
                        so that 'getarg' knows it is part of an 
                        option
                    */
                    if (_used < MAXUSED)
                        _usedarg[_used++] = optind;
                    else
                    {
                        _tell(": too many separated arguments -");
                        return (optbad);
                    }
                        
                    optarg = nargv[optind];    /* case 3b */
                }
            }
        }
        
        place = _emsgstr;
        ++optind;
    }
    
    /* remove surrounding quotes (if any) from the argument */
    
    if (optarg && (qti = _sindex(quotes, *optarg)))
    {
        qlen = strlen(optarg);
        optarg++;            /* skip leading quote */
        optarg[qlen-2] = 0;    /* and null out trailing one */
    }

    return(optopt);        /*    return option letter */
}

/*                                  
    <b>getarg</b>
    <dl>
    <dt>Original version:</dt>
        <dd>Martin Gregorie<br>    
        Harlow,<br>                
        U.K.</dd>
    </dl>
    <b>getarg</b> returns command line parameters ignored by <i>getopt()</i>.
    It must only be called after <i>getopt()</i> has completely scanned
    the command parameters. <u>optind</u> should be reset to 1 before the
    first call.

    <b>getarg</b> Should be called repeatedly until it returne EOF.
    Each call returns one of the following values:
    <ul>
        <li>0: the argument has been processed as an option or
             option value by <i>getopt()</i>. Ignore it.</li>
        <li>1: an argument was found. <u>optarg</u> points to it.</li>
        <li>EOF: all arguments have been processed.</li>
    </ul>
    
    <b>NOTE:</b>
    This version can ONLY be compiled under Linux, as the <i>_tell()</i>
    function uses <i>writeln()</i> to force a newline onto the terminal; 
    <i>writeln()</i> is not a standard UNIX function.

    <b>See also:</b> <i>getopt(), argval()</i>
*/
int getarg(int nargc, char **nargv)
{
    register char     *osi = NULL;    /* option start list index */
    int               reply;
    static int        scanused = 0;   /* readout index for _usedarg */
    
    if (!nargv)                       /* check for end of param list */
        return(EOF);
    if (nargc <= optind || !nargv[optind])
        return(EOF);
    if (optind >= nargc)
        return(EOF);
        
    if (optind <= 1)                  /* reset _usedarg scan if needed */
        scanused = 0;
        
     /*    
         Find the first relevant reference in the array. Its initialised 
         to high values, so we want the first entry thats not less than 
         optind. If theres a match then the argument was marked as part 
         of an option by getopt() and will be ignored.
     */
    while (_usedarg[scanused] < optind)
        scanused++;
        
    if (_usedarg[scanused] == optind)
    {                                 /* argument already used as a */
        optarg = _emsgstr;            /* separated argument to an   */
        reply = NOTHING;              /* an option                  */
    }
    else
    {        
        /* 
            See if the command line parameter is an option 
        */
        osi = _sindex(optstart, *nargv[optind]); 

        if (osi == NULL)
        {                            /* not an option */
            optarg = nargv[optind];
            reply = FOUND;
        }
        else
        {                            /* argument is an option */
            optarg = _emsgstr;
            reply = NOTHING;
        }
    }

    optind++;
    return(reply);
}
        
            
        
            
