#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>

#include "struct.h"
#include "set.h"
#include "decs.h"

bool makefree = false;

//static void line( char * fmt, long a, long b, long c, long d );
static void literalline( char * mesg );
static void h_declns( char * base, char * exports, bool init, declnlist d );
static void c_declns( char * base, char * globals, char * begin, set suppress, declnlist d );
static void ddtypes( declnlist d );
static void ddoneshape( decln d, shapelist s );
static void data_decls( declnlist decs );
static void declare_type( decln d );
static void declfields( paramlist p );
static void protos( bool init, char * base, declnlist d, bool prot );
static void proto_type( decln d, bool prot );
static void cons_fns( declnlist d );
static void cons_fn_proto( decln d, shapelist s, bool prot );
static void cons_fn( decln d, shapelist s );
static void decons_fns( declnlist d );
static void decons_fns_type( decln d );
static void deconskind_proto( decln d, bool prot );
static void deconskind_fn( decln d );
static void decons_fn_proto( decln d, shapelist s, bool prot );
static void decons_fn( decln d, shapelist s );
static void print_fn_proto( char * name, bool prot );
static void print_fns( declnlist d, set suppress );
static void print_fn_shape( declnlist d, shapelist s );
static void print_all_params( declnlist d, shapelist s );
static void print_param( shapelist s, paramlist p, bool Union );
static void free_fn_proto( char * name, bool prot );
static void free_fns( declnlist d );
static void free_fn_shape( declnlist d, shapelist s );
static void free_all_params( declnlist d, shapelist s );
static void free_param( shapelist s, paramlist p, bool Union );


static int numtabs = 0;
static FILE *outfile;


#define indent() numtabs++
#define outdent() numtabs--

#define nl()		fputc( '\n', outfile )
#define outchar(c)	fputc( c, outfile )
#define usefile(f)	outfile = f, numtabs = 0



/*VARARGS*/
static void line( fmt, a, b, c, d ) char *fmt; long a, b, c, d;
//static void line( char *fmt, long a, long b, long c, long d )
{
	int i;

	for( i=numtabs; i; i-- ) fputc( '\t', outfile );
	fprintf( outfile, fmt, a, b, c, d );
	nl();
}


static void literalline( char *mesg )
{
	int i;

	for( i=numtabs; i; i-- ) fputc( '\t', outfile );
	fputs( mesg, outfile );
	nl();
}


void make_declns( char *exports, char *globals, char *begin, declnlist d, set suppress, char *base )
{
	printf( "datadec: Making data declarations in %s.[ch]\n", base );
	h_declns( base, exports, *begin != '\0', d );
	c_declns( base, globals, begin, suppress, d );
}


static void h_declns( char *base, char *exports, bool init, declnlist d )
{
	char		tempname[256];
	char		*exportptr;
	FILE		*hfile;

	sprintf( tempname, "%s.h", base );
	hfile = fopen( tempname, "w" );
	if( hfile == NULL )
	{
		fprintf( stderr, "datadec: can't create '%s'\n", tempname );
		exit(1);
	}

	usefile( hfile );

	line( "/*" );
	line( " * Automatically Generated by DataDec" );
	line( " */\n" );
	line( "typedef char *string;\n" );

	//line( "typedef char bool;" );
	//line( "#define TRUE 1" );
	//line( "#define FALSE 0\n" );

	line( "#define NEW(t) ((t)malloc(sizeof(struct t)))\n\n" );

	exportptr = exports;
	if( *exports != '\0' )
	{
		line( "\n/* Contents of top part of EXPORT section */" );
		for( ; *exportptr; exportptr++ )
		{
			if( *exportptr == '@' && exportptr[1] == '@'
			&&   exportptr[2] == '\n' )
			{
				exportptr += 2;
				break;
			}
			outchar( *exportptr );
		}

		nl();
		nl();
	}

	data_decls( d );

	line( "\n/* Prototypes for all types */\n" );
	protos( init, base, d, true );

	if( *exportptr != '\0' )
	{
		nl();
		line( "/* Remaining contents of EXPORT section */" );
		literalline( exportptr );
		nl();
	}

	fclose( hfile );
}


