/*
 * This file is part of XForms.
 *
 *  XForms is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU Lesser General Public License as
 *  published by the Free Software Foundation; either version 2.1, or
 *  (at your option) any later version.
 *
 *  XForms 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with XForms.  If not, see <http://www.gnu.org/licenses/>.
 */


/**
 * \file fd_printC.c
 *
 *  This file is part of XForms package
 *  Copyright (c) 1996-2002  T.C. Zhao and Mark Overmars
 *  All rights reserved.
 *
 *  Generate header/C files
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "fd_main.h"
#include "private/flsnprintf.h"
#include "fd_spec.h"
#include "sp_menu.h"

#ifdef WIN32
#include <io.h>
#endif


/***************************************
 ***************************************/

void
make_backup( const char *s )
{
    char *buf;

    if ( access( s, R_OK ) )
    {
        if ( errno != ENOENT )
            M_err( "make_backup", "Creating backup file %s.bak failed", s );
        return;
    }

    buf = malloc( strlen( s ) + 5 );
    sprintf( buf, "%s.bak", s );

#ifdef  __EMX__
    if ( unlink( buf ) )
    {
        M_err( "make_backup", "Creating backup file %s failed", buf );
        free( buf );
        return;
    }
#endif

    if ( rename( s, buf ) )
        M_err( "make_backup", "Creating backup file %s failed %s", buf, strerror( errno ) );

    free( buf );
}


/***************************************
 ***************************************/

static char const *
filename_only( char const * filename )
{
    char const * ptr = strrchr( filename, '/' );

    if ( ptr )
        return ptr + 1;
    return filename;
}


/***************************************
 ***************************************/

static int
build_fname( char       * fname,
             size_t       fname_capacity,
             char const * filename,
             char const * ext )
{
    int npc;

    if ( fdopt.output_dir )
        npc = fli_snprintf( fname, fname_capacity, "%s%s%s%s",
                            fdopt.output_dir,
                            fdopt.output_dir[ strlen( fdopt.output_dir ) - 1 ]
                                                              != '/' ? "/" : "",
                           filename_only( filename ), ext );
    else
        npc = fli_snprintf( fname, fname_capacity, "%s%s", filename, ext );

    /* Older libc return -1 if text doesn't, newer ones the total number
       of chars that would have been written if there would have been
       enough space */

    return npc >= 0 && ( size_t ) npc <= fname_capacity;
}


/***************************************
 * Note: 'filename' has no extensions
 ***************************************/

int
C_output( const char * filename,
          FRM        * forms,
          int          fnumb )
{
    char fname[ PATH_MAX + 1 ];
    const char *name_only;
    int i;
    FILE *fn;
    FL_OBJECT *obj;
    int use_glcanvas = 0;

    /* Figure out the name of the file without any path */

    if ( ( name_only = strrchr( filename, '/' ) ) )
        ++name_only;
    else
        name_only = filename;

    if ( ! build_fname( fname, sizeof fname, filename, ".h" ) )
    {
        if ( ! fdopt.conv_only )
            fl_show_alert( "Can't create header file!",
                           "Filename is too long:", "", 1 );
        else
            M_err( "C_output",
                   "Can't create header file, filename is too long" );
        return 0;
    }

    make_backup( fname );

    if ( ! ( fn = fopen( fname, "w" ) ) )
    {
        if ( ! fdopt.conv_only )
            fl_show_alert( "Can't open header file!", fname, "", 1 );
        else
            M_err( "C_output", "Can't create open header file '%s'", fname );
        return 0;
    }

    fprintf( fn, "/* Header file generated by fdesign on %s */\n\n"
                 "#ifndef %s_h_\n"
                 "#define %s_h_\n\n"
                 "#include %c%s%c\n",
             fl_now( ),
             get_fd_name( forms[ 0 ].fname ),
             get_fd_name( forms[ 0 ].fname ),
             xform_header[ 0 ] == '"' ? ' ' : '<',
             xform_header,
             xform_header[ 0 ] == '"' ? ' ' : '>' );

    fprintf( fn,
             "\n#if defined __cplusplus\n"
             "extern \"C\"\n"
             "{\n"
             "#endif\n" );

    for ( i = 0; i < fnumb && ! use_glcanvas; i++ )
    {
        obj = forms[ i ].form->first;
        while ( ( obj = obj->next ) != NULL )
            if ( obj->objclass == FL_GLCANVAS )
            {
                use_glcanvas = 1;
                break;
            }
    }

    if ( use_glcanvas )
        fprintf( fn, "#include <%s>\n", glcanvas_header );

    fprintf( fn, "\n/* Callbacks, globals and object handlers */\n\n" );

    for ( i = 0; i < fnumb; i++ )
        print_callbacks_and_globals( fn, forms[ i ].form, 0 );

    fprintf( fn, "\n/* Forms and Objects */\n\n" );
    for ( i = 0; i < fnumb; i++ )
        print_header( fn, forms[ i ].form, forms[ i ].fname );

    if ( fdopt.altformat )
        fprintf( fn, "\n/* Creation Routine */\n\n"
                     "void %s( void );\n", main_name );

    fprintf( fn,
             "\n#if defined __cplusplus\n"
             "}\n"
             "#endif\n" );

    fprintf( fn, "\n#endif /* %s_h_ */\n", get_fd_name( forms[ 0 ].fname ) );
    fclose( fn );

    /* Make the .c file. */

    if ( ! build_fname( fname, sizeof fname, filename, ".c" ) )
    {
        if ( ! fdopt.conv_only )
            fl_show_alert( "Can't create C file!",
                           "Filename is too long.", "", 1 );
        else
            M_err( "C_output", "Can't create C file, filename is too long" );
        return 0;
    }

    make_backup( fname );

    if ( ! ( fn = fopen( fname, "w" ) ) )
    {
        if ( ! fdopt.conv_only )
            fl_show_alert( "Can't open C file!", fname, "", 1 );
        else
            M_err( "C_output", "Can't open C file '%s'", fname );
        return 0;
    }

    fprintf( fn, "/* Form definition file generated by fdesign on %s */\n\n"
                 "#include <stdlib.h>\n"
                 "#include \"%s.h\"\n\n", fl_now( ), name_only );

    for ( i = 0; i < fnumb; i++ )
        print_form( fn, forms[ i ].form, forms[ i ].fname );

    if ( fdopt.altformat )
    {
        fprintf( fn, "void %s( void )\n{\n", main_name );
        for ( i = 0; i < fnumb; i++ )
            fprintf( fn, "    create_form_%s( );\n", forms[ i ].fname );
        fprintf( fn, "}\n" );
    }

    fclose( fn );

    /* Check if we need to output a template for the main program */

    if ( fdopt.emit_main )
    {
        if ( ! build_fname( fname, sizeof fname, filename, "_main.c" ) )
        {
            if ( ! fdopt.conv_only )
                fl_show_alert( "Can't create C file for main() function!",
                               "Filename is too long:", "", 1 );
            else
                M_err( "C_output", "Can't create C file for main() function, "
                       "filename is too long" );
            reset_dupinfo_cache( );
            return 0;
        }

        make_backup( fname );

        if ( ! ( fn = fopen( fname, "w" ) ) )
        {
            if ( ! fdopt.conv_only )
                fl_show_alert( "Can't open file for main() function!",
                               "", "", 1 );
            else
                M_err( "C_output", "Can't open file for main() function!" );
            reset_dupinfo_cache( );
            return 0;
        }

        /* Print out the include for the header file and then all the code */

        fprintf( fn, "#include \"%s.h\"\n\n", name_only );
        output_main( fn, forms, fnumb );

        fclose( fn );
    }

    /* Output callback stubs */

    if ( fdopt.emit_cb )
    {
        if ( ! build_fname( fname, sizeof fname, filename, "_cb.c" ) )
        {
            if ( ! fdopt.conv_only )
                fl_show_alert( "Can't create C file for callbacks!",
                               "Filename is too long:", "", 1 );
            else
                M_err( "C_output", "Can't create C file for callbacks, "
                       "filename is too long" );
            reset_dupinfo_cache( );
            return 0;
        }

        make_backup( fname );
        if ( ! ( fn = fopen( fname, "w" ) ) )
        {
            if ( ! fdopt.conv_only )
                fl_show_alert( "Can't open C file for callbacks!", fname,
                               "", 1 );
            else
                M_err( "C_output",
                       "Can't open C file for callbacks '%s'", fname );
            reset_dupinfo_cache( );
            return 0;
        }

        /* Print out the include for the header file and then all the code */

        fprintf( fn, "#include \"%s.h\"\n\n", name_only );
        output_callbacks( fn, forms, fnumb );

        fclose( fn );
    }

    reset_dupinfo_cache( );
    return 1;
}



