/*************************************************************************
** ^FILE: amiga_args.c - parse AmigaDOS argument vectors
**
** ^DESCRIPTION:
**    This file contains the routines used to parse AmigaDOS argument
**    vectors and to print AmigaDOS usage messages.
**
** ^HISTORY:
**    27/08/91 	Earl Chew 	<cechew@bruce.cs.monash.edu.au>
**    - Use ProgNameLen when accessing ProgName
**    - Use get_argdesc() to access description
**
**    01/02/91 	Brad Appleton 	<brad@ssd.csd.harris.com>
**    - Added structured block comments
**    - Added optional arguments to keywords
**
**    --/--/--  Peter da Silva  <peter@ferranti.com>    Created
***^^**********************************************************************/

#include <ctype.h>
#include <useful.h>
#include "strfuncs.h"
#include "pgopen.h"
#include "exit_codes.h"

#define PARSEARGS_PRIVATE   /* include private definitions */
#include "parseargs.h"

EXTERN  VOID  syserr       ARGS((const char *, ...));
EXTERN  VOID  usrerr       ARGS((const char *, ...));
EXTERN  char *getenv       ARGS((const char *));
EXTERN  VOID  get_winsize  ARGS((int, int *, int *));

VERSIONID("$Header: parseargs.c,v 2.1 89/12/30 20:59:48 eric Exp $");

/***************************************************************************
** ^GLOBAL-VARIABLE: Usage_Requested
**
** ^VISIBILITY:
**    static-global (visible to all functions in this file).
**
** ^DESCRIPTION:
**    Indicates whether a usage message was requested by the user
**    (as opposed to triggerred by a syntax error).  If the message
**    is requested by the user then it is always printed in verbose
**    mode and does not return an error-status-code.
***^^**********************************************************************/
static  BOOL  Usage_Requested = FALSE;


/***************************************************************************
** ^FUNCTION: amiga_parse - parse Amiga_DOS arg-vectors
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   int amiga_parse( argv, argd )
/*
** ^PARAMETERS:
*/
   char *argv[];
/*    -- the vector of string arguments from the command-line
*/
   ARGDESC argd[];
/*    -- the programmer description of the command and its args
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Amiga_parse will parse the arguments in the given vector of strings,
**    assign the corresponding values to the command-line arguments specified
**    in argd, and check the syntax of the command-line.
**
** ^REQUIREMENTS:
**    The final element in argv must be a NULL pointer.
**
** ^SIDE-EFFECTS:
**    argd is modified according to the command-line description and parameters
**
** ^RETURN-VALUE:
**    pe_SUCCESS (0) if no errors are encountered
**    pe_SYSTEM (-1) if a system error is encountered
**    pe_SYNTAX if a syntax error is encountered
**
** ^ALGORITHM:
**    - for each command-line argument
**       - attempt to match the argument as a keyword
**       - if it is a keyword argument
**          - record and convert its value (if any)
**         else it is a positional parameter
**          - record and convert its value (if any)
**         else there are too many arguments
**          - return pe_SYNTAX
**         end-if
**       end-for
***^^**********************************************************************/
#ifdef __ANSI_C__
   int amiga_parse( char **argv, ARGDESC argd[] )