static void c_declns( char *base, char *globals, char *begin, set suppress, declnlist d )
{
	char	tempname[256];
	FILE	*cfile;
	char	*globalptr;

	sprintf( tempname, "%s.c", base );
	cfile = fopen( tempname, "w" );
	if( cfile == NULL )
	{
		fprintf( stderr, "datadec: can't create '%s'\n", tempname );
		exit(1);
	}

	usefile( cfile );

	line( "/*" );
	line( " * Automatically Generated by DataDec" );
	line( " */\n" );
	line( "#include <stdio.h>" );
	line( "#include <stdlib.h>" );
	line( "#include <stdbool.h>" );
	line( "#include <assert.h>" );

	globalptr=globals;
	if( *globals != '\0' )
	{
		line( "\n/* Contents of top part of GLOBAL section */" );
		for( ; *globalptr; globalptr++ )
		{
			if( *globalptr == '@' && globalptr[1] == '@'
			&&   globalptr[2] == '\n' )
			{
				globalptr += 2;
				break;
			}
			outchar( *globalptr );
		}

		nl();
		nl();
	}

	line( "#include \"%s.h\"\n\n", base );

	cons_fns( d );
	decons_fns( d );
	print_fns( d, suppress );
	if( makefree )
	{
		free_fns( d );
	}

	if( *globalptr != '\0' )
	{
		nl();
		nl();
		line( "/* Remaining contents of GLOBAL section */" );
		literalline( globalptr );
		nl();
	}

	if( *begin != '\0' )
	{
		line( "/* Contents of BEGIN section */" );
		line( "void init_%s()", base );
		line( "{" );
		line( begin );
		line( "}" );
	}
	fclose( cfile );
}


void make_metadata( declnlist d )
{
	usefile( stdout );
	ddtypes( d );
}


static void ddtypes( declnlist d )
{
	for( ; d != NULL; d=d->next )
	{
		shapelist s = d->shapes;
		for( ; s != NULL; s=s->next )
		{
			ddoneshape( d, s );
		}
	}
}


static void ddoneshape( decln d, shapelist s )
{
	paramlist p;
	bool first;

	fprintf( outfile, "%s %s ", d->name, s->name );
	first = true;

	for( p=s->params; p != NULL; p=p->next, first = false )
	{
		if( !first ) fputs( ",", outfile );
		fputs( p->type, outfile );
	}
	fputc( '\n', outfile );
}


/* ------------------------ Declare the types ------------------------------ */


static void data_decls( declnlist decs )
{
	declnlist	d;

	for( d=decs; d != NULL; d=d->next )
	{
		if( d->Struct )
		{
			line("struct %s;", d->name);
			line("typedef struct %s *%s;", d->name, d->name );
		} else
		{
			line( "typedef int %s;", d->name );
		}
	}
	nl();
	nl();
	for( d=decs; d != NULL; d=d->next )
	{
		declare_type( d );
	}
}


static void declare_type( decln d )
{
	shapelist	s;

	line( "/* ---- Type %s ---- */\n", d->name );

	if( d->ManyShapes )
	{
		line( "typedef enum {" );
		indent();

		for( s = d->shapes; s != NULL; s=s->next )
		{
			line( "%s_is_%s,", d->name, s->name );
		}
		outdent();
		line( "} kind_of_%s;\n", d->name );
	}

	if( d->Struct )
	{
		line( "struct %s {", d->name );
		indent();
		if( d->TagField )
		{
			line( "kind_of_%s\ttag;", d->name );
		}
		if( d->Union )
		{
			line( "union {" );
			indent();
		}
		for( s = d->shapes; s != NULL; s=s->next )
		{
			if( s->params == NULL ) continue;

			if( d->Union )
			{
				if( s->params->next == NULL )
				{
					/* don't need internal struct */
					/* but use {shape name}_{field name} */
					line( "%s\t%s_%s;", s->params->type,
						s->name, s->params->name );
				} else
				{
					/* need internal struct */
					line( "struct {" );
					indent();
					declfields( s->params );
					outdent();
					line( "} %s;", s->name );
				}
			} else
			{
				/* only one chunk of data: this one */
				declfields( s->params );
			}
		}
		if( d->Union )
		{
			outdent();
			line( "} u;" );
		}
		outdent();
		line( "};\n" );
	}
}


