/* for utility

    FOR var IN list DO cmd_list DONE

  list may contain wildcards
  cmd_list is a semicolon separated list of commands with options
    (and $var). Several commands may be included in () to execute them
    in a subshell. The command CD is handled in a special way.

  The following two lines are equal

    FOR dir IN dir1 dir2 dir2 DO cd $dir ";" dir ";" cd / DONE
    FOR dir IN dir1 dir2 dir2 DO ( cd $dir ";" dir ) DONE
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <exec/memory.h>
#include <dos/dos.h>
#include <dos/dostags.h>
#include <dos/dosasl.h>
#include <proto/exec.h>
#include <proto/dos.h>

#include "util.h"

#define FULLPATHSIZE	    1024L
#define ANCHORSIZE	    (sizeof (struct AnchorPath) + FULLPATHSIZE)

static int show_debug;

int for_command (int, char **);
int cd_command (char *);
int execute_commands (int, char **);

/* Remember the current dir and start the first level of FOR */
int main (int argc, char ** argv)
{
    int status;
    BPTR cd, orig;

    if (!(cd = Lock ("", SHARED_LOCK)) )
    {
	PrintFault (IoErr(), "FOR: Cannot lock current directory");
	return 20;
    }

    orig = CurrentDir (cd);

    status = for_command (argc, argv);

    UnLock (CurrentDir (orig));

    return status;
}

/* Set a local variable according to a list of patterns and execute a
    list of commands with each pattern */
int for_command (int argc, char ** argv)
{
    char * var;
    char * pat;
    int first_pat, last_pat;
    int first_cmd, last_cmd;
    int t, level, len, has_wild;
    int status = 0;

    /* There have to be 5 args min: FOR var IN DO DONE */
    if (argc < 5 || argv[1][0] == '?')
    {
show_usage:
	puts ("Usage: FOR var IN list DO cmd_list DONE");
	return 10;
    }

    t = 1;

    /* Enable debugging */
    if (argv[t][0] == '-' && argv[t][1] == 'd')
    {
	show_debug = 1;
	t ++;
    }

    /* Read name of variable */
    var = argv[t ++];

    /* Show it, if neccessary */
    if (show_debug)
	printf ("Var=\"%s\"\n", var);

    /* Now IN must follow */
    if (strcmpi (argv[t], "IN"))
    {
	fprintf (stderr, "FOR: Missing IN\n");
	goto show_usage;
    }

    t ++;

    /* This is the start of the patterns */
    first_pat = t;

    /* Search until the end of arguments or DO is found */
    while (t<argc && strcmpi (argv[t],"DO"))
	t ++;

    /* If we ran out of arguments, this is an error */
    if (t == argc)
    {
	fprintf (stderr, "FOR: Missing DO\n");
	goto show_usage;
    }

    /* Set the end of patterns to the pre-last visited arg */
    last_pat = t-1;

    /* If the last pattern is ";" (like in the bourne shell), ignore it */
    if (argv[last_pat][0] == ';' && !argv[last_pat][1])
	last_pat--;

    /* If there are no patterns, don't do anything (this is NO error !) */
    if (last_pat < first_pat)
	return 0;

    /* Skip DO */
    t ++;

    /* If the first command is ";" (like in the bourne shell), ignore it */
    if (argv[t][0] == ';' && !argv[t][1])
	t ++;

    /* Now check the rest of the line. The current position is the first
       command to execute */
    first_cmd = t;

    /* Search until the end of arguments or DONE is found and that DONE
	belongs to the current FOR */
    level = 0;
    while (t<argc)
    {
	if (!strcmpi (argv[t], "FOR"))
	    level ++;
	else if (!strcmpi (argv[t], "DONE"))
	{
	    if (!level)
		break;

	    level --;
	}

	t ++;
    }

    /* If there are more arguments or the last thing is not DONE, show
	usage */
    if (t == argc || level)
    {
	fprintf (stderr, "FOR: Missing DONE (t=%d, argc=%d, level=%d)\n",
		t, argc, level);
	goto show_usage;
    }

    /* Set the pointer to the last cmd */
    last_cmd = t-1;

    /* If the last pattern is ";" (like in the bourne shell), ignore it */
    if (argv[last_cmd][0] == ';' && !argv[last_cmd][1])
	last_cmd --;

    /* If's ok to have no commands at all */
    if (last_cmd < first_cmd)
	return 0;

    /* Now execute the commands for each pattern */
    for (t=first_pat; t<=last_pat && !status; t++)
    {
	len = (strlen (argv[t])+1)*2;
	pat = AllocMem (len, MEMF_ANY);

	if (!pat)
	{
	    fprintf (stderr, "FOR: Ran our of memory\n");
	    return 20;
	}

	has_wild = ParsePatternNoCase (argv[t], pat, len);

	switch (has_wild)
	{
	case -1: /* Error in pattern */
	    fprintf (stderr, "FOR: Error parsing \"%s\":", argv[t]);
	    PrintFault (IoErr(), NULL);
	    status = 10;
	    break;

	case 0: /* No pattern, just a simple string */
	    if (!SetVar (var, argv[t], -1, GVF_LOCAL_ONLY))
	    {
		fprintf (stderr, "FOR: Error setting %s to \"%s\":", var,
		    argv[t]);
		PrintFault (IoErr(), "FOR: Error parsing \"%s\":");
		status = 10;
	    }
	    else
		status = execute_commands (last_cmd-first_cmd+1,
		    &argv[first_cmd]);
	    break;

	case 1: { /* Wildcards */
	    struct AnchorPath * ap;
	    LONG error;

	    if (!(ap = AllocMem (ANCHORSIZE, MEMF_CLEAR)) )
	    {
		fprintf (stderr, "FOR: Ran out of memory\n");
		status = 10;
	    }
	    else
	    {
		ap->ap_BreakBits = SIGBREAKF_CTRL_C;
		ap->ap_Strlen	 = FULLPATHSIZE;

		for (error=MatchFirst(argv[t],ap); !error && !status;
		    error=MatchNext(ap))
		{
		    if (!SetVar (var, ap->ap_Buf, -1, GVF_LOCAL_ONLY))
		    {
			fprintf (stderr, "FOR: Error setting %s to \"%s\":",
			    var, argv[t]);
			PrintFault (IoErr(), "FOR: Error parsing \"%s\":");
			status = 10;
		    }
		    else
			status = execute_commands (last_cmd-first_cmd+1,
			    &argv[first_cmd]);
		}

		if (error != ERROR_NO_MORE_ENTRIES)
		{
		    PrintFault (error, "FOR");
		    status = 10;
		}

		FreeMem (ap, ANCHORSIZE);
	    }
	    break; }
	}

	FreeMem (pat, len);
    }

    return status;
} /* for_command */

