#include <stdlib.h>
#include <stdio.h>


struct arg_const {
    char const * name;
    int const value;
};


#ifndef ARG_HELP
#define ARG_HELP help
#endif
#ifndef ARG_VERSION
#define ARG_VERSION version
#endif
#ifndef ARG_USAGE
#define ARG_USAGE usage
#endif
#ifndef ARG_FORMAT_HEADER
#define ARG_FORMAT_HEADER       "Recognized values for <%s>:\n"
#endif
#ifndef ARG_FORMAT_NAME
#define ARG_FORMAT_NAME         "  %s\n"
#endif
#ifndef ARG_FORMAT_POSITIONAL
#define ARG_FORMAT_POSITIONAL   "No %s specified"
#endif
#ifndef ARG_FORMAT_COLON
#define ARG_FORMAT_COLON        "Option -%c requires an argument"
#endif
#ifndef ARG_FORMAT_QUESTIONMARK
#define ARG_FORMAT_QUESTIONMARK "Unrecognized option: '-%c'"
#endif
#ifndef ARG_FORMAT_DEFAULT
#define ARG_FORMAT_DEFAULT      "Failed to parse option: '-%c'"
#endif
#ifndef ARG_FORMAT_FIND
#define ARG_FORMAT_FIND         "Unrecognized %s: '%s'"
#endif
#ifndef ARG_FORMAT_CATCH
#define ARG_FORMAT_CATCH        "Unrecognized argument: '%s'"
#endif

#define ARG_PRINT(STREAM, ARG, ARGS) \
    fprintf(STREAM, ARG_FORMAT_HEADER, #ARG); \
    for (size_t argi = 0; argi < sizeof(ARGS) / sizeof(*ARGS); ++argi) { \
        void const * arg = &((char const *)ARGS)[argi * sizeof(*ARGS)]; \
        fprintf(STREAM, ARG_FORMAT_NAME, *(char const **)arg); \
    }

#define ARG_SPECIALS() \
    if (argc > 1) { \
        if ( \
            0 == strcmp(argv[1], "-h") || \
            0 == strcmp(argv[1], "--help") \
        ) { \
            ARG_HELP(stdout); \
            exit(EXIT_SUCCESS); \
        } \
        if ( \
            0 == strcmp(argv[1], "--version") \
        ) { \
            ARG_VERSION(stdout); \
            exit(EXIT_SUCCESS); \
        } \
    }

#define ARG_DECLARE(ARG, DEFAULT) \
    char const * arg_##ARG = DEFAULT;

#define ARG_POSITIONAL(ARG) \
    if (optind >= argc) { \
        fprintf(stderr, ARG_FORMAT_POSITIONAL "\n\n", #ARG); \
        ARG_USAGE(stderr); \
        exit(EXIT_FAILURE); \
    } \
    arg_##ARG = argv[optind++];

#define ARG_GETOPT_BEGIN(OPTSTRING) \
    { \
        int opt; \
        int opterr = 0; \
        while (-1 != (opt = getopt(argc, argv, ":" OPTSTRING))) { \
            switch(opt) {

#define ARG_GETOPT(OPT, ARG) \
                case OPT: arg_##ARG = argv[optind]; break;

#define ARG_GETOPT_WITH_ARGUMENT(OPT, ARG) \
                case OPT: arg_##ARG = optarg; break;

#define ARG_GETOPT_END() \
                case ':': \
                    fprintf(stderr, ARG_FORMAT_COLON "\n\n", optopt); \
                    opterr = 1; \
                    break; \
                case '?': \
                    fprintf(stderr, ARG_FORMAT_QUESTIONMARK "\n\n", optopt); \
                    opterr = 1; \
                    break; \
                default: \
                    fprintf(stderr, ARG_FORMAT_DEFAULT "\n\n", opt); \
                    opterr = 1; \
                    break; \
            } \
        } \
        if (opterr) { \
            ARG_USAGE(stderr); \
            exit(EXIT_FAILURE); \
        } \
    }

#define ARG_CONVERT_BOOL(ARG) \
    int ARG = !!arg_##ARG;

#define ARG_CONVERT_NULL(ARG) \
    char const * ARG = *(arg_##ARG) ? (arg_##ARG) : NULL;

#define ARG_CONVERT_FIND(TYPE, ARG, ARGS) \
    struct TYPE const * ARG = NULL; \
    for (size_t argi = 0; argi < sizeof(ARGS) / sizeof(*ARGS); ++argi) { \
        void const * arg = &((char const *)ARGS)[argi * sizeof(*ARGS)]; \
        if (0 == strcmp(arg_##ARG, *(char const **)arg)) \
            ARG = arg; \
    } \
    if (!ARG) { \
        fprintf(stderr, ARG_FORMAT_FIND "\n\n", #ARG, arg_##ARG); \
        ARG_USAGE(stderr); \
        exit(EXIT_FAILURE); \
    } \

#define ARG_CONVERT_FIND_CONST(ARG, ARGS) \
    struct arg_const const * arg_const_##ARG = NULL; \
    for (size_t argi = 0; argi < sizeof(ARGS) / sizeof(*ARGS); ++argi) { \
        void const * arg = &((char const *)ARGS)[argi * sizeof(*ARGS)]; \
        if (0 == strcmp(arg_##ARG, *(char const **)arg)) \
            arg_const_##ARG = arg; \
    } \
    if (!arg_const_##ARG) { \
        fprintf(stderr, ARG_FORMAT_FIND "\n\n", #ARG, arg_##ARG); \
        ARG_USAGE(stderr); \
        exit(EXIT_FAILURE); \
    } \
    int ARG = arg_const_##ARG->value; \

#define ARG_CATCH() \
    if (optind < argc) { \
        fprintf(stderr, ARG_FORMAT_CATCH "\n\n", argv[optind]); \
        ARG_USAGE(stderr); \
        exit(EXIT_FAILURE); \
    }