#include <cstdio>
#include <cstring>
#include <vector>

#define no_argument 0
#define required_argument 1
#define optional_argument 2

struct option
{
    const char *name;
    int has_arg;
    int *flag;
    int val;
};

char *optarg = 0;
int optopt = 0;
int optind = 1;

std::vector<char *> free_arguments;
std::vector<char *> other_arguments;

int
getopt_long(int argc, char * /*const*/ argv[], const char *optstring,
            const struct option *longopts, int *longindex)
{
    // check that there are more args
    if (optind > (argc - 1))
    {
        unsigned int i = 1;
        for (std::vector<char *>::const_iterator it = other_arguments.begin();
             it != other_arguments.end(); it++)
        {
            argv[i] = *it;
            i++;
        }
        optind = (int)i;
        for (std::vector<char *>::const_iterator it = free_arguments.begin();
             it != free_arguments.end(); it++)
        {
            argv[i] = *it;
            i++;
        }
        return -1;
    }

    // skip free arguments
    while (*(argv[optind]) != '-')
    {
        free_arguments.push_back(argv[optind]);
        optind++;
        if (optind > (argc - 1))
        {
            unsigned int i = 1;
            for (std::vector<char *>::const_iterator it
                 = other_arguments.begin();
                 it != other_arguments.end(); it++)
            {
                argv[i] = *it;
                i++;
            }
            optind = (int)i;
            for (std::vector<char *>::const_iterator it
                 = free_arguments.begin();
                 it != free_arguments.end(); it++)
            {
                argv[i] = *it;
                i++;
            }
            return -1;
        }
    }

    other_arguments.push_back(argv[optind]);

    // strdup because we are possibly modifying the argument
    char *arg = strdup(argv[optind]); // free() should be called at the end...

    // skip initial '-' signs
    while (*arg == '-')
        arg++;

    // empty arg string
    if (*arg == '\0')
    {
        optopt = -2;
        return '?';
    }

    // whether arg is used in its short form: -f(=bar)
    bool short_option = false;
    arg++;
    if (*arg == '\0' || *arg == '=')
        short_option = true;
    arg--;

    // whether option argument is given after an '=' sign (--foo=bar, -f=bar)
    bool eq_used = false;
    char *argptr = arg; // this will point to the char after the '=' sign if
                        // eq_used is true
    while (*argptr != '\0')
    {
        if (*argptr == '=')
        {
            *argptr = '\0'; // change '=' into '\0' to make string comparison
                            // easier
            argptr++;
            eq_used = true;
            break;
        }
        argptr++;
    }

    // Go through all possible option strings
    while (longopts->name != 0)
    {
        // match found, short or long format
        if (strcmp(longopts->name, arg) == 0
            || (short_option && longopts->val == (int)*arg))
        {
            optind++;
            // no argument
            if (longopts->has_arg == no_argument)
            {
                // argument given for an option that does not take one
                if (eq_used)
                {
                    fprintf(stderr,
                            "warning: argument ignored for option '--%s'\n",
                            longopts->name);
                }
                return longopts->val;
            }
            // required argument
            else if (longopts->has_arg == required_argument
                     || longopts->has_arg == optional_argument)
            {
                if (eq_used)
                {
                    // we already have a pointer to the argument
                    optarg = strdup(argptr);
                    argptr--;
                    *argptr = '='; // change '\0' back to '=' (not sure if this
                                   // is needed...)
                    return longopts->val;
                }
                // no more args, argument thus missing
                if (optind > (argc - 1))
                {
                    if (longopts->has_arg == required_argument)
                    {
                        optopt = longopts->val;
                        return ':';
                    }
                    else
                    {
                        optopt = NULL;
                        return longopts->val;
                    }
                }
                // next arg is required argument (cannot be optional argument)
                if (longopts->has_arg == required_argument)
                {
                    optarg = strdup(argv[optind]);
                    other_arguments.push_back(argv[optind]);
                    optind++;
                    return longopts->val;
                }
                else
                {
                    optopt = NULL;
                    return longopts->val;
                }
            }
            // this should not happen
            else
            {
                return 0;
            }
        }
        longopts++;
    }

    // no match found
    optind++;
    optopt = -2;
    if (short_option)
        optopt = (int)*arg;
    return '?';
}
