/*
 * getopt - POSIX like getopt for Windows console Application
 *
 * win-c - Windows Console Library
 * Copyright (c) 2015 Koji Takami
 * Released under the MIT license
 * https://github.com/takamin/win-c/blob/master/LICENSE
 */
#include <stdio.h>
#include <string.h>
#include "getopt.h"

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

int postpone_count = 0;
int nextchar = 0;

static void postpone(int argc, char* const argv[], int index) {
    char** nc_argv = (char**)argv;
    char* p = nc_argv[index];
    int j = index;
    for(; j < argc - 1; j++) {
        nc_argv[j] = nc_argv[j + 1];
    }
    nc_argv[argc - 1] = p;
}
static int postpone_noopt(int argc, char* const argv[], int index) {
    int i = index;
    for(; i < argc; i++) {
        if(*(argv[i]) == '-') {
            postpone(argc, argv, index);
            return 1;
        }
    }
    return 0;
}
static int _getopt_(int argc, char* const argv[],
        const char* optstring,
        const struct option* longopts, int* longindex)
{
    while(1) {
        int c;
        const char* optptr = 0;
        if(optind >= argc - postpone_count) {
            c = 0;
            optarg = 0;
            break;
        }
        c = *(argv[optind] + nextchar);
        if(c == '\0') {
            nextchar = 0;
            ++optind;
            continue;
        }
        if(nextchar == 0) {
            if(optstring[0] != '+' && optstring[0] != '-') {
                while(c != '-') {
                    /* postpone non-opt parameter */
                    if(!postpone_noopt(argc, argv, optind)) {
                        break; /* all args are non-opt param */
                    }
                    ++postpone_count;
                    c = *argv[optind];
                }
            }
            if(c != '-') {
                if(optstring[0] == '-') {
                    optarg = argv[optind];
                    nextchar = 0;
                    ++optind;
                    return 1;
                }
                break;
            } else {
                if(strcmp(argv[optind], "--") == 0) {
                    optind++;
                    break;
                }
                ++nextchar;
                if(longopts != 0 && *(argv[optind] + 1) == '-') {
                    char const* spec_long = argv[optind] + 2;
                    char const* pos_eq = strchr(spec_long, '=');
                    int spec_len = (pos_eq == NULL) ?
                      (int)strlen(spec_long) : (int)(pos_eq - spec_long);
                    int index_search = 0;
                    int index_found = -1;
                    const struct option* optdef = 0;
                    while(longopts->name != 0) {
                        if(strncmp(spec_long, longopts->name, spec_len) == 0) {
                            if(optdef != 0) {
                                if(opterr) {
                                    fprintf(stderr, "ambiguous option: %s\n", spec_long);
                                }
                                return '?';
                            }
                            optdef = longopts;
                            index_found = index_search;
                        }
                        longopts++;
                        index_search++;
                    }
                    if(optdef == 0) {
                        if(opterr) {
                            fprintf(stderr, "no such a option: %s\n", spec_long);
                        }
                        return '?';
                    }
                    switch(optdef->has_arg) {
                        case no_argument:
                            optarg = 0;
                            if(pos_eq != 0) {
                                if(opterr) {
                                    fprintf(stderr, "no argument for %s\n", optdef->name);
                                }
                                return '?';
                            }
                            break;
                        case required_argument:
                            if(pos_eq == NULL) {
                                ++optind;
                                optarg = argv[optind];
                            } else {
                                optarg = (char*)pos_eq + 1;
                            }
                            break;
                    }
                    ++optind;
                    nextchar = 0;
                    if(longindex != 0) {
                        *longindex = index_found;
                    }
                    if(optdef->flag != 0) {
                        *optdef->flag = optdef->val;
                        return 0;
                    }
                    return optdef->val;
                }
                continue;
            }
        }
        optptr = strchr(optstring, c);
        if(optptr == NULL) {
            optopt = c;
            if(opterr) {
                fprintf(stderr,
                        "%s: invalid option -- %c\n",
                        argv[0], c);
            }
            ++nextchar;
            return '?';
        }
        if(*(optptr+1) != ':') {
            nextchar++;
            if(*(argv[optind] + nextchar) == '\0') {
                ++optind;
                nextchar = 0;
            }
            optarg = 0;
        } else {
            nextchar++;
            if(*(argv[optind] + nextchar) != '\0') {
                optarg = argv[optind] + nextchar;
            } else {
                ++optind;
                if(optind < argc - postpone_count) {
                    optarg = argv[optind];
                } else {
                    optopt = c;
                    if(opterr) {
                        fprintf(stderr,
                            "%s: option requires an argument -- %c\n",
                            argv[0], c);
                    }
                    if(optstring[0] == ':' ||
                       ((optstring[0] == '-' || optstring[0] == '+') &&
                        optstring[1] == ':'))
                    {
                        c = ':';
                    } else {
                        c = '?';
                    }
                }
            }
            ++optind;
            nextchar = 0;
        }
        return c;
    }

    /* end of option analysis */

    /* fix the order of non-opt params to original */
    while((argc - optind - postpone_count) > 0) {
        postpone(argc, argv, optind);
        ++postpone_count;
    }

    nextchar = 0;
    postpone_count = 0;
    return -1;
}

int getopt(int argc, char* const argv[],
            const char* optstring)
{
    return _getopt_(argc, argv, optstring, 0, 0);
}
int getopt_long(int argc, char* const argv[],
        const char* optstring,
        const struct option* longopts, int* longindex)
{
    return _getopt_(argc, argv, optstring, longopts, longindex);
}
/********************************************************
int getopt_long_only(int argc, char* const argv[],
        const char* optstring,
        const struct option* longopts, int* longindex)
{
    return -1;
}
********************************************************/
