/* ARISA - Scripting Engine
 * Copyright (C) 2003, 2004 Carl Ritson
 *
 * 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
 */

#define _ARISA_SCRIPT_ENG_C

#include "arisa.h"
#include "user.h"

#include <arpa/inet.h>
#include <sys/stat.h>

#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>
#include <errno.h>
#include <stdarg.h>
#include <ctype.h>

#include <signal.h>

#include <fnmatch.h>

#include "script-eng.h"

#ifdef USE_PERL
#include "perl/engine.h"
#endif

// Static Data
// No need to locking on these, as they are used by only 1 thread, 
// and generally only during the pre-multithread stage of operation.
static se_save_data_t 	**load_cache	= NULL;
static int		no_load_cache	= 0;

// Functions 

static se_msg_t *alloc_msg(void) {
	se_msg_t *m = xalloc(sizeof(se_msg_t));
	LOCK_INIT(&(m->lock));
	m->type		= SE_UNKNOWN;
	m->occured	= xtime();
	m->ref_count	= 1;
	return m;
}

void free_msg(se_msg_t *m) {
	LOCK(m);
	m->ref_count--;
	if(m->ref_count > 0) {
		UNLOCK(m);
		return;
	} else
		UNLOCK(m);
	
	LOCK_FREE(&(m->lock));
	switch(m->type) {
		case SE_LOAD:
		case SE_UNLOAD:
		case SE_RELOAD:
			if(m->script.name != NULL)
				xfree(m->script.name);
			break;
		case SE_IRC_MSG:
			if(m->irc.data != NULL)
				strarr_free(&m->irc.data,&m->irc.no_data);
			break;
		case SE_SEND_NOTIFICATION:
		case SE_RECV_NOTIFICATION:
		case SE_QUEUE_ENQUEUE:
		case SE_QUEUE_DEQUEUE:
			if(m->notification.send != NULL)
				free_send(m->notification.send);
			break;
		case SE_PACK_ADD:
		case SE_PACK_DEL:
		case SE_PACK_HASHED:
		case SE_PACKLIST_ADD:
			if(m->pack.pack != NULL)
				free_pack(m->pack.pack);
			break;
		case SE_SIGNAL:
			if(m->signal.name != NULL)
				xfree(m->signal.name);
			break;
		case SE_SAVE_DATA:
			LOCK_FREE(&(m->save.lock));
			pthread_cond_destroy(&(m->save.cond));
			if(m->save.data != NULL)
				xfree(m->save.data);
		default:
			break;
	}
	xfree(m);
}

static se_msg_t *duplicate_msg(se_msg_t *m) {
	if(m == NULL)
		return NULL;
	LOCK(m);
	m->ref_count++;
	UNLOCK(m);
	return m;
}

static int dispatch_msg(se_msg_t *msg) {
	int i,ret;
	
	LOCKR(&global->se.lock);
	for(i = 0,ret = 0; i < global->se.no_interpreters; ++i) {
		LOCK(global->se.interpreters[i]);
		pqueue_push_back(&global->se.interpreters[i]->msgqueue,
					duplicate_msg(msg));
		ret++;
		UNLOCK(global->se.interpreters[i]);
	}
	UNLOCKR(&global->se.lock);
	
	free_msg(msg);
	return ret;
}

int se_register_interpreter(interpreter_t *inp) {
	int pre,post;
	
	LOCKR(&global->se.lock);
	pre = global->se.no_interpreters;
	PTRARR_ADD(&global->se.interpreters,&global->se.no_interpreters,inp);
	post = global->se.no_interpreters;
	UNLOCKR(&global->se.lock);
	
	if(pre != post) { // did it get added it
		int i;
		LOCK(inp);
		// scan the load cache
		for(i = 0; i < no_load_cache; ++i) {
			if((inp->flags & SE_F_LOAD_SAVE) &&
					strcmp(load_cache[i]->name,inp->name) == 0) {
				se_msg_t *m = alloc_msg();
				m->type = SE_LOAD_DATA;
				m->load.data = load_cache[i]->data;
				m->load.size = load_cache[i]->size;
				xfree(load_cache[i]->name);
				xfree(load_cache[i]);
				PTRARR_DEL(&load_cache,&no_load_cache,
						load_cache[i]);
				pqueue_push_back(&inp->msgqueue,m);
				break;
			}
		}
		thread_create(&(inp->thread),inp->func,inp);
		UNLOCK(inp);
		return 0;
	} else
		return -1;
}