/* Some attributes query routines */

#define VN( v )    { v, #v, NULL, NULL }
#define PVN( v )   v, #v


VN_pair vn_btype[ ] =
{
    { PVN( FL_NO_BOX ),                "No box%r1",            "Nn#n" },
    { PVN( FL_UP_BOX ),                "Up box%r1",            "Uu#u" },
    { PVN( FL_DOWN_BOX ),              "Down box%r1",          "Dd#d" },
    { PVN( FL_BORDER_BOX ),            "Border box%r1",        "Bb#b" },
    { PVN( FL_SHADOW_BOX ),            "Shadow box%r1",        "Ss#s" },
    { PVN( FL_FRAME_BOX ),             "Frame box%r1",         "Ff#f" },
    { PVN( FL_ROUNDED_BOX ),           "Rounded box%r1",       "Rr#r" },
    { PVN( FL_EMBOSSED_BOX ),          "Embossed box%r1",      "Ee#e" },
    { PVN( FL_FLAT_BOX ),              "Flat box%r1",          "Ff#f" },
    { PVN( FL_RFLAT_BOX ),             "Rflat box%r1",         "lL#l" },
    { PVN( FL_RSHADOW_BOX ),           "Rshadow box%r1",       "wW#w" },
    { PVN( FL_OVAL_BOX ),              "Oval box%r1",          "Oo#o" },
    { PVN( FL_ROUNDED3D_UPBOX ),       "rounded3d upbox%r1",   "nN#n" },
    { PVN( FL_ROUNDED3D_DOWNBOX ),     "rounded3d downbox%r1", "oO#o" },
    { PVN( FL_OVAL3D_UPBOX ),          "Oval3d upbox%r1",      "vV#v" },
    { PVN( FL_OVAL3D_DOWNBOX ),        "Oval3d downbox%r1",    "lL#l" },
    { PVN( FL_OVAL3D_FRAMEBOX ),       "Oval3d framebox%r1",   "eE#e" },
    { PVN( FL_OVAL3D_EMBOSSEDBOX ),    "Oval3d embossed%r1",   "eE#e" },
#if 0
    { PVN( FL_TOPTAB_UPBOX ),          "toptab%r1",            "tT#t" },
    { PVN( FL_SELECTED_TOPTAB_UPBOX ), "selected toptab%r1",   "sS#s" },
#endif
    { PVN( -1 ),                       NULL,                   NULL   }
};

VN_pair vn_align[ ] =
{
    VN( FL_ALIGN_TOP          ),
    VN( FL_ALIGN_BOTTOM       ),
    VN( FL_ALIGN_LEFT         ),
    VN( FL_ALIGN_RIGHT        ),
    VN( FL_ALIGN_CENTER       ),
    VN( FL_ALIGN_RIGHT_TOP    ),
    VN( FL_ALIGN_LEFT_TOP     ),
    VN( FL_ALIGN_RIGHT_BOTTOM ),
    VN( FL_ALIGN_LEFT_BOTTOM  ),
    VN( FL_ALIGN_TOP_RIGHT    ),    /* need them for backward compatibility */
    VN( FL_ALIGN_TOP_LEFT     ),
    VN( FL_ALIGN_BOTTOM_RIGHT ),
    VN( FL_ALIGN_BOTTOM_LEFT  ),
    { -1, NULL, NULL, NULL    }
};