#endif
{
   register ARGDESC *cmd, *args, *ad = ARGDESCNULL;
   register char **av;
   register char *p = CHARNULL;
   argName_t   keyword;
   argMask_t   flags;
   int  parse_error = pe_SUCCESS;
   BOOL is_match = FALSE;

   if ( !argd )  return  parse_error;

      /* initialize command-structure */
   if ( !CMD_isINIT(argd) )  init_args( argd );
   cmd = argd;
   cmd_prev(cmd) = ARGDESCNULL;

      /* run through the argument vector */
   for ( av = argv ; *av ; av++ ) {
      char c = '\0';

         /* If looking for keywords, see if this is one */
      if( !BTEST(cmd_state(cmd), ps_NOFLAGS) ) {
         p = strpbrk(*av, s_ARG_SEP);
         if ( p ) {
            c = *p;
            *p++ = '\0';  /* skip past arg-separator character */
         }

         is_match = FALSE;
         for ( args = argd ; args  &&  !is_match ; args = cmd_defargs(args) ) {
            for (ad = ARG_FIRST(args) ; !ARG_isEND(ad) ; ARG_ADVANCE(ad)) {
               if (arg_type(ad) == argDummy)  continue;

               if (!ARG_isPOSONLY(ad)  &&  match(*av, arg_sname(ad)) == 0) {
                  is_match = TRUE;
                  break;
               }/*if*/
            }
         }

         if ( !is_match )  ad = ARGDESCNULL;
      }/*if !NOFLAGS*/

      if (c)  *(p-1) = c;  /* restore the equal sign */

         /* If we have a keyword here */
      if( !BTEST(cmd_state(cmd), ps_NOFLAGS)  &&  ad) {
         if ( cmd_prev(cmd) ) { /* a value may have been given but wasnt */
            if ( ARG_isVALOPTIONAL(cmd_prev(cmd)) ) {
               BSET( arg_flags(cmd_prev(cmd)), ARGGIVEN );
            }
            else {  /* value was required */
               (VOID)get_kwdname( arg_sname(cmd_prev(cmd)), keyword );
               usrerr( "value required for %s keyword", keyword );
               parse_error = pe_SYNTAX;
            }
            cmd_prev(cmd) = ARGDESCNULL;
         }

         if ( cmd_list(cmd) ) { /* end of list */
            cmd_list(cmd) = ARGDESCNULL;
         }

         flags = arg_flags(ad);    /* save flags */
         if ( ARG_isGIVEN(ad) ) {
            BCLEAR( arg_flags(ad), ARGVALSEP | ARGKEYWORD );
            if ( !ARG_isMULTIVAL(ad) )  BCLEAR( arg_flags(ad), ARGVALGIVEN );
         }

         if ( p ) { /* matched NAME=VALUE */
            if ( ARG_isMULTIVAL(ad) )
               cmd_list(cmd) = ad;
            else
               cmd_list(cmd) = ARGDESCNULL;

               /* try to convert the type */
            if ( !HANDLE(ad, p, cmd_flags(cmd)) ) {
               arg_flags(ad) = flags;  /* restore flags */
               parse_error = pe_SYNTAX;
            }
            else
               BSET( arg_flags(ad), ARGGIVEN | ARGVALGIVEN );
            ad = ARGDESCNULL;
         }
         else {
            if (arg_type(ad) == argUsage) {
               Usage_Requested = TRUE;
               usage(argd);
               exit(exit_USAGE);
            }
            else if (arg_type(ad) == argEnd) {
               BSET( cmd_state(cmd), ps_NOFLAGS );
               BSET( arg_flags(ad), ARGGIVEN );
            }
            else if ( ARG_isVALTAKEN(ad) ) {
               cmd_prev(cmd) = ad;
            }
            else if ( !HANDLE(ad, CHARNULL, cmd_flags(cmd)) ) {
               arg_flags(ad) = flags;  /* restore flags */
               parse_error = pe_SYNTAX;
            }
            else {
               BSET( arg_flags(ad), ARGGIVEN | ARGVALGIVEN );
            }
            ad = ARGDESCNULL;
         }/*else*/
      }
      else if (cmd_prev(cmd)) { /* expecting a vlue from previous arg */
         /* reset the argument flags - if this arg was already given, some
         ** of its flags may be set to indicate how it was given before.
         ** we need to know how it was given now (but save the old ones
         ** just in case the new one fails).
         */
         flags = arg_flags(cmd_prev(cmd));    /* save flags */
         if ( ARG_isGIVEN(cmd_prev(cmd)) ) {   /* reset flags */
            BCLEAR( arg_flags(cmd_prev(cmd)), ARGVALSEP );
            if ( !ARG_isMULTIVAL(ad) )  BCLEAR( arg_flags(ad), ARGVALGIVEN );
         }

            /* previous value may have required a keyword */
         BSET( arg_flags(cmd_prev(cmd)), ARGVALSEP );

         if ( ARG_isMULTIVAL(cmd_prev(cmd)) )
            cmd_list(cmd) = cmd_prev(cmd);
         else
            cmd_list(cmd) = ARGDESCNULL;

            /* try to convert the type */
         if ( !HANDLE(cmd_prev(cmd), *av, cmd_flags(cmd)) ) {
            arg_flags(cmd_prev(cmd)) = flags;  /* restore flags */
            parse_error = pe_SYNTAX;
         }
         else
            BSET( arg_flags(cmd_prev(cmd)), ARGGIVEN | ARGVALGIVEN );

         ad = ARGDESCNULL;
         cmd_prev(cmd) = ARGDESCNULL;
         continue;
      }
      else {  /* it's a positional argument or a list item */
         if ( cmd_list(cmd) ) { /* its a list item */
            /* reset the argument flags - if this arg was already given, some
            ** of its flags may be set to indicate how it was given before.
            ** we need to know how it was given now (but save the old ones
            ** just in case the new one fails).
            */
            flags = arg_flags(cmd_list(cmd));    /* save flags */
            if ( ARG_isGIVEN(cmd_list(cmd)) ) {   /* reset flags */
               BCLEAR( arg_flags(cmd_list(cmd)), ARGVALSEP );
            }
            BSET( arg_flags(cmd_list(cmd)), ARGVALSEP );

            if ( !HANDLE(cmd_list(cmd), *av, cmd_flags(cmd)) ) {
               arg_flags(cmd_list(cmd)) = flags;  /* restore flags */
               parse_error = pe_SYNTAX;
            }

            BSET( arg_flags(cmd_list(cmd)), ARGGIVEN | ARGVALGIVEN );
            continue;
         }
         else {  /* its a positional argument */

            /* if FLAGS1ST is set then first positional marks end-of-flags */
            if ( BTEST(cmd_flags(cmd), pa_FLAGS1ST) )
               BSET( cmd_state(cmd), ps_NOFLAGS );

            is_match = FALSE;
            for ( args = argd; args && !is_match; args = cmd_defargs(args) ) {
               for (ad = ARG_FIRST(args) ; !ARG_isEND(ad) ; ARG_ADVANCE(ad)) {
                  if (arg_type(ad) == argDummy)  continue;

                  if ( ARG_isPOSITIONAL(ad)  &&
                       (!ARG_isGIVEN(ad) || ARG_isMULTIVAL(ad)) ) {
                     is_match = TRUE;
                     break;
                  }/*if*/
               }
            }

            if ( !is_match ) {
               usrerr("too many arguments");
               parse_error = pe_SYNTAX;
               ad = ARGDESCNULL;
            }
            else {
               /* reset the argument flags - if this arg was already given, some
               ** of its flags may be set to indicate how it was given before.
               ** we need to know how it was given now (but save the old ones
               ** just in case the new one fails).
               */
               flags = arg_flags(ad);    /* save flags */
               if ( ARG_isGIVEN(ad) ) {   /* reset flags for this appearance */
                  BCLEAR( arg_flags(ad), ARGVALSEP );
                  if (! ARG_isMULTIVAL(ad))  BCLEAR(arg_flags(ad), ARGVALGIVEN);
               }
               BSET( arg_flags(ad), ARGVALSEP );

                  /* try to convert */
               if ( !HANDLE(ad, *av, cmd_flags(cmd)) ) {
                  arg_flags(ad) = flags;  /* restore flags */
                  parse_error = pe_SYNTAX;
               }
               else
                  BSET( arg_flags(ad), ARGGIVEN | ARGVALGIVEN );
               ad = ARGDESCNULL;
            }
         }/*else positional*/
      }/*else not keyword*/
   }/*while*/

   /* If last argument was a keyword and required an option
   ** then complain about it
   */
   if ( cmd_prev(cmd) ) { /* a value may have been given but wasnt */
      if ( ARG_isVALOPTIONAL(cmd_prev(cmd)) ) {
         BSET( arg_flags(cmd_prev(cmd)), ARGGIVEN );
      }
      else {  /* value was required */
         (VOID)get_kwdname( arg_sname(cmd_prev(cmd)), keyword );
         usrerr( "value required for %s keyword", keyword );
         parse_error = pe_SYNTAX;
      }
      cmd_prev(cmd) = ARGDESCNULL;
   }

   return  parse_error;
}


