/*
 Author: Marcus Boerger <helly@users.sourceforge.net>
*/

/* $Id: mbo_getopt.cc 698 2007-04-23 21:06:56Z helly $ */

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include "mbo_getopt.h"
#define OPTERRCOLON (1)
#define OPTERRNF (2)
#define OPTERRARG (3)

namespace re2c
{

static int mbo_opt_error(int, char * const *argv, int oint, int optchr, int err, int show_err)
{
	if (show_err)
	{
		fprintf(stderr, "Error in argument %d, char %d: ", oint, optchr + 1);

		switch (err)
		{

			case OPTERRCOLON:
			fprintf(stderr, ": in flags\n");
			break;

			case OPTERRNF:
			fprintf(stderr, "option not found %c\n", argv[oint][optchr]);
			break;

			case OPTERRARG:
			fprintf(stderr, "no argument for option %c\n", argv[oint][optchr]);
			break;

			default:
			fprintf(stderr, "unknown\n");
			break;
		}
	}

	return ('?');
}

int mbo_getopt(int argc, char* const *argv, const mbo_opt_struct *opts, char **optarg, int *optind, int show_err)
{
	static int optchr = 0;
	static int dash = 0; /* have already seen the - */
	int arg_start = 2;

	int opts_idx = -1;

	if (*optind >= argc)
	{
		return (EOF);
	}

	if (!dash)
	{
		if ((argv[*optind][0] != '-'))
		{
			return (EOF);
		}
		else
		{
			if (!argv[*optind][1])
			{
				/*
				* use to specify stdin. Need to let pgm process this and
				* the following args
				*/ 
				return (EOF);
			}
		}
	}

	if ((argv[*optind][0] == '-') && (argv[*optind][1] == '-'))
	{
		/* '--' indicates end of args if not followed by a known long option name */
		if (argv[*optind][2] == '\0') {
			(*optind)++;
			return(EOF);
		}

		while (1)
		{
			opts_idx++;

			if (opts[opts_idx].opt_char == '-')
			{
				(*optind)++;
				return (mbo_opt_error(argc, argv, *optind - 1, optchr, OPTERRARG, show_err));
			}
			else if (opts[opts_idx].opt_name && !strcmp(&argv[*optind][2], opts[opts_idx].opt_name))
			{
				break;
			}
		}

		optchr = 0;
		dash = 0;
		arg_start = 2 + strlen(opts[opts_idx].opt_name);
	}

	else
	{
		if (!dash)
		{
			dash = 1;
			optchr = 1;
		}

		/* Check if the guy tries to do a -: kind of flag */
		if (argv[*optind][optchr] == ':')
		{
			dash = 0;
			(*optind)++;
			return (mbo_opt_error(argc, argv, *optind - 1, optchr, OPTERRCOLON, show_err));
		}
		arg_start = 1 + optchr;
	}

	if (opts_idx < 0)
	{
		while (1)
		{
			opts_idx++;

			if (opts[opts_idx].opt_char == '-')
			{
				int errind = *optind;
				int errchr = optchr;

				if (!argv[*optind][optchr + 1])
				{
					dash = 0;
					(*optind)++;
				}
				else
				{
					optchr++;
					arg_start++;
				}

				return (mbo_opt_error(argc, argv, errind, errchr, OPTERRNF, show_err));
			}
			else if (argv[*optind][optchr] == opts[opts_idx].opt_char)
			{
				break;
			}
		}
	}

	if (opts[opts_idx].need_param)
	{
		/* Check for cases where the value of the argument
		is in the form -<arg> <val> or in the form -<arg><val> */
		dash = 0;

		if (!argv[*optind][arg_start])
		{
			(*optind)++;

			if (*optind == argc)
			{
				return (mbo_opt_error(argc, argv, *optind - 1, optchr, OPTERRARG, show_err));
			}

			*optarg = argv[(*optind)++];
		}
		else
		{
			*optarg = &argv[*optind][arg_start];
			(*optind)++;
		}

		return opts[opts_idx].opt_char;
	}
	else
	{
		if (arg_start >= 2 && !((argv[*optind][0] == '-') && (argv[*optind][1] == '-')))
		{
			if (!argv[*optind][optchr + 1])
			{
				dash = 0;
				(*optind)++;
			}
			else
			{
				optchr++;
			}
		}
		else
		{
			(*optind)++;
		}

		return opts[opts_idx].opt_char;
	}

	assert(0);
	return (0);	/* never reached */
}

} // end namespace re2c

