
/***************************************************************************
 *                                                                         *
 *                     _____     ____                                      *
 *                    |  __ \   / __ \   _     _ _____                     *
 *                    | |  \ \ / /  \_\ | |   | |  _  \                    *
 *                    | |   \ \| |      | |   | | |_| |                    *
 *                    | |   | || |      | |   | |  ___/                    *
 *                    | |   / /| |   __ | |   | |  _  \                    *
 *                    | |__/ / \ \__/ / | |___| | |_| |                    *
 *                    |_____/   \____/  |_____|_|_____/                    *
 *                                                                         *
 *                       Wiimms source code library                        *
 *                                                                         *
 ***************************************************************************
 *                                                                         *
 *        Copyright (c) 2012-2022 by Dirk Clemens <wiimm@wiimm.de>         *
 *                                                                         *
 ***************************************************************************
 *                                                                         *
 *   This library is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This library is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt       *
 *                                                                         *
 ***************************************************************************/

#define _GNU_SOURCE 1

#include "dclib-option.h"
#include <getopt.h>

//
///////////////////////////////////////////////////////////////////////////////
///////////////			    GenericOpt_t		///////////////
///////////////////////////////////////////////////////////////////////////////

void InitializeGenericOpt ( GenericOpt_t *go, char first_char )
{
    DASSERT(go);
    memset(go,0,sizeof(*go));
    InitializeMemPool(&go->str_pool,2000);

    char *dest = go->sopt;
    if (first_char)
	*dest++ = first_char;
    *dest++ = ':';
    *dest = 0;
}

///////////////////////////////////////////////////////////////////////////////

void ResetGenericOpt ( GenericOpt_t *go )
{
    DASSERT(go);
    ResetMemPool(&go->str_pool);
    FREE(go->lopt);
    InitializeGenericOpt(go,0);
}

///////////////////////////////////////////////////////////////////////////////

void AddGenericOpt
(
    GenericOpt_t	*go,		// data structure
    const GenericOptParm_t *gp,		// list to add, list end on name==NULL
    bool		rm_minus	// remove '-' on options for alternatives
)
{
    DASSERT(go);
    DASSERT(gp);

    char * sopt_ptr = go->sopt + strlen(go->sopt);
    char * sopt_end = go->sopt + sizeof(go->sopt) - 3;

    for ( ; gp->name; gp++ )
    {
	ccp ptr = gp->name;
	for(;;)
	{
	    while ( *ptr == '|' )
		ptr++;

	    uint have_minus = 0;
	    char name_buf[50], *name_ptr = name_buf;
	    while ( *ptr && *ptr != '|' )
	    {
		char ch = *ptr++;
		if ( ch == '-' )
		    have_minus++;
		if ( name_ptr < name_buf + sizeof(name_buf) - 1 )
		    *name_ptr++ = ch;
	    }
	    *name_ptr = 0;
	    uint len = name_ptr - name_buf;
	    if (!len)
		break;

	    //--- short option

	    if ( len == 1 )
	    {
		uchar ch = *name_buf;
		if ( ch > ' ' && ch < 0x80 && ch != ':'
			&& !go->sopt_id[ch] && sopt_ptr < sopt_end )
		{
		    go->sopt_id[ch] = gp->id;
		    *sopt_ptr++ = ch;
		    if ( gp->arg == 1 || gp->arg == 2 )
		    {
			*sopt_ptr++ = ':';
			if ( gp->arg == 2 )
			    *sopt_ptr++ = ':';
		    }
		}
	    }


	    //--- long option

	    for(;;)
	    {
	     #if 0 // avoid duplicates, needed?
		uint i;
		for ( i = 0; i < go->lopt_used; i++ )
		    if (!strcmp(name_buf,go->lopt[i].name))
			goto skip;
	     #endif

		if ( go->lopt_used == go->lopt_size )
		{
		    go->lopt_size += 100;
		    go->lopt = REALLOC( go->lopt, go->lopt_size * sizeof(*go->lopt) );
		}

		struct option *opt = go->lopt + go->lopt_used++;
		opt->name	= StrDupMemPool(&go->str_pool,name_buf);
		opt->has_arg	= gp->arg < 2 ? gp->arg : 2;
		opt->flag	= 0;
		opt->val	= gp->id;

	     //skip:;
		if ( !have_minus || !rm_minus )
		    break;
		have_minus = 0;

		char *src = name_buf, *dest = name_buf;
		while ( *src )
		{
		    if ( *src != '-' )
			*dest++ = *src;
		    src++;
		}
		*dest = 0;
	    }
	}
    }

    *sopt_ptr = 0;

    if ( go->lopt_used == go->lopt_size )
    {
	go->lopt_size += 100;
	go->lopt = REALLOC( go->lopt, go->lopt_size * sizeof(*go->lopt) );
    }
    struct option *opt = go->lopt + go->lopt_used;
    memset(opt,0,sizeof(*opt));
}

