/** dropt.h
  *
  * A deliberately rudimentary command-line option parser.
  *
  * Version 2.0.0
  *
  * Copyright (C) 2006-2018 James D. Lin <jamesdlin@berkeley.edu>
  *
  * The latest version of this file can be downloaded from:
  * <http://www.taenarum.com/software/dropt/>
  *
  * This software is provided 'as-is', without any express or implied
  * warranty.  In no event will the authors be held liable for any damages
  * arising from the use of this software.
  *
  * Permission is granted to anyone to use this software for any purpose,
  * including commercial applications, and to alter it and redistribute it
  * freely, subject to the following restrictions:
  *
  * 1. The origin of this software must not be misrepresented; you must not
  *    claim that you wrote the original software. If you use this software
  *    in a product, an acknowledgment in the product documentation would be
  *    appreciated but is not required.
  *
  * 2. Altered source versions must be plainly marked as such, and must not be
  *    misrepresented as being the original software.
  *
  * 3. This notice may not be removed or altered from any source distribution.
  */

#ifndef DROPT_H
#define DROPT_H

#include <stdio.h>
#include <wchar.h>

#if __STDC_VERSION__ >= 199901L
    #include <stdint.h>
    typedef uintptr_t dropt_uintptr;
#else
    typedef size_t dropt_uintptr;
#endif