static VN_pair vn_lsize[ ] =
{
    VN( FL_DEFAULT_SIZE ),
    VN( FL_TINY_SIZE    ),
    VN( FL_SMALL_SIZE   ),
    VN( FL_NORMAL_SIZE  ),
    VN( FL_MEDIUM_SIZE  ),
    VN( FL_LARGE_SIZE   ),
    VN( FL_HUGE_SIZE    ),

    VN( FL_DEFAULT_FONT ),
    VN( FL_TINY_FONT    ),
    VN( FL_SMALL_FONT   ),
    VN( FL_NORMAL_FONT  ),
    VN( FL_MEDIUM_FONT  ),
    VN( FL_LARGE_FONT   ),
    VN( FL_HUGE_FONT    ),
    { FL_SMALL_FONT,  "FL_NORMAL_FONT1", NULL, NULL },
    { FL_NORMAL_FONT, "FL_NORMAL_FONT2", NULL, NULL },
    { -1, NULL, NULL, NULL    }
};

static VN_pair vn_lstyle[ ] =
{
    VN( FL_NORMAL_STYLE ),
    VN( FL_BOLD_STYLE ),
    VN( FL_ITALIC_STYLE ),
    VN( FL_BOLDITALIC_STYLE ),
    VN( FL_FIXED_STYLE ),
    VN( FL_FIXEDBOLD_STYLE ),
    VN( FL_FIXEDITALIC_STYLE ),
    VN( FL_FIXEDBOLDITALIC_STYLE ),
    VN( FL_TIMES_STYLE ),
    VN( FL_TIMESBOLD_STYLE ),
    VN( FL_TIMESITALIC_STYLE ),
    VN( FL_TIMESBOLDITALIC_STYLE ),
    VN( FL_SHADOW_STYLE ),
    VN( FL_ENGRAVED_STYLE ),
    VN( FL_EMBOSSED_STYLE ),
    { -1, NULL, NULL, NULL    }
};

VN_pair vn_gravity[] =
{
    VN( FL_NoGravity ),
    VN( FL_NorthWest ),
    VN( FL_North ),
    VN( FL_NorthEast ),
    VN( FL_West ),

    VN( FL_East ),
    VN( FL_South ),
    VN( FL_SouthEast ),
    VN( FL_SouthWest ),

    VN( FL_ForgetGravity ),
    VN( ForgetGravity ),
    VN( NorthWestGravity ),
    VN( NorthGravity ),
    VN( NorthEastGravity ),
    VN( WestGravity ),

    VN( EastGravity ),
    VN( SouthGravity ),
    VN( SouthEastGravity ),
    VN( SouthWestGravity ),

    { -1, NULL, NULL, NULL    }
};

VN_pair vn_resize[ ] =
{
    VN( FL_RESIZE_NONE ),  
    VN( FL_RESIZE_X ),
    VN( FL_RESIZE_Y ),
    VN( FL_RESIZE_ALL ),
    { -1, NULL, NULL, NULL }
};

static VN_pair vn_unit[ ] =
{
    VN( FL_COORD_PIXEL ),
    VN( FL_COORD_MM ),
    VN( FL_COORD_centiMM ),
    VN( FL_COORD_POINT ),
    VN( FL_COORD_centiPOINT ),
    { FL_COORD_PIXEL,      "pixel",  NULL, NULL },
    { FL_COORD_MM,         "mm",     NULL, NULL },
    { FL_COORD_POINT,      "point",  NULL, NULL },
    { FL_COORD_centiPOINT, "cp",     NULL, NULL },
    { FL_COORD_centiMM,    "cmm",    NULL, NULL },
    { FL_COORD_centiPOINT, "cpoint", NULL, NULL },
    { -1,                  NULL,     NULL, NULL }
};


/***************************************
 ***************************************/

int
get_vn_val( VN_pair *    vn,
            const char * name )
{
    long val;
    char *ep;

    for ( ; vn->name; vn++ )
        if ( strcmp( name, vn->name ) == 0 )
            return vn->val;

    val = strtol( name, &ep, 10 );

    if ( ep != name && ! *ep && val >= INT_MIN && val <= INT_MAX )
        return val;

    return -1;
}


/***************************************
 ***************************************/

char *
get_vn_name( VN_pair * vn,
             int       val )
{
    static char buf[ MAX_TYPE_NAME_LEN ];

    for ( ; vn->name; vn++ )
    if ( vn->val == val )
        return vn->name;

    sprintf( buf, "%d", val );
    return buf;
}


static void output_object( FILE * fn, FL_OBJECT * obj, int );
static void pre_form_output( FILE * fn );
static void post_form_output( FILE * fn );


/***************************************
 ***************************************/

static void
emit_attrib( FILE *       fp,
             int          a,
             VN_pair    * vn,
             const char * aname )
{
    const char *s;

    if ( vn == vn_align )
        s = align_name( a, 1 );
    else
        s = get_vn_name( vn, a );

    fprintf( fp, "    %s( obj, %s );\n", aname, s );
}


/***************************************
 ***************************************/

static char *
pure_style_name( int val )
{
    VN_pair *vn = vn_lstyle;
    static char buf[ 64 ];

    for ( ; vn->name && vn->val != val; vn++ )
        /* empty */ ;

    if ( vn->val == val )
        return vn->name;
    else
    {
        sprintf( buf, "%d", val );
        return buf;
    }
}


/***************************************
 ***************************************/

static int
pure_style_val( const char * cc )
{
    VN_pair *vn = vn_lstyle;

    for ( ; vn->name && strcmp( cc, vn->name ); vn++ )
        /* empty */;
    return ( vn->name && ! strcmp( cc, vn->name ) ) ? vn->val : atoi( cc );
}