static int deliver_to_script_holder(se_msg_t *msg) {
	interpreter_t *inp;
	int i,j,ret = -1;
	
	LOCKR(&global->se.lock);
	for(i = 0; i < global->se.no_interpreters && ret == -1; ++i) {
		inp = global->se.interpreters[i];
		LOCK(inp);
		for(j = 0; j < inp->no_loaded && ret == -1; ++j) {
			if(strcmp(inp->loaded[j],msg->script.name) == 0) {
				pqueue_push_back(&inp->msgqueue,
						duplicate_msg(msg));
				ret = 0;
			}
		}
		UNLOCK(inp);
	}
	UNLOCKR(&global->se.lock);

	free_msg(msg);
	return ret;
}

int se_load_script(const char *script) {
	interpreter_t *inp;
	se_msg_t *m = alloc_msg();
	int i,j,ret = -1;
	
	m->type = SE_LOAD;
	m->script.name = xstrdup(script);
	
	LOCKR(&global->se.lock);
	for(i = 0; i < global->se.no_interpreters && ret == -1; ++i) {
		inp = global->se.interpreters[i];
		LOCK(inp);
		for(j = 0; j < inp->no_types; ++j) {
			D("");
			if(fnmatch(inp->types[j],script,0) == 0) {
				D("");
				pqueue_push_back(&inp->msgqueue,m);
				ret = 0;
			}
		}
		UNLOCK(inp);
	}
	UNLOCKR(&global->se.lock);

	if(ret == -1)
		free_msg(m);
	return ret;
}

int se_reload_script(const char *script) {
	se_msg_t *m = alloc_msg();
	
	m->type = SE_RELOAD;
	m->script.name = xstrdup(script);
	
	return deliver_to_script_holder(m);
}

int se_unload_script(const char *script) {
	se_msg_t *m = alloc_msg();
	
	m->type = SE_UNLOAD;
	m->script.name = xstrdup(script);
	
	return deliver_to_script_holder(m);
}

void se_global_start(void) {
	int i;

#ifdef USE_PERL
	perl_register();
#endif
	//python_register();
	
	// any unclaimed load structures are now redundant
	if(load_cache != NULL) {
		for(i = 0; i < no_load_cache; ++i) {
			xfree(load_cache[i]->name);
			if(load_cache[i]->data != NULL)
				xfree(load_cache[i]->data);
			xfree(load_cache[i]);
		}
		xfree(load_cache);
		load_cache	= NULL;
		no_load_cache	= 0;
	}
	
	LOCK(global);
	for(i = 0; i < global->settings->no_scripts; ++i)
		se_load_script(global->settings->scripts[i]);
	UNLOCK(global);
}

void se_global_shutdown(pqueue_t *threads) {
	se_msg_t *m = alloc_msg();
	int i;
	
	m->type = SE_SHUTDOWN;
	dispatch_msg(m);

	LOCKR(&global->se.lock);
	for(i = 0; i < global->se.no_interpreters; ++i) {
		interpreter_t *intp = global->se.interpreters[i];
		LOCK(intp);
		pqueue_push_back(threads,&(intp->thread));
		thread_end(&(intp->thread));
		UNLOCK(intp);
	}
	UNLOCKR(&global->se.lock);
}

void se_irc_msg(network_t *network, char **data, int no_data) {
	se_msg_t *m = alloc_msg();
	m->type = SE_IRC_MSG;
	
	m->irc.network	= network;
	m->irc.data	= strarr_dup(data,no_data,&m->irc.no_data);
	
	dispatch_msg(m);
}

