/**
 * $Id$
 *
 * ***** BEGIN GPL LICENSE BLOCK *****
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * Contributor(s): Nathan Letwory. 
 *
 * ***** END GPL LICENSE BLOCK *****
 */

#ifdef WITH_VERSE

#include <string.h>

#include "MEM_guardedalloc.h"

#include "DNA_listBase.h"
#include "DNA_userdef_types.h"
#include "DNA_text_types.h"

#include "BLI_dynamiclist.h"
#include "BLI_blenlib.h"
#include "BLI_arithb.h"

#include "BIF_verse.h"

#include "BKE_bad_level_calls.h"
#include "BKE_library.h"
#include "BKE_text.h"
#include "BKE_verse.h"
#include "BKE_global.h"
#include "BKE_main.h"

#include "verse.h"

/* helper struct for creating method descriptions */
typedef struct VMethodInfo {
	const char *name;
	uint8 param_count;
	const VNOParamType param_type[4];
	const char *param_name[4];
	uint16 id;
} VMethodInfo;

#ifdef VERSECHAT
/* array with methods for verse chat */
static VMethodInfo vmethod_info[] = {
	{ "join", 1, { VN_O_METHOD_PTYPE_STRING }, { "channel"}},
	{ "leave", 1, { VN_O_METHOD_PTYPE_STRING }, { "channel"}},
	{ "hear", 3, { VN_O_METHOD_PTYPE_STRING, VN_O_METHOD_PTYPE_STRING, VN_O_METHOD_PTYPE_STRING }, { "channel", "from", "msg"}}
};
#endif

/* lookup a method group based on its name */
struct VMethodGroup *lookup_vmethodgroup_name(ListBase *lb, const char *name) {
	struct VMethodGroup *vmg;

	for(vmg= lb->first; vmg; vmg= vmg->next)
		if(strcmp(vmg->name,name)==0) break;
	
	return vmg;
}

/* lookup a method group based on its group_id */
struct VMethodGroup *lookup_vmethodgroup(ListBase *lb, uint16 group_id) {
	struct VMethodGroup *vmg;

	for(vmg= lb->first; vmg; vmg= vmg->next)
		if(vmg->group_id==group_id) break;
	
	return vmg;
}

/* lookup a method based on its name */
struct VMethod *lookup_vmethod_name(ListBase *lb, const char *name) {
	struct VMethod *vm;
	for(vm= lb->first; vm; vm= vm->next)
		if(strcmp(vm->name,name)==0) break;

	return vm;
}

/* lookup a method based on its method_id */
struct VMethod *lookup_vmethod(ListBase *lb, uint8 method_id) {
	struct VMethod *vm;
	for(vm= lb->first; vm; vm= vm->next)
		if(vm->id==method_id) break;

	return vm;
}

#ifdef VERSECHAT
/*
 * send say command
 */
void send_say(const char *chan, const char *utter)
{
	struct VerseSession *session = (VerseSession*)current_verse_session();
	struct VNode *vnode;
	struct VMethodGroup *vmg;
	struct VMethod *vm;
	VNOPackedParams *utterpack;
	VNOParam args[2];
	
	vnode= (VNode *)(session->nodes.lb.first);

	for( ; vnode; vnode= vnode->next) {
		if(strcmp(vnode->name, "tawksrv")==0) {
			vmg= lookup_vmethodgroup_name(&(vnode->methodgroups), "tawk");
			if(!vmg) break;
			vm= lookup_vmethod_name(&(vmg->methods), "say");
			if(!vm) break;
			args[0].vstring= (char *)chan;
			args[1].vstring= (char *)utter;
			if((utterpack= verse_method_call_pack(vm->param_count, vm->param_type, args))!=NULL) {
				verse_send_o_method_call(vnode->id, vmg->group_id, vm->id, vnode->session->avatar, utterpack);
			}
			break;
		}

	}
}

/*
 * send logout command
 */