/***************************************
 ***************************************/

char *
style_name( int style )
{
    static char buf[ 64 ];
    int lstyle = style % FL_SHADOW_STYLE;
    int spstyle = ( style / FL_SHADOW_STYLE ) * FL_SHADOW_STYLE;

    strcpy( buf, pure_style_name( lstyle ) );
    if ( spstyle )
        strcat( strcat( buf, " + " ), pure_style_name( spstyle ) );
    return buf;
}


/***************************************
 ***************************************/

int
style_val( const char * cc )
{
    char lstyle[ MAX_TYPE_NAME_LEN ],
         spstyle[ MAX_TYPE_NAME_LEN ],
         *p;

    fli_sstrcpy( lstyle, cc, sizeof lstyle );
    *spstyle = '\0';
    if ( ( p = strchr( lstyle, '|' ) ) || ( p = strchr( lstyle, '+' ) ) )
    {
        strcpy( spstyle, p + 1 );
        *p = 0;
    }

    return pure_style_val( lstyle ) + pure_style_val( spstyle );
}


/***************************************
 ***************************************/

char *
lsize_name( int val )
{
    return get_vn_name( vn_lsize, val );
}


/***************************************
 ***************************************/

int
lsize_val( const char * cc )
{
    return get_vn_val( vn_lsize, cc );
}


/***************************************
 ***************************************/

char *
gravity_name( int val )
{
    return get_vn_name( vn_gravity, val );
}


/***************************************
 ***************************************/

int
gravity_val( const char * cc )
{
    return get_vn_val( vn_gravity, cc );
}


/***************************************
 ***************************************/

char *
resize_name( int val )
{
    return get_vn_name( vn_resize, val );
}


/***************************************
 ***************************************/

int
resize_val( const char * cc )
{
    return get_vn_val( vn_resize, cc );
}


/***************************************
 ***************************************/

const char *
align_name( int val,
            int with_spaces )
{
    static char buf[ 128 ];

    strcpy( buf, get_vn_name( vn_align, fl_to_outside_lalign( val ) ) );
    if ( fl_is_inside_lalign( val ) && ! fl_is_center_lalign( val ) )
    {
        strcat( buf, with_spaces ? " | " : "|" );
        strcat( buf, "FL_ALIGN_INSIDE" );
    }

    return buf;
}


/***************************************
 ***************************************/

int
align_val( const char * cc )
{
    char s[ MAX_TYPE_NAME_LEN ],
         *p;
    int val;

    fli_sstrcpy( s, cc, sizeof s );
    if ( ( p = strchr( s, '|' ) ) )
    {
        *p = '\0';
        while ( isspace( ( unsigned char ) *--p  ) )
            *p = '\0';
    }
    val = get_vn_val( vn_align, s );

    return p ? fl_to_inside_lalign( val ) : val;
}


/***************************************
 ***************************************/

char *
boxtype_name( int val )
{
    return get_vn_name( vn_btype, val );
}


/***************************************
 ***************************************/

int
boxtype_val( const char * cc )
{
    return get_vn_val( vn_btype, cc );
}


/***************************************
 ***************************************/

char *
unit_name( int val )
{
    return get_vn_name( vn_unit, val );
}


/***************************************
 ***************************************/

int
unit_val( const char * s )
{
    return get_vn_val( vn_unit, s );
}


/*** End of attributes query routines ***/

/*------------- Keeping track of array names. -------------------*/

#define MAXARNAME   100

static char *arnames[ MAXARNAME ];
static unsigned long arsizes[ MAXARNAME ];
static size_t anumb = 0;


/***************************************
 * Initializes the aray names.
 ***************************************/

static void
init_array_names( void )
{
    anumb = 0;
}


/***************************************
 * Checks whether an object name is an array name and remembers it
 ***************************************/

static int
check_array_name( char * aname )
{
    char tmpstr[ MAX_VAR_LEN ];
    char *p, *ep;
    unsigned long ind;
    size_t i;

    if ( ! ( p = strchr( aname, '[' ) ) )
        return FL_FALSE;

    strcpy( tmpstr, aname );
    p = tmpstr + ( p - aname );

    ind = strtoul( p + 1, &ep, 10 );
    if ( ep == p + 1 )
        ind = 0;

    *p = 0;

    for ( i = 0; i < anumb; i++ )
        if ( strcmp( arnames[ i ], tmpstr ) == 0 )
        {
            if ( ind + 1 > arsizes[ i ] )
                arsizes[ i ] = ind + 1;
            return FL_TRUE;
        }

    if ( anumb == MAXARNAME )
        return FL_FALSE;

    arnames[ anumb ] = malloc( MAX_VAR_LEN );
    strcpy( arnames[ anumb ], tmpstr);
    arsizes[ anumb++ ] = ind + 1;
    return FL_TRUE;
}


/***************************************
 ***************************************/

static int
are_there_array_names( void )
{
    return anumb > 0;
}


/***************************************
 * Prints the array names of the file.
 ***************************************/

static void
print_array_names( FILE * fn,
                   int    newf )
{
    size_t i;

    for ( i = 0; i < anumb; i++ )
        if ( ! newf )
        {
            fprintf( fn, "    *%s[ %lu ]", arnames[ i ], arsizes[ i ] );
            if ( i < anumb - 1 )
                fprintf( fn, ",\n" );
        }
        else
            fprintf( fn, "    FL_OBJECT * %s[ %lu ];\n",
                     arnames[ i ], arsizes[ i ] );
}


/*----------------------- Printing the C-code --------------------*/


/***************************************
 * Prints the form description in C-code. Note that no matter what
 * the internal coordinate system is, externally we always have
 * positive y pointing upward from the lower-left corner of the
 * screen
 ***************************************/

const char *
get_fd_name( const char * form_name )
{
    static char fdtname[ MAX_VAR_LEN ];

    sprintf( fdtname, "FD_%s", form_name );
    return fdtname;
}


/***************************************
 ***************************************/