static void declfields( paramlist p )
{
	for( ; p != NULL; p=p->next )
	{
		line( "%s\t%s;", p->type, p->name );
	}
}


/* ---------------------- Declare all prototypes -------------------------- */


static void protos( bool init, char *base, declnlist d, bool prot )
{
	for( ; d != NULL; d = d->next )
	{
		proto_type( d, prot );
	}
	if( init )
	{
		fprintf( outfile, "extern void init_%s( ", base );
		if( prot )
		{
			fputs( "void ", outfile );
		}
		fputs( ");\n", outfile );
	}
}


static void proto_type( decln d, bool prot )
{
	shapelist	s;

	s = d->shapes;
	if( d->Struct )
	{
		/* genuine constructor functions needed */
		if( d->UseNull )
		{
			/* except for first shape */
			line( "#define %s_%s() ((%s)NULL)",
				d->name, d->shapes->name, d->name );
			s = s->next;
		}
		for( ; s != NULL; s = s->next )
		{
			cons_fn_proto( d, s, prot );
		}
	} else
	{
		/* enumeration: use #define for all constructors */
		for( ; s != NULL; s = s->next )
		{
			line( "#define %s_%s() ((int)%s_is_%s)",
				d->name, s->name,
				d->name, s->name );
		}
	}
	if( d->ManyShapes )
	{
		/* need a deconstructor kind function/macro */
		if( d->Struct )
		{
			/* a true function is required */
			deconskind_proto( d, prot );
		} else
		{
			/* enumeration: a macro will do */
			line( "#define %s_kind(this) ((kind_of_%s) this)",
				d->name, d->name );
		}
	}
	if( d->Struct )
	{
		for( s = d->shapes; s != NULL; s = s->next )
		{
			if( s->params != NULL )
			{
				decons_fn_proto( d, s, prot );
			}
		}
	}
	print_fn_proto( d->name, prot );
	if( makefree )
	{
		free_fn_proto( d->name, prot );
	}
}


/* ------------------------------- Constructors --------------------------- */


static void cons_fns( declnlist d )
{
	shapelist	s;

	for( ; d != NULL; d=d->next )
	{
		/* ignore enumerations: constructors already in .h file */
		if( ! d->Struct ) continue;

		/* We need constructor functions for this type */
		s = d->shapes;
		if( d->UseNull )
		{
			/* Null cons func is a #define in .h file: skip it */
			s = s->next;
		}
		for( ; s != NULL; s=s->next )
		{
			cons_fn( d, s );
		}
	}
}


static void cons_fn_proto( decln d, shapelist s, bool prot )
{
	paramlist p;
	bool first;

	if( ! d->Struct )
	{
		fprintf( stderr, "cons_fn_proto: d->Struct is false!\n" );
		exit(1);
	}

	fprintf( outfile, "extern %s %s_%s( ", d->name, d->name, s->name );
	if( prot )
	{
		if( s->params == NULL )
		{
			fputs( "void ", outfile );
		} else
		{
			first = true;
			for( p = s->params; p != NULL ; p=p->next, first=false )
			{
				if( !first ) fputs( ", ", outfile );
				fputs( p->type, outfile );
				fputc( ' ', outfile );
			}
		}
	}
	fputs( ");\n", outfile );
}


static void cons_fn( decln d, shapelist s )
{
	paramlist p;
	bool first;

	if( ! d->Struct )
	{
		fprintf( stderr, "cons_fn: d->Struct is false!\n" );
		exit(1);
	}

	fprintf( outfile, "%s %s_%s( ", d->name, d->name, s->name );
	first = true;

	if( s->params == NULL )
	{
		fprintf( outfile, "void" );
	}
	for( p=s->params; p != NULL; p=p->next, first = false )
	{
		if( !first ) fputs( ", ", outfile );
		fprintf( outfile, "%s %s", p->type, p->name );
	}
	fputs( " )", outfile );

	line( "\n{" );
	indent();
	line( "%s\tnew = NEW(%s);", d->name, d->name );
	fprintf( outfile, "\tassert( new != NULL );\n" );
	if( d->TagField )
	{
		line( "new->tag = %s_is_%s;", d->name, s->name );
	}
	if( d->Union && s->params != NULL && s->params->next == NULL )
	{
		/* no internal struct needed for this shape */
		line( "new->u.%s_%s = %s;", s->name,
			s->params->name, s->params->name );
	} else
	{
		paramlist p;

		for( p=s->params; p != NULL; p=p->next )
		{
			if( d->Union )
			{
				/* an internal struct */
				line( "new->u.%s.%s = %s;", s->name,
					p->name, p->name );
			} else
			{
				/* only one chunk of data: this one */
				line( "new->%s = %s;", p->name, p->name );
			}
		}
	}
	line( "return new;" );
	outdent();
	line( "}\n\n" );
}


