#include "output.h"
#include "marshall.h"

PNode* p_self_name(Member* o){
	return p_c_ident(DEF(o->my_class)->type->name);
}

PNode* p_object_member(Member* m){
	DataMember* d;
	if(m->membertype != MEMBER_DATA)
		return p_nil;
	d=(DataMember*)m;
	if(d->kind!=DATA_DIRECT)
		return p_nil;
	return p_fmt("\t~ ~;\n",
		     p_type(&d->type),
		     p_c_ident(m->name));
}

PNode* p_object_body(ObjectDef* o){
	return p_fmt("struct _~ {\n"
		     "\t~ parent;\n"
		     "~"
		     "};\n",
		     p_primtype(DEF(o)->type),
		     p_primtype(o->parent),
		     p_for(o->members, p_object_member, p_nil));
}

PNode* p_object_decl(ObjectDef* o){
	PrimType* n=DEF(o)->type;
	return p_fmt("typedef struct _~ ~;\n",
		     p_primtype(n),
		     p_primtype(n));
}

PNode* p_class_member(Member* m){
	ParamOptions o = {TRUE,FALSE,TRUE};
	if(m->membertype == MEMBER_DATA){
		DataMember* d=(DataMember*)m;
		if(d->kind != DATA_STATIC_VIRTUAL)
			return p_nil;
		else
			return p_fmt("\t~ ~;\n",
				     p_type(&d->type),
				     p_c_ident(m->name));
	}else if (m->membertype == MEMBER_METHOD){
		Method* d=(Method*)m;
		FunParams* p;
		PNode* parnode;
		
		if(d->kind==METH_STATIC ||
		   d->kind==METH_DIRECT)
			return p_nil;
		p=fparams("tp",
			  &m->my_class->self_type[d->self_const],
			  p_nil, p_nil,
			  d->params);
		parnode=p_params(p, &o);
		fparams_free(p);
		return p_fmt("\t~ (*~) (~);\n",
			     p_type(&d->ret_type),
			     p_c_ident(m->name),
			     parnode);
	}else
		return p_nil;
}

PNode* p_class_name(PrimType* o){
	return p_fmt("~Class",
		     p_primtype(o));
}

PNode* p_impl_name(ObjectDef* o){
	return p_fmt("~Impl",
		     p_primtype(DEF(o)->type));
}

PNode* p_class_body(ObjectDef* o){
	return p_fmt("struct _~ {\n"
		     "\t~ parent_class;\n"
		     "~"
		     "};\n",
		     p_class_name(DEF(o)->type),
		     p_class_name(o->parent),
		     p_for(o->members, p_class_member, p_nil));
}

PNode* p_class_decl(ObjectDef* o){
	return p_fmt("typedef struct _~ ~;\n",
		     p_class_name(DEF(o)->type),
		     p_class_name(DEF(o)->type));
}


PNode* p_real_varname(PrimType* t, PNode* name){
	return p_fmt("~_real",
		     p_varname(t, name));
}	


PNode* p_signal_handler_type(Method* s){
	Member* m=MEMBER(s);
	return p_fmt("~Handler_~",
		     p_primtype(DEF(m->my_class)->type),
		     p_c_ident(m->name));
}


void output_connector(PRoot* out, Method* m){
	FunParams* par=fparams("t",
			       &MEMBER(m)->my_class->self_type[m->self_const],
			       p_self_name(MEMBER(m)),
			       p_nil);

	output_func(out, "functions",
		    &m->ret_type,
		    p_varname(DEF(MEMBER(m)->my_class)->type,
			      p_fmt("connect_~",
				    p_c_ident(MEMBER(m)->name))),
		    p_fmt("\n\t~ handler,\n\tgpointer user_data",
			  p_signal_handler_type(m)),
		    par,
		    p_fmt("\t~gtk_signal_connect((GtkObject*)~,\n"
			  "\t\t\"~\",\n"
			  "\t\t(GtkSignalFunc)handler,\n"
			  "\t\tuser_data);\n",
			  m->ret_type.prim?p_str("return "):p_nil,
			  p_self_name(MEMBER(m)),
			  p_str(MEMBER(m)->name)));
	output_var_import(out, DEF(MEMBER(m)->my_class)->type,
			  p_fmt("connect_~",
				p_c_ident(MEMBER(m)->name)));
}
		    
PNode* p_param_marshtype(gpointer p){
	Param* param=p;
	return p_fmt(",\n\t\t~",
		     p_gtktype(&param->type));
}
	