double
get_conversion_factor( void )
{
    double sc = 1.0;

    if ( fdopt.unit == FL_COORD_POINT )
        sc = 72.00 / fl_dpi;
    else if ( fdopt.unit == FL_COORD_MM )
        sc = 25.40 / fl_dpi;
    else if ( fdopt.unit == FL_COORD_centiPOINT )
        sc = 7200.00 / fl_dpi;
    else if ( fdopt.unit == FL_COORD_centiMM )
        sc = 2540.00 / fl_dpi;

    return sc;
}


/***************************************
 ***************************************/

int
convert_u( FL_Coord l )
{
    return FL_nint( get_conversion_factor( ) * l );
}


/*
 * emit fl_set_xxxx_shortcut instead of fl_set_object_shortcut
 */

static VN_pair scclass[ ] =
{
    { FL_BUTTON,       "button", 0, 0 },
    { FL_LIGHTBUTTON,  "button", 0, 0 },
    { FL_ROUNDBUTTON,  "button", 0, 0 },
    { FL_CHECKBUTTON,  "button", 0, 0 },
    { FL_BITMAPBUTTON, "button", 0, 0 },
    { FL_PIXMAPBUTTON, "button", 0, 0 },
    { FL_INPUT,        "input",  0, 0 },
    { -1,              NULL,     0, 0 }   /* sentinel */
};


/***************************************
 ***************************************/

static const char *
supported_shortcut( int objclass )
{
    VN_pair *vn = scclass;

    for ( ; vn->val >= 0; vn++ )
        if ( vn->val == objclass )
            return vn->name;
    return "object";
}


/***************************************
 * Generate the C file for the forms we've defined. Header is generated
 * elsewhere. All default attributes are omitted.
 ***************************************/

static void
print_form_newformat( FILE       * fn,
                      FL_FORM    * form,
                      const char * fname )
{
    FL_OBJECT *obj;
    char fdtname[ MAX_VAR_LEN ],
         fdvname[ MAX_VAR_LEN ];

    strcpy( fdtname, get_fd_name( fname ) );
    strcpy( fdvname, "fdui" );

    /* Check if object specific stuff wants to write anything */

    for ( obj = form->first; obj; obj = obj->next )
        emit_objclass_spec_header( fn, obj );

    fprintf( fn, "\n/***************************************\n"
                 " ***************************************/\n\n" );

    fprintf( fn, "%s *\ncreate_form_%s( void )\n{\n    FL_OBJECT *obj;\n",
             fdtname, fname );

    /* Note: we output code that casts the return value of fl_malloc()
       in order to make the it acceptable for C++ compilers */

    fprintf( fn, "    %s *%s = ( %s * ) fl_malloc( sizeof *%s );\n\n",
             fdtname, fdvname, fdtname, fdvname );

    /* Take care of unit, borderwidth etc. that affect the entire form */

    pre_form_output( fn );

    fprintf( fn, "    %s->vdata = %s->cdata = NULL;\n"
                 "    %s->ldata = 0;\n\n", fdvname, fdvname, fdvname );

    fprintf( fn, "    %s->%s = fl_bgn_form( FL_NO_BOX, %d, %d );\n",
             fdvname, fname, convert_u( form->w ), convert_u( form->h ) );

    /* Don't output the first object, it's a box only used by fdesign */

    if ( form->first )
        for ( obj = form->first->next; obj; obj = obj->next )
            output_object( fn, obj, 0 );

    fprintf( fn, "\n    fl_end_form( );\n\n");

    if ( fdopt.compensate )
        fprintf( fn, "    fl_adjust_form_size( %s->%s );\n", fdvname, fname );

    fprintf( fn, "    %s->%s->%s = %s;\n", fdvname, fname, fdvname, fdvname );

    /* Restore */

    post_form_output( fn );

    fprintf( fn, "\n    return %s;\n}\n", fdvname );
}


/***************************************
 * Output C files for the forms defined, but slightly different from
 * the earlier routines. Activated by -altformat on the command line
 ***************************************/

static void
print_form_altformat( FILE       * fn,
                      FL_FORM    * form,
                      const char * fname )
{
    FL_OBJECT *obj;
    char name[ MAX_VAR_LEN ];
    int first = 1;

    /* Print the form declaration */

    fprintf( fn, "FL_FORM *%s;\n\n", fname );

    /* Print the object declarations */

    init_array_names( );
    obj = form->first;

    while ( ( obj = obj->next ) != NULL )
    {
        get_object_name( obj, name, NULL, NULL );
        if ( *name )
        {
            if ( ! check_array_name( name ) )
            {
                fprintf( fn, first ? "FL_OBJECT\n" : ",\n" );
                first = 0;
                fprintf( fn, "        *%s", name );
            }
        }
    }

    if ( are_there_array_names( ) )
    {
        fprintf( fn, first ? "FL_OBJECT\n" : ",\n");
        first = 0;
        print_array_names( fn, 0 );
    }

    fprintf( fn, first ? "\n" : ";\n\n" );

    /* Check if object specific stuff wants to write anything */

    for ( obj = form->first; obj; obj = obj->next )
        emit_objclass_spec_header( fn, obj );

    /* Print the defining procedure */

    fprintf( fn, "\n\n/***************************************\n"
                 " ***************************************/\n\n" );

    fprintf( fn, "void\ncreate_form_%s( void )\n{\n    FL_OBJECT *obj;\n\n",
             fname );

    pre_form_output( fn );

    fprintf( fn, "    if ( %s )\n"
                 "        return;\n\n", fname );
    fprintf( fn, "    %s = fl_bgn_form( FL_NO_BOX, %d, %d );\n",
             fname, convert_u( form->w ), convert_u( form->h ) );

    for ( obj = form->first; obj; obj = obj->next )
        output_object( fn, obj, 1 );

    fprintf( fn, "    fl_end_form( );\n" );
    if ( fdopt.compensate )
        fprintf( fn, "    fl_adjust_form_size( %s );\n", fname );

    post_form_output( fn );

    fprintf( fn, "\n}\n\n\n/*---------------------------------------*/\n\n" );
}