/***************************************************************************
** ^FUNCTION: fmtarg - format command-argument syntax
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   static int fmtarg(ad, buf)
/*  
** ^PARAMETERS:
*/
   ARGDESC *ad;
/*    -- pointer to the argument to format
*/
   char *buf;
/*    -- character buffer to hold the formatted result
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Fmtarg will determine the proper command-line syntax for the
**    given argument and write the result to the given buffer.
**
** ^REQUIREMENTS:
**    buf must be large enough to hold the formatted result (100 characters
**    should do the trick).
**
** ^SIDE-EFFECTS:
**    buf is overwritten.
**
** ^RETURN-VALUE:
**    The number of printable characters in the argument-syntax-string
**
** ^ALGORITHM:
**    Print argument usage based on whether or not the argument is
**    positional, hidden, multi-valued (list or vector), etc ....
**    Optional arguments and values are enclosed in square braces.
***^^**********************************************************************/
#ifdef __ANSI_C__
   static int fmtarg( const ARGDESC *ad, char *buf )
#endif
{
      /* buf must already be large enough */
   char * pos;
   argName_t  keyword, name;

   (VOID) get_argname(arg_sname(ad), name);

   if ( ARG_isPOSITIONAL(ad) ) {
      sprintf( buf, "<%s>", name );
   }
   else {
      (VOID) get_kwdname(arg_sname(ad), keyword);
      (VOID) strcpy( buf, keyword );
      pos = buf + strlen(buf);

      if ( ARG_isVALTAKEN(ad) && !ARG_isBOOLEAN(ad) && !ARG_isPSEUDOARG(ad) ) {
         if ( ARG_isVALOPTIONAL(ad) )
            sprintf( pos, " [<%s>]", name );
         else
            sprintf( pos, " <%s>", name );
      }
   }/*else*/

   return  strlen(buf);
}


