/*  Copyright (C) 1993,1994 by the author(s).
 
 This software is published in the hope that it will be useful, but
 WITHOUT ANY WARRANTY for any part of this software to work correctly
 or as described in the manuals. See the ShapeTools Public License
 for details.

 Permission is granted to use, copy, modify, or distribute any part of
 this software but only under the conditions described in the ShapeTools 
 Public License. A copy of this license is supposed to have been given
 to you along with ShapeTools in a file named LICENSE. Among other
 things, this copyright notice and the Public License must be
 preserved on all copies.
 */
/*
 * parseargs.c -- parse command line
 *
 * Author: Uli Pralle (Uli.Pralle@cs.tu-berlin.de)
 *
 * $Header: parseargs.c[4.0] Thu Jun 24 17:43:19 1993 andy@cs.tu-berlin.de frozen $
 */

#include "config.h"
#include "sttk.h"

#define MINUS_CHAR_REQUIRED 1

LOCAL int GetNextToken (str, odesc, minus_char_required)
     char *str;
     StOptDesc odesc[];
     int minus_char_required;
{
  int i;

  if (!str) return (-1);

  if (minus_char_required && (*str != '-')) return (-1);

  if (*str == '-') str++;

  for (i = 0; odesc[i].OptName; i++) {
    if (!strncmp(odesc[i].OptName, str, strlen(odesc[i].OptName)))
      return (i);
  }
  return (-1);
}

LOCAL char *SkipThisToken (str, length)
     char *str;
     int length;
{
  if (*str == '-') str++;
  return (str + length);
}

LOCAL int cmp_length (left, right)
     StOptDesc *left, *right;
{
  return (strlen(right->OptName) - strlen(left->OptName));
}

LOCAL int cmp_alpha (left, right)
     StOptDesc *left, *right;
{
  return (strcmp(left->OptName, right->OptName));
}

LOCAL void RearrangeOptDesc (odesc, cmp_function)
     StOptDesc odesc[];
     int (*cmp_function)();	/* function that compares */
{
  int nelem;

  for (nelem = 0; odesc[nelem].OptName; nelem++) ;
  qsort((char *) odesc, nelem, sizeof (StOptDesc), cmp_function);
}

EXPORT void stShortUsage (progname, odesc, extra_text)
     char *progname;
     StOptDesc odesc[];
     char *extra_text;
{
  int i;
  int twidth = stGetTermWidth (2); /* stderr */
  int c_printed = 1;
  int length = 0;
  int nextlineoff;
  char buf[ST_MSGLEN];

  RearrangeOptDesc (odesc, cmp_alpha);

  if (progname && *progname) {
    sprintf (stMessage, " %s:", progname);
    stLog (stMessage, ST_LOG_MSGERR | ST_LOG_NONL);
    c_printed += strlen(progname) + 2;
  }

  nextlineoff = c_printed + 3;

  /* first switches than option requiring an agrument */
  sprintf (stMessage, " [-");
  stLog (stMessage, ST_LOG_MSGERR | ST_LOG_NONL);
  c_printed += 3;

  for (i = 0; odesc[i].OptName; i++) {
    if ((odesc[i].OptKind & PATTRS) == PHIDDEN) continue;

    if ((odesc[i].OptKind & PKIND) == PSWITCH) {
      length = strlen(odesc[i].OptName); length++;
      if (c_printed + length > twidth) {
	c_printed = length + nextlineoff;
	sprintf (buf, "\n%%%dc", nextlineoff);
	sprintf (stMessage, buf, ' ');
	stLog (stMessage, ST_LOG_MSGERR | ST_LOG_NONL);
      }
      else {
	c_printed += length;
      }
      sprintf (stMessage, " %s", odesc[i].OptName);
      stLog (stMessage, ST_LOG_MSGERR | ST_LOG_NONL);
    }
  }

  sprintf (stMessage, "]"); c_printed++;
  stLog (stMessage, ST_LOG_MSGERR | ST_LOG_NONL);
  nextlineoff -= 3;

  for (i = 0; odesc[i].OptName; i++) {
    if ((odesc[i].OptKind & PATTRS) == PHIDDEN) continue;

    length = strlen(odesc[i].OptName);

    switch (odesc[i].OptKind & PKIND) {
    case PSWITCH:
        break;
    case POARG:
	if ((c_printed + 14 + length) > twidth) {
	  c_printed = length + nextlineoff + 14;
	  sprintf (buf, "\n%%%dc", nextlineoff);
	  sprintf (stMessage, buf, ' ');
	  stLog (stMessage, ST_LOG_MSGERR | ST_LOG_NONL);
	}
	else {
	  c_printed += length + 14;
	}

	sprintf (stMessage, " [-%s <opt arg>]", odesc[i].OptName);
	stLog (stMessage, ST_LOG_MSGERR | ST_LOG_NONL);
      break;
    case PARG:
	if ((c_printed + 10 + length) > twidth) {
	  c_printed = length + nextlineoff + 10;
	  sprintf (buf, "\n%%%dc", nextlineoff);
	  sprintf (stMessage, buf, ' ');
	  stLog (stMessage, ST_LOG_MSGERR | ST_LOG_NONL);
	}
	else {
	  c_printed += length + 10;
	}

	sprintf (stMessage, " [-%s <arg>]", odesc[i].OptName);
	stLog (stMessage, ST_LOG_MSGERR | ST_LOG_NONL);
      break;
    default:
      break;
    }
  }

  if ((extra_text) && ((strlen(extra_text) + c_printed + 1) > twidth)) {
    sprintf (buf, "\n%%%dc", nextlineoff);
    sprintf (stMessage, buf, ' ');
    stLog (stMessage, ST_LOG_MSGERR | ST_LOG_NONL);
  }
  sprintf (stMessage, " %s", extra_text ? extra_text : "");
  stLog (stMessage, ST_LOG_MSGERR);
}