void output_method(PRoot* out, Method* m){
	PrimType* t=DEF(MEMBER(m)->my_class)->type;
	PNode* name=p_c_ident(MEMBER(m)->name);
	MethodKind k=m->kind;
	ParamOptions o={TRUE, TRUE, FALSE};
	FunParams* par;
	PNode* dispatch;
	GSList* params;
	
	if(k == METH_STATIC)
		par = fparams("p", m->params);
	else
		par = fparams("tp",
			       &MEMBER(m)->my_class->self_type[m->self_const],
			      p_self_name(MEMBER(m)),
			      p_nil,
			      m->params);
	
	switch(k){
		SignalType* sig;
	case METH_EMIT_PRE:
	case METH_EMIT_POST:
	case METH_EMIT_BOTH:
		sig=sig_type(m);
		output_var(out, NULL,
			   p_str("guint"),
			   p_signal_id(m));
		o.names=FALSE;
		o.types=TRUE;
		pr_put(out, "source_sigtypes", sig);
		pr_put(out, "member_class_init",
		       p_fmt("\t~ =\n"
			     "\tgtk_signal_new(\"~\",\n"
			     "\t\tGTK_RUN_~,\n"
			     "\t\tobklass->type,\n"
			     "\t\tGTK_SIGNAL_OFFSET (~, ~),\n"
			     "\t\t~,\n"
			     "\t\t~,\n"
			     "\t\t~"
			     "~);\n"
			     "\tgtk_object_class_add_signals(obklass,\n"
			     "\t\t&~,\n"
			     "\t\t1);\n",
			     p_signal_id(m),
			     p_str(MEMBER(m)->name),
			     p_str(m->kind==METH_EMIT_PRE
				   ?"FIRST"
				   :m->kind==METH_EMIT_POST
				   ?"LAST"
				   :"BOTH"),
			     p_class_name(t),
			     name,
			     p_signal_demarshaller_name(sig),
			     p_gtktype(&m->ret_type),
			     p_prf("%d", g_slist_length(m->params)),
			     p_for(m->params, p_param_marshtype, p_nil),
			     p_signal_id(m)));
		
		pr_put(out, "functions",
		       p_fmt("typedef ~ (*~)("
			     "~,\n"
			     "\tgpointer);\n",
			     p_type(&m->ret_type),
			     p_signal_handler_type(m),
			     p_params(par, &o)));
		output_connector(out, m);
		o.names=TRUE;
		o.types=FALSE;
		dispatch=p_sig_marshalling(m);
		break;
	case METH_STATIC:
	case METH_DIRECT:{
		PNode* impl_name;
		o.names=TRUE;
		o.types=FALSE;

		impl_name=p_fmt("~_~",
				p_c_macro(t->name),
				p_c_macro(MEMBER(m)->name));
				
		dispatch=p_fmt("#ifdef ~\n"
			       "\t~~ (~);\n"
			       "#else\n"
			       "\tg_error(\"Not implemented: ~.~.~\");\n"
			       "#endif\n",
			       impl_name,
			       m->ret_type.prim
			       ? p_str("return ")
			       : p_nil,
			       impl_name,
			       p_params(par, &o),
			       p_str(t->module->package->name),
			       p_str(t->name),
			       p_str(MEMBER(m)->name));
		
		break;
	}
	case METH_VIRTUAL:
		dispatch=p_fmt("\t~((~*)((GtkObject*) ~)->klass)->~ (~);\n",
			       m->ret_type.prim?
			       p_str("return "):
			       p_nil,
			       p_class_name(DEF(MEMBER(m)->my_class)->type),
			       p_c_ident(t->name),
			       name,
			       p_params(par, &o));
		break;
	}
	output_func
	(out,
	 m->prot==METH_PUBLIC?"functions":"protected",
	 &m->ret_type,
	 p_varname(t, name),
	 p_nil,
	 par, 
	 dispatch);

	output_var_import(out, t, name);
	for(params = m->params; params; params = params->next)
		pr_put(out,
		       m->prot==METH_PUBLIC?"func_depends":"prot_depends",
		       ((Param*)(params->data))->type.prim->module);
	
		
	
	
	
	fparams_free(par);
}

void output_data_member(PRoot* out, DataMember* m){
	PrimType* t=DEF(MEMBER(m)->my_class)->type;
	PNode* name = p_c_ident(MEMBER(m)->name);
	PNode* self = p_self_name(MEMBER(m));

	
	switch(m->prot){
		FunParams* par;
	case DATA_READWRITE: {
		par=fparams("tt", &MEMBER(m)->my_class->self_type[FALSE],
			    self,
			    p_nil,
			    &m->type,
			    name,
			    p_nil);
		output_var_import(out, t, p_fmt("set_~", name));
		output_func(out,
			    "functions",
			    NULL,
			    p_varname(t, p_fmt("set_~", name)),
			    p_nil,
			    par,
			    p_fmt("~"
				  "\t~->~ = ~;\n",
				  (m->type.prim->kind==TYPE_OBJECT
				   && m->type.indirection==1)
				  ?m->type.notnull
				  ?p_fmt("\tgtk_object_ref "
					 "((GtkObject*) ~);\n"
					 "\tgtk_object_unref "
					 "((GtkObject*) ~->~);\n",
					 name,
					 self,
					 name)
				  :p_fmt("\tif(~)\n"
					 "\t\tgtk_object_ref "
					 "((GtkObject*) ~);\n"
					 "\tif(~->~)\n"
					 "\t\tgtk_object_unref "
					 "((GtkObject*) ~->~);\n",
					 name,
					 name,
					 self,
					 name,
					 self,
					 name)
				  :p_nil,
				  self,
				  name,
				  name));
		fparams_free(par);
	}
	/* fall through */
	case DATA_READONLY:
		pr_put(out, "func_depends", m->type.prim->module);
		par=fparams("t", &MEMBER(m)->my_class->self_type[TRUE],
			    self,
			    p_nil);
		output_var_import(out, t, name);
		output_func(out,
			    "functions",
			    &m->type,
			    p_varname(t, name),
			    p_nil,
			    par,
			    p_fmt("\treturn ~->~;\n",
				  self,
				  name));
		fparams_free(par);
		/* fall through */
	case DATA_PROTECTED:
		pr_put(out, "prot_depends", m->type.prim->module);
	}
}

	
void output_member(PRoot* out, Member* m){
	switch(m->membertype){
	case MEMBER_METHOD:
		output_method(out, (Method*)m);
		break;
	case MEMBER_DATA:
		output_data_member(out, (DataMember*)m);
		break;
	}
}