/***************************************************************************
** ^FUNCTION: amiga_usage - print a usage message
**
** ^SYNOPSIS:
*/
#ifndef __ANSI_C__
   VOID amiga_usage( argd, usage_flags )
/*  
** ^PARAMETERS:
*/
   ARGDESC *argd;
/*    -- the command-descriptor array
*/
   argMask_t usage_flags;
/*    -- flags set by $USAGECNTL
*/
#endif  /* !__ANSI_C__ */

/* ^DESCRIPTION:
**    Amiga_usage will print the AmigaDOS command-line usage of the given
**    command on standard diagnostic output (stderr). The content of the
**    usage message is controlled by the bitmasks in usage_flags which
**    correspond to the settings in the user's USAGECNTL variable.
**
** ^REQUIREMENTS:
**    argd should be a non-null command-line argument-descriptor array
**
** ^SIDE-EFFECTS:
**    Prints on stderr.
**
** ^RETURN-VALUE:
**    None.
**
** ^ALGORITHM:
**    - if no usage is desired then exit
**    - if paging is requested print to the pager instead of stderr
**    - print the command-line syntax
**    - if the description is requested print it
**    - if verbose mode is requested, print the description of each argument
***^^**********************************************************************/
#ifdef __ANSI_C__
   void amiga_usage( const ARGDESC *argd, argMask_t usage_flags )
