/** dropt_handlers.c
  *
  * Default type handlers for dropt.
  *
  * 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.
  */

#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <float.h>
#include <errno.h>
#include <assert.h>

#include "dropt.h"
#include "dropt_string.h"


#define CONCAT(s, t) s ## t
#define XCONCAT(s, t) CONCAT(s, t)

#define STATIC_ASSERT(cond) \
  enum { XCONCAT(static_assert_line_,  __LINE__) = 1 / ((cond) != 0) }

#define ABS(x) (((x) < 0) ? -(x) : (x))

typedef enum { false, true } bool;


/** dropt_handle_bool
  *
  *     Stores a boolean value parsed from the given string if possible.
  *
  * PARAMETERS:
  *     IN/OUT context    : The options context.
  *     IN option         : The matched option.  For more information, see
  *                         `dropt_option_handler_decl`.
  *     IN optionArgument : A string representing a boolean value (0 or 1).
  *                         If `NULL`, the boolean value is assumed to be true.
  *     OUT dest          : A `dropt_bool*`.
  *                         On success, set to the interpreted boolean value.
  *                         On error, left untouched.
  *
  * RETURNS:
  *     dropt_error_none
  *     dropt_error_unknown
  *     dropt_error_bad_configuration
  *     dropt_error_mismatch
  */
dropt_error
dropt_handle_bool(dropt_context* context,
                  const dropt_option* option,
                  const dropt_char* optionArgument,
                  void* dest)
{
    dropt_error err = dropt_error_none;
    bool val = false;
    dropt_bool* out = dest;

    if (out == NULL)
    {
        DROPT_MISUSE("No handler destination specified.");
        err = dropt_error_bad_configuration;
    }
    else if (optionArgument == NULL)
    {
        /* No explicit argument implies that the option is being turned on. */
        val = true;
    }
    else if (optionArgument[0] == DROPT_TEXT_LITERAL('\0'))
    {
        err = dropt_error_mismatch;
    }
    else
    {
        unsigned int i = 0;
        err = dropt_handle_uint(context, option, optionArgument, &i);
        if (err == dropt_error_none)
        {
            switch (i)
            {
                case 0:
                    val = false;
                    break;
                case 1:
                    val = true;
                    break;
                default:
                    err = dropt_error_mismatch;
                    break;
            }
        }
        else if (err == dropt_error_overflow)
        {
            err = dropt_error_mismatch;
        }
    }

    if (err == dropt_error_none) { *out = val; }
    return err;
}


/** dropt_handle_verbose_bool
  *
  *     Like `dropt_handle_bool` but accepts "true" and "false" string values.
  *
  * PARAMETERS:
  *     IN/OUT context    : The options context.
  *     IN option         : The matched option.  For more information, see
  *                         `dropt_option_handler_decl`.
  *     IN optionArgument : A string representing a boolean value.
  *                         If `NULL`, the boolean value is assumed to be true.
  *     OUT dest          : A `dropt_bool*`.
  *                         On success, set to the interpreted boolean value.
  *                         On error, left untouched.
  *
  * RETURNS:
  *     See dropt_handle_bool.
  */
dropt_error
dropt_handle_verbose_bool(dropt_context* context,
                          const dropt_option* option,
                          const dropt_char* optionArgument,
                          void* dest)
{
    dropt_error err = dropt_handle_bool(context, option, optionArgument, dest);
    if (err == dropt_error_mismatch)
    {
        bool val = false;
        dropt_bool* out = dest;

        /* `dropt_handle_bool` already checks for this. */
        assert(out != NULL);

        if (dropt_stricmp(optionArgument, DROPT_TEXT_LITERAL("false")) == 0)
        {
            val = false;
            err = dropt_error_none;
        }
        else if (dropt_stricmp(optionArgument, DROPT_TEXT_LITERAL("true")) == 0)
        {
            val = true;
            err = dropt_error_none;
        }

        if (err == dropt_error_none) { *out = val; }
    }
    return err;
}


/** dropt_handle_int
  *
  *     Stores an integer parsed from the given string.
  *
  * PARAMETERS:
  *     IN/OUT context    : The options context.
  *     IN option         : The matched option.  For more information, see
  *                         `dropt_option_handler_decl`.
  *     IN optionArgument : A string representing a base-10 integer.
  *                         If `NULL`, returns
  *                           `dropt_error_insufficient_arguments`.
  *     OUT dest          : An `int*`.
  *                         On success, set to the interpreted integer.
  *                         On error, left untouched.
  *
  * RETURNS:
  *     dropt_error_none
  *     dropt_error_unknown
  *     dropt_error_bad_configuration
  *     dropt_error_insufficient_arguments
  *     dropt_error_mismatch
  *     dropt_error_overflow
  */
