/*
 *  This file is part of ixemul.library for the Amiga.
 *  Copyright (C) 1991, 1992  Markus M. Wild
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  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
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 *	This routine is called from the _main() routine and is used to
 *	parse the arguments passed from the CLI to the program. It sets
 *	up an array of pointers to arguments in the global variables and
 *	and sets up _argc and _argv which will be passed by _main() to
 *	the main() procedure. If no arguments are ever going to be
 *	parsed, this routine may be replaced by a stub routine to reduce
 *	program size.
 *
 *  ---------------
 *
 *  I modified this, so that if you set the variable
 *  "expand_cmd_line", wildcards in the command-line will be expanded
 *  according to ARP wildcard-rules. Normally, if you supply Wildcards
 *  along with a path, you'd expect to get expanded filenames together
 *  with their path, if you ONLY want to expand filenames, set the
 *  variable "dont_expand_full_path_name".
 *
 *  Example: normally, ram:t/* will get you
 *				ram:t/foo1 ram:t/foo2 ram:t/foo3 and so on
 *  but if you set "dont_expand_full_path_name", you'll instead get:
 *				foo1 foo2 foo3 and so on
 *
 *  1990-09-14 Markus Wild - first version
 *  1990-11-04 Markus Wild - modified for use in own libc
 *                           all Manx code removed, so now really free
 *  1991-12-15 Markus Wild - Use 2.04 DOS ASL functions instead of the ARP
 *			     stuff. They really *are* the ARP stuff ;-))
 *  1991-12-30 Markus Wild - Converted into form used by shared library
 *  1992-03-14 Markus Wild - Reenabled stdio code, now that stdio is in
 *			     shared library. Added signal blocking.
 */

#define KERNEL
#include "ixemul.h"

#ifdef DEBUG
#define DP(a) kprintf a
#else
#define DP(a)
#endif


#define NO_PROTOTYPES
/* we'll use inline headers, they would collide with these definitions.. */
#include <libraries/arpbase.h>
#undef NO_PROTOTYPES
#include <packets.h>
#include <string.h>
#include <ctype.h>

#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

#define BASE_EXT_DECL
#define BASE_PAR_DECL	
#define BASE_PAR_DECL0	
#define BASE_NAME	ix.ix_arp_base
#include <inline/arp.h>

#if __GNUC__ != 2
#define alloca __builtin_alloca
#endif

extern int _dos20;

/* CAN'T include inline20/dos.h, because this would collide with many
 * definitions inside arpbase.h. Those functions are 99% identical to the
 * ARP equivalents, they probably originated in ARP.. */
#define BASE_EXT_DECL
#define BASE_PAR_DECL	
#define BASE_PAR_DECL0	
#define BASE_NAME	ix.ix_dos_base
__inline static LONG MatchFirst(BASE_PAR_DECL const UBYTE* pat, struct AnchorPath* anchor)
{
	BASE_EXT_DECL
	register LONG res __asm("d0");
	register void *a6 __asm ("a6");
	register const UBYTE* d1 __asm("d1");
	register struct AnchorPath* d2 __asm("d2");

	a6 = BASE_NAME;
	d1 = pat;
	d2 = anchor;
	__asm volatile ("
	jsr a6@(-0x336)"
	: "=g" (res)
	: "g" (a6), "g" (d1), "g" (d2)
	: "d0", "d1", "a0", "a1", "d2");
	return res;
	*(char *)d2=*(char *)d2;
}
__inline static LONG MatchNext(BASE_PAR_DECL struct AnchorPath* anchor)
{
	BASE_EXT_DECL
	register LONG res __asm("d0");
	register void *a6 __asm ("a6");
	register struct AnchorPath* d1 __asm("d1");

	a6 = BASE_NAME;
	d1 = anchor;
	__asm volatile ("
	jsr a6@(-0x33c)"
	: "=g" (res)
	: "g" (a6), "g" (d1)
	: "d0", "d1", "a0", "a1");
	*(char *)d1=*(char *)d1;
	return res;
}
__inline static void MatchEnd(BASE_PAR_DECL struct AnchorPath* anchor)
{
	BASE_EXT_DECL
	register void *a6 __asm ("a6");
	register struct AnchorPath* d1 __asm("d1");

	a6 = BASE_NAME;
	d1 = anchor;
	__asm volatile ("
	jsr a6@(-0x342)"
	: /* no output */
	: "g" (a6), "g" (d1)
	: "d0", "d1", "a0", "a1");
	*(char *)d1=*(char *)d1;
}
#undef BASE_EXT_DECL
#undef BASE_PAR_DECL
#undef BASE_PAR_DECL0
#undef BASE_NAME