/***************************************
 * This is the routine called the output rotuine in fd_forms.c
 ***************************************/

void
print_form( FILE       * fn,
            FL_FORM    * form,
            const char * fname )
{
    ( ! fdopt.altformat ? print_form_newformat : print_form_altformat )
        ( fn, form, fname );
}


/***************************************
 * Check if a callback stub has already been emitted so we can suppress
 * duplicates as callbacks can be bound to multiple objects.
 * Potential problems: this check is only for a particular form,
 * if a callback is bound to multiple objects across many forms,
 * the callback will be output at least once per form.
 ***************************************/

static int
already_emitted( FL_OBJECT  * first,
                 FL_OBJECT  * curobj,
                 const char * cb )
{
    char cbname[ MAX_VAR_LEN ];
    FL_OBJECT *ob = first;

    for ( ob = first->next; ob && ob != curobj; ob = ob->next )
    {
        get_object_name( ob, NULL, cbname, NULL );
        if ( *cbname && strcmp( cbname, cb ) == 0 )
            return 1;
    }

    return 0;
}


#define MAXFREEOBJ 16


/***************************************
 ***************************************/

static char *
get_free_handle( FL_OBJECT  * ob,
                 const char * name )
{
    static int n;
    static char buf[ 1024 ];
    static FL_OBJECT *freeobj[ MAXFREEOBJ ];

    if ( ob->c_vdata )
        strcpy( buf, ob->c_vdata );
    else if ( *name )
        sprintf( buf, "freeobj_%s_handler", name );
    else if ( *ob->label )
        sprintf( buf, "freeobj_%s_handler", ob->label );
    else
    {
        int i,
            k;

        for ( k = -1, i = 0; i < MAXFREEOBJ && k < 0; i++ )
            if ( freeobj[ i ] == ob )
                k = i;

        if ( k < 0 )
        {
            k = ++n;
            freeobj[ k ] = ob;
        }

        sprintf( buf, "freeobj%d_handler", k );
    }

    return buf;
}


/***************************************
 * Prints the callback routines used in form
 ***************************************/

void
print_callbacks_and_globals( FILE    * fn,
                             FL_FORM * form,
                             int       code )
{
    FL_OBJECT *obj;
    char name[ MAX_VAR_LEN ],
         cbname[ MAX_VAR_LEN ];

    obj = form->first;

    while ( ( obj = obj->next ) != NULL )
    {
        get_object_name( obj, name, cbname, NULL );

        if ( obj->objclass == FL_FREE )
        {
            if ( ! code )
            {
                fprintf( fn, "int %s( FL_OBJECT *, int, FL_Coord, "
                         "FL_Coord, int, void * );\n",
                         get_free_handle( obj, name ) );
            }
            else
            {
                const char * hname = get_free_handle( obj, name );
                int width = strlen( hname ) + 1;

                fprintf( fn, "/***************************************\n"
                             " ***************************************/\n\n"
                             "int\n%s( FL_OBJECT * obj,\n"
                             "%*s int         ev,\n"
                             "%*s FL_Coord    mx,\n"
                             "%*s FL_Coord    my,\n"
                             "%*s int         key,\n"
                             "%*s void      * xev )\n"
                             "{\n"
                             "    /* Free object handler code */\n\n"
                             "    return 0;\n"
                             "}\n\n\n",
                         hname, width, " ", width, " ", width, " ",
                         width, " ", width, " " );
            }
        }

        if (    *cbname
             && ! strstr( cbname, "::" )
             && ! already_emitted( form->first, obj, cbname ) )
        {
            if ( ! code )
                fprintf( fn, "void %s( FL_OBJECT *, long );\n", cbname );
            else
                fprintf( fn, "/***************************************\n"
                             " ***************************************/\n\n"
                             "void\n%s( FL_OBJECT * obj,\n"
                             "%*s long        data )\n"
                             "{\n"
                             "    /* Fill-in code for callback here */\n"
                         "}\n\n\n", cbname, ( int ) strlen( cbname ) + 1, " " );
        }

        if ( obj->objclass == FL_MENU )
            menu_emit_item_callback_headers( fn, obj, code );

        if ( ! code )
            emit_objclass_spec_global( fn, obj );
    }

    fprintf( fn, "\n" );
}


/***************************************
 * Output header file that contains the form definition.
 ***************************************/

static void
print_header_newformat( FILE       * fn,
                        FL_FORM    * form,
                        const char * fname)
{
    FL_OBJECT *obj;
    char name[ MAX_VAR_LEN ],
         fdtname[ MAX_VAR_LEN ];

    sprintf( fdtname, "FD_%s", fname );
    fprintf( fn, "typedef struct {\n"
                 "    FL_FORM   * %s;\n", fname );
    fprintf( fn, "    void      * vdata;\n"
                 "    char      * cdata;\n"
                 "    long        ldata;\n");

    init_array_names( );

    for ( obj = form->first; obj; obj = obj->next )
    {
        get_object_name( obj, name, NULL, NULL );
        if ( *name && ! check_array_name( name ) )
            fprintf( fn, "    FL_OBJECT * %s;\n", name );
    }

    if ( are_there_array_names( ) )
        print_array_names( fn, 1 );

    fprintf( fn, "} %s;\n", fdtname );

    fprintf( fn, "\n%s * create_form_%s( void );\n",
             fdtname, fname );
}


/***************************************
 * Prints the object declarations used in form
 ***************************************/

