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

extern int yyparse();

char *prefix = "";

int copythrough = 0;

static int do_oneperfile = 0;
static int do_cplusplus = 0;
static const char *filedir = NULL;
static const char *srcext = ".c";

static void file_preamble(void)
{
	format("/* -*- %s -*-\n * DO NOT EDIT BY HAND!\n *\n",
	       do_cplusplus ? "C++" : "C");
	format(" * This was automatically generated by gencode.\n */\n");
	if (do_cplusplus)
	{
		format("#ifdef __cplusplus\n");
		format("#if __GNUC__ == 2\n");
		format("#pragma implementation\n");
		format("#endif /* __GNUC__ */\n");
		format("#endif /* __cplusplus */\n");
	}
	format("#include \"%s.h\"\n", prefix);
}

static void preamble(Type *type, const char *op, int co, const char *name)
{
	if (do_oneperfile)
	{
		char buf[256];

		sprintf(buf, "%s_%s%s", op, name, srcext);
		
		if ((form_out = fopen(buf, "w")) == NULL)
		{
			fprintf(stderr, "Failed to create %s: %s\n",
				buf, strerror(errno));
			exit(1);
		}
		file_preamble();
	}

	format("\n");
#if 0
	if (!do_oneperfile && (type->anon || type->demand))
		format("__inline__ static ");
#endif
	format("\nunsigned char *%s_%s(%s%s *sp, unsigned char *buf)\n{\n",
	       op, name, co ? "const " : "", name);
}

static void postamble()
{
	format("return buf;\n}\n");
	
	if (do_oneperfile)
		fclose(form_out);
}

#define NAMEOF(t)	((t)->name->i.typedef_i.n)

void encode_type(Type *type, const char *typename)
{
	Var *var;
	
	assert(type != NULL);
	
	if (type->nocode || type->coded == 1)
		return;

	type->coded = 1;
	
	switch(type->tclass)
	{
		Type *tt;
		
#define PRE() 	preamble(type, "encode", 1, typename)
#define POST()	postamble()
		
	case Typedef:
		tt = realtype(type);
		encode_type(tt, NAMEOF(tt));
		PRE();
		format("buf = encode_%s(sp, buf);\n", NAMEOF(tt));
		POST();
		break;

	case Scalar:
		PRE();
		format("buf = encode_%s(sp, buf);\n", NAMEOF(type));
		POST();
		break;

	case Array:
		if (type->norep)
		{
			PRE();
			format("/* No representation */\n");
			POST();
			break;
		}
		tt = realtype(type->i.array_i);
		
		encode_type(tt, NAMEOF(tt));
		PRE();
		format("{\nunsigned i;\n\n");
		format("buf = encode_Ulong(&sp->nelem, buf);\n");
		format("for(i = 0; i < sp->nelem; i++) {\n");
		format("buf = encode_%s(&sp->elems[i], buf);\n",
		       NAMEOF(tt));
		format("}\n}\n");
		POST();
		break;

	case CArray:
		tt = realtype(type->i.carray_i.type);
		encode_type(tt, NAMEOF(tt));
		PRE();
		format("{\nunsigned i;\n\n");
		format("for(i = 0; i < %d; i++) {\n", type->i.carray_i.size);
		format("buf = encode_%s(&(*sp)[i], buf);\n",
		       NAMEOF(tt));
		format("}\n}\n");
		POST();
		break;
		
	case Struct:
		for(var = type->i.struct_i; var != NULL; var = var->next)
		{
			tt = realtype(var->type);
			encode_type(tt, NAMEOF(tt));
		}
		
		PRE();
		for(var = type->i.struct_i; var != NULL; var = var->next)
		{
			if (var->type->norep)
			{
				format("/* Unrepresentable type */\n");
				continue;
			}

			tt = realtype(var->type);
			
			format("buf = encode_%s(&sp->%s, buf);\n",
			       NAMEOF(tt), var->name);
		}
		POST();
		break;

	case Pointer:
		tt = realtype(type->i.pointer_i);
		encode_type(tt, NAMEOF(tt));
		PRE();
		format("buf = encode_%s(*sp, buf);\n",
		       NAMEOF(tt));
		POST();
		break;

	default:
		printf("#error \"undealt-with type %d\"\n", type->tclass);
		break;
#undef PRE
#undef POST
	}
}