#ifdef __cplusplus
extern "C" {
#endif


#ifndef DROPT_USE_WCHAR
#if defined _UNICODE && (defined _MSC_VER || defined DROPT_NO_STRING_BUFFERS)
#define DROPT_USE_WCHAR 1
#endif
#endif

#ifdef DROPT_USE_WCHAR
    /* This may be used for both char and string literals. */
    #define DROPT_TEXT_LITERAL(s) L ## s

    typedef wchar_t dropt_char;
#else
    #define DROPT_TEXT_LITERAL(s) s

    typedef char dropt_char;
#endif


enum
{
    /* Errors in the range [0x00, 0x7F] are reserved for dropt. */
    dropt_error_none,
    dropt_error_unknown,
    dropt_error_bad_configuration,
    dropt_error_insufficient_memory,
    dropt_error_invalid_option,
    dropt_error_insufficient_arguments,
    dropt_error_mismatch,
    dropt_error_overflow,
    dropt_error_underflow,

    /* Errors in the range [0x80, 0xFFFF] are free for clients to use. */
    dropt_error_custom_start = 0x80,
    dropt_error_custom_last = 0xFFFF
};
typedef unsigned int dropt_error;

typedef unsigned char dropt_bool;

/* Opaque. */
typedef struct dropt_context dropt_context;

/* Forward declarations. */
typedef struct dropt_option dropt_option;


/** `dropt_option_handler_func` callbacks are responsible for parsing
  * individual options and storing the parsed value.
  *
  * `dropt_option_handler_decl` may be used for declaring the callback
  * functions (see the stock option handlers below for examples).
  * `dropt_option_handler_func` is the actual function pointer type.
  *
  * `option` points to the `dropt_option` entry that matched the option
  * supplied by the user.  This will never be `NULL` when dropt invokes the
  * handler.
  *
  * `optionArgument` will be `NULL` if no argument is specified for an option.
  * It will be the empty string if the user explicitly passed an empty string
  * as the argument (e.g. `--option=""`).
  *
  * An option that doesn't expect an argument still can receive a non-null
  * value for `optionArgument` if the user explicitly specified one (e.g.
  * `--option=arg`).
  *
  * If the option's argument is optional, the handler might be called twice:
  * once with a candidate argument, and if that argument is rejected by the
  * handler, again with no argument.  Handlers should be aware of this if they
  * have side-effects.
  *
  * `dest` is the client-specified pointer to a variable for the handler to
  * modify.
  */
typedef dropt_error dropt_option_handler_decl(dropt_context* context,
                                              const dropt_option* option,
                                              const dropt_char* optionArgument,
                                              void* dest);
typedef dropt_option_handler_decl* dropt_option_handler_func;

/** `dropt_error_handler_func` callbacks are responsible for generating error
  * messages.  The returned string must be allocated on the heap and must be
  * freeable with `free()`.
  */
typedef dropt_char* (*dropt_error_handler_func)(dropt_error error,
                                                const dropt_char* optionName,
                                                const dropt_char* optionArgument,
                                                void* handlerData);

/** `dropt_strncmp_func` callbacks allow callers to provide their own (possibly
  * case-insensitive) string comparison function.
  */
typedef int (*dropt_strncmp_func)(const dropt_char* s, const dropt_char* t,
                                  size_t n);


/** Properties defining each option:
  *
  * short_name:
  *     The option's short name (e.g. the 'h' in `-h`).
  *     Use '\0' if the option has no short name.
  *
  * long_name:
  *     The option's long name (e.g. "help" in `--help`).
  *     Use `NULL` if the option has no long name.
  *
  * description:
  *     The description shown when generating help.
  *     May be `NULL` for undocumented options.
  *
  * arg_description:
  *     The description for the option's argument (e.g. `--option=argument` or
  *     `--option argument`), printed when generating help.
  *     Use `NULL` if the option does not take an argument.
  *
  * handler:
  *     The handler callback and data invoked in response to encountering the
  *     option.
  *
  * dest:
  *     The address of a variable for the handler to modify, if necessary.
  *
  * attr:
  *     Miscellaneous attributes.  See below.
  *
  * extra_data:
  *     Additional callback data for the handler.
  */
struct dropt_option
{
    dropt_char short_name;
    const dropt_char* long_name;
    const dropt_char* description;
    const dropt_char* arg_description;
    dropt_option_handler_func handler;
    void* dest;
    unsigned int attr;
    dropt_uintptr extra_data;
};


/** Bitwise flags for option attributes:
  *
  * dropt_attr_halt:
  *     Stop processing when this option is encountered.
  *
  * dropt_attr_hidden:
  *     Don't list the option when generating help.  Use this for undocumented
  *     options.
  *
  * dropt_attr_optional_val:
  *     The option's argument is optional.  If an option has this attribute,
  *     the handler callback may be invoked twice (once with a potential
  *     argument, and if that fails, again with a `NULL` argument).
  */
enum
{
    dropt_attr_halt = (1 << 0),
    dropt_attr_hidden = (1 << 1),
    dropt_attr_optional_val = (1 << 2)
};


typedef struct dropt_help_params
{
    unsigned int indent;
    unsigned int description_start_column;
    dropt_bool blank_lines_between_options;
} dropt_help_params;


dropt_context* dropt_new_context(const dropt_option* options);
void dropt_free_context(dropt_context* context);

const dropt_option* dropt_get_options(const dropt_context* context);

void dropt_set_error_handler(dropt_context* context,
                             dropt_error_handler_func handler,
                             void* handlerData);
void dropt_set_strncmp(dropt_context* context, dropt_strncmp_func cmp);

/* Use this only for backward compatibility purposes. */
void dropt_allow_concatenated_arguments(dropt_context* context,
                                        dropt_bool allow);

dropt_char** dropt_parse(dropt_context* context, int argc, dropt_char** argv);

dropt_error dropt_get_error(const dropt_context* context);
void dropt_get_error_details(const dropt_context* context,
                             dropt_char** optionName,
                             dropt_char** optionArgument);
const dropt_char* dropt_get_error_message(dropt_context* context);
void dropt_clear_error(dropt_context* context);

#ifndef DROPT_NO_STRING_BUFFERS
dropt_char* dropt_default_error_handler(dropt_error error,
                                        const dropt_char* optionName,
                                        const dropt_char* optionArgument);

void dropt_init_help_params(dropt_help_params* helpParams);
dropt_char* dropt_get_help(const dropt_context* context,
                           const dropt_help_params* helpParams);
void dropt_print_help(FILE* f, const dropt_context* context,
                      const dropt_help_params* helpParams);
#endif


/* Stock option handlers for common types. */
dropt_option_handler_decl dropt_handle_bool;
dropt_option_handler_decl dropt_handle_verbose_bool;
dropt_option_handler_decl dropt_handle_int;
dropt_option_handler_decl dropt_handle_uint;
dropt_option_handler_decl dropt_handle_double;
dropt_option_handler_decl dropt_handle_string;
dropt_option_handler_decl dropt_handle_const;

#define DROPT_MISUSE(message) dropt_misuse(message, __FILE__, __LINE__)
void dropt_misuse(const char* message, const char* filename, int line);

#ifdef __cplusplus
} /* extern "C" */
#endif

#endif /* DROPT_H */