dropt_error
dropt_handle_int(dropt_context* context,
                 const dropt_option* option,
                 const dropt_char* optionArgument,
                 void* dest)
{
    dropt_error err = dropt_error_none;
    int val = 0;
    int* out = dest;

    if (out == NULL)
    {
        DROPT_MISUSE("No handler destination specified.");
        err = dropt_error_bad_configuration;
    }
    else if (   optionArgument == NULL
             || optionArgument[0] == DROPT_TEXT_LITERAL('\0'))
    {
        err = dropt_error_insufficient_arguments;
    }
    else
    {
        dropt_char* end;
        long n;
        errno = 0;
        n = dropt_strtol(optionArgument, &end, 10);

        /* Check that we matched at least one digit.
         * (`strtol`/`strtoul` will return 0 if fed a string with no digits.)
         */
        if (*end == DROPT_TEXT_LITERAL('\0') && end > optionArgument)
        {
            if (errno == ERANGE || n < INT_MIN || n > INT_MAX)
            {
                err = dropt_error_overflow;
                val = (n < 0) ? INT_MIN : INT_MAX;
            }
            else if (errno == 0)
            {
                val = (int) n;
            }
            else
            {
                err = dropt_error_unknown;
            }
        }
        else
        {
            err = dropt_error_mismatch;
        }
    }

    if (err == dropt_error_none) { *out = val; }
    return err;
}


/** dropt_handle_uint
  *
  *     Stores an unsigned integer parsed from the given string.
  *
  * PARAMETERS:
  *     IN/OUT context    : The options context.
  *     IN option         : The matched option.  For more information, see
  *                         `dropt_option_handler_decl`.
  *     IN optionArgument : A string representing an unsigned base-10 integer.
  *                         If `NULL`, returns
  *                           `dropt_error_insufficient_arguments`.
  *     IN option         : The matched option.  For more information, see
  *                         dropt_option_handler_decl.
  *     OUT dest          : An `unsigned int*`.
  *                         On success, set to the interpreted integer.
  *                         On error, left untouched.
  *
  * RETURNS:
  *     dropt_error_none
  *     dropt_error_unknown
  *     dropt_error_bad_configuration
  *     dropt_error_insufficient_arguments
  *     dropt_error_mismatch
  *     dropt_error_overflow
  */
dropt_error
dropt_handle_uint(dropt_context* context,
                  const dropt_option* option,
                  const dropt_char* optionArgument,
                  void* dest)
{
    dropt_error err = dropt_error_none;
    int val = 0;
    unsigned int* out = dest;

    if (out == NULL)
    {
        DROPT_MISUSE("No handler destination specified.");
        err = dropt_error_bad_configuration;
    }
    else if (   optionArgument == NULL
             || optionArgument[0] == DROPT_TEXT_LITERAL('\0'))
    {
        err = dropt_error_insufficient_arguments;
    }
    else if (optionArgument[0] == DROPT_TEXT_LITERAL('-'))
    {
        err = dropt_error_mismatch;
    }
    else
    {
        dropt_char* end;
        unsigned long n;
        errno = 0;
        n = dropt_strtoul(optionArgument, &end, 10);

        /* Check that we matched at least one digit.
         * (`strtol`/`strtoul` will return 0 if fed a string with no digits.)
         */
        if (*end == DROPT_TEXT_LITERAL('\0') && end > optionArgument)
        {
            if (errno == ERANGE || n > UINT_MAX)
            {
                err = dropt_error_overflow;
                val = UINT_MAX;
            }
            else if (errno == 0)
            {
                val = (unsigned int) n;
            }
            else
            {
                err = dropt_error_unknown;
            }
        }
        else
        {
            err = dropt_error_mismatch;
        }
    }

    if (err == dropt_error_none) { *out = val; }
    return err;
}


/** dropt_handle_double
  *
  *     Stores a `double` parsed from the given string.
  *
  * PARAMETERS:
  *     IN/OUT context    : The options context.
  *     IN option         : The matched option.  For more information, see
  *                         `dropt_option_handler_decl`.
  *     IN optionArgument : A string representing a base-10 floating-point
  *                           number.
  *                         If `NULL`, returns
  *                           `dropt_error_insufficient_arguments`.
  *     OUT dest          : A `double*`.
  *                         On success, set to the interpreted `double`.
  *                         On error, left untouched.
  *
  * RETURNS:
  *     dropt_error_none
  *     dropt_error_unknown
  *     dropt_error_bad_configuration
  *     dropt_error_insufficient_arguments
  *     dropt_error_mismatch
  *     dropt_error_overflow
  *     dropt_error_underflow
  */