#endif
{
   register CONST ARGDESC  *ad, *args, *cmd;
   int  max_cols = 80, max_lines  = 24;
   int  margin, ll, pl, keywords, longest, positionals;
   BOOL first = TRUE;
   FILE *fp;

   if ( !argd )  return;

      /* initialize command-structure */
   if ( !CMD_isINIT(argd) )  init_args( (ARGDESC *)argd );
   cmd = argd;

      /* force verbose-mode if requested */
   if ( Usage_Requested )   BSET( usage_flags, usg_VERBOSE );

   if ( BTEST(usage_flags, usg_NONE) )  return;

   fp = ( BTEST(usage_flags, usg_PAGED) )
      ? pgopen( stderr, getenv("USAGE_PAGER") )
      : stderr;

      /* get screen size */
   get_winsize( fileno(stderr), &max_lines, &max_cols );
   fprintf(fp, "Format: %.*s", ProgNameLen, (ProgName) ? ProgName : "");
   ll = ProgNameLen + 8;
   margin = ll + 1;
   longest = 0;

   for ( positionals = 0 ; positionals < 2 ; positionals++ ) {
      for ( args = argd ; args ; args = cmd_defargs(args) ) {
         for ( ad = ARG_FIRST(args) ; !ARG_isEND(ad) ; ARG_ADVANCE(ad) ) {
            argName_t  buf;

               /* don't display hidden arguments */
            if ( ARG_isHIDDEN(ad) )  continue;
            if ( !positionals  &&  ARG_isPOSITIONAL(ad) )  continue;
            if ( positionals  &&  !ARG_isPOSITIONAL(ad) )  continue;

               /* figure out how wide this parameter is (for printing) */
            pl = fmtarg(ad, buf);

            if ( pl > longest)  longest = pl;

            if ( !ARG_isREQUIRED(ad) ) {
               pl += 2;  /* [] */
            }
            if ( ARG_isMULTIVAL(ad) ) {
               strcat( buf, "..." );
               pl += 3;
            }

               /* see if this will fit */
            if ( (ll + pl + 1) > (max_cols - first) ) {
                  /* no... start a new line */
               fprintf(fp, "\n%*s", margin, "");
               ll = margin;
            }
            else {
                  /* yes... just throw in a space */
               fputc(' ', fp);
               ++ll;
            }
            ll += pl;

               /* show the argument */
            if ( !ARG_isREQUIRED(ad) )  fputc('[', fp);
            fprintf(fp, buf);
            if ( !ARG_isREQUIRED(ad) )  fputc(']', fp);

            first = FALSE;  /* not first line anymore */
         }/*for each ad */
      }/* for each argd */
   }/* for each parm-type */

   fputc('\n', fp);

   if ( BTEST(usage_flags, usg_DESCRIPTION) ) {
      CONST char *description = cmd_description(cmd);

      if ( description  &&  *description ) {
         fprintf( fp, "Description:\n" );
         indent_para(fp, max_cols, 8, "", 0, description, 0);
         fputc( '\n', fp );
      }
   }/*if*/

   if ( !BTEST(usage_flags, usg_VERBOSE) )  {
      if ( pgactive(fp) )  (VOID) pgclose( fp );
      return;
   }

   keywords = 0;
   for ( positionals = 0 ; positionals < 2 ; positionals++ ) {
      for ( args = argd ; args ; args = cmd_defargs(args) ) {
         for ( ad = ARG_FIRST(args) ; !ARG_isEND(ad) ; ARG_ADVANCE(ad) ) {
            argName_t  buf;
            char  *desc;
            int  desclen;

               /* don't display hidden arguments */
            if ( ARG_isHIDDEN(ad) )  continue;
            if ( !positionals  &&  ARG_isPOSITIONAL(ad) )  continue;
            if ( positionals  &&  !ARG_isPOSITIONAL(ad) )  continue;

            if( !(keywords++) )  fprintf(fp, "Keywords/Arguments:\n");
            (VOID) fmtarg(ad, buf);
            desc = get_argdesc(arg_description(ad), &desclen);
            indent_para(fp, max_cols, 8, buf, longest+2, desc, desclen);
         }/*for each ad */
      }/* for each argd */
   }/* for each parm-type */

   if ( pgactive(fp) )  (VOID) pgclose( fp );
}
