/*
 *	hexdump - dump a file as hexadecimal
 *	Copyright (c) 1991 Peter Miller
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 1, or (at your option)
 *	any later version.
 *
 *	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 GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <math.h>

#include <main.h>
#include <arglex.h>
#include <error.h>
#include <mem.h>


static arglex_table_t table[] =
{
	{ "-",			arglex_token_stdio,		},
	{ "-Help",		arglex_token_help,		},
	{ "-List",		arglex_token_list,		},
	{ "-VERSion",		arglex_token_version,		},
	0, /* end marker */
};

static int	argc;
static char	**argv;
arglex_value_t	arglex_value;
arglex_token_t	arglex_token;
char		*progname;
static arglex_table_t *utable;
static	char	*partial;


static char *basename _((char *));

static char *
basename(s)
	char	*s;
{
	char	*bp;
	char	*ep;

	bp = s;
	ep = 0;
	while (*s)
	{
		if (s[0] == '/' && s[1] && s[1] != '/')
			bp = s + 1;
		if (s > bp && s[0] == '/' && s[-1] != '/')
			ep = s;
		++s;
	}
	if (!ep)
		ep = s;
	*s = 0;
	return bp;
}

void
arglex_init(ac, av, tp)
	int	ac;
	char	**av;
	arglex_table_t *tp;
{
	progname = basename(av[0]);

	argc = ac - 1;
	argv = av + 1;
	utable = tp;
}


int
arglex_compare(formal, actual)
	char	*formal;
	char	*actual;
{
	char	fc;
	char	ac;
	int	result;

	for (;;)
	{
		ac = *actual++;
		if (isupper(ac))
			ac = tolower(ac);
		fc = *formal++;
		switch (fc)
		{
		case 0:
			result = !ac;
			goto ret;
			
		case '_':
			if (ac == '-')
				break;
			/* fall through... */

		case 'a': case 'b': case 'c': case 'd': case 'e':
		case 'f': case 'g': case 'h': case 'i': case 'j':
		case 'k': case 'l': case 'm': case 'n': case 'o':
		case 'p': case 'q': case 'r': case 's': case 't':
		case 'u': case 'v': case 'w': case 'x': case 'y':
		case 'z': 
			/*
			 * optional characters
			 */
			if (ac == fc && arglex_compare(formal, actual))
			{
				result = 1;
				goto ret;
			}
			/*
			 * skip forward to next
			 * mandatory character, or after '_'
			 */
			while (islower(*formal))
				++formal;
			if (*formal == '_')
			{
				++formal;
				if (ac == '_' || ac == '-')
					++actual;
			}
			--actual;
			break;

		case '*':
			/*
			 * This is a hack, it should really 
			 * check for a match match the stuff after
			 * the '*', too, a la glob.
			 */
			if (!ac)
			{
				result = 0;
				goto ret;
			}
			partial = actual - 1;
			result = 1;
			goto ret;

		case '\\':
			if (actual[-1] != *formal++)
			{
				result = 0;
				goto ret;
			}
			break;

		case 'A': case 'B': case 'C': case 'D': case 'E':
		case 'F': case 'G': case 'H': case 'I': case 'J':
		case 'K': case 'L': case 'M': case 'N': case 'O':
		case 'P': case 'Q': case 'R': case 'S': case 'T':
		case 'U': case 'V': case 'W': case 'X': case 'Y':
		case 'Z': 
			fc = tolower(fc);
			/* fall through... */

		default:
			/*
			 * mandatory characters
			 */
			if (fc != ac)
			{
				result = 0;
				goto ret;
			}
			break;
		}
	}
ret:
	return result;
}


static int
is_a_number(s)
	char	*s;
{
	long	n;
	int	sign;

	n = 0;
	switch (*s)
	{
	default:
		sign = 1;
		break;

	case '+':
		s++;
		sign = 1;
		break;

	case '-':
		s++;
		sign = -1;
		break;
	}
	switch (*s)
	{
	case '0':
		if ((s[1] == 'x' || s[1] == 'X') && s[2])
		{
			s += 2;
			for (;;)
			{
				switch (*s)
				{
				case '0': case '1': case '2': case '3':
				case '4': case '5': case '6': case '7':
				case '8': case '9':
					n = n * 16 + *s++ - '0';
					continue;

				case 'A': case 'B': case 'C':
				case 'D': case 'E': case 'F':
					n = n * 16 + *s++ - 'A' + 10;
					continue;

				case 'a': case 'b': case 'c':
				case 'd': case 'e': case 'f':
					n = n * 16 + *s++ - 'a' + 10;
					continue;
				}
				break;
			}
		}
		else
		{
			for (;;)
			{
				switch (*s)
				{
				case '0': case '1': case '2': case '3':
				case '4': case '5': case '6': case '7':
					n = n * 8 + *s++ - '0';
					continue;
				}
				break;
			}
		}
		break;

	case '1': case '2': case '3': case '4':
	case '5': case '6': case '7': case '8': case '9':
		for (;;)
		{
			switch (*s)
			{
			case '0': case '1': case '2': case '3':
			case '4': case '5': case '6': case '7':
			case '8': case '9':
				n = n * 10 + *s++ - '0';
				continue;
			}
			break;
		}
		break;

	default:
		return 0;
	}
	if (*s)
		return 0;
	arglex_value.alv_number = n * sign;
	return 1;
}


arglex_token_t
arglex()
{
	arglex_table_t	*tp;
	int		j;
	arglex_table_t	*hit[20];
	int		nhit;

	if (argc <= 0)
	{
		arglex_token = arglex_token_eoln;
		arglex_value.alv_string = "";
		goto ret;
	}
	arglex_value.alv_string = argv[0];
	argc--;
	argv++;

	if (is_a_number(arglex_value.alv_string))
	{
		arglex_token = arglex_token_number;
		goto ret;
	}

	nhit = 0;
	partial = 0;
	for (tp = table; tp->name; tp++)
	{
		if (arglex_compare(tp->name, arglex_value.alv_string))
			hit[nhit++] = tp;
	}
	if (utable)
	{
		for (tp = utable; tp->name; tp++)
		{
			if (arglex_compare(tp->name, arglex_value.alv_string))
				hit[nhit++] = tp;
		}
	}
	switch (nhit)
	{
	case 0:
		break;

	case 1:
		if (partial)
			arglex_value.alv_string = partial;
		arglex_token = hit[0]->token;
		goto ret;

	default:
		{
			size_t	len;
			char	*buf;

			len = strlen(hit[0]->name + 1);
			for (j = 1; j < nhit; ++j)
				len += strlen(hit[j]->name) + 2;
			buf = mem_alloc(len);
			strcpy(buf, hit[0]->name);
			for (j = 1; j < nhit; ++j)
			{
				strcat(buf, ", ");
				strcat(buf, hit[j]->name);
			}
			fatal
			(
				"option \"%s\" ambiguous (%s)",
				arglex_value.alv_string,
				buf
			);
		}
	}

	/* not found in the table */
	if (arglex_value.alv_string[0] == '-')
		arglex_token = arglex_token_option;
	else
		arglex_token = arglex_token_string;

ret:
#if 0
	printf("arglex %d\n", arglex_token); 
#endif
	return arglex_token;
}