#ifndef MAXPATHLEN
#define MAXPATHLEN 1024
#endif

extern int __read(), __write(), __ioctl(), __fselect(), __close();


/* shudder what did they do.. with 2.04 patterns you have to specify ** instead
 * of * to get what you really want, a match-all pattern. compatibility is
 * something wonderful, but not this way! Get rid of it, QUICK ! */
static inline const UBYTE *
star_kludge (const char *patt)
{
  if (patt[0] == '*' && patt[1] == 0)
    return (const UBYTE *) "**";
  else
    return (const UBYTE *) patt;
}

/* We will store all arguments in this double linked list, the list
 * is always sorted according to elements, ie. order of given arguments
 * is preserved, but all elements, that get expanded will be sorted
 * alphabetically in this list. */

struct ArgList {
  struct MinList al_list;  /* the list - head */
  long		 al_num;   /* number of arguments in the whole list */
};

struct Argument {
  struct MinNode a_node;   /* the link in the arg-list */
  char		*a_arg;	   /* a malloc'd string, the argument */
};

/* insert a new argument into the argument vector, we have to keep the
 * vector sorted, but only element-wise, otherwise we would break the
 * order of arguments, and "copy b a" is surely not the same as "copy a b"..
 * so we don't scan the whole list, but start with element "start",
 * if set, else we start at the list head */

static void 
AddArgument (struct ArgList *ArgList,
	     struct Argument *start, struct Argument *arg, long size)
{
  register struct Argument *el, *nel;

  /* depending on "start", start scan for right position in list at
   * successor of start or at head of list  */
  for (el = (struct Argument *)
	    (start ? start->a_node.mln_Succ : ArgList->al_list.mlh_Head);
       nel = (struct Argument *) el->a_node.mln_Succ;
       el = nel)
    if (strcmp (el->a_arg, arg->a_arg) > 0) break;

  Insert ((struct List *) ArgList, (struct Node *) arg, (struct Node *) el);

  /* and bump up the argument counter once */
  ++ ArgList->al_num;
}

/* if an argument contains one or more of these characters, we have to
 * call the FindFirst/FindNext stuff, else don't bother expanding and
 * quickly append to list. Here's the meaning of all these characters, some
 * seem to be not widely used:
 * *	match any number (incl. zero) of characters
 * []   match any character that's contained in the set inside the brackets
 * ?	match any character (exactly one)
 * ~	negate the following expression
 * #	match any number (incl. zero) of the following expression
 * (|)  group expressions, the | seperates expressions, an empty expression
 *      is legal
 * %	the empty expression, can explicitely be given in groupings, 
 *	eg. copy foo(%|1|2) ram: will copy the files foo, foo1 & foo2 into
 *	the RAM disk. */

#if 0
#define iswild(ch) ((ch=='*') || (ch=='[') || (ch==']') || (ch=='?') || \
		   (ch=='~') || (ch=='#') || (ch=='(') || (ch==')') || \
		   (ch=='|') || (ch=='%'))
#else
#define iswild(ch) (index ("*[]?~#()|%", ch) ? 1 : 0)
#endif

