/*
 * Command line parsing functions.
 * 2018-10-14 (c) Udo Krebelder <udo@krebelder.at>
 */

/* Disable POSIX function name depreciated warning */
//#define _CRT_NONSTDC_NO_WARNINGS 1

#include "winutil.h"

#ifndef STATIC
  #define STATIC static
#endif

/*
 * Command line switch start character.
 */
#ifdef _WIN32
  #define SWITCH_START_CHAR(c) ((c) == '-' || (c) == '/')
#else
  /* '/' is Unix path separator */
  #define SWITCH_START_CHAR(c) ((c) == '-')
#endif


/*
 * Valid switch characters are [a-zA-Z?].
 * This function must handle non-ASCII input.
 */
#define VALID_SWITCH_CHAR(c) (ISALPHA(c) || (c) == '?')


STATIC PTSTR  swarg;     /* pointer to current switch or parameter */
STATIC INT    swind;     /* index into parameter array */
STATIC INT    swstop;    /* flag to stop parameter processing */


#ifndef NO_CMDLINE_LONG_OPTION
/* Null terminated array of switch names,
 * first character is the return value. */
STATIC PTSTR* swargv;
#endif


/*
 * Return next command line switch, or -1 if no valid switch is found.
 * argv ..... NULL terminated array of program parameters
 * switchv .. NULL terminated array of named parameters
 *          (e.g. -help or --help, NULL if not used)
 *
 * Typical usage
 * -------------
 * // Vector of named parameters (first character is return value)
 * PCTSTR LongSwitches[] = {
 *  _T("d") _T("debug"),
 *  _T("v") _T("verbose"),
 *  _T("f") _T("config-file"),
 *  _T("q") _T("quiet"),
 *  _T("?") _T("help"),
 *  NULL
 * };
 *
 * while ((c = CmdSwitch(argv, LongSwitches)) != -1) {
 *      switch (c) {
 *      case 'd':
 *          // set debug log mask
 *          if (CmdUIntParam(&n))
 *              usage();
 *          g_DebugMask = n;
 *          break;
 *      case 'v':
 *          // increas verbose level
 *          LogVerboseLevel++;
 *          break;
 *      case 'f'
 *          // config option needs a filename
 *          if (!(config_file = CmdParam()))
 *              usage();
 *          ReadConfigFile(f);
 *          break;
 *      case 'q'
 *          // 'q' switch seen
 *          quiet = 1;
 *          break;
 *      case '?':
 *      default:
 *          // print short help screen
 *          usage();
 *      }
 * }
 *
 * // process remaining command line paramters
 * while ((p = CmdParam())
 *  do_something_with_parameter(p);
 *
 * // do the real work
 * do_real_work();
 *
 */
INT CmdSwitch(PTSTR argv[], PCTSTR switchv[])
{
    INT c, i;

    if (argv != swargv) {
        /* initialization or reset */
        swargv = argv;
        swind = 1;  /* first parameter is argv[1] */
        swstop = 0;
        if (swargv && swargv[0])
            swarg = swargv[1];
        else
            swarg = NULL;
    }

    /* no parameter left or stop flag set? */
    if (!swarg || swstop)
        return -1;

    /* swarg points to switch character */
    c = *swarg;

    /* at the beginning of a command line parameter? */
    if (swarg == swargv[swind]) {

        /* command line switch starts with '-'? */
        if (!SWITCH_START_CHAR(c))
            return -1;  /* no switch start */

        /* switch character */
        c = swarg[1];

        if (!c) {
#ifndef NO_CMDLINE_SINGLE_HYPHEN
            /* single "-" stops switch processing */
            swstop = 1;
            swarg = swargv[++swind];
#endif
            return -1;  /* single "-" */
        }

#ifndef NO_CMDLINE_DOUBLE_HYPHEN
        if (SWITCH_START_CHAR(c) && !swarg[2]) {
            /* "--" stops switch processing */
            swstop = 1;
            swarg = swargv[++swind];
            return -1;
        }
#endif

#ifndef NO_CMDLINE_LONG_OPTION
        if (switchv) {
            /* Search named switch entry (-hello or --hello).
             * Each entry has the format "hHelp",
             * the first character 'h' is the return value. */
            PCTSTR p, pp;

            /* pointer to the "hello" part of "-hello" or "--hello" */
            p = swarg + 1;
            if (SWITCH_START_CHAR(c)) p++;

            /* Linear search through the array of named switches */
            for (i = 0; switchv[i]; i++) {
                pp = switchv[i];
                if (!_tcsicmp(p, pp + 1)) {
                    /* Found named switch.
                     * Increment param pointer and set c to first char. */
                    swarg = swargv[++swind];
                    return pp[0];
                }
            }
        }
#endif
    }

    if (!VALID_SWITCH_CHAR(c))
        return -1;

    if (swarg == swargv[swind])
        swarg++;                    /* go over "-" */

    if (!*++swarg)
        swarg = swargv[++swind];    /* next argument */

    /* return switch character */
    return c;
}