static send_t *clone_send(send_t *s) {
	send_t *c = xalloc(sizeof(send_t));
	
	memcpy(c,s,sizeof(send_t));

	LOCK_INIT(&(c->lock));

	if(c->nick != NULL)
		c->nick = xstrdup(c->nick);
	if(c->host != NULL)
		c->host = xstrdup(c->host);
	if(c->pack != NULL)
		pack_ref(c->pack);
	if(c->reason != NULL)
		c->reason = xstrdup(c->reason);
	c->aux = NULL;

	return c;
}

static void se_notification(se_msg_type_t type, int dolock, 
		interface_t *intf, queue_t *queue,
		send_t *s, int aux) {
	se_msg_t *m = alloc_msg();
	m->type = type;
	
	if(dolock)
		LOCK(s);
	m->notification.interface	= intf;
	m->notification.pool		= s->pool;
	m->notification.queue		= queue;
	m->notification.send		= clone_send(s);
	m->notification.aux		= aux;
	if(dolock)
		UNLOCK(s);

	dispatch_msg(m);
}

void se_notify_send(int dolock, interface_t *intf, send_t *s, int err) {
	se_notification(SE_SEND_NOTIFICATION, dolock, intf, NULL, s, err);
}

void se_notify_recv(int dolock, interface_t *intf, send_t *s, int err) {
	se_notification(SE_RECV_NOTIFICATION, dolock, intf, NULL, s, err);
}

void se_notify_enqueue(int dolock, queue_t *queue, send_t *s, int pos) {
	se_notification(SE_QUEUE_ENQUEUE, dolock, NULL, queue, s, pos);
}

void se_notify_dequeue(int dolock, queue_t *queue, send_t *s) {
	se_notification(SE_QUEUE_DEQUEUE, dolock, NULL, queue, s, 0);
}

static void se_pack_notification(se_msg_type_t type,
		int dolock, packlist_t *list, pack_t *pack) {
	se_msg_t *m = alloc_msg();
	m->type = type;

	m->pack.list = list;
	if(pack != NULL)
		m->pack.pack = pack_clone(pack);
	else
		m->pack.pack = NULL;

	dispatch_msg(m);
}

void se_notify_pack_add(int dolock, packlist_t *list, pack_t *pack) {
	se_pack_notification(SE_PACK_ADD, dolock, list, pack);
}

void se_notify_pack_del(int dolock, packlist_t *list, pack_t *pack) {
	se_pack_notification(SE_PACK_DEL, dolock, list, pack);
}

void se_notify_pack_hashed(int dolock, pack_t *pack) {
	se_pack_notification(SE_PACK_HASHED, dolock, NULL, pack);
}

void se_notify_packlist_add(packlist_t *list) {
	se_pack_notification(SE_PACKLIST_ADD, 0, list, NULL);
}

void se_signal(int no) {
	se_msg_t *m = alloc_msg();
	m->type = SE_SIGNAL;
	if(no == SIGHUP)
		m->signal.name = xstrdup("SIGHUP");
	else if(no == SIGUSR1)
		m->signal.name = xstrdup("SIGUSR1");
	else if(no == SIGUSR2)
		m->signal.name = xstrdup("SIGUSR2");
	else {
		char buffer[32];
		snprintf(buffer,sizeof(buffer),"SIG%d",no);
	}
	
	dispatch_msg(m);
}

int se_ui_call(interpreter_t *intp, void *data) {
	se_msg_t *m = alloc_msg();
	
	m->type = SE_UI_CALL;
	m->ui.data = data;

	LOCK(intp);
	pqueue_push_back(&intp->msgqueue,m);
	UNLOCK(intp);

	return 0;
}

#define DISPATCH(t,struc,ptr)	 		\
	do {					\
		se_msg_t *m = alloc_msg();	\
		m->type = t;			\
		m->struc.struc = ptr;		\
		dispatch_msg(m);		\
	} while(0)
#define DISPATCH2(t,struc1,ptr1,struc2,ptr2)	\
	do {					\
		se_msg_t *m = alloc_msg();	\
		m->type = t;			\
		m->struc1.struc1 = ptr1;	\
		m->struc1.struc2 = ptr2;	\
		dispatch_msg(m);		\
	} while(0)
		