dropt_error
dropt_handle_double(dropt_context* context,
                    const dropt_option* option,
                    const dropt_char* optionArgument,
                    void* dest)
{
    dropt_error err = dropt_error_none;
    double val = 0.0;
    double* out = dest;

    if (out == NULL)
    {
        DROPT_MISUSE("No handler destination specified.");
        err = dropt_error_bad_configuration;
    }
    else if (   optionArgument == NULL
             || optionArgument[0] == DROPT_TEXT_LITERAL('\0'))
    {
        err = dropt_error_insufficient_arguments;
    }
    else
    {
        dropt_char* end;
        errno = 0;
        val = dropt_strtod(optionArgument, &end);

        /* Check that we matched at least one digit.
         * (`strtod` will return 0 if fed a string with no digits.)
         */
        if (*end == DROPT_TEXT_LITERAL('\0') && end > optionArgument)
        {
            if (errno == ERANGE)
            {
                /* Note that setting `errno` to `ERANGE` for underflow errors
                 * is implementation-defined behavior, but glibc, BSD's
                 * libc, and Microsoft's CRT all have implementations of
                 * `strtod` documented to return 0 and to set `errno` to
                 * `ERANGE` for such cases.
                 */
                err = (ABS(val) <= DBL_MIN)
                      ? dropt_error_underflow
                      : dropt_error_overflow;
            }
            else if (errno != 0)
            {
                err = dropt_error_unknown;
            }
        }
        else
        {
            err = dropt_error_mismatch;
        }
    }

    if (err == dropt_error_none) { *out = val; }
    return err;
}


/** dropt_handle_string
  *
  *     Stores a string.
  *
  * PARAMETERS:
  *     IN/OUT context    : The options context.
  *     IN option         : The matched option.  For more information, see
  *                         `dropt_option_handler_decl`.
  *     IN optionArgument : A string.
  *                         If `NULL`, returns
  *                           `dropt_error_insufficient_arguments`.
  *     OUT dest          : A `dropt_char**`.
  *                         On success, set to the input string.  The string is
  *                           NOT copied from the original `argv` array, so do
  *                           not free it.
  *                         On error, left untouched.
  *
  * RETURNS:
  *     dropt_error_none
  *     dropt_error_bad_configuration
  *     dropt_error_insufficient_arguments
  */
dropt_error
dropt_handle_string(dropt_context* context,
                    const dropt_option* option,
                    const dropt_char* optionArgument,
                    void* dest)
{
    dropt_error err = dropt_error_none;
    const dropt_char** out = dest;

    if (out == NULL)
    {
        DROPT_MISUSE("No handler destination specified.");
        err = dropt_error_bad_configuration;
    }
    else if (optionArgument == NULL)
    {
        err = dropt_error_insufficient_arguments;
    }

    if (err == dropt_error_none) { *out = optionArgument; }
    return err;
}


/** dropt_handle_const
  *
  *     Stores a predefined value.  This can be used to set a single variable
  *     to different values in response to different boolean-like command-line
  *     options.
  *
  * PARAMETERS:
  *     IN/OUT context    : The options context.
  *     IN option         : The matched option.  For more information, see
  *                         `dropt_option_handler_decl`.
  *     IN optionArgument : Must be `NULL`.
  *     OUT dest          : A `dropt_uintptr*`.
  *                         On success, set to the constant value specified by
  *                           `option->extra_data`.
  *                         On error, left untouched.
  *
  * RETURNS:
  *     dropt_error_none
  *     dropt_error_bad_configuration
  *     dropt_error_mismatch
  */

dropt_error
dropt_handle_const(dropt_context* context,
                   const dropt_option* option,
                   const dropt_char* optionArgument,
                   void* dest)
{
    dropt_error err = dropt_error_none;
    dropt_uintptr* out = dest;

    STATIC_ASSERT(sizeof (dropt_uintptr) >= sizeof (void*));

    if (out == NULL)
    {
        DROPT_MISUSE("No handler destination specified.");
        err = dropt_error_bad_configuration;
    }
    else if (option == NULL)
    {
        DROPT_MISUSE("No option entry given.");
        err = dropt_error_bad_configuration;
    }
    else if (optionArgument != NULL)
    {
        err = dropt_error_mismatch;
    }

    if (err == dropt_error_none) { *out = option->extra_data; }
    return err;
}