void decode_type(Type *type, const char *typename)
{
	Var *var;
	
	assert(type != NULL);

	if (type->nocode || type->coded == 2)
		return;

	type->coded = 2;

	switch(type->tclass)
	{
		Type *tt;
		
#define PRE()	preamble(type, "decode", 0, typename)
#define POST()	postamble()
		
	case Typedef:
		tt = realtype(type);
		decode_type(tt, NAMEOF(tt));
		PRE();
		format("buf = decode_%s(sp, buf);\n",
		       NAMEOF(tt));
		POST();
		break;

	case Scalar:
		PRE();
		format("buf = decode_%s(sp, buf);\n", NAMEOF(type));
		POST();
		break;

	case Array:
		if (type->norep)
		{
			PRE();
			format("/* No representation */\n");
			POST();
			break;
		}

		tt = realtype(type->i.array_i);
		decode_type(tt, NAMEOF(tt));
		PRE();
		format("{\nunsigned i;\n");
		format("buf = decode_Ulong(&sp->nelem, buf);\n");
		format("if (sp->nelem == 0)\n");
		format("{\nsp->elems = 0;\nreturn buf;\n}\n");
		if (do_cplusplus)
		{
			format("#ifdef __cplusplus\n");
			format("sp->alloc(sp->nelem);\n");
			format("#else\n");
		}
		format("sp->elems = (%s *)ALLOC(sizeof(%s) * sp->nelem);\n",
		       NAMEOF(tt), NAMEOF(tt));
		if (do_cplusplus)
			format("#endif /* __cplusplus */\n");
		format("if (sp->elems == 0) {\nreturn buf;\n}\n");
		
		format("for(i = 0; i < sp->nelem; i++) {\n");
		format("buf = decode_%s(&sp->elems[i], buf);\n",
		       NAMEOF(tt));
		format("}\n}\n");
		POST();
		break;

	case CArray:
		tt = realtype(type->i.carray_i.type);
		decode_type(tt, NAMEOF(tt));
		PRE();
		format("{\nunsigned i;\n\n");
		format("for(i = 0; i < %d; i++) {\n", type->i.carray_i.size);
		format("buf = decode_%s(sp[i], buf);\n",
		       NAMEOF(tt));
		format("}\n}\n");
		POST();
		break;
		
	case Struct:
		for(var = type->i.struct_i; var != NULL; var = var->next)
		{
			tt = realtype(var->type);
			decode_type(tt, NAMEOF(tt));
		}
		PRE();
		for(var = type->i.struct_i; var != NULL; var = var->next)
		{
			if (var->type->norep)
			{
				format("/* Unrepresentable type */\n");
				continue;
			}
			tt = realtype(var->type);
			format("buf = decode_%s(&sp->%s, buf);\n",
			       NAMEOF(tt), var->name);
		}
		POST();
		break;

	case Pointer:
		tt = realtype(type->i.pointer_i);
		decode_type(tt, NAMEOF(tt));
		PRE();
		format("*sp = (%s *)ALLOC(sizeof(%s));\n",
		       NAMEOF(tt), NAMEOF(tt));
		format("if (*sp == NULL) {\nreturn buf;\n}\n");
		format("buf = decode_%s(*sp, buf);\n",
		       NAMEOF(tt));
		POST();
		break;

#undef PRE
#undef POST
	default:
		printf("#error \"undealt-with type %d\"\n", type->tclass);
		break;
	}
}

static void s_preamble(const Type *type, const char *name)
{
	if (do_oneperfile)
	{
		char buf[256];

		sprintf(buf, "sizeof_%s%s", name, srcext);
		
		if ((form_out = fopen(buf, "w")) == NULL)
		{
			fprintf(stderr, "Failed to create %s: %s\n",
				buf, strerror(errno));
			exit(1);
		}
		file_preamble();
	}
#if 0
	if (!do_oneperfile && (type->nocode || type->anon || type->demand))
		format("static __inline__ ");
#endif
	format("unsigned int sizeof_%s(const %s *sp)\n{\n", name, name);
	format("unsigned int sz=0;\n\n");
}

static void s_postamble(void)
{
	format("return sz;\n}\n");

	if (do_oneperfile)
		fclose(form_out);
}