void se_notify_interface_add(interface_t *intf) {
	DISPATCH2(SE_POOL_ADD,interface,intf,pool,NULL);
}

void se_notify_pool_add(interface_t *intf, pool_t *p) {
	DISPATCH2(SE_POOL_ADD,interface,intf,pool,p);
}

void se_notify_queue_add(queue_t *q) {
	DISPATCH(SE_QUEUE_ADD,queue,q);
}

void se_notify_network_add(network_t *net) {
	DISPATCH2(SE_NETWORK_ADD,network,net,channel,NULL);
}

void se_notify_channel_add(network_t *net, channel_t *chan) {
	DISPATCH2(SE_CHANNEL_ADD,network,net,channel,chan);
}

void se_notify_user_add(user_t *u) {
	DISPATCH(SE_USER_ADD,user,u);
}

void se_notify_admin_logon(chat_t *ac, user_t *u) {
	DISPATCH2(SE_ADMIN_LOGON,admin,ac,user,u);
}

void se_notify_admin_logoff(user_t *u) {
	DISPATCH2(SE_ADMIN_LOGOFF,admin,NULL,user,u);
}

se_save_data_t **se_save_data(int *no_data) {
	se_save_data_t **data = NULL;
	interpreter_t **intps = NULL;
	int i, no_intps = 0;
	
	*no_data = 0;
	
	LOCKR(&(global->se.lock));
	intps = PTRARR_DUP(global->se.interpreters,
			global->se.no_interpreters,&no_intps);
	UNLOCKR(&(global->se.lock));
	
	for(i = 0; i < no_intps; ++i) {
		interpreter_t *intp = intps[i];
		LOCK(intp);
		if(intp->flags & SE_F_LOAD_SAVE) {
			struct timespec timeout;
			struct timeval now;
			se_msg_t *m = alloc_msg();
			
			m->type = SE_SAVE_DATA;
			LOCK_INIT(&(m->save.lock));
			pthread_cond_init(&(m->save.cond),NULL);
			m->save.data = NULL;
			m->save.size = 0;
			
			LOCKR(&(m->save.lock));
			pqueue_push_back(&intp->msgqueue,duplicate_msg(m));
			UNLOCK(intp);
			
			gettimeofday(&now,NULL);
			timeout.tv_sec	= now.tv_sec + 3;
			timeout.tv_nsec	= now.tv_usec * 1000;
			if(pthread_cond_timedwait(&(m->save.cond),&(m->save.lock),&timeout) == 0) {
				if(m->save.data != NULL) {
					se_save_data_t *d = xalloc(sizeof(se_save_data_t));
					LOCK(intp);
					d->name = xstrdup(intp->name);
					UNLOCK(intp);
					d->data = m->save.data;
					d->size = m->save.size;

					m->save.data = NULL;
					PTRARR_ADD(&data,no_data,d);
				}
			}
			UNLOCKR(&(m->save.lock));

			free_msg(m);
		} else 
			UNLOCK(intp);
	}

	if(intps != NULL)
		xfree(intps);

	return data;
}

int se_load_data(const char *name, uint8_t *data, size_t size) {
	int ret = -1;
	
	if(global == NULL) {
		se_save_data_t *d = xalloc(sizeof(se_save_data_t));
		d->name = xstrdup(name);
		d->data = data;
		d->size = size;
		PTRARR_ADD(&load_cache,&no_load_cache,d);
		ret = 0;
	} else {
		int i;
		LOCKR(&(global->se.lock));
		for(i = 0; i < global->se.no_interpreters; ++i) {
			interpreter_t *intp = global->se.interpreters[i];
			LOCK(intp);
			if((intp->flags & SE_F_LOAD_SAVE) &&
					strcmp(intp->name,name) == 0) {
				se_msg_t *m = alloc_msg();
				m->type = SE_LOAD_DATA;
				m->load.data = data;
				m->load.size = size;
				pqueue_push_back(&(intp->msgqueue),m);
				UNLOCK(intp);
				ret = 0;
				break;
			} else {
				UNLOCK(intp);
			}
		}
		UNLOCKR(&(global->se.lock));
	}
	
	return ret;
}
