
/***************************************************************************
 *                                                                         *
 *                     _____     ____                                      *
 *                    |  __ \   / __ \   _     _ _____                     *
 *                    | |  \ \ / /  \_\ | |   | |  _  \                    *
 *                    | |   \ \| |      | |   | | |_| |                    *
 *                    | |   | || |      | |   | |  ___/                    *
 *                    | |   / /| |   __ | |   | |  _  \                    *
 *                    | |__/ / \ \__/ / | |___| | |_| |                    *
 *                    |_____/   \____/  |_____|_|_____/                    *
 *                                                                         *
 *                       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 <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "dclib-basics.h"
#include "dclib-ui.h"

//
///////////////////////////////////////////////////////////////////////////////
///////////////			struct control_t		///////////////
///////////////////////////////////////////////////////////////////////////////

typedef struct control_t
{
    FILE		* cf;		// output c file
    FILE		* hf;		// output h file
    FILE		* df;		// output def file

    const info_t	* info;		// pointer to first info_t
    const info_t	* end;		// pointer to end of info_t

    int			n_cmd;		// number of commands
    int			n_grp;		// number of helper groups
    int			n_opt_specific;	// number of cmd specific options (=OPT__N_SPECIFIC)
    int			n_opt;		// number of options (=OPT__N_TOTAL)
    int			n_cmd_opt;	// number of options of current command
    bool		need_sep;	// separator needed
    ccp			cmd_name;	// name of current command
    ccp			opt_prefix;	// prefix for option variables, never NULL

    u8			*opt_allow_grp;	// grp allowed options: field[n_grp][n_opt_specific]
    u8			*opt_allow_cmd;	// cmd allowed options: field[n_cmd][n_opt_specific]

    StringField_t	gopt;		// global options
    StringField_t	copt;		// command specfic options
    StringField_t	opt_done;	// option handled

} control_t;

//
///////////////////////////////////////////////////////////////////////////////
///////////////			    helpers			///////////////
///////////////////////////////////////////////////////////////////////////////

static char tabs[] = "\t\t\t\t";

static char sep1[] =
	"////////////////////////////////////////"
	"///////////////////////////////////////";

static char sep2[] =
	"########################################"
	"#######################################";

//-----------------------------------------------------------------------------

static void print_section ( FILE *f, ccp sep, ccp name )
{
    const size_t namelen = strlen(name);
    const int fill = ( 49 - namelen ) /2;
    const int fw   = 49 - fill;
    fprintf(f,"\n%.2s\f\n%s\n%.15s%*s%*s%.15s\n%s\n\n",
		sep, sep, sep, fill, "", -fw, name, sep, sep );
}

//-----------------------------------------------------------------------------

static void print_info_opt
(
    control_t		* ctrl,
    const info_t	* info,
    const info_t	* default_info,
    const info_t	* first_info,	// NULL or first info of a list of infos
    ccp			cmd_name
)
{
    ASSERT(ctrl);
    ASSERT(info);

    FILE * cf = ctrl->cf;
    ASSERT(cf);

    if (cmd_name)
	fprintf(cf,"static const InfoOption_t option_%s_%s_%s =\n",
		ctrl->opt_prefix, cmd_name, info->c_name );

    fprintf(cf,"    {\tOPT_%s, %s, %s, %s, %s, %s, ",
		info->c_name,
		info->type & F_OPT_OPTPARAM	? "true" : "false",
		info->type & F_HIDDEN		? "true" : "false",
		info->type & F_DEPRECATED	? "true" : "false",
		info->type & F_IGNORE		? "true" : "false",
		first_info && info>first_info && info[-1].type&T_SEP_OPT ? "true" : "false" );
    ccp start = ( default_info ? default_info : info ) -> namelist;
    if ( *start && start[1] == '|' )
    {
	fprintf(cf,"'%c', ",*start);
	start += 2;
    }
    else
	fprintf(cf,"0, ");

    ccp end = start;
    while ( *end && *end != '|' )
	end++;
 #ifdef TEST0
    fprintf(cf,"\"%.*s\", // hid=%x, dep=%x, sep=%x\n",
		(int)(end-start), start,
		info->type & F_HIDDEN,
		info->type & F_DEPRECATED,
		info > ctrl->info ? info[-1].type & T_SEP_OPT : 0 );
 #else
    fprintf(cf,"\"%.*s\",\n", (int)(end-start), start );
 #endif

    ccp param = info->param;
    ccp help  = info->help;
    if (default_info)
    {
	if (!help)
	    help  = default_info->help;
	if (!param)
	    param  = default_info->param;
    }

    if ( info->type & F_OPT_XPARAM || default_info && default_info->type & F_OPT_XPARAM )
	fprintf(cf,"\t\"%s\",\n", param && *param ? param : "param" );
    else
	fprintf(cf,"\t0,\n");

    DumpText(cf,0,0,help,-1,0,"");
    fprintf(cf,"\n    }%c\n\n",cmd_name ? ';' : ',' );
}

//-----------------------------------------------------------------------------

static void print_opt
(
    control_t		* ctrl,
    const info_t	* info,
    const info_t	* first_info	// NULL or first info of a list of infos
)
{
    ASSERT(ctrl);
    ASSERT(info);

    ctrl->n_opt++;
    print_info_opt(ctrl,info,0,first_info,0);
    fprintf( ctrl->hf, "\tOPT_%s,\n", info->c_name );
};

//-----------------------------------------------------------------------------

static void print_opt_link ( control_t * ctrl, const info_t * info )
{
    ASSERT(ctrl);
    ASSERT(ctrl->cf);
    ASSERT(ctrl->opt_prefix);
    ASSERT(info);

    if (!InsertStringField(&ctrl->opt_done,info->c_name,false))
	return;

    if ( ctrl->need_sep )
    {
	ctrl->need_sep = false;
	if ( ctrl->n_cmd_opt )
	    fprintf(ctrl->cf,"\n\tOptionInfo + OPT_NONE, // separator\n\n" );
    }

    if ( !info->help || info->type & T_DEF_OPT )
	fprintf(ctrl->cf,"\tOptionInfo + OPT_%s,\n", info->c_name );
    else
	fprintf(ctrl->cf,"\t&option_%s_%s,\n", ctrl->opt_prefix, info->c_name );

    if (ctrl->cmd_name)
    {
	fprintf(ctrl->df,"#:def_cmd_opt( \"%s\", \"%s\", \\\n",
		ctrl->cmd_name, info->c_name );
	DumpText(ctrl->df,0,0,info->param,-1,1,", \\\n");
	DumpText(ctrl->df,0,0,info->help,-1,1," )\n\n");
    }

    ctrl->n_cmd_opt++;
}

//-----------------------------------------------------------------------------

static const info_t * print_links_iterator
	( control_t * ctrl, const info_t * info, ccp prefix );

//-----------------------------------------------------------------------------

static void print_cmd_links ( control_t * ctrl, const info_t * ref )
{
    ASSERT(ctrl);
    ASSERT(ref);

    const info_t * info;
    for ( info = ctrl->info; info < ctrl->end; info++ )
	if ( info->type & T_CMD_BEG && !strcmp(info->c_name,ref->c_name) )
	{
	    print_links_iterator(ctrl,info,"cmd");
	    break;
	}
}

//-----------------------------------------------------------------------------

static void print_grp_links ( control_t * ctrl, const info_t * ref )
{
    ASSERT(ctrl);
    ASSERT(ref);

    const info_t * info;
    for ( info = ctrl->info; info < ctrl->end; info++ )
	if ( info->type & T_GRP_BEG && !strcmp(info->c_name,ref->c_name) )
	{
	    print_links_iterator(ctrl,info,"grp");
	    break;
	}
}

//-----------------------------------------------------------------------------

static const info_t * print_links_iterator
	( control_t * ctrl, const info_t * info, ccp prefix )
{
    ASSERT(ctrl);
    ASSERT(info);

    ccp saved_prefix = ctrl->opt_prefix;
    char buf[100];
    if (prefix)
    {
	snprintf(buf,sizeof(buf),"%s_%s",prefix,info->c_name);
	ctrl->opt_prefix = buf;
    };

    for ( info++; info < ctrl->end && !(info->type & (T_CMD_BEG|T_GRP_BEG) ); info++ )
    {
	if ( info->type & F_HIDDEN )
	{
	    // ignored -> do nothing
	}
	else if ( info->type & T_SEP_OPT )
	    ctrl->need_sep = true;
	else if ( info->type & (F_OPT_GLOBAL|T_CMD_OPT) )
	    print_opt_link(ctrl,info);
	else if ( info->type & T_COPY_CMD )
	    print_cmd_links(ctrl,info);
	else if ( info->type & T_COPY_GRP )
	    print_grp_links(ctrl,info);
    }

    ctrl->opt_prefix = saved_prefix;
    return info;
}

//-----------------------------------------------------------------------------

static void dump_help ( const info_t * info_cmd )
{
    char * temp_param	= iobuf;
    char * temp_help	= iobuf + sizeof(iobuf)/8;
    char * temp_xhelp	= iobuf + sizeof(iobuf)/4;

    DumpText(0,temp_param,iobuf+sizeof(iobuf),info_cmd->param,-1,0,"");
    ccp ff = info_cmd->help ? strchr(info_cmd->help,'\f') : 0;
    if (ff)
    {
	DumpText(0,temp_help,iobuf+sizeof(iobuf),info_cmd->help,ff-info_cmd->help,0,"");
	DumpText(0,temp_xhelp,iobuf+sizeof(iobuf),ff+1,-1,0,"");
    }
    else
    {
	DumpText(0,temp_help,iobuf+sizeof(iobuf),info_cmd->help,-1,0,"");
	StringCopyE(temp_xhelp,iobuf+sizeof(iobuf),"\t0");
    }
}

//-----------------------------------------------------------------------------

static void print_links ( control_t * ctrl )
{
    ASSERT(ctrl);
    FILE * cf = ctrl->cf;
    ASSERT(cf);

    print_section(cf,sep1,"InfoOption tabs");

    char * temp_param	= iobuf;
    char * temp_help	= iobuf + sizeof(iobuf)/8;
    char * temp_xhelp	= iobuf + sizeof(iobuf)/4;
    char * sum_beg	= iobuf + sizeof(iobuf)/2;
    char * sum_end	= iobuf + sizeof(iobuf);
    char * sum		= sum_beg;

    const info_t * info_cmd = ctrl->info;
    ASSERT ( info_cmd->type & T_DEF_TOOL );
    fprintf(cf,"static const InfoOption_t * option_tab_tool[] =\n{\n");

    ctrl->n_cmd_opt	= 0;
    ctrl->need_sep	= false;

    print_links_iterator(ctrl,info_cmd,"def");
    fprintf(cf,"\n\t0\n};\n\n");

    dump_help(info_cmd);
    sum = snprintfE(sum,sum_end,
			"    {\t0,\n"		// id
			"\tfalse,\n"		// hidden
			"\tfalse,\n"		// deprecated
			"\tfalse,\n"		// separator
			"\t\"%s\",\n"		// name1
			"\t0,\n"		// name2
			"%s,\n"			// param
			"%s,\n"			// help
			"%s,\n"			// xhelp
			"\t%u,\n"		// n_opt
			"\toption_tab_tool,\n"	// opt
			"\t0\n"			// opt_allowed
			"    },\n\n"
			,info_cmd->c_name
			,temp_param
			,temp_help
			,temp_xhelp
			,ctrl->n_cmd_opt
			);

    bool separator = false;
    for ( info_cmd++; info_cmd < ctrl->end; info_cmd++ )
    {
	if ( info_cmd->type & T_SEP_CMD )
	    separator = true;
	//if ( !( info_cmd->type & T_DEF_CMD ) || info_cmd->type & F_HIDDEN )
	if ( !( info_cmd->type & T_DEF_CMD ) )
	    continue;

	fprintf(cf,"static const InfoOption_t * option_tab_cmd_%s[] =\n{\n",
		info_cmd->c_name);

	ctrl->n_cmd_opt	= 0;
	ctrl->need_sep	= false;
	ctrl->cmd_name	= info_cmd->c_name;
	ResetStringField(&ctrl->opt_done);

	const info_t * info;
	for ( info = info_cmd;  info < ctrl->end; info++ )
	    if ( info->type & T_CMD_BEG && !strcmp(info->c_name,info_cmd->c_name) )
	    {
		print_links_iterator(ctrl,info,"cmd");
		break;
	    }
	fprintf(cf,"\n\t0\n};\n\n");

	ccp name1 = info_cmd->namelist, ptr = name1;
	while ( *ptr && *ptr != '|' )
	    ptr++;
	const int len1 = ptr - name1;
	char name2[100] = "0";
	if ( *ptr == '|' )
	{
	    ccp n2 = ++ptr;
	    while ( *ptr && *ptr != '|' )
		ptr++;
	    if ( ptr > n2 )
		snprintf(name2,sizeof(name2),"\"%.*s\"",(int)(ptr-n2),n2);
	}

	dump_help(info_cmd);
	sum = snprintfE(sum,sum_end,
			"    {\tCMD_%s,\n"		// id
			"\t%s,\n"			// hidden
			"\t%s,\n"			// deprecated
			"\t%s,\n"			// separator
			"\t\"%.*s\",\n"			// name1
			"\t%s,\n"			// name2
			"%s,\n"				// param
			"%s,\n"				// help
			"%s,\n"				// xhelp
			"\t%u,\n"			// n_opt
			"\toption_tab_cmd_%s,\n"	// opt
			"\toption_allowed_cmd_%s\n"	// opt_allowed
			"    },\n\n"
			,info_cmd->c_name
			,info_cmd->type & F_HIDDEN	 ? "true" : "false"
			,info_cmd->type & F_DEPRECATED	 ? "true" : "false"
			,separator ? "true" : "false"
			,len1 ,name1
			,name2
			,temp_param
			,temp_help
			,temp_xhelp
			,ctrl->n_cmd_opt
			,info_cmd->c_name
			,info_cmd->c_name
			);
	separator = false;
    }

    print_section(cf,sep1,"InfoCommand");
    fprintf(cf,"static const InfoCommand_t CommandInfo[CMD__N+1] =\n{\n");
    fputs(sum_beg,cf);
    fprintf(cf,"    {0,0,0,0,0,0,0,0,0,0,0,0}\n};\n");
}

//
///////////////////////////////////////////////////////////////////////////////
///////////////			    Generate()			///////////////
///////////////////////////////////////////////////////////////////////////////

static char var_buf[10000];

//-----------------------------------------------------------------------------

static void CheckUsedName ( StringField_t *list, ccp name, uint len, ccp title )
{
    //printf("CHECK: |%.*s|\n",len,name);
    ccp key = MEMDUP(name,len);
    if (!InsertStringField(list,key,len))
	fprintf(stderr,"!UI-WARNING: %s already defined: %.*s\n", title, len, name );
}

//-----------------------------------------------------------------------------

static enumError Generate ( control_t * ctrl )
{
    ASSERT(ctrl);
    ASSERT(ctrl->info);

    FILE * cf = ctrl->cf;
    FILE * hf = ctrl->hf;
    ASSERT(cf);
    ASSERT(hf);

    FREE(ctrl->opt_allow_grp);
    FREE(ctrl->opt_allow_cmd);
    ctrl->opt_allow_grp = ctrl->opt_allow_cmd = 0;

    const info_t *info;
    StringField_t used_name;
    InitializeStringField(&used_name);


    //----- ui header

    fprintf(cf,text_ui_head);
    fprintf(hf,text_ui_head);


    //----- setup guard

    char guard[100];
    snprintf(guard,sizeof(guard),"SZS_UI_%s_h",ctrl->info->c_name);
    char * ptr;
    for ( ptr = guard; *ptr; ptr++ )
	*ptr = *ptr == '-' ? '_' : toupper((int)*ptr);
    fprintf(hf,"\n#ifndef %s\n#define %s\n",guard,guard);


    //----- header

    ASSERT( ctrl->info->type & T_DEF_TOOL );
    ccp tool_name = ctrl->info->c_name;

    fprintf(cf,"#include <getopt.h>\n");
    fprintf(cf,"#include \"ui-%s.h\"\n",tool_name);

    fprintf(hf,"#include \"dclib-basics.h\"\n");
    fprintf(hf,"#include \"dclib-ui.h\"\n");


    //----- print enum enumOptions & OptionInfo[]

    print_section(cf,sep1,"OptionInfo[]");
    print_section(hf,sep1,"enum enumOptions");

    char * var_ptr = var_buf;
    char * var_end = var_buf + sizeof(var_buf);
    var_ptr = snprintfE(var_ptr,var_end,
		"//extern const InfoOption_t OptionInfo[OPT__N_TOTAL+1];\n");

    fprintf(cf,
	    "static const InfoOption_t OptionInfo[OPT__N_TOTAL+1] =\n"
	    "{\n"
	    "    {0,0,0,0,0,0,0,0,0,0}, // OPT_NONE,\n"
	    "\n"
	    );

    fprintf(hf,
	    "typedef enum enumOptions\n"
	    "{\n"
	    "\tOPT_NONE,\n"
	    "\n"
	    );

    ctrl->n_opt = 1;
    ctrl->n_opt_specific = 0;
    if ( ctrl->n_cmd )
    {
	fprintf(cf,"    //----- command specific options -----\n\n");
	fprintf(hf,"\t//----- command specific options -----\n\n");

	const info_t *first_info = 0;
	for ( info = ctrl->info; info < ctrl->end; info++ )
	    if ( info->type & F_OPT_COMMAND )
	    {
		if (!first_info)
		    first_info = info;
		print_opt(ctrl,info,first_info);
	    }

	ctrl->n_opt_specific = ctrl->n_opt;

	fprintf(cf,
		"    {0,0,0,0,0,0,0,0,0,0}, // OPT__N_SPECIFIC == %d\n\n"
		"    //----- global options -----\n\n",
		ctrl->n_opt_specific );

	fprintf(hf,
		"\n\tOPT__N_SPECIFIC, // == %d\n\n"
		"\t//----- global options -----\n\n",
		ctrl->n_opt_specific );
    }

    const info_t *first_info = 0;
    for ( info = ctrl->info; info < ctrl->end; info++ )
	if ( info->type & F_OPT_GLOBAL )
	{
	    if (!first_info)
		first_info = info;
	    print_opt(ctrl,info,first_info);
	}

    fprintf(cf,
	    "    {0,0,0,0,0,0,0,0,0,0} // OPT__N_TOTAL == %d\n\n"
	    "};\n"
	    ,ctrl->n_opt );

    fprintf(hf,
	    "\n\tOPT__N_TOTAL // == %d\n\n"
	    "} enumOptions;\n"
	    ,ctrl->n_opt );

    if (ctrl->n_opt_specific)
    {
	noTRACE("opt_allowed = ( %2u + %2u ) * %2u\n",
		ctrl->n_grp, ctrl->n_cmd, ctrl->n_opt_specific );
	if (ctrl->n_grp)
	{
	    ctrl->opt_allow_grp = CALLOC(ctrl->n_grp,ctrl->n_opt_specific);
	}
	ctrl->opt_allow_cmd = CALLOC(ctrl->n_cmd,ctrl->n_opt_specific);
    }


    //----- print alternate option infos

    bool done = false;
    const info_t * last_cmd = ctrl->info;
    ctrl->opt_prefix = "def";
    for ( info = ctrl->info; info < ctrl->end; info++ )
    {
	if ( info->type & T_CMD_BEG )
	{
	    ctrl->opt_prefix = "cmd";
	    last_cmd = info;
	}
	else if ( info->type & T_GRP_BEG )
	{
	    ctrl->opt_prefix = "grp";
	    last_cmd = info;
	}
	else if ( info->type & T_CMD_OPT && info->help && *info->help )
	{
	    if (!done)
	    {
		print_section(cf,sep1,"alternate option infos");
		done = true;
	    }

	    const info_t * info0;
	    for ( info0 = ctrl->info; info0 < info; info0++ )
		if ( info0->type & T_DEF_OPT && !strcmp(info->c_name,info0->c_name) )
		{
		    print_info_opt(ctrl,info,info0,0,last_cmd->c_name);
		    break;
		}
	}
    }

    //----- print enum enumOptionsBit

    if ( ctrl->n_cmd )
    {
	print_section(hf,sep1,"enum enumOptionsBit");

	fprintf(hf,
		"//\t*****  only for verification  *****\n"
		"\n"
		"//typedef enum enumOptionsBit\n"
		"//{\n"
		"//\t//----- command specific options -----\n"
		"//\n"
		);

	for ( info = ctrl->info; info < ctrl->end; info++ )
	    if ( info->type & F_OPT_COMMAND )
		fprintf(hf,"//\tOB_%s%.*s= 1llu << OPT_%s,\n",
			info->c_name,
			( 28 - (int)strlen(info->c_name) ) / 8, tabs,
			info->c_name );

	fprintf(hf,"//\n//\t//----- group & command options -----\n");

	for ( info = ctrl->info; info < ctrl->end; )
	{
	    ccp cmd_name;
	    u8 * opt_allow = 0;
	    if ( info->type & T_CMD_BEG )
	    {
		cmd_name = info->c_name;
		fprintf(hf,"//\n//\tOB_CMD_%s%.*s=",
			info->c_name,
			( 24 - (int)strlen(info->c_name) ) / 8, tabs );
		if (ctrl->opt_allow_cmd)
		{
		    //PRINT("SELECT ALLOW CMD %u/%s\n",info->index,info->c_name);
		    opt_allow = ctrl->opt_allow_cmd + info->index * ctrl->n_opt_specific;
		}
	    }
	    else if ( info->type & T_GRP_BEG )
	    {
		cmd_name = info->c_name;
		fprintf(hf,"//\n//\tOB_GRP_%s%.*s=",
			info->c_name,
			( 24 - (int)strlen(info->c_name) ) / 8, tabs );
		if (ctrl->opt_allow_grp)
		{
		    //PRINT("SELECT ALLOW GRP %u/%s\n",info->index,info->c_name);
		    opt_allow = ctrl->opt_allow_grp + info->index * ctrl->n_opt_specific;
		}
	    }
	    else
	    {
		info++;
		continue;
	    }

	    info++;
	    char * dest = iobuf;
	    while ( info < ctrl->end )
	    {
		if ( info->type & T_ALL_OPT )
		{
		    dest += sprintf(dest,"\n//\t\t\t\t| ~(u64)0");
		    if (opt_allow)
		    {
			//PRINT("ALLOW ALL\n");
			memset(opt_allow,1,ctrl->n_opt_specific);
		    }
		}
		else if ( info->type & T_COPY_CMD )
		{
		    dest += sprintf(dest,"\n//\t\t\t\t| OB_CMD_%s",info->c_name);
		    if (opt_allow)
		    {
			//PRINT("OR CMD %u/%s\n",info->index,info->c_name);
			DASSERT(ctrl->opt_allow_cmd);
			u8 * src = ctrl->opt_allow_cmd + info->index * ctrl->n_opt_specific;
			u8 * dest = opt_allow;
			int count = ctrl->n_opt_specific;
			while ( count-- > 0 )
			    *dest++ |= *src++;
		    }
		}
		else if ( info->type & T_COPY_GRP )
		{
		    dest += sprintf(dest,"\n//\t\t\t\t| OB_GRP_%s",info->c_name);
		    if ( opt_allow && ctrl->opt_allow_grp )
		    {
			//PRINT("OR GRP %u/%s\n",info->index,info->c_name);
			u8 * src = ctrl->opt_allow_grp + info->index * ctrl->n_opt_specific;
			u8 * dest = opt_allow;
			int count = ctrl->n_opt_specific;
			while ( count-- > 0 )
			    *dest++ |= *src++;
		    }
		}
		else if ( info->type & T_CMD_OPT )
		{
		    if (FindStringField(&ctrl->copt,info->c_name))
		    {
			dest += sprintf(dest,"\n//\t\t\t\t| OB_%s",info->c_name);
			if ( opt_allow && info->index )
			{
			    //PRINT("ALLOW OPT %u/%s\n",info->index,info->c_name);
			    opt_allow[info->index] = 1;
			}
		    }
		    else if (!FindStringField(&ctrl->gopt,info->c_name))
			ERROR0(ERR_SEMANTIC,"Option not defined: %s %s --%s",
				tool_name, cmd_name, info->c_name );
		}
		else if ( info->type & (T_CMD_BEG|T_GRP_BEG) )
		    break;
		ASSERT( dest < iobuf + sizeof(iobuf) );
		info++;
	    }
	    if ( dest == iobuf )
		fprintf(hf," 0,\n");
	    else
		fprintf(hf,"%s,\n",iobuf+8);
	}

	fprintf(hf,"//\n//} enumOptionsBit;\n");
    }


    //----- print enum enumCommands & CommandTab[]

    print_section(hf,sep1,"enum enumCommands");
    fprintf(hf,
	    "typedef enum enumCommands\n"
	    "{\n"
	    "\tCMD__NONE,"
	    );

    if ( ctrl->n_cmd )
    {
	print_section(cf,sep1,"CommandTab[]");
	fputs("\n\n",hf);

	var_ptr = snprintfE(var_ptr,var_end,
		"//extern const KeywordTab_t CommandTab[];\n");

	fprintf(cf,
		"static const KeywordTab_t CommandTab[] =\n"
		"{\n"
		);

	for ( info = ctrl->info; info < ctrl->end; info++ )
	    if ( info->type & T_DEF_CMD )
	    {
		fprintf( hf, "\tCMD_%s,\n",info->c_name);
		ccp ptr = info->namelist;
		while (*ptr)
		{
		    ccp n1 = ptr;
		    while ( *ptr && *ptr != '|' )
			ptr++;
		    const int l1 = ptr - n1;
		    CheckUsedName(&used_name,n1,l1,"Command");
		    while ( *ptr == '|' )
			ptr++;
		    if (*ptr)
		    {
			ccp n2 = ptr;
			while ( *ptr && *ptr != '|' )
			    ptr++;
			const int l2 = ptr - n2;
			CheckUsedName(&used_name,n2,l2,"Command");

			fprintf(cf,
				"    { CMD_%s,%.*s\"%.*s\",%.*s\"%.*s\",%.*s0 },\n",
				info->c_name, (20-(int)strlen(info->c_name))/8, tabs,
				l1, n1, (20-l1)/8, tabs,
				l2, n2, (20-l2)/8, tabs );

			while ( *ptr == '|' )
			    ptr++;
		    }
		    else
			fprintf(cf, "    { CMD_%s,%.*s\"%.*s\",%.*s0,\t\t0 },\n",
				info->c_name, (20-(int)strlen(info->c_name))/8, tabs,
				l1, n1, (20-l1)/8, tabs );
		}
	    }
	    else if ( info->type == T_SEP_CMD )
		fprintf(hf,"\n");

	fprintf(cf,
		"\n    { CMD__N,0,0,0 }\n"
		"};\n"
		);

	ResetStringField(&used_name);
    }

    fprintf(hf,
	    "\n\tCMD__N // == %u\n\n"
	    "} enumCommands;\n"
	    , ctrl->n_cmd );


    //----- print options

    print_section(cf,sep1,"OptionShort & OptionLong");

    char * dest = iobuf;
    for ( info = ctrl->info; info < ctrl->end; info++ )
	if ( info->type & T_DEF_OPT && info->namelist[1] == '|' )
	{
	    CheckUsedName(&used_name,info->namelist,1,"Short option");
	    *dest++ = info->namelist[0];
	    if ( ( info->type & (F_OPT_OPTPARAM|F_OPT_PARAM) ) == F_OPT_PARAM )
		*dest++ = ':';
	}
    *dest = 0;
    fprintf(cf,"static const char OptionShort[] = \"%s\";\n\n",iobuf);
    var_ptr = snprintfE(var_ptr,var_end,
		"//extern const char OptionShort[];\n");

    ccp opt_buf[UIOPT_INDEX_SIZE];
    memset(opt_buf,0,sizeof(opt_buf));
    int getopt_idx = UIOPT_LONG_BASE;
    ResetStringField(&used_name);

    fprintf(cf,"static const struct option OptionLong[] =\n{\n");
    var_ptr = snprintfE(var_ptr,var_end,
		"//extern const struct option OptionLong[];\n");

    for ( info = ctrl->info; info < ctrl->end; info++ )
	if ( info->type & T_DEF_OPT )
	{
	    ccp ptr = info->namelist;
	    const int pmode = (info->type & F_OPT_OPTPARAM)
				? 2
				: (info->type & F_OPT_PARAM)
					? 1
					: 0;

	    if ( info->namelist[1] == '|' )
	    {
		snprintf(iobuf,sizeof(iobuf),"%d, 0, '%c'",
			pmode, info->namelist[0] );
		ptr += 2;
		opt_buf[(u8)(info->namelist[0])] = info->c_name;
	    }
	    else
	    {
		snprintf(iobuf,sizeof(iobuf),"%d, 0, GO_%s",
			pmode, info->c_name );
		ASSERT_MSG( getopt_idx < UIOPT_INDEX_SIZE,
				"getopt_idx >= UIOPT_INDEX_SIZE [ 0x%x >= 0x%x ]\n",
				getopt_idx, UIOPT_INDEX_SIZE );
		if ( getopt_idx < UIOPT_INDEX_SIZE )
		    opt_buf[getopt_idx++] = info->c_name;
	    }

	    int indent = 0;
	    while (*ptr)
	    {
		ccp start = ptr;
		while ( *ptr && *ptr != '|' )
		    ptr++;
		const int len = ptr - start;
		if (len)
		{
		    CheckUsedName(&used_name,start,len,"Long option");
		    fprintf(cf,"\t%s{ \"%.*s\",%.*s%s },\n",
			indent ? " " : "", len, start, (26-len-indent)/8, tabs, iobuf );
		    indent = 1;
		}
		if (*ptr)
		    ptr++;
	    }
	}
    fprintf(cf,"\n\t{0,0,0,0}\n};\n");


    //----- print enumGetOpt

    print_section(hf,sep1,"enumGetOpt");
    fprintf(hf,"typedef enum enumGetOpt\n{");

    // add '?' temporary;
    ASSERT(!opt_buf['?']);
    opt_buf['?'] = "_ERR";

    static const int septab[] = { 0, '0', '9'+1, '?', '?'+1,
				  'A', 'Z'+1, 'a', 'z'+1,
				  UIOPT_LONG_BASE, UIOPT_INDEX_SIZE };
    const int * sepptr = septab;
    int i;
    for ( i = 0; i < UIOPT_INDEX_SIZE; i++ )
	if ( opt_buf[i] )
	{
	    if ( i >= *sepptr )
	    {
		fputc('\n',hf);
		while ( i >= *sepptr )
		    sepptr++;
	    }
	    if ( i < UIOPT_LONG_BASE )
		fprintf(hf,"\tGO_%s%.*s= '%c',\n",
			opt_buf[i], (28-(int)strlen(opt_buf[i]))/8, tabs, i );
	    else if ( i == UIOPT_LONG_BASE )
		fprintf(hf,"\tGO_%s%.*s= 0x%02x,\n",
			opt_buf[i], (28-(int)strlen(opt_buf[i]))/8, tabs, i );
	    else
		fprintf(hf,"\tGO_%s,\n",opt_buf[i]);
	}

    fprintf(hf,"\n} enumGetOpt;\n");
    opt_buf['?'] = 0;


    //----- print option index

    print_section(cf,sep1,"OptionUsed & OptionIndex");

    fprintf(cf,"static u8 OptionUsed[OPT__N_TOTAL+1] = {0};\n\n");
    var_ptr = snprintfE(var_ptr,var_end,
		"//extern u8 OptionUsed[OPT__N_TOTAL+1];\n");

    fprintf(cf,"static const OptionIndex_t OptionIndex[UIOPT_INDEX_SIZE] =\n{\n");
    var_ptr = snprintfE(var_ptr,var_end,
		"//extern const OptionIndex_t OptionIndex[UIOPT_INDEX_SIZE];\n"
		"//UIOPT_INDEX_SIZE := 0x%x = %u\n",
		UIOPT_INDEX_SIZE, UIOPT_INDEX_SIZE );

    for ( i = 0; i < UIOPT_INDEX_SIZE; )
    {
	int start = i;
	while ( i < UIOPT_INDEX_SIZE && !opt_buf[i] )
	    i++;
	int len = i - start;
	while ( len > 0 )
	{
	    const int now_len = len < 16 ? len : 16 - start % 16;
	    fprintf(cf,"\t/* 0x%0*x   */\t %.*s\n",
			UIOPT_INDEX_FW,
			start,
			2*now_len + now_len/4,
			"0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0," );
	    start += now_len;
	    len -= now_len;
	}

	while ( i < UIOPT_INDEX_SIZE && opt_buf[i] )
	{
	    fprintf(cf,"\t/* 0x%0*x %c */\tOPT_%s,\n",
		UIOPT_INDEX_FW, i, i > ' ' && i < 0x7f ? i : ' ', opt_buf[i]);
	    i++;
	}
    }
    fprintf(cf,"};\n");


    //----- option allowed

    if (ctrl->opt_allow_cmd)
    {
	print_section(cf,sep1,"opt_allowed_cmd_*");
	for ( info = ctrl->info; info < ctrl->end; info++ )
	{
	    if ( !( info->type & T_DEF_CMD ) )
		continue;

	    fprintf(cf,"static u8 option_allowed_cmd_%s[%u] = // cmd #%u\n{",
		info->c_name, ctrl->n_opt_specific, info->index );

	    int i;
	    u8 * ptr = ctrl->opt_allow_cmd + info->index * ctrl->n_opt_specific;
	    for ( i = 0; i < ctrl->n_opt_specific; i++ )
		fprintf(cf, "%s%u%s",
			    !(i%30) ? "\n    " : !(i%10) ? "  " : !(i%5) ? " " : "",
			    ptr[i],
			    i < ctrl->n_opt_specific-1 ? "," : "" );

	    fprintf(cf,"\n};\n\n");
	}
    }

    //----- InfoCommand

    print_links(ctrl);
    var_ptr = snprintfE(var_ptr,var_end,
		"//extern const InfoCommand_t CommandInfo[CMD__N+1];\n");

    //----- InfoUI

    char xname[100];
    {
	StringCopyS(xname,sizeof(xname),ctrl->info->c_name);
	char *ptr;
	for ( ptr = xname; *ptr; ptr++ )
	    if (!isalnum((int)*ptr))
		*ptr = '_';
    }

    print_section(cf,sep1,"InfoUI");

    var_ptr = snprintfE(var_ptr,var_end,
		"extern const InfoUI_t InfoUI_%s;\n",
		xname );

    fprintf(cf,
	    "const InfoUI_t InfoUI_%s =\n"
	    "{\n"
	    "\t\"%s\",\n"	// tool_name
	    "\t%s\n"		// n_cmd
	    "\t%s\n"		// cmd_tab
	    "\tCommandInfo,\n"	// cmd_info
	    "\t%s\n"		// n_opt_specific
	    "\tOPT__N_TOTAL,\n"	// n_opt_total
	    "\tOptionInfo,\n"	// opt_info
	    "\tOptionUsed,\n"	// opt_used
	    "\tOptionIndex,\n"	// opt_index
	    "\tOptionShort,\n"	// opt_short
	    "\tOptionLong\n"	// opt_long
	    "};\n",
	    xname,
	    ctrl->info->c_name,
	    ctrl->n_cmd ? "CMD__N," : "0, // n_cmd",
	    ctrl->n_cmd ? "CommandTab," : "0, // cmd_tab",
	    ctrl->n_cmd ? "OPT__N_SPECIFIC," : "0, // n_opt_specific" );


    //----- external vars

    print_section(hf,sep1,"external vars");
    fputs(var_buf,hf);


    //----- terminate

    print_section(cf,sep1,"END");
    print_section(hf,sep1,"END");
    fprintf(hf,"#endif // %s\n\n",guard);
    ResetStringField(&used_name);

    return ERR_OK;
};

