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


static GHashTable* sigtype_hash;

typedef enum {
	MARSHALL_INT,
	MARSHALL_DOUBLE,
	MARSHALL_POINTER,
	MARSHALL_VOID,
	MARSHALL_LONG
} MarshallType;


struct _SignalType {
	Id package;
	Id module;
	MarshallType rettype;
	GSList* argtypes;
};

MarshallType marshalling_type(Type* t){
	if(!t)
		return MARSHALL_VOID;
	if(t->indirection)
		return MARSHALL_POINTER;
	if(!t->prim)
		return MARSHALL_VOID;
	switch(t->prim->kind){
	case TYPE_INT:
	case TYPE_FLAGS:
	case TYPE_ENUM:
		return MARSHALL_INT;
	case TYPE_LONG:
		return MARSHALL_LONG;
	case TYPE_DOUBLE:
		return MARSHALL_DOUBLE;
	case TYPE_NONE:
		return MARSHALL_VOID;
	default:
		g_assert_not_reached();
		return MARSHALL_VOID;
	}
}

/* Yes, this is hyperbly kludgetical */

SignalType* sig_type(Method* m){
	SignalType *t=g_new(SignalType, 1), *s;
	GSList* p=m->params, *a=NULL;
	PNode* g;
	gchar* f;
	t->package = DEF(MEMBER(m)->my_class)->type->module->package->name;
	t->module = DEF(MEMBER(m)->my_class)->type->module->name;
	t->rettype = marshalling_type(&m->ret_type);
	if(!sigtype_hash)
		sigtype_hash = g_hash_table_new(g_str_hash, g_str_equal);
	while(p){
		Param* param=p->data;
		MarshallType* t=g_new(MarshallType, 1);
		*t=marshalling_type(&param->type);
		a=g_slist_prepend(a, t);
		p=p->next;
	}
	a=g_slist_reverse(a);
	t->argtypes=a;
	g = p_signal_demarshaller_name(t);
	f = p_to_str(g, NULL);
	p_unref(g);
	s = g_hash_table_lookup(sigtype_hash, f);
	if(!s){
		g_hash_table_insert(sigtype_hash, f, t);
		return t;
	}
	else{
		sig_type_free(t);
		g_free(f);
		return s;
	}
}

void sig_type_free(SignalType* t){
	GSList* l=t->argtypes;
	while(l){
		g_free(l->data);
		l=l->next;
	}
	g_slist_free(t->argtypes);
	g_free(t);
}

typedef enum{
	GTKNAME,
	ENCODING,
	CTYPE
} MMap;


PNode* p_gtype_name(MarshallType t, MMap map){
	static const struct GTypeName{
		MarshallType type;
		Id gtkname;
		Id encoding;
		Id ctype;
	}names[]={
		{MARSHALL_POINTER, "POINTER", "P", "gpointer"},
		{MARSHALL_INT, "INT", "I", "gint"},
		{MARSHALL_DOUBLE, "DOUBLE", "D", "gdouble"},
		{MARSHALL_LONG, "LONG", "L", "glong"},
		{MARSHALL_VOID, "NONE", "0", "void"},
	};
	gint i;
	
	for(i=0;i<(gint)(sizeof(names)/sizeof(names[0]));i++)
		if(names[i].type==t){
			Id id;
			switch(map){
			case GTKNAME:
				id = names[i].gtkname;
				break;
			case ENCODING:
				id = names[i].encoding;
				break;
			case CTYPE:
				id = names[i].ctype;
				break;
			}
			return p_str(id);
		}
	
	g_assert_not_reached();
	return NULL;
}

PNode* p_gtabbr(gpointer p){
	MarshallType* t=p;
	return p_gtype_name(*t, ENCODING);
}



PNode* p_gtktype(Type* t){
	if(t->indirection==0){
		if(!t->prim)
			return p_str("GTK_TYPE_NONE");
		switch(t->prim->kind){
		case TYPE_INT:
			return p_str("GTK_TYPE_INT");
		case TYPE_DOUBLE:
			return p_str("GTK_TYPE_DOUBLE");
		case TYPE_ENUM:
		case TYPE_FLAGS:
			return p_macro_name(t->prim, "TYPE", NULL);
		case TYPE_CHAR:
			return p_str("GTK_TYPE_CHAR");
		case TYPE_FOREIGN:
			g_error("Cannot marshall foreign type %s.%s!",
				t->prim->module->package->name,
				t->prim->name);
			return NULL;
		default:
			g_error("Cannot marshall type by value: %s.%s",
				t->prim->module->package->name,
				t->prim->name);
			return NULL;
		}
	}else if(t->indirection==1
		 && t->prim
		 && (t->prim->kind==TYPE_BOXED
		     || t->prim->kind==TYPE_OBJECT))
		return p_macro_name(t->prim, "TYPE", NULL);
	else
		return p_str("GTK_TYPE_POINTER");
}
			

PNode* p_signal_func_name(SignalType* t, PNode* basename){
#if 1
	return p_fmt("_~_~_~_~~",
		     p_c_ident(t->package),
		     p_c_ident(t->module),
		     basename,
		     p_gtype_name(t->rettype, ENCODING),
		     p_for(t->argtypes, p_gtabbr, p_nil));
#else
	return p_fmt("_~_~_~~",
		     p_c_ident(t->package),
		     basename,
		     p_gtype_name(t->rettype, ENCODING),
		     p_for(t->argtypes, p_gtabbr, p_nil));
#endif
}