/* ------------------------- Deconstructors ------------------------------- */


static void decons_fns( declnlist d )
{
	for( ; d != NULL; d=d->next )
	{
		if( d->Struct )
		{
			decons_fns_type( d );
		}
		/* enumerations need none (decon kind macro already in .h) */
	}
}


static void decons_fns_type( decln d )
{
	shapelist	s;

	if( ! d->Struct )
	{
		fprintf( stderr, "decons_fns_type: d->Struct is false!\n" );
		exit(1);
	}

	if( d->ManyShapes )
	{
		/* We need a true deconstructor kind function */
		deconskind_fn( d );
	}
	/* We need (some) deconstructor functions */
	for( s = d->shapes; s != NULL; s=s->next )
	{
		if( s->params != NULL )
		{
			decons_fn( d, s );
		}
	}
}


/* ------------------- Deconstructor Kind procedure ----------------------- */


static void deconskind_proto( decln d, bool prot )
{
	if( ! d->ManyShapes )
	{
		fprintf( stderr,
			"deconskind_proto: d->ManyShapes is false!\n" );
		exit(1);
	}
	if( ! d->Struct )
	{
		fprintf( stderr, "deconskind_proto: d->Struct is false!\n" );
		exit(1);
	}

	fprintf( outfile, "extern kind_of_%s %s_kind( ", d->name, d->name );
	if( prot )
	{
		fprintf( outfile, "%s ", d->name );
	}
	fputs( ");\n", outfile );
}


static void deconskind_fn( decln d )
{
	shapelist s = d->shapes;

	if( ! d->ManyShapes )
	{
		fprintf( stderr, "deconskind_fn: d->ManyShapes is false!\n" );
		exit(1);
	}
	if( ! d->Struct )
	{
		fprintf( stderr, "deconskind_fn: d->Struct is false!\n" );
		exit(1);
	}

	fprintf( outfile, "kind_of_%s %s_kind( %s this )\n",
		 d->name, d->name, d->name );

	line( "{" );
	indent();

	if ( d->UseNull )
	{
		line( "if( this == NULL )" );
		line( "{" );
		indent();
		line( "return %s_is_%s;", d->name, s->name );
		outdent();
		line( "}" );
		s = s->next;
	}
	if( d->TagField )
	{
		line( "return this->tag;" );
	}
	else
	{
		line( "return %s_is_%s;", d->name, s->name );
	}
	outdent();
	line( "}\n\n" );
}


/* ----------------------- Deconstructor functions ------------------------ */


static void decons_fn_proto( decln d, shapelist s, bool prot )
{
	if( ! d->Struct )
	{
		fprintf( stderr, "decons_fn_proto: d->Struct is false!\n" );
		exit(1);
	}
	if( s->params == NULL )
	{
		fprintf( stderr, "decons_fn_proto: no fields in shape!!\n" );
		exit(1);
	}

	fprintf( outfile, "extern void get_%s_%s(", d->name, s->name );
	if( prot )
	{
		paramlist p;

		fprintf( outfile, " %s ", d->name );
		for( p = s->params; p != NULL ; p=p->next )
		{
			fprintf( outfile, ", %s * ", p->type );
		}
	}
	fputs( ");\n", outfile );
}