void
_cli_parse(struct Process *this_proc, long alen, char *_aptr,
	   int *argc, char ***argv)
{
  char *arg0;
  struct CommandLineInterface *cli;
  int c;
  struct Argument *arg, *narg;
  char *line, *next, *lmax, **cpp;
  int do_expand, do_file_expand;
  int arglen;
  char *aptr;
  struct AnchorPath *ap;
  struct ArgList ArgList;
  int expand_cmd_line = u.u_expand_cmd_line;
  struct file *fin, *fout;
  int fd;  
  BPTR fh;
  int omask;

  /* this stuff has been in ix_open before, but it really belongs here, since
   * I don't want it to happen by default on OpenLibrary, since it would
   * disturb any vfork() that wants to inherit files from its parent 
   */

  omask = syscall (SYS_sigsetmask, ~0);
  ix_lock_base ();

  if (! falloc (&fin, &fd))
    {
      /*
       * NOTE: if there's an error creating one of the standard
       *       descriptors, we just go on, the descriptor in
       *       question will then not be set up, no problem ;-)
       */
      if (fd != 0)
	ix_panic ("allocated stdin is not fd #0!");
		       
      if (! falloc (&fout, &fd))
	{
	  if (fd != 1)
	    ix_panic ("allocated stdout is not fd #1!");

	  /* this opens stderr (if possible) */
	  syscall (SYS_open, "*", 2);

	  if (fh = Input ())
	    {
	      fin->f_name = "<Standard Input>";
	      fin->f_fh   = (struct FileHandle *)BTOCPTR(fh);
	      __init_std_packet(&fin->f_sp);
	      __fstat(fin);
	      fin->f_flags = FREAD|FEXTOPEN;
	      fin->f_type  = DTYPE_FILE;
	      fin->f_read  = __read;
	      fin->f_write = 0;
	      fin->f_ioctl = __ioctl;
	      fin->f_close = __close;
	      fin->f_select= __fselect;
	    }
	  else
	    {
	      u.u_ofile[0] = 0;
	      fin->f_count--;
	    }
			        
	  if (fh = Output ())
	    {
	      fout->f_name = "<Standard Output>";
	      fout->f_fh   = (struct FileHandle *)BTOCPTR(fh);
	      __init_std_packet(&fout->f_sp);
	      __fstat(fout);
	      fout->f_flags = FWRITE|FEXTOPEN;
	      fout->f_type  = DTYPE_FILE;
	      fout->f_read  = 0;
	      fout->f_write = __write;
	      fout->f_ioctl = __ioctl;
	      fout->f_close = __close;
	      fout->f_select= __fselect;
	    }
	  else
	    {
	      u.u_ofile[1] = 0;
	      fout->f_count--;
	    }
	} /* falloc (&fout, &fd) */
    } /* falloc (&fin, &fd) */

  ix_unlock_base ();
  /* base unlocked, but signals still blocked */

  aptr = alloca (alen + 1);
  bcopy (_aptr, aptr, alen + 1);

  cli = (struct CommandLineInterface *) BTOCPTR (this_proc->pr_CLI);
  arg0 = (char *) BTOCPTR (cli->cli_CommandName);

  /* init our argument list */
  NewList ((struct List *) &ArgList);
  /* lets start humble.. no arguments at all:-)) */
  ArgList.al_num = 0;

  /* we have to allocate
   * enough space to carry even the weirdest path-names...
   * memalign() is used, because struct Anchor contains a FileInfo
   * block, and this one *has* to be long-aligned for DOS not to produce
   * unpredictable things...
   */
  if (!expand_cmd_line)
    ap = syscall (SYS_memalign, 4, sizeof(*ap));
  else
    {
      ap = syscall (SYS_memalign, 4, sizeof(*ap)+MAXPATHLEN);
      ap->ap_StrLen = MAXPATHLEN - 1;
    }

  /* give the user a chance to break out of a neverending scan... */
  ap->ap_BreakBits = SIGBREAKF_CTRL_C;
  ap->ap_Flags = APF_DoDot;

  /* find end of command-line, stupid BCPL-stuff.. line can end
   * either with \n or with \0 .. */
  for (lmax = aptr; *lmax && *lmax != '\n' && *lmax != '\r'; ++lmax) ;
  *lmax = 0;

  /* loop over all arguments, expand all */
  for (line = aptr, narg = arg = 0; line < lmax; )
    {
      do_expand = 0;
      do_file_expand = 0;

      /* skip over leading whitespace */
      while (isspace (*line)) ++line;
      if (line >= lmax) break;

      /* strange kind of BCPL-quoting, if you want to get a " thru,
       * you have to quote it with a ', eq. HELLO'"WORLD'" will preserve
       * the " inside the argument. Since hardly anyone knows this
       * "feature", I allow for the more common Unix-like escaping, ie
       * \" will give you the same effect as '". */
      if ((*line == '\'' || *line == '\\') && line[1] == '\"')
	{
	  ++line;
	  goto parsenormal;
	}
      /* if argument starts with ", don't expand it and remove the " */
      else if (*line == '\"')
	{
	  /* scan for end of quoted argument, this can be either at
	   * end of argumentline or at a second " */
	  ++line;
	  next = line;
	  while (next < lmax && *next != '\"')
	    {
	      /* prevent, that the loop terminates due to an escaped
	       * quote */
	      if ((*next == '\'' || *next == '\\') && next[1] == '\"')
		{
		  /* in this case we have to shift the whole remaining
		   * line one position to the left to skip the 
		   * escape-character */
		  bcopy (next+1, next, (lmax - next) + 1);
		  --lmax;
		}

	      ++next;
	    }
	  *next = 0;
	}
      else
	{
	  if (*line == '@') do_file_expand = 1;
parsenormal:
	  /* plain, vanilla argument.. */
	  next = line+1;
	  /* check, whether we have to run thru the expander, or
	   * if we rather can just copy over the whole argument */
	  do_expand = iswild (*line);
	  /* skip over element and make it 0-terminated .. */
	  while (next < lmax && !isspace (*next))
	    {
	      do_expand |= iswild (*next);
	      if ((*next == '\'' || *next == '\\') && next[1] == '\"')
		{
		  bcopy (next+1, next, (lmax - next) + 1);
		  --lmax;
		}

	      ++next;
	    }
	  *next = 0;
	}

      if (do_file_expand)
	{
#if 1
	  /* now that stdio is in the shared library, I can resort to the
	     old algorithm, and don't need the read() kludge below, hiphip ;-) */

	  FILE *in;
	  char buf[1024], *cs, *ce;
	  
	  if (in = fopen (line+1, "r"))
	    {
	      while (!feof (in))
		{
		  if (! fgets (buf, 1024, in)) break;
		  for (cs = buf;
		       isspace (*cs) && (cs < &buf[1024]);
		       ++cs) ;
		  for (ce = buf+(strlen(cs) - 1);
		       isspace(*ce) && (ce > buf);
		       *ce-- = 0) ;

		  arg = (struct Argument *) malloc (sizeof (*arg));
		  arglen = strlen (cs);
		  arg->a_arg = (char *) malloc (arglen+1);
		  strcpy (arg->a_arg, cs);
		  AddArgument (&ArgList, narg, arg, arglen);
		  narg = arg;
		}
	      fclose (in);
	    }
#else
	  /* I don't want to use stdio at this point of the library. Since we
	   * know, that a file of this size is implemented with TYPE_MEMFILE
	   * anyway, we can just as well use sysio in this case */
	  int in;
	  char buf[1024], *cs, *ce;
	  
	  if ((in = syscall (SYS_open, line+1, 0)) >= 0)
	    {
	      for (;;)
		{
		  /* simulate an fgets() */
	      	  for (cs = buf;
		       syscall (SYS_read, in, cs, 1) == 1 && *cs++ != '\n'; ) ;
	      	  if (cs == buf) break;
	      	  *cs = 0;
	      	  
		  for (cs = buf;
		       isspace (*cs) && (cs < &buf[1024]);
		       ++cs) ;
		  for (ce = buf+(strlen(cs) - 1);
		       isspace (*ce) && (ce > buf);
		       *ce-- = 0) ;
		
		  arg = (struct Argument *) syscall (SYS_malloc, sizeof (*arg));
		  arglen = strlen (cs);
		  arg->a_arg = (char *) syscall (SYS_malloc, arglen+1);
		  strcpy (arg->a_arg, cs);
		  AddArgument (&ArgList, narg, arg, arglen);
		  narg = arg;
		}

	      syscall (SYS_close, in);
	    }
#endif
	}
      else if (expand_cmd_line && do_expand)
	{
	  int num_expand=0;

	  ap->ap_FoundBreak = 0;
	  /* init the scanner */
	  if (! (_dos20 ? MatchFirst (star_kludge (line), ap) 
			: FindFirst (line, ap)))
	    {
	      do
		{
		  register char *name;

		  /* signals are blocked, but we break this loop if one
		     is pending ! */
		     
		  if (u.p_sig) break;

		  arg = (struct Argument *) syscall (SYS_malloc, sizeof (*arg));
		  /* where will the name be stored ?? */
		  name = ap->ap_Buf;
		  arglen = strlen (name);
		  arg->a_arg = (char *) syscall (SYS_malloc, arglen+1);
		  strcpy (arg->a_arg, name);
		  AddArgument (&ArgList, narg, arg, arglen);
		  ++num_expand;
		  /* we do this as long as there are no errors. I
		   * don't care WHICH error will terminate the loop,
		   * if the regular ERROR_NO_MORE_ENTRIES, or some
		   * real error. */
		}
	      while (! (_dos20 ? MatchNext (ap) : FindNext (ap)));
	      /* after we're done with the work, we have to free
	       * the whole anchor-list, to make it usable again
	       * for a new argument */
	      if (_dos20)
		MatchEnd (ap);
	      else
		FreeAnchorChain (ap);
	    }

	  /* if break, really break.. stop scanning arguments...*/
	  if (ap->ap_FoundBreak || u.p_sig)
	    {
	      syscall (SYS_sigsetmask, omask);	/* this might already abort.. */
	      syscall (SYS_write, 2, (UBYTE *) "^C\n", 3);
	      syscall (SYS__exit, 20);
	    }

	  /* simulate Unix sh, if there was a wildcard in the
	   * argument, but it didn't expand into anything, pass the
	   * original argument to the program. Csh on the other hand
	   * just outputs an error-msg "no match", but I think this
	   * should be handled by the program... */
	  if (!num_expand) goto vanilla_arg;
	}
      else  /* ! do_expand */
	{
vanilla_arg:
	  /* just add the argument "as is" */
	  arg = (struct Argument *) syscall (SYS_malloc, sizeof (*arg));
	  arglen = strlen (line);
	  arg->a_arg = (char *) syscall (SYS_malloc, arglen+1);
	  strcpy (arg->a_arg, line);
	  AddArgument (&ArgList, narg, arg, arglen);
	}

      narg = (struct Argument *) ArgList.al_list.mlh_TailPred;
      line = next+1;
    } /* for */

  /* prepend the program name */
  arg = (struct Argument *) syscall (SYS_malloc, sizeof (*arg));

  /* some stupid shells (like Wsh...) pass the WHOLE path of the
   * started program. We simply cut off what we don't want ;-)) */
  for (arglen = arg0[0]; arglen; --arglen)
    if (arg0[arglen] == ':' || arg0[arglen] == '/') break;

  line = &arg0[arglen+1];
  arglen = arg0[0] - arglen;

  arg->a_arg = (char *) syscall (SYS_malloc, arglen+1);

  strncpy (arg->a_arg, line, arglen);
  arg->a_arg[arglen] = 0;
  /* did I tell you, that I like those kernel list calls ?? */
  AddHead ((struct List*) &ArgList, (struct Node*) arg);
  ++ ArgList.al_num;

  /* build _argv array */
  *argv = (char **) syscall (SYS_malloc, (ArgList.al_num+1) * sizeof(char *));
  for (cpp = *argv, arg = (struct Argument *) ArgList.al_list.mlh_Head;
       narg = (struct Argument *) arg->a_node.mln_Succ;
       arg = narg)
    *cpp++ = arg->a_arg;

  /* guarantee last element == 0 */		
  *cpp = 0;
  *argc = ArgList.al_num;

  syscall (SYS_sigsetmask, omask);
}