LOCAL int handle (progname, odesc, idx, arg)
     char *progname;
     StOptDesc *odesc;
     int idx;
     char *arg;
{
  switch (odesc[idx].OptKind & PACTION)
    {
    case PCALL:
      return (odesc[idx].OptFunc(odesc[idx].OptName, arg));
    case PSET:
      *odesc[idx].OptVar = 1;
      return (0);
    case PCLEAR:
      *odesc[idx].OptVar = 0;
      return (0);
    case PTOGGLE:
      *odesc[idx].OptVar = *odesc[idx].OptVar ? 0 : 1;
      return (0);
    case PUSAGE:
      stShortUsage(progname, odesc, odesc[idx].OptStr);
      return (0);
    case PFAIL:
      stShortUsage(progname, odesc, odesc[idx].OptStr);
      return (1);
    }
  /* ERROR: unknown flag type */
  return (1);
}

/*=====================
 * parseArgs
 *=====================*/

EXPORT int stParseArgs (argc, argv, nargc, nargv, odesc)
     int argc; char **argv;
     int *nargc; char *(*nargv[]);
     StOptDesc odesc[];
{
  char **localArgv, *tkn;
  int opt, minus_char_required, opterr = 0;

  *nargc = 0;
  /* allocate some memory for newav */
  if ((localArgv = (char **) malloc((unsigned) (sizeof (char *) * (argc + 1)))) == NULL) {
    /* ERROR: out of memory */
    return (-1);
  }

  /* sort by length */
  RearrangeOptDesc (odesc, cmp_length);

  while (argv && *argv) {
    tkn = *argv;		/* beginning of next argument */
    minus_char_required = 1;	/* require a '-' if argument is a option */

    while (tkn && *tkn) {
      if ((opt = GetNextToken(tkn, odesc, minus_char_required)) >= 0) {
	tkn = SkipThisToken(tkn, strlen (odesc[opt].OptName));
	/* allow joined options */
	minus_char_required = 0;

	switch (odesc[opt].OptKind & PKIND) {
	case PSWITCH:
	  opterr += handle (stProgramName, odesc, opt, "");
	  break;
	case POARG:
	  /*
	   * at this point, tkn points to the next char after
	   * the detected option or tkn has reached the end.
	   */
	  if (!*tkn) {
	    if (*++argv) { /* next arg exists */
	      tkn = *argv;
	      /* always '-' required */
	      if (GetNextToken (tkn, odesc, MINUS_CHAR_REQUIRED) >= 0) {
		opterr += handle (stProgramName, odesc, opt, "");
		tkn = NULL;
		argv--;
	      }
	      else {
		opterr += handle (stProgramName, odesc, opt, tkn);
		tkn = NULL;
	      }
	    }
	    else {
	      /* end of argv reached */
	      opterr += handle (stProgramName, odesc, opt, "");
	      tkn = NULL;
	      argv--;
	    }
	  }
	  else {
	    if (!(GetNextToken (tkn, odesc, (! MINUS_CHAR_REQUIRED)) >= 0)) {
	      /* rest of tkn contains arg */
	      opterr += handle (stProgramName, odesc, opt, tkn);
	      tkn = NULL;
	    }
	    else {
	      opterr += handle (stProgramName, odesc, opt, "");
	    }
	  }
	  break;
	case PARG:
	  if (!*tkn) {
	    /* end of this arg reached */
	    argv++;
	    tkn = *argv;
	    if (!tkn) {
	      /* end of argv reached */
	      opterr += handle (stProgramName, odesc, opt, "");
	      tkn = NULL;
	      argv--;
	    }
	    else {
	      opterr += handle (stProgramName, odesc, opt, tkn);
	      tkn = NULL;
	    }
	  }
	  else {
	    opterr += handle (stProgramName, odesc, opt, tkn);
	    tkn = NULL;
	  }
	  break;
	default:
	  /* ERROR: Non option detected as option */
	  return (-1);
	} /* end switch */
      } /* end then */
      else {
	/* argument is not a options */
	localArgv[*nargc] = tkn;
	(*nargc)++;
	tkn = NULL;
      }
    }
    /* select next argument */
    argv++;
  }

  localArgv[*nargc] = (char *) NULL;
  *nargv = localArgv;
  /* return # of errors. 0 means no error */
  return (opterr);
}