static void decons_fn( decln d, shapelist s )
{
	paramlist p;

	if( ! d->Struct )
	{
		fprintf( stderr, "decons_fn: d->Struct is false!\n" );
		exit(1);
	}
	if( s->params == NULL )
	{
		fprintf( stderr, "decons_fn: no fields in shape!!\n" );
		exit(1);
	}

	fprintf( outfile, "void get_%s_%s( %s this", d->name, s->name, d->name );
	for( p=s->params; p != NULL; p=p->next )
	{
		fprintf( outfile, ", %s *%s", p->type, p->name );
	}
	fprintf( outfile, " )" );
	line( "\n{" );
	indent();

	if( d->Union && s->params != NULL && s->params->next == NULL )
	{
		/* no internal struct: one field only */
		line( "*%s = this->u.%s_%s;",
			s->params->name, s->name, s->params->name );
	} else
	{
		for( p=s->params; p != NULL; p=p->next )
		{
			if( d->Union )
			{
				/* internal struct */
				line( "*%s = this->u.%s.%s;",
					p->name, s->name, p->name );
			} else
			{
				/* only one chunk of data: this one */
				line( "*%s = this->%s;", p->name, p->name );
			}
		}
	}
	outdent();
	line( "}\n\n" );
}


/* -------------------------- Print functions ----------------------------- */


static void print_fn_proto( char *name, bool prot )
{
	fprintf( outfile, "extern void print_%s(", name );
	if( prot )
	{
		fprintf( outfile, " FILE *, %s ", name );
	}
	fputs( ");\n", outfile );
}


static void print_fns( declnlist d, set suppress )
{
	for( ; d != NULL; d = d->next )
	{
		char fname[2048];
		sprintf( fname, "print_%s", d->name );
		if( setIn( suppress, fname ) )
		{
			fprintf( stderr, "Suppressing %s\n", fname );
			continue;
		}
		line( "void %s( FILE *f, %s p )", fname, d->name );
		line( "{" );
		indent();
		shapelist s = d->shapes;
		if( d->UseNull )
		{
			line( "if( p == NULL )" );
			line( "{" );
			indent();
			print_all_params( d, s );
			line( "return;" );
			outdent();
			line( "}" );
			s = s->next;
		}
		if( d->TagField )
		{
			line( "switch( p->tag )" );
			line( "{" );
			for( ; s != NULL; s = s->next )
			{
				print_fn_shape( d, s );
			}
			line( "default:" );
			indent();
			line( "fprintf( stderr," );
			line( " \"print_%s: impossible tag %%d\\n\", p->tag );",
				d->name );
			line( "exit(1);" );
			outdent();
			line( "}" );
		}
		else if( ! d->TagField && d->Struct )	/* one shape left */
		{
			print_all_params( d, s );
		}
		else if( ! d->Struct && d->ManyShapes ) /* enumerated type */
		{
			line( "switch( p )" );
			line( "{" );
			for( ; s != NULL; s = s->next )
			{
				line( "case %s_%s():", d->name, s->name );
				indent();
				print_all_params( d, s );
				line( "break;" );
				outdent();
			}
			line( "}" );
		} else
		{
			fprintf( stderr,
				 "datadec: internal error in print_fns\n" );
			exit(1);
		}
		outdent();
		line( "}\n\n" );
	}
}


static void print_fn_shape( declnlist d, shapelist s )
{
	line( "case %s_is_%s:", d->name, s->name );
	indent();
	print_all_params( d, s );
	line( "break;" );
	outdent();
}


static void print_all_params( declnlist d, shapelist s )
{
	printlist	pl;
	paramlist	p;
	int		n;

	if( s->pl == NULL )
	{
		line( "fputs( \"%s\", f );", s->name );
		if( s->params )
		{
			line( "fputc( '(', f );" );
		}
		for( p = s->params; p != NULL; p = p->next )
		{
			print_param( s, p, d->Union );
			if( p->next != NULL )
			{
				line( "fputc( ',', f );" );
			}
		}
		if( s->params )
		{
			line( "fputc( ')', f );" );
		}
	} else
	{
		for( pl = s->pl; pl != NULL; pl = pl->next )
		{
			if( pl->item->tag == printitem_is_str )
			{
				line( "fputs( \"%s\", f );",
					pl->item->str );
			} else
			{
				n = pl->item->num;
				for( p = s->params; p != NULL && --n;
					p = p->next )
					;

				if( p == NULL )
				{
					fprintf( stderr,
	"datadec: bad printitem in shape %s of type %s\n",
	s->name, d->name );
					exit(1);
				}
				print_param( s, p, d->Union );
			}
		}
	}
}


