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

/* Public Domain (file originally from re2c) */

#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 (-1);
    }

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

    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(-1);
        }

        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

