/*
   +----------------------------------------------------------------------+
   | Copyright © The PHP Group and Contributors.                          |
   +----------------------------------------------------------------------+
   | This source file is subject to the Modified BSD License that is      |
   | bundled with this package in the file LICENSE, and is available      |
   | through the World Wide Web at <https://www.php.net/license/>.        |
   |                                                                      |
   | SPDX-License-Identifier: BSD-3-Clause                                |
   +----------------------------------------------------------------------+
   | Author: Marcus Boerger <helly@php.net>                               |
   +----------------------------------------------------------------------+
*/

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include "php_getopt.h"

#define OPTERRCOLON 1
#define OPTERRNF 2
#define OPTERRARG 3

// Print error message to stderr and return -2 to distinguish it from '?' command line option.
static int php_opt_error(int argc, char * const *argv, int optint, int optchr, int err, int show_err) /* {{{ */
{
	if (show_err) {
		fprintf(stderr, "Error in argument %d, char %d: ", optint, optchr+1);
		switch(err)
		{
		case OPTERRCOLON:
			fprintf(stderr, ": in flags\n");
			break;
		case OPTERRNF:
			fprintf(stderr, "option not found %c\n", argv[optint][optchr]);
			break;
		case OPTERRARG:
			fprintf(stderr, "no argument for option %c\n", argv[optint][optchr]);
			break;
		default:
			fprintf(stderr, "unknown\n");
			break;
		}
	}
	return PHP_GETOPT_INVALID_ARG;
}
/* }}} */

PHPAPI int php_optidx = -1;

PHPAPI int php_getopt(int argc, char* const *argv, const opt_struct opts[], char **optarg, int *optind, int show_err, int arg_start) /* {{{ */
{
	static int optchr = 0;
	static int dash = 0; /* have already seen the - */
	static char **prev_optarg = NULL;

	php_optidx = -1;
	if (optarg) {
		*optarg = NULL;
	}

	if (prev_optarg && prev_optarg != optarg) {
		/* reset the state */
		optchr = 0;
		dash = 0;
	}
	prev_optarg = optarg;

	if (*optind >= argc) {
		return EOF;
	}
	if (!dash) {
		if (argv[*optind][0] !=  '-') {
			return EOF;
		}
		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] == '-')) {
		const char *pos;
		size_t arg_end = strlen(argv[*optind])-1;

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

		arg_start = 2;

		/* Check for <arg>=<val> */
		if ((pos = memchr(&argv[*optind][arg_start], '=', arg_end - arg_start)) != NULL) {
			arg_end = pos-&argv[*optind][arg_start];
			arg_start++;
		} else {
			arg_end--;
		}

		while (1) {
			php_optidx++;
			if (opts[php_optidx].opt_char == '-') {
				(*optind)++;
				return php_opt_error(argc, argv, *optind-1, optchr, OPTERRARG, show_err);
			} else if (opts[php_optidx].opt_name && !strncmp(&argv[*optind][2], opts[php_optidx].opt_name, arg_end) && arg_end == strlen(opts[php_optidx].opt_name)) {
				break;
			}
		}

		optchr = 0;
		dash = 0;
		arg_start += (int)strlen(opts[php_optidx].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 php_opt_error(argc, argv, *optind-1, optchr, OPTERRCOLON, show_err);
		}
		arg_start = 1 + optchr;
	}
	if (php_optidx < 0) {
		while (1) {
			php_optidx++;
			if (opts[php_optidx].opt_char == '-') {
				int errind = *optind;
				int errchr = optchr;

				if (!argv[*optind][optchr+1]) {
					dash = 0;
					(*optind)++;
				} else {
					optchr++;
					arg_start++;
				}
				return php_opt_error(argc, argv, errind, errchr, OPTERRNF, show_err);
			} else if (argv[*optind][optchr] == opts[php_optidx].opt_char) {
				break;
			}
		}
	}
	if (opts[php_optidx].need_param) {
		/* Check for cases where the value of the argument
		is in the form -<arg> <val>, -<arg>=<varl> or -<arg><val> */
		dash = 0;
		if (!argv[*optind][arg_start]) {
			(*optind)++;
			if (*optind == argc) {
				/* Was the value required or is it optional? */
				if (opts[php_optidx].need_param == 1) {
					return php_opt_error(argc, argv, *optind-1, optchr, OPTERRARG, show_err);
				}
			/* Optional value is not supported with -<arg> <val> style */
			} else if (opts[php_optidx].need_param == 1) {
				*optarg = argv[(*optind)++];
			}
		} else if (argv[*optind][arg_start] == '=') {
			arg_start++;
			*optarg = &argv[*optind][arg_start];
			(*optind)++;
		} else {
			*optarg = &argv[*optind][arg_start];
			(*optind)++;
		}
		return opts[php_optidx].opt_char;
	} else {
		/* multiple options specified as one (exclude long opts) */
		if (arg_start >= 2 && !((argv[*optind][0] == '-') && (argv[*optind][1] == '-'))) {
			if (!argv[*optind][optchr+1]) {
				dash = 0;
				(*optind)++;
			} else {
				optchr++;
			}
		} else {
			(*optind)++;
		}
		return opts[php_optidx].opt_char;
	}
	assert(0);
	return 0;	/* never reached */
}
/* }}} */