static void
print_header_altformat( FILE       * fn,
                        FL_FORM    * form,
                        const char * fname)
{
    FL_OBJECT *obj;
    char name[ MAX_VAR_LEN ];
    int first = 1;

    fprintf( fn, "extern FL_FORM *%s;\n\n", fname );
    init_array_names( );
    obj = form->first;

    while ( ( obj = obj->next ) != NULL )
    {
        get_object_name( obj, name, NULL, NULL );
        if ( *name )
        {
            if ( ! check_array_name( name ) )
            {
                fprintf( fn, first ? "extern FL_OBJECT\n" : ",\n" );
                first = 0;
                fprintf( fn, "        *%s", name );
            }
        }
    }

    if ( are_there_array_names( ) )
    {
        fprintf( fn, first ? "extern FL_OBJECT\n" : ",\n" );
        first = 0;
        print_array_names( fn, 0 );
    }

    if ( ! first )
        fprintf( fn, ";\n\n" );
}


/***************************************
 ***************************************/

void
print_header( FILE       * fn,
              FL_FORM    * form,
              const char * fname )
{
    ( ! fdopt.altformat ? print_header_newformat : print_header_altformat )
        ( fn, form, fname );
}


/*
 * All callback stubs
 */

/***************************************
 ***************************************/

void
output_callbacks( FILE * fn,
                  FRM  * fdform,
                  int    nform )
{
    int i;

    for ( i = 0; i < nform; i++ )
    {
        fprintf( fn, "/* Callbacks and freeobj handlers for form %s */\n\n\n",
                 fdform[ i ].fname );
        print_callbacks_and_globals( fn, fdform[i].form, 1 );
        fprintf( fn, "\n" );
    }
}


/***************************************
 ***************************************/

const char *
get_placement( FL_FORM * form )
{
    if ( ! form->first )
        return "FL_PLACE_CENTER";
    else if ( ! form->first->next )
        return "FL_PLACE_CENTER";
    else if ( form->first->next->resize )
        return "FL_PLACE_CENTERFREE";

    return "FL_PLACE_CENTER";
}


/*
 * A Main stub.
 */

/***************************************
 ***************************************/

static void
output_main_newformat( FILE * fn,
                       FRM  * fdform,
                       int    nform )
{
    char fdtname[ MAX_VAR_LEN ];
    char * fname;
    int i;

    /* Only output callback stubs if not already output */

    if ( ! fdopt.emit_cb )
        output_callbacks( fn, fdform, nform );

    fprintf( fn, "\n/***************************************\n"
                 " ***************************************/\n\n"
                 "int\nmain( int    argc,\n      char * argv[ ] )\n{\n" );

    for ( i = 0; i < nform; i++ )
    {
        fname = fdform[ i ].fname;
        sprintf( fdtname, "FD_%s", fname );
        fprintf( fn, "    %s *fd_%s;\n", fdtname, fname );
    }

    fprintf( fn, "\n" );

    fprintf( fn, "    fl_initialize( &argc, argv, 0, 0, 0 );\n" );

    for ( i = 0; i < nform; i++ )
    {
        fname = fdform[ i ].fname;
        fprintf( fn, "    fd_%s = create_form_%s( );\n", fname, fname );
    }

    fprintf( fn, "\n    /* Fill-in form initialization code */\n\n" );

    fprintf( fn, "    /* Show the first form */\n\n" );
    fprintf( fn, "    fl_show_form( fd_%s->%s, %s, FL_FULLBORDER, "
                 "\"%s\" );\n\n",
             fdform[ 0 ].fname, fdform[ 0 ].fname,
             get_placement( fdform[ 0 ].form),
             fdform[ 0 ].fname );

    fprintf( fn, "    fl_do_forms( );\n\n" );
    for ( i = 0; i < nform; i++ )
        fprintf( fn, "    if ( fl_form_is_visible( fd_%s->%s ) )\n"
                     "        fl_hide_form( fd_%s->%s );\n"
                     "    fl_free( fd_%s );\n",
                 fdform[ i ].fname, fdform[ i ].fname, fdform[ i ].fname,
                 fdform[ i ].fname, fdform[ i ].fname );
    fprintf( fn, "    fl_finish( );\n\n"
                 "    return 0;\n"
                 "}\n" );
}

/***************************************
 ***************************************/

static void
output_main_altformat( FILE * fn,
                       FRM  * fdform,
                       int    nform )
{
    /* Only output callback stubs if not already output */

    if ( ! fdopt.emit_cb )
        output_callbacks( fn, fdform, nform );

    fprintf( fn, "int\nmain( int    argc,\n      char * argv[ ] )\n{\n" );

    fprintf( fn, "    fl_initialize( &argc, argv, 0, 0, 0 );\n\n" );
    fprintf( fn, "\n     %s( );\n\n", main_name );
    fprintf( fn, "\n     /* Fill-in form initialization code */\n\n" );
    fprintf( fn, "    /* Show the first form */\n\n" );
    fprintf( fn, "    fl_show_form( %s, FL_PLACE_CENTER, FL_FULLBORDER, "
             "\"%s\" );\n", fdform[ 0 ].fname, fdform[ 0 ].fname);
    fprintf( fn, "    fl_do_forms( );\n\n    return 0;\n}\n" );
}


/***************************************
 ***************************************/

void
output_main( FILE * fn,
             FRM  * fdform,
             int    nform )
{
    ( fdopt.altformat ? output_main_altformat : output_main_newformat )
        ( fn, fdform, nform );
}


/***************************************
 * set some defaults, unit/bw etc.
 ***************************************/

static void
pre_form_output( FILE * fn )
{
    /* Some global defaults */

    if ( fdopt.unit != FL_COORD_PIXEL )
        fprintf( fn, "    int old_unit = fl_get_coordunit( );\n" );

    if ( fd_bwidth != FL_BOUND_WIDTH && fd_bwidth != 0 )
        fprintf( fn, "    int old_bw = fl_get_border_width( );\n" );

    if ( fdopt.unit != FL_COORD_PIXEL )
        fprintf( fn, "    fl_set_coordunit( %s );\n", unit_name( fdopt.unit ) );

    if ( fd_bwidth != FL_BOUND_WIDTH && fd_bwidth != 0 )
        fprintf( fn, "    fl_set_border_width( %d );\n", fd_bwidth );

    if (    fdopt.unit != FL_COORD_PIXEL
         || ( fd_bwidth != FL_BOUND_WIDTH && fd_bwidth != 0 ) )
        fprintf( fn, "\n" );
}