void send_logout(VNode *vnode)
{
	struct VMethodGroup *vmg;
	struct VMethod *vm;
	VNOPackedParams *pack;

	vnode->chat_flag = CHAT_LOGGED;
	vmg= lookup_vmethodgroup_name(&(vnode->methodgroups), "tawk");
	if(!vmg) return;
	vm= lookup_vmethod_name(&(vmg->methods), "logout");
	if(!vm) return;

	if((pack= verse_method_call_pack(vm->param_count, vm->param_type, NULL))!=NULL) {
		verse_send_o_method_call(vnode->id, vmg->group_id, vm->id, vnode->session->avatar, pack);
	}
	vnode->chat_flag = CHAT_NOTLOGGED;
}

/*
 * send join command
 */
void send_join(VNode *vnode, const char *chan)
{
	struct VMethodGroup *vmg;
	struct VMethod *vm;
	VNOPackedParams *join;
	VNOParam channel[1];

	vmg= lookup_vmethodgroup_name(&(vnode->methodgroups), "tawk");
	if(!vmg) return;
	vm= lookup_vmethod_name(&(vmg->methods), "join");
	if(!vm) return;

	channel[0].vstring= (char *)chan;
	if((join= verse_method_call_pack(vm->param_count, vm->param_type, channel))!=NULL) {
		verse_send_o_method_call(vnode->id, vmg->group_id, vm->id, vnode->session->avatar, join);
	}
}

/*
 * send leave command
 */
void send_leave(VNode *vnode, const char *chan)
{
	struct VMethodGroup *vmg;
	struct VMethod *vm;
	VNOPackedParams *leave;
	VNOParam channel[1];

	vmg= lookup_vmethodgroup_name(&(vnode->methodgroups), "tawk");
	if(!vmg) return;
	vm= lookup_vmethod_name(&(vmg->methods), "leave");
	if(!vm) return;

	channel[0].vstring= (char *)chan;
	if((leave= verse_method_call_pack(vm->param_count, vm->param_type, channel))!=NULL) {
		verse_send_o_method_call(vnode->id, vmg->group_id, vm->id, vnode->session->avatar, leave);
	}
}

/*
 * send login command
 */
void send_login(VNode *vnode)
{
	struct VMethodGroup *vmg;
	struct VMethod *vm;
	VNOPackedParams *login;
	VNOParam param[1];

	vnode->chat_flag = CHAT_LOGGED;
	vmg= lookup_vmethodgroup_name(&(vnode->methodgroups), "tawk");
	if(!vmg) return;
	vm= lookup_vmethod_name(&(vmg->methods), "login");
	if(!vm) return;

	param[0].vstring= U.verseuser;

	if((login= verse_method_call_pack(vm->param_count, vm->param_type, param))!=NULL) {
		verse_send_o_method_call(vnode->id, vmg->group_id, vm->id, vnode->session->avatar, login);
	}
	vnode->chat_flag = CHAT_LOGGED;

	vnode= lookup_vnode(vnode->session, vnode->session->avatar);
	vmg= lookup_vmethodgroup_name(&(vnode->methodgroups), "tawk-client");
	if(!vmg)
		verse_send_o_method_group_create(vnode->session->avatar, ~0, "tawk-client");
}
#endif

/*
 * Free a VMethod
 */
void free_verse_method(VMethod *vm) {
	if(!vm) return;

	MEM_freeN(vm->param_type);
}

/*
 * Free methods for VMethodGroup
 */
void free_verse_methodgroup(VMethodGroup *vmg)
{
	struct VMethod *vm, *tmpvm;

	if(!vmg) return;

	vm= vmg->methods.first;
	while(vm) {
		tmpvm=vm->next;
		free_verse_method(vm);
		vm= tmpvm;
	}
	BLI_freelistN(&(vmg->methods));
}