void output_member_cb(gpointer a, gpointer b){
	output_member(b, a);
}

void output_class_macros(PRoot* out, ObjectDef* o){
	PrimType* t=DEF(o)->type;
	pr_put(out, "type",
	       p_fmt("#define ~(o) GTK_CHECK_TYPE(o, ~)\n"
		     "#define ~(o) GTK_CHECK_CAST(o, ~, ~)\n",
		     p_macro_name(t, "is", NULL),
		     p_macro_name(t, "type", NULL),
		     p_macro_name(t, NULL, NULL),
		     p_macro_name(t, "type", NULL),
		     p_primtype(t)));
	output_macro_import(out, t, "is", NULL);
	output_macro_import(out, t, NULL, NULL);
}

void output_object_type_init(PRoot* out, ObjectDef* o){
	PrimType* t=DEF(o)->type;
	PNode* type_var=p_internal_varname(t, p_str("type"));
	output_func(out,
		    "type",
		    NULL,
		    p_internal_varname(t, p_str("init_type")),
		    p_nil,
		    NULL,
		    p_fmt("\tstatic GtkTypeInfo info = {\n"
			  "\t\t\"~\",\n"
			  "\t\tsizeof (~),\n"
			  "\t\tsizeof (~),\n"
			  "\t\t(GtkClassInitFunc) ~,\n"
			  "\t\t(GtkObjectInitFunc) ~,\n"
			  "\t\tNULL,\n"
			  "\t\tNULL,\n"
			  "\t\tNULL,\n"
			  "\t};\n"
			  "\tif (!~)\n"
			  "\t\t~ = gtk_type_unique (~, &info);\n",
			  p_primtype(t),
			  p_primtype(t),
			  p_class_name(t),
			  p_varname(t, p_str("class_init")),
			  p_varname(t, p_str("init")),
			  type_var,
			  type_var,
			  p_macro_name(o->parent, "type", NULL),
			  type_var));
}


void output_object_init(PRoot* out, ObjectDef* o){
	PrimType* t = DEF(o)->type;
	output_func(out, NULL,
		    NULL,
		    p_varname(t, p_str("init")),
		    p_fmt("~ ~",
			  p_type(&o->self_type[FALSE]),
			  p_c_ident(t->name)),
		    NULL,
		    p_fmt("\t(void) ~;\n"
			  "#ifdef ~_INIT\n"
			  "\t~_INIT (~);\n"
			  "#endif\n",
			  p_c_ident(t->name),
			  p_c_macro(t->name),
			  p_c_macro(t->name),
			  p_c_ident(t->name)));
}

void output_class_init(PRoot* out, ObjectDef* o){
	PrimType* t = DEF(o)->type;
	output_func(out, NULL,
		    NULL,
		    p_varname(t, p_str("class_init")),
		    p_fmt("~* klass",
			  p_class_name(t)),
		    NULL,
		    p_fmt("\tGtkObjectClass* obklass = "
			  "(GtkObjectClass*) klass;\n"
			  "~"
			  "\t(void) obklass;\n"
			  "~"
			  "#ifdef ~_CLASS_INIT\n"
			  "\t~_CLASS_INIT (klass);\n"
			  "#endif\n",
			  p_col("class_init_head", NULL),
			  p_col("member_class_init", NULL),
			  p_c_macro(t->name),
			  p_c_macro(t->name)));
}

void output_object(PRoot* out, Def* d){
	ObjectDef* o = (ObjectDef*)d;
	pr_put(out, "func_depends", d->type->module);
	pr_put(out, "func_parent_depends", o->parent->module);
	pr_put(out, "prot_parent_depends", o->parent->module);
	pr_put(out, "source_prot_depends", d->type->module);
	pr_put(out, "import_depends", o->parent->module);
	output_object_type_init(out, o);
	output_class_init(out, o);
	output_object_init(out, o);
	pr_put(out, "protected", p_class_decl(o));
	pr_put(out, "protected", p_class_body(o));
	pr_put(out, "type", p_object_decl(o));
	output_class_macros(out, o);
	pr_put(out, "protected", p_object_body(o));
	g_slist_foreach(o->members, output_member_cb, out);
}