/*
 * Return next command line parameter or NULL at end.
 */
PTSTR CmdParam(VOID)
{
    PTSTR p;

    if ((p = swarg) != NULL)
        swarg = swargv[++swind];
    return p;
}


/*
 * Skip ASCII white space characters in string.
 * Return pointer to first character after white space.
 */
PTSTR SkipWhiteSpace(PCTSTR str)
{
    UINT c;
    PTBYTE p = (PTBYTE)str;

    do {
        c = *p++;
    } while (ISSPACE(c));

    return (PTSTR)(--p);
}


/*
 * Get integer command line argument between min and max
 * Return:
 *   1 at end of command line,
 *  -1 if integer conversion error,
 *   0 if integer was stored in argument ptr.
 */
INT CmdIntParam(INT *ptr, INT min, INT max)
{
    INT64 x;
    PTSTR str = swarg, stop;

    if (!ptr || !str)
        return 1;

    //x = _tcstox(str, &stop, STRTOX_SKIP_SPACE | STRTOX_NO_STOP_OVERFLOW | STRTOX_NO_OCTAL);
    x = _tcstoui64(str, &stop, 0);

    if (str == stop || *stop != '\0' || x < min || x > max)
        return -1;

    *ptr = (INT)x;
    swarg = swargv[++swind];

    return 0;
}


/*
 * Get unsigned integer command line argument.
 * Ignore overflow.
 */
INT CmdUIntParam(UINT *ptr)
{
    UINT64 x;
    PTSTR str = swarg, stop;

    if (!ptr || !str)
        return 1;

    //x = _tcstox(str, &stop, STRTOX_SKIP_SPACE | STRTOX_IGNORE_OVERFLOW | STRTOX_NO_OCTAL);
    x = _tcstoui64(str, &stop, 0);

    if (str == stop || *stop != '\0')
        return -1;

    *ptr = (UINT)x;
    swarg = swargv[++swind];

    return 0;
}


/*
 * Get 64 bit integer command line argument between min and max.
 */
INT CmdInt64Param(INT64 *ptr, INT64 min, INT64 max)
{
    INT64 x;
    PTSTR str = swarg, stop;

    if (!ptr || !str)
        return 1;

    //x = _tcstox(str, &stop, STRTOX_SKIP_SPACE | STRTOX_NO_STOP_OVERFLOW | STRTOX_NO_OCTAL);
    x = _tcstoui64(str, &stop, 0);


    if (str == stop || *stop != '\0' || x < min || x > max)
        return -1;

    *ptr = x;
    swarg = swargv[++swind];

    return 0;
}


/*
 * Get 64 bit unsigned integer command line argument.
 * Ignore overflow.
 */
INT CmdUInt64Param(UINT64 *ptr)
{
    UINT64 x;
    PTSTR str = swarg, stop;

    if (!ptr || !str)
        return 1;

    //x = _tcstox(str, &stop, STRTOX_SKIP_SPACE | STRTOX_IGNORE_OVERFLOW | STRTOX_NO_OCTAL);
    x = _tcstoui64(str, &stop, 0);

    if (str == stop || *stop != '\0')
        return -1;

    *ptr = x;
    swarg = swargv[++swind];

    return 0;
}


/*
 * Get double command line argument between min and max.
 */
INT CmdDoubleParam(DOUBLE *ptr, DOUBLE min, DOUBLE max)
{
    DOUBLE x;
    PTSTR str = swarg, stop;

    if (!ptr || !str)
        return 1;

    x = _tcstod(str, &stop);

    if (str == stop || *SkipWhiteSpace(stop) || x < min || x > max)
        return -1;

    *ptr = x;
    swarg = swargv[++swind];

    return 0;
}