/* callback for method group creation */
static void cb_o_method_group_create(
		void *user_data,
		VNodeID node_id,
		uint16 group_id,
		const char *name)
{
	struct VerseSession *session = (VerseSession*)current_verse_session();
	struct VNode *vnode;
	struct VMethodGroup *vmg;

	if(!session) return;

	vnode = BLI_dlist_find_link(&(session->nodes), (unsigned int)node_id);

	vmg = lookup_vmethodgroup(&(vnode->methodgroups), group_id);
	
	/* create method group holder in node node_id */
	if(!vmg) {
		vmg= MEM_mallocN(sizeof(VMethodGroup), "VMethodGroup");
		vmg->group_id = group_id;
		vmg->methods.first = vmg->methods.last = NULL;
		BLI_addtail(&(vnode->methodgroups), vmg);
		printf("new method group with name %s (group_id %d) for node %u created\n", name, group_id, node_id);
	}

	/* this ensures name of an existing group gets updated, in case it is changed */
	BLI_strncpy(vmg->name, (char *)name, 16);

	/* subscribe to method group */
	verse_send_o_method_group_subscribe(node_id, group_id);

#ifdef VERSECHAT
	/* if this is our own method group, register our methods */
	if(node_id==session->avatar) {
		verse_send_o_method_create(node_id, group_id, (uint8)~0u, vmethod_info[0].name,
				vmethod_info[0].param_count,
				(VNOParamType *)vmethod_info[0].param_type,
				(const char **)vmethod_info[0].param_name);
		b_verse_update();
		verse_send_o_method_create(node_id, group_id, (uint8)~0u, vmethod_info[1].name,
				vmethod_info[1].param_count,
				(VNOParamType *)vmethod_info[1].param_type,
				(const char **)vmethod_info[1].param_name);
		b_verse_update();
		verse_send_o_method_create(node_id, group_id, (uint8)~0u, vmethod_info[2].name,
				vmethod_info[2].param_count,
				(VNOParamType *)vmethod_info[2].param_type,
				(const char **)vmethod_info[2].param_name);
		b_verse_update();
	}
#endif
}

/* callback for method group destruction */
static void cb_o_method_group_destroy(
		void *user_data,
		VNodeID node_id,
		uint16 group_id,
		const char *name)
{
	struct VerseSession *session = (VerseSession*)current_verse_session();
	struct VNode *vnode;
	struct VMethodGroup *vmg;
	struct VMethod *vm;

	printf("method group %d destroyed\n", group_id);

	if(!session) return;

	vnode = BLI_dlist_find_link(&(session->nodes), (unsigned int)node_id);
	for(vmg= vnode->methodgroups.first; vmg; vmg= vmg->next)
		if(vmg->group_id==group_id) break;

	if(!vmg) return; /* method group doesn't exist? */

	vmg->group_id = 0;
	vmg->name[0] = '\0';
	vm= vmg->methods.first;
	while(vm) {
		/* free vm */
		
	}

	/* TODO: unsubscribe from method group */
	BLI_remlink(&(vnode->methodgroups),vmg);
	MEM_freeN(vmg);
}

/* callback for method creation */
static void cb_o_method_create(
		void *user_data,
		VNodeID node_id,
		uint16 group_id,
		uint16 method_id,
		const char *name,
		uint8 param_count,
		const VNOParamType *param_type,
		const char *param_name[])
{
	struct VerseSession *session = (VerseSession*)current_verse_session();
	struct VNode *vnode;
	struct VMethodGroup *vmg;
	struct VMethod *vm;
	unsigned int size;
	unsigned int i;
	char *put;

	if(!session) return;

	vnode = BLI_dlist_find_link(&(session->nodes), (unsigned int)node_id);

	vmg= lookup_vmethodgroup((&vnode->methodgroups), group_id);

	if(!vmg) return;

	vm= lookup_vmethod((&vmg->methods), method_id);

	if(!vm) {
		vm= MEM_mallocN(sizeof(VMethod), "VMethod");
		vm->id= method_id;
		vm->param_count= param_count;
		size= param_count* (sizeof(*vm->param_type) + sizeof(*vm->param_name));
		for(i= 0; i <param_count; i++) {
			size+=strlen(param_name[i])+1;
		}
		vm->param_type= MEM_mallocN(size, "param_type and param_name");
		memcpy(vm->param_type, param_type, sizeof(VNOParamType)*param_count);
		vm->param_name= (char **)(vm->param_type + param_count);
		put= (char *)(vm->param_name + param_count);
		for(i= 0; i < param_count; i++) {
			vm->param_name[i]= put;
			strcpy(put, param_name[i]);
			put += strlen(param_name[i]) + 1;
		}

		BLI_addtail(&(vmg->methods), vm);
#ifdef VERSECHAT
		if(strcmp(vmethod_info[0].name, name)==0) {
			vmethod_info[0].id = method_id;
		}
#endif
		printf("method %s in group %d of node %u created\n", name, group_id, node_id);
	}

	BLI_strncpy(vm->name, (char *)name, 500);
}