static void print_param( shapelist s, paramlist p, bool Union )
{
	char pname[200];

	if( Union )
	{
		if( s->params->next == NULL )
		{
			/* no internal struct */
			sprintf( pname, "p->u.%s_%s", s->name, p->name );
		} else
		{
			/* internal struct */
			sprintf( pname, "p->u.%s.%s", s->name, p->name );
		}
	} else
	{
		sprintf( pname, "p->%s", p->name );
	}

	if( streq( p->type, "int" ) )
	{
		line( "fprintf( f, \"%%d\", %s );", pname );
	} else if( streq( p->type, "char" ) )
	{
		line( "fputc( %s, f );", pname );
	} else if( streq( p->type, "BOOL" ) || streq( p->type, "bool" ) )
	{
		line( "fputs( %s?\"true\":\"false\", f );", pname );
	} else if( streq( p->type, "string" ) )
	{
		line( "fputs( %s, f );", pname );
	} else
	{
		line( "print_%s( f, %s );", p->type, pname );
	}
}


/* ------------------- experimental free functions ------------------------ */


static void free_fn_proto( char *name, bool prot )
{
	fprintf( outfile, "extern void free_%s(", name );
	if( prot )
	{
		fprintf( outfile, " %s ", name );
	}
	fputs( ");\n", outfile );
}


static void free_fns( declnlist d )
{
	for( ; d != NULL; d = d->next )
	{
		shapelist s;

		line( "void free_%s( %s p )",d->name, d->name );
		line( "{" );
		indent();
		s = d->shapes;
		if( d->UseNull )
		{
			line( "if( p == NULL )" );
			line( "{" );
			indent();
			line( "return;" );
			outdent();
			line( "}" );
			s = s->next;
		}
		if( d->TagField )
		{
			line( "switch( p->tag )" );
			line( "{" );
			for( ; s != NULL; s = s->next )
			{
				free_fn_shape( d, s );
			}
			line( "default:" );
			indent();
			line( "fprintf( stderr," );
			line( " \"free_%s: impossible tag %%d\\n\", p->tag );",
				d->name );
			line( "exit(1);" );
			outdent();
			line( "}" );
		}
		else if( ! d->TagField && d->Struct )	/* one shape left */
		{
			free_all_params( d, s );
		}
		else if( ! d->Struct && d->ManyShapes ) /* enumerated type */
		{
			line( "switch( p )" );
			line( "{" );
			for( ; s != NULL; s = s->next )
			{
				line( "case %s_%s():", d->name, s->name );
				indent();
				free_all_params( d, s );
				line( "break;" );
				outdent();
			}
			line( "}" );
		} else
		{
			fprintf( stderr,
				 "datadec: internal error in free_fns\n" );
			exit(1);
		}
		if( d->Struct )		/* now need to free the node itself */
		{
			line( "free( p );" );
		}
		outdent();
		line( "}\n\n" );
	}
}


static void free_fn_shape( declnlist d, shapelist s )
{
	line( "case %s_is_%s:", d->name, s->name );
	indent();
	free_all_params( d, s );
	line( "break;" );
	outdent();
}


static void free_all_params( declnlist d, shapelist s )
{
	paramlist	p;

	for( p = s->params; p != NULL; p = p->next )
	{
		free_param( s, p, d->Union );
	}
}


static void free_param( shapelist s, paramlist p, bool Union )
{
	char pname[200];

	if( Union )
	{
		if( s->params->next == NULL )
		{
			/* no internal struct */
			sprintf( pname, "p->u.%s_%s", s->name, p->name );
		} else
		{
			/* internal struct */
			sprintf( pname, "p->u.%s.%s", s->name, p->name );
		}
	} else
	{
		sprintf( pname, "p->%s", p->name );
	}

	if( ! p->dontfree )
	{
		line( "free_%s( %s );", p->type, pname );
	}
}