/* Execute a list of commands. Each command is separated by ";".
    Commands may be grouped in () to form a sub-shell. Currently
    this sub-shell just restores the current directory */
int execute_commands (int argc, char ** argv)
{
    int t;
    int status = 0;
    int first_arg, last_arg;
    int level;
    BPTR cd, orig;

    /* Check all arguments. Note that this is not the only
	place where t might change */
    for (t=0; t<argc && !status; t++)
    {
	/* Find out where we are */
	if (argv[t][0] == '(' && !argv[t][1]) /* Subshell */
	{
	    if ((cd = Lock ("", SHARED_LOCK)))
	    {
		/* Find all commands which are in the subshell */
		first_arg = ++t;
		level = 0;

		while (t<argc)
		{
		    if (argv[t][0] == '(' && !argv[t][1])
			level ++;
		    else if (argv[t][0] == ')' && !argv[t][1])
		    {
			if (!level)
			    break;

			level --;
		    }

		    t ++;
		}

		if (t != argc)
		{
		    last_arg = t-1;

		    /* Remeber the current dir */
		    orig = CurrentDir (cd);

		    /* Execute the commands of the sub-shell */
		    status = execute_commands (last_arg-first_arg+1,
			&argv[first_arg]);

		    /* Come back to the original dir */
		    UnLock (CurrentDir (orig));
		}
		else
		{
		    fprintf (stderr, "FOR: Missing ) in sub shell\n");
		    status = 10;
		}
	    }
	    else
	    {
		PrintFault (IoErr(), "FOR: Cannot lock current dir");
		status = 10;
	    }
	}
	else if (!strcmpi (argv[t], "CD")) /* cd command */
	{
	    /* ignore the CD */
	    t ++;

	    /* Change the dir */
	    status = cd_command (t!=argc ? argv[t] : NULL);
	}
	else /* Normal command */
	{
	    first_arg = t;

	    while (t<argc && !(argv[t][0] == ';' && !argv[t][1]))
		t ++;

	    last_arg = t-1;

	    status = execute_command (last_arg-first_arg+1, &argv[first_arg]);
	}
    }

    return status;
} /* execute_commands */

int cd_command (char * path)
{
    BPTR lock;
    char * realpath;

    if (path)
    {
	realpath = replace_vars (path);

	if (show_debug)
	    printf ("CD (%s)\n", realpath);

	if (!(lock = Lock (realpath, SHARED_LOCK)) )
	{
	    fprintf (stderr, "FOR: Cannot Lock \"%s\"\n", realpath);
	    free (realpath);
	    PrintFault (IoErr(), NULL);
	    return 10;
	}

	free (realpath);
	UnLock (CurrentDir (lock));
    }
    else
    {
	if (!(lock = Lock ("", SHARED_LOCK)) )
	{
	    fprintf (stderr, "FOR: Cannot Lock current dir");
	    PrintFault (IoErr(), NULL);
	    return 10;
	}

	realpath = malloc (4096);

	NameFromLock (lock, realpath, 4096);
	puts (realpath);
	free (realpath);
    }

    return 0;
} /* cd_command */