/* callback for method destruction */
static void cb_o_method_destroy(
		void *user_data,
		VNodeID node_id,
		uint16 group_id,
		uint16 method_id,
		const char *name,
		uint8 param_count,
		const VNOParamType *param_type,
		const char *param_name[])
{
	struct VerseSession *session = (VerseSession*)current_verse_session();
	struct VNode *vnode;
	struct VMethodGroup *vmg;
	struct VMethod *vm;

	if(!session) return;

	vnode = BLI_dlist_find_link(&(session->nodes), (unsigned int)node_id);
	for(vmg= vnode->methodgroups.first; vmg; vmg= vmg->next)
		if(vmg->group_id==group_id) break;

	if(!vmg) return; /* method group doesn't exist? */

	for(vm= vmg->methods.first; vm; vm= vm->next)
		if(vm->id==method_id) break;

	if(!vm) return;

	BLI_remlink(&(vmg->methods), vm);
	MEM_freeN(vm->param_type);
	MEM_freeN(vm);
}

/* callback for method calls */
static void cb_o_method_call(void *user_data, VNodeID node_id, uint8 group_id, uint8 method_id, VNodeID sender, VNOPackedParams *params)
{
	struct VerseSession *session = (VerseSession*)current_verse_session();
	struct VNode *vnode;
	struct VMethodGroup *vmg;
	struct VMethod *vm;
	Text *text;
	int method_idx= -1;

	VNOParam arg[3];

	if(!session) return;

	if(session->avatar!=node_id) return;

	vnode = BLI_dlist_find_link(&(session->nodes), (unsigned int)node_id);
	vmg= lookup_vmethodgroup(&(vnode->methodgroups), group_id);
	if(!vmg) return;

	vm= lookup_vmethod(&(vmg->methods), method_id);
	if(!vm) return;
#ifdef VERSECHAT
	if(strcmp(vm->name, "join")==0) method_idx=0;
	if(strcmp(vm->name, "leave")==0) method_idx=1;
	if(strcmp(vm->name, "hear")==0) method_idx=2;
	if(method_idx>-1)
		verse_method_call_unpack(params, vmethod_info[method_idx].param_count, vmethod_info[method_idx].param_type, arg);

	switch(method_idx) {
		case 0:
			printf("Joining channel %s\n",arg[0].vstring);
			text=add_empty_text();
			text->flags |= TXT_ISCHAT;
			rename_id(&(text->id), arg[0].vstring);
			break;
		case 1:
			printf("Leaving channel %s\n",arg[0].vstring);
			break;
		case 2:
			{
				ListBase lb = G.main->text;
				ID *id= (ID *)lb.first;
				char showstr[1024];
				showstr[0]='\0';
				text = NULL;
				sprintf(showstr, "%s: %s\n", arg[1].vstring, arg[2].vstring);
				for(; id; id= id->next) {
					if(strcmp(id->name+2, arg[0].vstring)==0 && strcmp(arg[0].vstring, "#server")!=0) {
						text = (Text *)id;
						break;
					}
				}
				if(text) {
					txt_insert_buf(text, showstr);
					txt_move_eof(text, 0);
					allqueue(REDRAWCHAT, 0);
				} else {
					printf("%s> %s: %s\n",arg[0].vstring, arg[1].vstring, arg[2].vstring);
				}
			}
			break;
	}
#endif
}

void set_method_callbacks(void)
{
	/* create and destroy method groups */
	verse_callback_set(verse_send_o_method_group_create, cb_o_method_group_create, NULL);
	verse_callback_set(verse_send_o_method_group_destroy, cb_o_method_group_destroy, NULL);

	/* create and destroy methods */
	verse_callback_set(verse_send_o_method_create, cb_o_method_create, NULL);
	verse_callback_set(verse_send_o_method_destroy, cb_o_method_destroy, NULL);

	/* call methods */
	verse_callback_set(verse_send_o_method_call, cb_o_method_call, NULL);
}

#endif