//
///////////////////////////////////////////////////////////////////////////////
///////////////			  dummy functions		///////////////
///////////////////////////////////////////////////////////////////////////////

static void print_title ( FILE * f ) {}

//
///////////////////////////////////////////////////////////////////////////////
///////////////			  AddTables()			///////////////
///////////////////////////////////////////////////////////////////////////////

static void AddTables ( FILE * df )
{
    DASSERT(df);
}

//
///////////////////////////////////////////////////////////////////////////////
///////////////			    main()			///////////////
///////////////////////////////////////////////////////////////////////////////

int ui_main ( int argc, char ** argv )
{
    static ccp def_null_name = "/dev/null";
    FILE * nf = fopen(def_null_name,"wb");
    if (!nf)
    {
	fprintf(stderr,"!!! Can't create file: %s\n",def_null_name);
	return ERR_CANT_CREATE;
    }

    static ccp def_fname = "src/ui/ui.def";
    FILE * df = fopen(def_fname,"wb");
    if (!df)
    {
	fprintf(stderr,"!!! Can't create file: %s\n",def_fname);
	return ERR_CANT_CREATE;
    }

    char fname_c[200], fname_c_temp[210];
    char fname_h[200], fname_h_temp[210];
    info_t * info = info_tab;

    while ( info->type != T_END )
    {
	if ( ! ( info->type & T_DEF_TOOL ) )
	{
	    fprintf(stderr,"!!! Missing T_DEF_TOOL entry.\n");
	    return ERR_SYNTAX;
	}

	control_t ctrl;
	memset(&ctrl,0,sizeof(ctrl));
	ctrl.df = nf;
	ctrl.opt_prefix = "";
	InitializeStringField(&ctrl.gopt);
	InitializeStringField(&ctrl.copt);
	InitializeStringField(&ctrl.opt_done);

	if ( !( info->type & F_HIDDEN ) )
	{
	    ctrl.df = df;
	    snprintf(iobuf,sizeof(iobuf),"Tool '%s'",info->c_name);
	    print_section(df,sep2,iobuf);
	    fprintf(df,"#:def_tool( \"%s\", \\\n",info->c_name);
	    DumpText(df,0,0,info->param,-1,1,", \\\n");
	    DumpText(df,0,0,info->help,-1,1," )\n\n");
	}

	snprintf(fname_c,sizeof(fname_c),"src/ui/ui-%s.c",info->c_name);
	StringCat2S(fname_c_temp,sizeof(fname_c_temp),fname_c,".temp");
	ctrl.cf = fopen(fname_c_temp,"wb");
	if (!ctrl.cf)
	{
	    fprintf(stderr,"!!! Can't create file: %s\n",fname_c_temp);
	    return ERR_CANT_CREATE;
	}

	snprintf(fname_h,sizeof(fname_h),"src/ui/ui-%s.h",info->c_name);
	StringCat2S(fname_h_temp,sizeof(fname_h_temp),fname_h,".temp");
	ctrl.hf = fopen(fname_h_temp,"wb");
	if (!ctrl.hf)
	{
	    fclose(ctrl.cf);
	    fprintf(stderr,"!!! Can't create file: %s\n",fname_h_temp);
	    return ERR_CANT_CREATE;
	}

	ctrl.info = info++;
	while ( ! ( info->type & (T_END|T_DEF_TOOL)) )
	{
	    if ( info->type & T_DEF_OPT )
	    {
		if ( info->type & F_OPT_GLOBAL )
		{
		    InsertStringField(&ctrl.gopt,info->c_name,false);
		    ++ctrl.n_opt;
		    //opt_index = 0 for global options
		}
		else
		{
		    InsertStringField(&ctrl.copt,info->c_name,false);
		    info->index = ++ctrl.n_opt_specific;
		}

		if ( !info->help )
		{
		    // copy 'param' and 'help' info from previous tool
		    const info_t * search;
		    for ( search = info; search >= info_tab; search-- )
			if ( search->type & T_DEF_OPT
			    && search->help
			    && !strcmp(search->c_name,info->c_name) )
			{
			    info->help  = search->help;
			    if (!info->param)
				info->param = search->param;
			    break;
			}
		}

		if ( !( info->type & F_HIDDEN ) )
		{
		    fprintf(ctrl.df,"#:def_opt( \"%s\", \"%s\", \"%s%s%s%s%s\", \\\n",
			info->c_name, info->namelist,
			info->type & F_OPT_COMMAND  ? "C" : "",
			info->type & F_OPT_GLOBAL   ? "G" : "",
			info->type & F_OPT_MULTIUSE ? "M" : "",
			info->type & F_OPT_PARAM    ? "P" : "",
			info->type & F_OPT_OPTPARAM ? "O" : "" );
		    DumpText(ctrl.df,0,0,info->param,-1,1,", \\\n");
		    DumpText(ctrl.df,0,0,info->help,-1,1," )\n\n");
		}
	    }
	    else if ( info->type & T_DEF_CMD )
	    {
		info->index = ++ctrl.n_cmd;

		if ( !info->help )
		{
		    // copy 'param' and 'help' info from previous tool
		    const info_t * search = info-1;
		    for ( search = info; search >= info_tab; search-- )
			if ( search->type & T_DEF_CMD
			    && search->help
			    && !strcmp(search->c_name,info->c_name) )
			{
			    info->help  = search->help;
			    if (!info->param)
				info->param = search->param;
			    break;
			}
		}

		if ( !( info->type & F_HIDDEN ) )
		{
		    fprintf(ctrl.df,"#:def_cmd( \"%s\", \"%s\", \\\n",
			info->c_name, info->namelist );
		    DumpText(ctrl.df,0,0,info->param,-1,1,", \\\n");

		    ccp ff = info->help ? strchr(info->help,'\f') : 0;
		    if (ff)
		    {
			DumpText(ctrl.df,0,0,info->help,ff-info->help,1,", \\\n");
			DumpText(ctrl.df,0,0,ff+1,-1,1," )\n\n");
		    }
		    else
			DumpText(ctrl.df,0,0,info->help,-1,1," )\n\n");
		}
	    }
	    else if ( info->type & T_GRP_BEG )
		info->index = ctrl.n_grp++; // NULL based
	    else if ( info->type & (T_CMD_OPT|T_CMD_BEG|T_COPY_GRP|T_COPY_CMD) )
	    {
		const int type	= info->type & T_CMD_OPT  ? T_DEF_OPT
				: info->type & T_COPY_GRP ? T_GRP_BEG
				: T_DEF_CMD;

		const info_t * search;
		for ( search = ctrl.info; search < info; search++ )
		    if ( search->type & type
			&& !strcmp(search->c_name,info->c_name) )
		    {
			//PRINT("COPY INDEX #%x: %s\n",info->type,info->c_name);
			info->index = search->index;
			break;
		    }
	    }

	    info++;
	}
	ctrl.end = info;

	if (ctrl.n_cmd)
	    ctrl.n_cmd++; // one more for CMD_NONE;

	if (ctrl.n_opt_specific)
	    ctrl.n_opt += ++ctrl.n_opt_specific;

	TRACE("N: cmd=%u, grp=%u, opt=%d/%d\n",
		ctrl.n_cmd, ctrl.n_grp, ctrl.n_opt_specific, ctrl.n_opt );
	const enumError err = Generate(&ctrl);

	fclose(ctrl.cf);
	fclose(ctrl.hf);
	rename(fname_c_temp,fname_c);
	rename(fname_h_temp,fname_h);

	ResetStringField(&ctrl.gopt);
	ResetStringField(&ctrl.copt);
	ResetStringField(&ctrl.opt_done);

	if (err)
	    return err;
    }

    AddTables(df);
    print_section(df,sep2,"END");
    fclose(df);
    fclose(nf);
    return ERR_OK;
}

//
///////////////////////////////////////////////////////////////////////////////
///////////////			    E N D			///////////////
///////////////////////////////////////////////////////////////////////////////
