#include "com.h"
#include "symtab.h"
#include "format.h"
#include "misc.h"
#include <getopt.h>
#include <ctype.h>

extern int yyparse();

int copythrough = 1;

static int do_cplusplus = 0;

char *prefix;

static inline Type *symb(Type *t)
{
	if (t->name != NULL)
		return t->name;
	assert(t->tclass == Typedef);
	return t;
}

void dumptype(Type *type, const char *name)
{
	char *str;
	Var *var;
	int ret = 1;
	
	assert(type != NULL);

	switch(type->tclass)
	{
	case Typedef:
		format(type->i.typedef_i.n);
		break;

	case Scalar:
		if (type->i.scalar_i.Unsigned)
			format("unsigned ");
		switch(type->i.scalar_i.size)
		{
		case Char:	str = "char"; break;
		case Short:	str = "short"; break;
		case Long:	str = "long"; break;
		case LongLong:	str = "long long"; break;
		case Float:	str = "float"; break;
		case Double:	str = "double"; break;
		default:	str = "???"; break;
		}

		format(str);
		break;

	case Array:
		if (do_cplusplus)
		{
			static int scount = 1;
			char n[128];

			sprintf(n, "_%s_S%03d", prefix, scount++);
			
			format("struct %s {\n", n);
			format("Ulong nelem;\n");
			dumptype(mkpointer(type->i.array_i), "elems");
			format(";\n");
			format("#ifdef __cplusplus\n");
			format("int alloced;\n");
			format("void %s::alloc(unsigned size)\t{elems = new ", n);
			dumptype(symb(type->i.array_i),"");
			format("[size]; alloced = 1; }\n");
			format("%s::%s()\t{ alloced = 0; }\n", n, n);
			format("%s::~%s()\t{\n", n, n);
			format("if (alloced) {\n");
			format("delete [] elems; alloced = 0;\n}\n}\n");
			format("#endif /* __cplusplus */\n}");
		}
		else
		{
			format("struct\n{\n");
			format("Ulong nelem;\n");
			dumptype(mkpointer(type->i.array_i), "elems");
			format(";\n}");
		}
		break;

	case CArray:
		dumptype(symb(type->i.carray_i.type), name);
		format("[%d]", type->i.carray_i.size);
		ret = 0;
		break;
		
	case Struct:
		format("struct\n{\n");
		for(var = type->i.struct_i; var != NULL; var = var->next)
		{
			if (var->type->norep)
			{
				format("/* No representation */\n");
				continue;
			}
			dumptype(symb(var->type), var->name);
			format(";\n");
		}
		format("}");
		break;

	case Pointer:
		dumptype(symb(type->i.pointer_i), NULL);
		format("*");
		break;
		
	default:
		printf("undealt-with type %d", type->tclass);
		break;
	}
	if (name != NULL && ret)
		format(" %s", name);
}
			
void gen_hdr(Var *table)
{
	format("/* Encoded types */\n\n");
	for(; table != NULL; table = table->next)
	{
		Type *type = table->type;
		
		assert(type->tclass == Typedef);
		
		if (type->nohdr)
			continue;

		format("typedef ");
		dumptype(type->i.typedef_i.t, table->name);
		format(";\n");
	}
}

void gen_proto(Var *table)
{
	for(; table != NULL; table = table->next)
	{
		Type *type;
		const char *name = table->name;
		const char *st = table->type->nocode ? "static " : "";
		
		assert(table->type->tclass == Typedef);

		type = table->type->i.typedef_i.t;

		format("%sunsigned char *encode_%s(const %s *, unsigned char *);\n",
		       st, name, name);
		format("%sunsigned char *decode_%s(%s *, unsigned char *);\n",
		       st, name, name);
		format("%sunsigned int sizeof_%s(const %s *);\n\n",
		       st, name, name);
		       
	}
}

extern FILE *yyin;
char *file;

extern int yydebug;

int
main(int argc, char *argv[])
{
	char *upname, *cp;
	int c, err = 0;
	char buf[1024];
	struct cmdarg *cppargs = NULL;

	cppargs = add_cmdarg(cppargs, CPP);

	while((c = getopt(argc, argv, "U:I:D:YC")) != EOF)
		switch(c)
		{
		case 'D':
		case 'I':
		case 'U':
			sprintf(buf, "-%c%s", c, optarg);
			cppargs = add_cmdarg(cppargs, buf);
			break;
			
		case 'Y':
			yydebug = 1;
			break;

		case 'C':
			do_cplusplus = 1;
			break;
			
		default:
			err++;
			break;
		}
		
	if (err || optind >= argc)
	{
		fprintf(stderr, "Usage: %s [-C] [cpp-opts...] typedef-file\n", argv[0]);
		fprintf(stderr, "\t-C\tGenerate C++ classes rather than C\n");
		fprintf(stderr, "\tcpp-opts\tPass -I, -D and -U options to cpp\n");
		exit(1);
	}

	file = strdup(argv[optind]);
	
	cppargs = add_cmdarg(cppargs, file);
	
	if ((yyin = popen(cat_args(cppargs), "r")) == NULL)
	{
		perror("file open failed");
		exit(1);
	}

	if ((cp = strrchr(file, '/')) == NULL)
		cp = file;
	else
		cp++;

	prefix = strdup(cp);
	cp = strrchr(prefix, '.');
	if (cp)
		*cp = 0;

	init_symtab();

	upname = cp = strdup(prefix);

	for(; *cp; cp++)
		if (islower(*cp))
			*cp = toupper(*cp);
	
	format("/* -*- %s -*-\n * DO NOT EDIT BY HAND!\n *\n",
	       do_cplusplus ? "C++" : "C");
	format(" * This was automatically generated from \"%s\"",
	       argv[optind]);
	format(" by genhdr.\n */\n");
	format("#ifndef __%s_H_SEEN__\n", upname);
	format("#define __%s_H_SEEN__\n\n", upname);

	if (yyparse())
	{
		fprintf(stderr, "Bad input file\n");
		exit(1);
	}

	format("#ifndef _NO_CODER_H_\n");
	format("/* Type encode/decode routines */\n");
	format("#include \"coder.h\"\t/* Standard routines */\n");
	format("#endif /* _NO_CODER_H_ */\n");
	if (do_cplusplus)
	{
		format("#ifdef __cplusplus\n");
		format("#if __GNUC__ == 2\n");
		format("#pragma interface\n");
		format("#endif /* __GNUC__ */\n");
		format("#endif /* __cplusplus */\n");
	}
 	global = name_types(global);
	
	gen_hdr(global);
	gen_proto(global);
	
	format("#endif /* __%s_H_SEEN__ */\n", upname);

	exit(0);
}