void sizeof_type(Type *type, const char *typename)
{
	Var *var;
	
	assert(type != NULL);

	if (type->nocode || type->coded == 3)
		return;

	type->coded = 3;
	
	switch(type->tclass)
	{
		Type *tt;
		
#define PRE() 	s_preamble(type, typename)
#define POST()	s_postamble()
		
	case Typedef:
		tt = realtype(type);
		sizeof_type(tt, NAMEOF(tt));
		PRE();
		format("sz += sizeof_%s(sp);\n",
		       NAMEOF(tt));
		POST();
		break;

	case Scalar:
		PRE();
		format("sz += sizeof(%s);\n",
		       NAMEOF(type));
		POST();
		break;

	case Array:
		if (type->norep)
		{
			PRE();
			format("/* No representation */\n");
			POST();
			break;
		}
		tt = realtype(type->i.array_i);
		
		sizeof_type(tt, NAMEOF(tt));
		PRE();
		format("sz += sizeof_Ulong(&sp->nelem);\n");
		format("{\nunsigned i;\n");
		format("for(i = 0; i < sp->nelem; i++) {\n");
		format("sz += sizeof_%s(&sp->elems[i]);\n", NAMEOF(tt));
		format("}\n}\n");
		POST();
		break;

	case CArray:
		tt = realtype(type->i.carray_i.type);
		sizeof_type(tt, NAMEOF(tt));
		PRE();
		format("{\nunsigned i;\n");
		format("for(i = 0; i < %d; i++) {\n",
		       type->i.carray_i.size);
		format("sz += sizeof_%s(sp[i]);\n}\n}\n", NAMEOF(tt));
		POST();
		break;
		
	case Struct:
		for(var = type->i.struct_i; var != NULL; var = var->next)
		{
			tt = realtype(var->type);
			sizeof_type(tt, NAMEOF(tt));
		}
		
		PRE();
		for(var = type->i.struct_i; var != NULL; var = var->next)
		{
			if (var->type->norep)
			{
				format("/* Unrepresentable type */\n");
				continue;
			}
			tt = realtype(var->type);
			format("sz += sizeof_%s(&sp->%s);\n",
			       NAMEOF(tt),
			       var->name);
		}
		POST();
		break;

	case Pointer:
		tt = realtype(type->i.pointer_i);
		sizeof_type(tt, NAMEOF(tt));
		PRE();
		format("sz += sizeof_%s(*sp);\n",
		       NAMEOF(tt));
		POST();
		break;

	case Void:
		PRE();
		POST();
		break;
		
	default:
		printf("#error \"undealt-with type %d\"\n", type->tclass);
		break;
#undef PRE
	}
}


typedef void (*Genfunc)(Type *, const char *);

static void gen_code(Var *table, Genfunc func)
{
	for(; table != NULL; table = table->next)
	{
		Type *type;

		type = table->type;

		assert(table->type->tclass == Typedef);

		if (type->demand)
			continue;

		(*func)(type->i.typedef_i.t, type->i.typedef_i.n);
	}
}

extern FILE *yyin;
char *file;

extern int yydebug;

int
main(int argc, char *argv[])
{
	char *cp;
	int och, err = 0;
	int do_encode = 0, do_decode = 0, do_sizeof = 0;
	char buf[1024];
	struct cmdarg *cppargs = NULL;

	cppargs = add_cmdarg(cppargs, CPP);
	
	while((och = getopt(argc, argv, "sedCD:I:U:l:E:Y")) != EOF)
	{
		switch(och)
		{
		case 'D':
		case 'I':
		case 'U':
			sprintf(buf, "-%c%s", och, optarg);
			cppargs = add_cmdarg(cppargs, buf);
			break;
			
		case 'd':
			do_decode = 1;
			break;

		case 'e':
			do_encode = 1;
			break;

		case 's':
			do_sizeof = 1;
			break;

		case 'l':
			filedir = optarg;
			do_oneperfile = 1;
			break;

		case 'E':
			srcext = optarg;
			break;
			
		case 'C':
			do_cplusplus = 1;
			srcext = ".cc";
			break;
			
		case 'Y':
			yydebug = 1;
			break;
			
		default:
			err++;
			break;
		}
	}
	
	if (err || optind >= argc)
	{
		fprintf(stderr, "Usage: %s [-des] [-l dir] [-E ext] [cpp-opts] types-file\n",
			argv[0]);
		fprintf(stderr, "\t-d\tGenerate decode functions\n");
		fprintf(stderr, "\t-e\tGenerate encode functions\n");
		fprintf(stderr, "\t-s\tGenerate sizeof functions\n");
		fprintf(stderr, "\t-l dir\tGenerate one function per file in directory `dir'\n");
		fprintf(stderr, "\t-E ext\tUse extention `ext' on files (-l only)\n");
		fprintf(stderr, "\tcpp-opts\tPass -I, -D and -U options to cpp\n");
		exit(1);
	}

	if (!(do_encode || do_decode || do_sizeof))
		fprintf(stderr, "Warning: no functions will be generated\n");
	
	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(argv[optind], '/')) == NULL)
		cp = argv[optind];
	else
		cp++;

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

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

	}
	
	global = name_types(global);

	if (!do_oneperfile)
		file_preamble();
	else
	{
		if (chdir(filedir) == -1)
		{
			perror("Couldn't change to output directory");
			exit(1);
		}
	}
	
	if (do_encode)
		gen_code(global, encode_type);

	if (do_decode)
		gen_code(global, decode_type);

	if (do_sizeof)
		gen_code(global, sizeof_type);
	
	exit(0);
}