/***************************************
 ***************************************/

static void
post_form_output( FILE * fn )
{
    if ( fdopt.unit != FL_COORD_PIXEL )
        fprintf( fn, "    fl_set_coordunit( old_unit );\n" );
    if ( fd_bwidth != FL_BOUND_WIDTH && fd_bwidth )
        fprintf( fn, "    fl_set_border_width( old_bw );\n" );
}


/***************************************
 ***************************************/

static void
output_object( FILE      * fp,
               FL_OBJECT * obj,
               int         altfmt )
{
    FL_OBJECT * defobj,
                fakeobj;
    char name[ MAX_VAR_LEN ],
         cbname[ MAX_VAR_LEN ],
         argname[ MAX_VAR_LEN ];
    char * p,
           fdvname[ MAX_VAR_LEN ];
    char *label;
    double sc = get_conversion_factor( );

    if ( obj->parent )
        return;

    strcpy( fdvname, "fdui" );
    get_object_name( obj, name, cbname, argname );

    if ( obj->objclass == FL_BEGIN_GROUP )
    {
        if ( *name )
        {
            if ( ! altfmt )
                fprintf( fp, "\n    %s->%s = ", fdvname, name );
            else
                fprintf( fp, "\n    %s = ", name );
        }
        else
            fprintf( fp, "\n    %s", name );

        fprintf( fp, "fl_bgn_group( );\n" );
    }
    else if ( obj->objclass == FL_END_GROUP )
        fprintf( fp, "\n    fl_end_group( );\n");
    else
    {
        defobj = find_class_default( obj->objclass, obj->type );
        if ( ! defobj )
        {
            M_err( "output_object",
                   "Failed to create default (class = %s, type = %s)",
                   find_class_name( obj->objclass ),
                   find_type_name( obj->objclass, obj->type ) );
            exit( 1 );
        }

        fprintf( fp, "\n    " );

        if ( *name )
        {
            if ( ! altfmt )
                fprintf( fp, "%s->%s = ", fdvname, name );
            else
                fprintf( fp, "%s = ", name );
        }

        fprintf( fp, "obj = " );
        fprintf( fp, "fl_add_%s( ", find_class_name( obj->objclass ) );
        fprintf( fp, "FL_%s,", find_type_name( obj->objclass, obj->type ) );

        fakeobj.x = obj->x;
        fakeobj.y = obj->y;
        fakeobj.w = obj->w;
        fakeobj.h = obj->h;
        fli_scale_object( &fakeobj, sc, sc );

        label = get_label( obj, 1 );
        if ( obj->objclass != FL_FREE )
            fprintf( fp, " %d, %d, %d, %d, \"%s\" );\n", fakeobj.x, fakeobj.y,
                     fakeobj.w, fakeobj.h, label );
        else
            fprintf( fp, "% d, %d, %d, %d, \"%s\", %s );\n",
                     fakeobj.x, fakeobj.y, fakeobj.w, fakeobj.h,
                     label, get_free_handle( obj, name ) );
        fl_free( label );
        
        if  (    ( p = get_shortcut_string( obj ) )
              && *p
              && obj->type != FL_RETURN_BUTTON )
            fprintf( fp, "    fl_set_%s_shortcut( obj, \"%s\", 1 );\n",
                     supported_shortcut( obj->objclass ),
                     get_shortcut_string( obj ) );

        if ( obj->boxtype != defobj->boxtype && obj->objclass != FL_BOX )
        {
            if ( obj->objclass != FL_CANVAS && obj->objclass != FL_FRAME )
                emit_attrib( fp, obj->boxtype, vn_btype,
                             "fl_set_object_boxtype" );
        }

        if ( obj->col1 != defobj->col1 || obj->col2 != defobj->col2 )
        {
            if ( obj->objclass != FL_CANVAS )
                fprintf( fp, "    fl_set_object_color( obj, %s, %s );\n",
                         fli_query_colorname( obj->col1 ),
                         fli_query_colorname( obj->col2 ) );
        }

        if ( obj->lcol != defobj->lcol )
            fprintf( fp, "    fl_set_object_lcolor( obj, %s );\n",
                     fli_query_colorname( obj->lcol ) );

        if ( obj->lsize != defobj->lsize )
            emit_attrib( fp, obj->lsize, vn_lsize, "fl_set_object_lsize" );

        if ( obj->align != defobj->align )
            emit_attrib( fp, obj->align, vn_align, "fl_set_object_lalign" );

        if ( obj->lstyle != defobj->lstyle )
            fprintf( fp, "    fl_set_object_lstyle( obj, %s );\n",
                     style_name( obj->lstyle ) );

        /* 'resize' must be checked for consistency with the gravity settings */

        obj->resize = check_resize( obj->resize,
                                    obj->nwgravity, obj->segravity );
        if ( obj->resize != defobj->resize )
            fprintf( fp, "    fl_set_object_resize( obj, %s );\n",
                     resize_name( obj->resize ) );

        if (    obj->nwgravity != defobj->nwgravity
             || obj->segravity != defobj->segravity)
            fprintf( fp, "    fl_set_object_gravity( obj, %s, %s );\n",
                     gravity_name( obj->nwgravity ),
                     gravity_name( obj->segravity ) );

        if ( *cbname )
            fprintf( fp, "    fl_set_object_callback( obj, %s, %s );\n",
                     cbname, argname );

        if ( obj->how_return != defobj->how_return )
            fprintf( fp, "    fl_set_object_return( obj, %s );\n",
                     get_how_return_name( obj->how_return, 1 ) );
    }

    /* Generate object class specifc settings */

    emit_objclass_spec_info( fp, obj );
}


/*
 * Local variables:
 * tab-width: 4
 * indent-tabs-mode: nil
 * End:
 */