PNode* p_signal_marshaller_name(SignalType* t){
	return p_signal_func_name(t, p_str("marshall"));
}

PNode* p_signal_demarshaller_name(SignalType* t){
	return p_signal_func_name(t, p_str("demarshall"));
}

PNode* p_handler_type(SignalType* t){
#if 1
	return p_fmt("_~~Handler_~~",
		     p_str(t->package),
		     p_str(t->module),
		     p_gtype_name(t->rettype, ENCODING),
		     p_for(t->argtypes, p_gtabbr, p_nil));
#else
	return p_fmt("_~Handler_~~",
		     p_str(t->package),
		     p_gtype_name(t->rettype, ENCODING),
		     p_for(t->argtypes, p_gtabbr, p_nil));
#endif
}

PNode* p_signal_id(Method* s){
	PrimType* t=DEF(MEMBER(s)->my_class)->type;
	return p_fmt("_~_~_signal_~",
		     p_c_ident(t->module->package->name),
		     p_c_ident(t->name),
		     p_c_ident(MEMBER(s)->name));
}

typedef struct{
	PNode* args;
	gint idx;
}ArgMarshData;

PNode* p_arg_marsh(gpointer p, gpointer d){
	Param* par=p;
	gint* idx=d;
	(*idx)++;
	return p_fmt(/* "\targs[~].type=~;\n" unnecessary... */
		     "\tGTK_VALUE_~(args[~]) = ~;\n",
		     /* p_prf("%d", *idx),
			p_gtktype(&par->type), */
		     p_gtype_name(marshalling_type(&par->type), GTKNAME),
		     p_prf("%d", *idx),
		     p_c_ident(par->name));
}
		     
PNode* p_sig_marshalling(Method* m){
	gint idx=-1;
	gint nargs = g_slist_length(m->params);
	gboolean ret = marshalling_type(&m->ret_type)!=MARSHALL_VOID;
	return p_fmt("\t{\n"
		     "~"
		     "~"
		     "~"
		     "~"
		     "\tgtk_signal_emitv((GtkObject*)~, ~, ~);\n"
		     "~"
		     "\t}\n",
		     nargs > 0
		     ? p_fmt("\tGtkArg args[~];\n",
			     p_prf("%d", nargs+ret))
		     : p_nil,
		     ret
		     ?p_fmt("\t~ retval;\n",
			    p_type(&m->ret_type))
		     :p_nil,
		     p_for(m->params, p_arg_marsh, &idx),
		     ret
		     /* cannot use retloc here, ansi forbids casted lvalues */
		     ?p_fmt("\tGTK_VALUE_POINTER(args[~]) = &retval;\n",
			    p_prf("%d", nargs))
		     :p_nil,
		     p_c_ident(DEF(MEMBER(m)->my_class)->type->name),
		     p_signal_id(m),
		     nargs > 0
		     ? p_str("args")
		     : p_str("NULL"),
		     ret
		     ?p_str("\treturn retval;\n")
		     :p_nil);
}
		     
		
PNode* p_arg_demarsh(gpointer p, gpointer d){
	MarshallType* t=p;
	gint* data=d;
	(*data)++;
	return p_fmt("\t\tGTK_VALUE_~(args[~]),\n",
		     p_gtype_name(*t, FALSE),
		     p_prf("%d", *data));
}

PNode* p_sig_arg_ctype(MarshallType* t){
	return p_fmt(", ~", p_gtype_name(*t, CTYPE));
}

PNode* p_sigdemarsh_decl(SignalType* t){
#if 1
	return p_fmt("static void ~ (GtkObject*, GtkSignalFunc, "
		     "gpointer, GtkArg*);\n"
		     "typedef ~ (*~)(GtkObject*~, gpointer);\n",
		     p_signal_demarshaller_name(t),
		     p_gtype_name(t->rettype, CTYPE),
		     p_handler_type(t),
		     p_for(t->argtypes, p_sig_arg_ctype, p_nil));
#else
	return p_fmt("extern void ~ (GtkObject*, GtkSignalFunc, "
		     "gpointer, GtkArg*);\n",
		     p_signal_demarshaller_name(t));
#endif
}

PNode* p_demarshaller(SignalType* t){
	gint idx=-1;
	return p_fmt("static void ~ (\n"
		     "\tGtkObject* object,\n"
		     "\tGtkSignalFunc func,\n"
		     "\tgpointer user_data,\n"
		     "\tGtkArg* args){\n"
		     "\t(void)args;\n"
		     "~~"
		     "}\n",
		     p_signal_demarshaller_name(t),
		     (t->rettype==TYPE_NONE)
		     ? p_fmt("\t*(GTK_RETLOC_~(args[~])) =\n",
			     p_gtype_name(t->rettype, FALSE),
			     p_prf("%d", g_slist_length(t->argtypes)))
		     : p_nil,
		     p_fmt("\t~(object,\n"
			   "~"
			   "\tuser_data);\n",
			   p_cast(p_handler_type(t), p_str("func")),
			   p_for(t->argtypes, p_arg_demarsh, &idx)));
}