//
///////////////////////////////////////////////////////////////////////////////
///////////////		    global options: parameters		///////////////
///////////////////////////////////////////////////////////////////////////////

GParamValueType_t GetGParamValueType ( GOPT_t gt )
{
    if ( gt & GOPT_RESERVED_BEG )
	gt = GetGOPType(gt);

    switch (gt)
    {
	case GOPT_IDX_INC0:
	case GOPT_IDX_INC:
	case GOPT_IDX_DEC0:
	case GOPT_IDX_DEC:
	case GOPT_IDX_SETP:
	case GOPT_IDX_SETM:
	case GOPT_IDX_SINT:
	    return GPT_SN;

	case GOPT_IDX_UINT:
	case GOPT_IDX_OFF_ON:
	case GOPT_IDX_OR:
	case GOPT_IDX_SETBIT:
	    return GPT_UN;

	case GOPT_IDX_SETP64:
	case GOPT_IDX_SETM64:
	case GOPT_IDX_S64:
	    return GPT_SN64;

	case GOPT_IDX_U64:
	case GOPT_IDX_OR64:
	case GOPT_IDX_SETBIT64:
	case GOPT_IDX_HEX:
	case GOPT_IDX_SIZE:
	    return GPT_UN64;

	case GOPT_IDX_DOUBLE:
	case GOPT_IDX_DURATION:
	    return GPT_D;

	case GOPT_IDX_STRING:
	    return GPT_STR;

	case GOPT_IDX_BIT:
	    return GPT_BIT;

	default:
	    return GPT_NONE;
     }
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

GetGParam_t GetGParamINT ( const GParam_t *par, int *return_val )
{
    if (par)
    {
	DASSERT( par->vtype == GetGParamValueType(par->ptype) );
	switch (par->vtype)
	{
	    case GPT_NONE:
	    case GPT_STR:
	    case GPT_BIT:
		return GGP_NONE;

	    case GPT_SN:
		if (return_val)
		    *return_val = par->sn;
		return GGP_OK;

	    case GPT_UN:
		if (return_val)
		    *return_val = par->un;
		return GGP_SIGN;

	    case GPT_SN64:
		if (return_val)
		    *return_val = par->sn64;
		return GGP_CONVERT;

	    case GPT_UN64:
		if (return_val)
		    *return_val = par->un64;
		return GGP_CONVERT;

	    case GPT_D:
		if (return_val)
		    *return_val = par->d;
		return GGP_CONVERT;
	}
    }
    return GGP_NONE;
}

///////////////////////////////////////////////////////////////////////////////

GetGParam_t GetGParamUINT ( const GParam_t *par, uint *return_val )
{
    if (par)
    {
	DASSERT( par->vtype == GetGParamValueType(par->ptype) );
	switch (par->vtype)
	{
	    case GPT_NONE:
	    case GPT_STR:
	    case GPT_BIT:
		return GGP_NONE;

	    case GPT_SN:
		if (return_val)
		    *return_val = par->sn;
		return GGP_SIGN;

	    case GPT_UN:
		if (return_val)
		    *return_val = par->un;
		return GGP_OK;

	    case GPT_SN64:
		if (return_val)
		    *return_val = par->sn64;
		return GGP_CONVERT;

	    case GPT_UN64:
		if (return_val)
		    *return_val = par->un64;
		return GGP_CONVERT;

	    case GPT_D:
		if (return_val)
		    *return_val = par->d;
		return GGP_CONVERT;
	}
    }
    return GGP_NONE;
}

///////////////////////////////////////////////////////////////////////////////

GetGParam_t GetGParamS64 ( const GParam_t *par, s64 *return_val )
{
    if (par)
    {
	DASSERT( par->vtype == GetGParamValueType(par->ptype) );
	switch (par->vtype)
	{
	    case GPT_NONE:
	    case GPT_STR:
	    case GPT_BIT:
		return GGP_NONE;

	    case GPT_SN:
		if (return_val)
		    *return_val = par->sn;
		return GGP_OK;

	    case GPT_UN:
		if (return_val)
		    *return_val = par->un;
		return GGP_SIGN;

	    case GPT_SN64:
		if (return_val)
		    *return_val = par->sn64;
		return GGP_OK;

	    case GPT_UN64:
		if (return_val)
		    *return_val = par->un64;
		return GGP_SIGN;

	    case GPT_D:
		if (return_val)
		    *return_val = par->d;
		return GGP_CONVERT;
	}
    }
    return GGP_NONE;
}

///////////////////////////////////////////////////////////////////////////////

GetGParam_t GetGParamU64 ( const GParam_t *par, u64 *return_val )
{
    if (par)
    {
	DASSERT( par->vtype == GetGParamValueType(par->ptype) );
	switch (par->vtype)
	{
	    case GPT_NONE:
	    case GPT_STR:
	    case GPT_BIT:
		return GGP_NONE;

	    case GPT_SN:
		if (return_val)
		    *return_val = par->sn;
		return GGP_SIGN;

	    case GPT_UN:
		if (return_val)
		    *return_val = par->un;
		return GGP_OK;

	    case GPT_SN64:
		if (return_val)
		    *return_val = par->sn64;
		return GGP_SIGN;

	    case GPT_UN64:
		if (return_val)
		    *return_val = par->un64;
		return GGP_OK;

	    case GPT_D:
		if (return_val)
		    *return_val = par->d;
		return GGP_CONVERT;
	}
    }
    return GGP_NONE;
}

///////////////////////////////////////////////////////////////////////////////

GetGParam_t GetGParamDBL ( const GParam_t *par, double *return_val )
{
    if (par)
    {
	DASSERT( par->vtype == GetGParamValueType(par->ptype) );
	switch (par->vtype)
	{
	    case GPT_NONE:
	    case GPT_STR:
	    case GPT_BIT:
		return GGP_NONE;

	    case GPT_SN:
		if (return_val)
		    *return_val = par->sn;
		return GGP_CONVERT;

	    case GPT_UN:
		if (return_val)
		    *return_val = par->un;
		return GGP_CONVERT;

	    case GPT_SN64:
		if (return_val)
		    *return_val = par->sn64;
		return GGP_CONVERT;

	    case GPT_UN64:
		if (return_val)
		    *return_val = par->un64;
		return GGP_CONVERT;

	    case GPT_D:
		if (return_val)
		    *return_val = par->d;
		return GGP_OK;
	}
    }
    return GGP_NONE;
}

///////////////////////////////////////////////////////////////////////////////

GetGParam_t GetGParamCCP ( const GParam_t *par, ccp *return_val )
{
    DASSERT( !par || par->vtype == GetGParamValueType(par->ptype) );
    if ( par && par->vtype == GPT_STR )
    {
	if (return_val)
	    *return_val = par->str;
	return GGP_OK;
    }
    return GGP_NONE;
}

///////////////////////////////////////////////////////////////////////////////

GetGParam_t GetGParamBIT ( const GParam_t *par, u32 *return_val, u32 *return_mask )
{
    DASSERT( !par || par->vtype == GetGParamValueType(par->ptype) );
    if ( par && par->vtype == GPT_BIT )
    {
	if (return_val)
	    *return_val = par->bit.val;
	if (return_mask)
	    *return_mask = par->bit.mask;
	return GGP_OK;
    }
    return GGP_NONE;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

ccp PrintGParam ( const GParam_t *par )
{
    DASSERT(par);
    DASSERT( par->vtype == GetGParamValueType(par->ptype) );
    char buf[100];
    ccp extra = "";

    switch (par->ptype)
    {
	case GOPT_IDX_NULL:
	    snprintf(buf,sizeof(buf),"[-]");
	    break;

	case GOPT_IDX_INC0:
	case GOPT_IDX_DEC0:
	case GOPT_IDX_DEC:
	    extra = "?";
	case GOPT_IDX_INC:
	    snprintf(buf,sizeof(buf),"[%sINC] %d",extra,par->sn);
	    break;

	case GOPT_IDX_SETP:
	case GOPT_IDX_SETM:
	    extra = "?";
	case GOPT_IDX_SINT:
	    snprintf(buf,sizeof(buf),"[%sSINT] %d",extra,par->sn);
	    break;

	case GOPT_IDX_OR:
	case GOPT_IDX_SETBIT:
	case GOPT_IDX_OFF_ON:
	    extra = "?";
	case GOPT_IDX_UINT:
	    snprintf(buf,sizeof(buf),"[%sUINT] %u",extra,par->un);
	    break;

	case GOPT_IDX_BIT:
	    snprintf(buf,sizeof(buf),"[BIT] set=%x, clr=%x",
			par->bit.val, par->bit.mask & ~par->bit.val );
	    break;

	case GOPT_IDX_SETP64:
	case GOPT_IDX_SETM64:
	    extra = "?";
	case GOPT_IDX_S64:
	    snprintf(buf,sizeof(buf),"[%sS64] %lld",extra,par->sn64);
	    break;

	case GOPT_IDX_OR64:
	case GOPT_IDX_SETBIT64:
	    extra = "?";
	case GOPT_IDX_U64:
	    snprintf(buf,sizeof(buf),"[%sU64] %llu",extra,par->un64);
	    break;

	case GOPT_IDX_HEX:
	    snprintf(buf,sizeof(buf),"[HEX] 0x%llx",par->un64);
	    break;

	case GOPT_IDX_SIZE:
	    snprintf(buf,sizeof(buf),"[SIZE] %llu = %s",
		par->un64, PrintSize1024(0,0,par->un64,0) );
	    break;

	case GOPT_IDX_DOUBLE:
	    snprintf(buf,sizeof(buf),"[DBL] %12g",par->d);
	    break;

	case GOPT_IDX_DURATION:
	    snprintf(buf,sizeof(buf),"[DUR] %12g = %s",
		par->d, PrintTimerUSec6(0,0,double2usec(par->d),false) );
	    break;

	case GOPT_IDX_STRING:
	    snprintf(buf,sizeof(buf),"[STR] \"%s\"",par->str);
	    break;


	default:
	    snprintf(buf,sizeof(buf),"[?%u]",par->ptype);
	    break;
    }

    const uint size = strlen(buf) + 1;
    char *res = GetCircBuf(size);
    memcpy(res,buf,size);
    return res;
}

//
///////////////////////////////////////////////////////////////////////////////
///////////////			ScanGOptionsHelper()		///////////////
///////////////////////////////////////////////////////////////////////////////

const GenericOptParm_t StandardGOptions[] =
{
	{ GOPT_O_TEST_OPT,	0, "@opt" },
	{ GOPT_O_HELP,		0, "h|help" },
	{ GOPT_O_CLEAR,		0, "c|clear" },
	{ GOPT_O_PAGER,		0, "p|pager" },
	{ GOPT_O_WIDTH,		1, "w|width" },
	{ GOPT_O_QUIT,		0, "Q|quit" },
	{ GOPT_O_QUIET,		0, "q|quiet" },
	{ GOPT_O_VERBOSE,	3, "v|verbose" },
	{ GOPT_O_DEBUG,		3, "D|d|debug" },
	{0,0,0}
};

///////////////////////////////////////////////////////////////////////////////

static int ScanGOffOn ( GenericOpt_t *go, int opt_stat, bool use_max, ccp optarg )
{
    ccp object = "'off/on' value";

    char buf[100];
    if (go)
    {
	uint i;
	for ( i = 0; i < go->lopt_used; i++ )
	{
	    struct option *lp = go->lopt + i;
	    if ( lp->val == opt_stat && lp->has_arg && lp->name )
	    {
		snprintf(buf,sizeof(buf),"'off/on' value of --%s",lp->name);
		object = buf;
		break;
	    }
	}
    }

    const uint max_num = use_max ? ( opt_stat & GOPT_M_VALUE ) >> GOPT_S_VALUE : 0;
    return ScanKeywordOffOn(optarg,max_num,object);
}

///////////////////////////////////////////////////////////////////////////////

enumError ScanGOptionsHelper
(
    GOptions_t		*gopt,		// valid pointer
    GenericOpt_t	*go,		// NULL or list with options
					//	=> ResetGenericOpt() is called at finish
    int			argc,		// argument counter
    char		**argv,		// list of arguments
    int			max_param,	// >=0: max allowed parameters
    ScanGOpt_t		scan_opt,	// scan options

    //--- call back functions
    ScanOptionFunc	OnAddOptions,	// not NULL: called once to add options
    ScanOptionFunc	OnOption,	// not NULL: called for each option
    ScanOptionFunc	OnArgument,	// not NULL: called for each argument
    void		*any_ptr	// NULL or user specific pointer for call back
)
{
    DASSERT(gopt);
    DASSERT(argc>1);
    DASSERT(argv);

    //--- setup options

    GenericOpt_t go0;
    if (!go)
    {
	go = &go0;
	InitializeGenericOpt( go, scan_opt & SGO_KNOWN ? '+' : 0 );
    }

    if (OnAddOptions)
	OnAddOptions(go,gopt,any_ptr,scan_opt,0);
    AddGenericOpt(go,StandardGOptions,true);

    if ( argc > 1 )
    {
	// skip command
	argc--;
	argv++;
    }

    optind = 0; // '0' is correct for GNU to reset scanning
 #if !defined(TEST) && !defined(DEBUG)
    opterr = 0; // suppress error messages to stderr
 #endif


    //--- check options

    InitializeGOptions(gopt,gopt->max_width);

    int err = 0, test_opt = 0;
    enumError stat = ERR_OK;
    while ( stat == ERR_OK )
    {
      int opt_stat = getopt_long(argc,argv,go->sopt,go->lopt,0);
      if ( opt_stat == -1 )
	break;

      // translate short option names
      if ( (uint)opt_stat < sizeof(go->sopt_id)/sizeof(*go->sopt_id) && go->sopt_id[opt_stat] )
	opt_stat = go->sopt_id[opt_stat];

      if ( opt_stat >= GOPT_RESERVED_BEG && opt_stat < GOPT_RESERVED_END )
      {
	const uint pidx = opt_stat & GOPT_M_INDEX;
	if ( pidx >= GOPT_N_PARAM )
	{
	    printf("%s!!! %s: Parameter index %u must be <%u: %s%s\n",
			colout->warn, *argv,
			pidx, GOPT_N_PARAM, argv[optind-1],
			colout->reset );
	    stat = ERR_SYNTAX;
	    break;
	}
	GParam_t *par = gopt->param + pidx;

	const uint ptype = GetGOPType(opt_stat);
	switch(ptype)
	{
	 //--- increment & decrement: indicator 'ptype' is always GOPT_IDX_INC

	 case GOPT_IDX_INC0:
	    if ( par->ptype != GOPT_IDX_INC )
	    {
		par->ptype = GOPT_IDX_INC;
		par->sn = 1;
	    }
	    else
		par->sn = par->sn >= 0 ? par->sn+1 : 0;
	    break;

	 case GOPT_IDX_INC:
	    if ( par->ptype != GOPT_IDX_INC )
	    {
		par->ptype = GOPT_IDX_INC;
		par->sn = 1;
	    }
	    else
		par->sn = par->sn > 0 ? par->sn+1 : 1;
	    break;

	 case GOPT_IDX_DEC0:
	    if ( par->ptype != GOPT_IDX_INC )
	    {
		par->ptype = GOPT_IDX_INC;
		par->sn = -1;
	    }
	    else
		par->sn = par->sn <= 0 ? par->sn-1 : 0;
	    break;

	 case GOPT_IDX_DEC:
	    if ( par->ptype != GOPT_IDX_INC )
	    {
		par->ptype = GOPT_IDX_INC;
		par->sn = -1;
	    }
	    else
		par->sn = par->sn < 0 ? par->sn-1 : -1;
	    break;


	 //--- set values: indicator 'ptype' is GOPT_IDX_SINT or GOPT_IDX_S64

	 case GOPT_IDX_SETP:
	    par->ptype = GOPT_IDX_SINT;
	    par->un  = ( opt_stat & GOPT_M_VALUE ) >> GOPT_S_VALUE;
	    break;

	 case GOPT_IDX_SETM:
	    par->ptype = GOPT_IDX_SINT;
	    par->sn  = - ( ( opt_stat & GOPT_M_VALUE ) >> GOPT_S_VALUE );
	    break;

	 case GOPT_IDX_SETP64:
	    par->ptype = GOPT_IDX_S64;
	    par->un64  = ( opt_stat & GOPT_M_VALUE ) >> GOPT_S_VALUE;
	    break;

	 case GOPT_IDX_SETM64:
	    par->ptype = GOPT_IDX_S64;
	    par->sn64  = - ( ( opt_stat & GOPT_M_VALUE ) >> GOPT_S_VALUE );
	    break;


	 //--- set bits: indicator 'ptype' is GOPT_IDX_UINT or GOPT_IDX_U64

	 case GOPT_IDX_OR:
	    if ( par->ptype != GOPT_IDX_UINT )
	    {
		par->ptype = GOPT_IDX_UINT;
		par->un = 0;
	    }
	    par->un |= ( opt_stat & GOPT_M_VALUE ) >> GOPT_S_VALUE;
	    break;

	 case GOPT_IDX_SETBIT:
	    {
		if ( par->ptype != GOPT_IDX_UINT )
		{
		    par->ptype = GOPT_IDX_UINT;
		    par->un = 0;
		}
		const uint bitnum = ( opt_stat & GOPT_M_VALUE ) >> GOPT_S_VALUE;
		if ( bitnum < 8*sizeof(par->un) )
		    par->un |= 1u << bitnum;
	    }
	    break;

	 case GOPT_IDX_OR64:
	    if ( par->ptype != GOPT_IDX_U64 )
	    {
		par->ptype = GOPT_IDX_U64;
		par->un64 = 0;
	    }
	    par->un64 |= ( opt_stat & GOPT_M_VALUE ) >> GOPT_S_VALUE;
	    break;

	 case GOPT_IDX_SETBIT64:
	    if ( par->ptype != GOPT_IDX_U64 )
	    {
		par->ptype = GOPT_IDX_U64;
		par->un64 = 0;
	    }
	    {
		const uint bitnum = ( opt_stat & GOPT_M_VALUE ) >> GOPT_S_VALUE;
		if ( bitnum < 64 )
		    par->un64 |= 1llu << bitnum;
	    }
	    break;

	 case GOPT_IDX_BIT:
	    {
		if ( par->ptype != GOPT_IDX_BIT )
		{
		    par->ptype = GOPT_IDX_BIT;
		    par->bit.val = par->bit.mask = 0;
		}

		const int stat = ScanGOffOn(go,opt_stat,false,optarg);
		if ( stat >= 0 )
		{
		    const uint bitnum = ( opt_stat & GOPT_M_VALUE ) >> GOPT_S_VALUE;
		    if ( bitnum < 32 )
		    {
			const u32 m = 1u << bitnum;
			par->bit.mask |= m;
			if ( stat > 0 )
			    par->un |= m;
			else
			    par->un &= ~m;
		    }
		}
	    }
	    break;


	 //--- scan parameters

	 case GOPT_IDX_SINT:
	    par->ptype = GOPT_IDX_SINT;
	    par->sn = str2l(optarg,0,10);
	    break;

	 case GOPT_IDX_UINT:
	    par->ptype = GOPT_IDX_UINT;
	    par->un = str2ul(optarg,0,10);
	    break;

	 case GOPT_IDX_OFF_ON:
	    {
		const int stat = ScanGOffOn(go,opt_stat,true,optarg);
		par->un = stat >= 0 ? stat : 0;
		par->ptype = GOPT_IDX_UINT;
	    }
	    break;

	 case GOPT_IDX_S64:
	    par->ptype = GOPT_IDX_S64;
	    par->sn64 = str2ll(optarg,0,10);
	    break;

	 case GOPT_IDX_U64:
	    par->ptype = GOPT_IDX_U64;
	    par->un64 = str2ull(optarg,0,10);
	    break;

	 case GOPT_IDX_HEX:
	    par->ptype = GOPT_IDX_HEX;
	    par->un64 = str2ull(optarg,0,16);
	    break;

	 case GOPT_IDX_SIZE:
	    par->ptype = GOPT_IDX_SIZE;
	    ScanSizeU64(&par->un64,optarg,1,1,0); break;
	    break;

	 case GOPT_IDX_DOUBLE:
	    par->ptype = GOPT_IDX_DOUBLE;
	    par->d = strtod(optarg,0);
	    break;

	 case GOPT_IDX_DURATION:
	    par->ptype = GOPT_IDX_DURATION;
	    ScanDuration(&par->d,optarg,1,SDUMD_M_DEFAULT); break;
	    break;

	 case GOPT_IDX_STRING:
	    par->ptype = GOPT_IDX_STRING;
	    par->str = optarg;
	    break;

	 default:
	    printf("!!! %s: Undefinef GOPT_* type 0x%02x [0x%02x]: %s\n",
			*argv, GetGOPType(opt_stat), opt_stat, argv[optind-1] );
	    stat = ERR_ERROR;
	}
	par->vtype = GetGParamValueType(par->ptype);
	continue;
      }

      switch (opt_stat)
      {
	case '?':
	    if ( optopt && (uint)optopt < 0x7f )
		printf("%s!!! %s: Invalid option: %s-%c [0x%02x]%s\n",
			colout->warn, *argv,
			colout->value, optopt, optopt,
			colout->reset );
	    else
		printf("%s!!! %s: Invalid option: %s%s%s\n",
			colout->warn, *argv,
			colout->value, argv[optind-1],
			colout->reset );
	    stat = ERR_SYNTAX;
	    break;

	case ':':
	    printf("%s!!! %s: Missing parameter for %s%s%s\n",
			colout->warn, *argv,
			colout->value, argv[optind-1],
			colout->reset );
	    stat = ERR_SYNTAX;
	    break;


    //--- standard options

	case GOPT_O_QUIET:
	    gopt->verbose = gopt->verbose > -1 ? -1 : gopt->verbose - 1;
	    break;

	case GOPT_O_VERBOSE:
	    if (optarg)
		gopt->verbose = str2l(optarg,0,10);
	    else
		gopt->verbose = gopt->verbose < 0 ?  0 : gopt->verbose + 1;
	    break;

	case GOPT_O_DEBUG:
	    if (optarg)
		gopt->debug = str2l(optarg,0,10);
	    else
		gopt->debug++;
	    break;

	case GOPT_O_WIDTH:
	    {
		char *end;
		int num = str2l(optarg,&end,10);
		if ( num > 0 && !*end )
		    gopt->width = num < 40 ? 40 : num <= gopt->max_width ? num : gopt->max_width;
	    }
	    break;

	case GOPT_O_HELP:	gopt->help++; scan_opt |= SGO_PAGER; break;
	case GOPT_O_CLEAR:	gopt->clear++; break;
	case GOPT_O_PAGER:	gopt->pager++; break;
	case GOPT_O_QUIT:	gopt->quit++; break;
	case GOPT_O_TEST_OPT:	test_opt++; break;


    //--- misc

	default:
	    if (OnOption)
	    {
		const int ostat = OnOption(go,gopt,any_ptr,opt_stat,optarg);
		if ( ostat <= 0 )
		{
		    if (!ostat)
			printf("!!! %s: Internal error while scanning options [0x%02x]!\n",
				*argv, opt_stat );
		    stat = ERR_ERROR;
		}
	    }
	    break;
      }
    }

    if (test_opt)
    {
	printf("\n## %sShort options:%s %s\n## %sLong options (%u/%u):%s\n",
		colout->caption, colout->reset, go->sopt,
		colout->caption, go->lopt_used, go->lopt_size, colout->reset );

	for ( const struct option *opt = go->lopt; opt->name; opt++ )
	    printf("## %6x %c %s\n", opt->val,
			opt->has_arg ? '0'+opt->has_arg : '-',
			opt->name );
	putchar('\n');
    }

    char **dest = argv + optind;
    gopt->argv = dest;

    uint i;
    for ( i = optind; i < argc; i++ )
    {
	char *arg = argv[i];
	if ( !OnArgument || !OnArgument(go,gopt,any_ptr,dest-gopt->argv,arg) )
	    *dest++ = arg;
    }
    gopt->argc = dest - gopt->argv;

    if (err)
    {
	printf("%s!!! %s: Syntax error!%s\n",colout->warn,*argv,colout->reset);
	stat = ERR_SYNTAX;
	goto abort;
    }

    if ( max_param >= 0 && gopt->argc > max_param )
    {
	if (!max_param)
	    printf(
			"%s!!! %s: No parameters allowed, but %u found!%s\n",
			colout->warn, *argv, gopt->argc, colout->reset );
	else
	    printf(
			"%s!!! %s: Maximal %u parameter%s allowed, but %u found!%s\n",
			colout->warn, *argv,
			max_param, max_param == 1 ? "" : "s",
			gopt->argc, colout->reset );
	stat = ERR_SYNTAX;
	goto abort;
    }

    if ( scan_opt & SGO_PAGER && !gopt->pager )
	gopt->pager++;

 abort:
    ResetGenericOpt(go);
    if (stat)
	InitializeGOptions(gopt,gopt->max_width);
    return stat;
}

//
///////////////////////////////////////////////////////////////////////////////
///////////////				END			///////////////
///////////////////////////////////////////////////////////////////////////////

