/* ARISA - Interface and DCC Send/Recv/Chat Functions
 * 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
 */

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

/* internal function, doesn't take global or object locks */
static void set_default_interface(interface_t *in) {
	int i;
	for(i = 0; i < global->no_interfaces; ++i) {
		if(global->interfaces[i] != in) {
			LOCK(global->interfaces[i]);
			if(!global->interfaces[i]->deleted &&
				global->interfaces[i]->type == in->type &&
				global->interfaces[i]->def == 1)
				global->interfaces[i]->def = 1;
			UNLOCK(global->interfaces[i]);
		}
	}
	in->def = 1;
}

int interface_start(interface_t *in) {
	if(!thread_is_running(&(in->thread))) {
		thread_create(&(in->thread),interface_thread,in);
		return 0;
	} else
		return -1;
}

int interface_add(interface_t *in) {
	int i,no_same,ret = -1;
	LOCK(global);
	LOCK(in);
	for(i = 0,no_same = 0; i < global->no_interfaces; ++i) {
		LOCK(global->interfaces[i]);
		if(!global->interfaces[i]->deleted &&
			strcmp(global->interfaces[i]->name,in->name) == 0) {
			LOGP(L_ERR,"Error trying to add duplicate interface: %s",
					in->name);
			UNLOCK(global->interfaces[i]);
			break;
		}
		if(!global->interfaces[i]->deleted
				&&  global->interfaces[i]->type == in->type)
			no_same++;
		UNLOCK(global->interfaces[i]);
	}
	if(i == global->no_interfaces) {
		PTRARR_ADD(&global->interfaces,&global->no_interfaces,in);
		validity_insert(in);
		LOGP(L_INF,"Added Interface %s",in->name);
		interface_start(in);
		if(no_same == 0)
			in->def = 1; // set this as default
		else if(in->def == 1)
			set_default_interface(in);
		ret = 0;
	}
	UNLOCK(global);
	if(ret == 0 && in->type == INTERFACE_RECV)
		access_verify_entries(in->recv.access);
	UNLOCK(in);
	if(ret == 0)
		se_notify_interface_add(in);
	return ret;
}

interface_t *interface_find(const char *name) {
	interface_t *ret = NULL;
	int i;
	if(name == NULL)
		return NULL;

	LOCK(global);
	for(i = 0; i < global->no_interfaces && ret == NULL; ++i) {
		LOCK(global->interfaces[i]);
		if(!global->interfaces[i]->deleted &&
			strcmp(global->interfaces[i]->name,name) == 0) {
			ret = global->interfaces[i];
			validity_insert(ret);
		}
		UNLOCK(global->interfaces[i]);
	}
	UNLOCK(global);
	return ret;
}

int interface_del(interface_t *in) {
	int i,j,okay = 1;
	interfacetype_t type;
	
	LOCK(in);
	type = in->type;
	UNLOCK(in);
	
	if(type == INTERFACE_SEND) {
		LOCK(in);
		for(i = 0; i < in->send.no_pools && okay; ++i) {
			LOCK(in->send.pools[i]);
			if(!in->send.pools[i]->deleted) {
				LOGP(L_ERR,"Error trying to remove SEND Interface with pools attached");
				okay = 0;
			}
			UNLOCK(in->send.pools[i]);
		}
		UNLOCK(in);
	} else if(type == INTERFACE_CHAT) {
		LOCK(global);
		for(i = 0, j = 0; i < global->no_packlists; ++i) {
			LOCK(global->packlists[i]);
			if(!global->packlists[i]->deleted &&
				global->packlists[i]->type == LIST_CHAT)
				j++;
			UNLOCK(global->packlists[i]);
		}
		if(j > 0) {
			for(i = 0, j = 0; i < global->no_interfaces; ++i) {
				LOCK(global->interfaces[i]);
				if(!global->interfaces[i]->deleted &&
					global->interfaces[i]->type == INTERFACE_CHAT)
					j++;
				UNLOCK(global->interfaces[i]);
			}
			if(j < 2) {
				LOGP(L_ERR,"Error trying to remove only CHAT Interface while chat type packlists exist");
				okay = 0;
			} else {
				for(i = 0, j = 0; i < global->no_packlists; ++i) {
					LOCK(global->packlists[i]);
					if(!global->packlists[i]->deleted &&
						global->packlists[i]->type == LIST_CHAT &&
						global->packlists[i]->chat_interface == in)
						global->packlists[i]->chat_interface = NULL;
					UNLOCK(global->packlists[i]);
				}
			}
		}
		UNLOCK(global);
	}
	
	if(!okay)
		return -1;
	
	LOCK(global);
	LOCK(in);
	if(in->def == 1) {
		for(i = 0,j = 0; i < global->no_interfaces && j == 0; ++i) {
			if(global->interfaces[i] != in) {
				LOCK(global->interfaces[i]);
				if(!global->interfaces[i]->deleted &&
					global->interfaces[i]->type == in->type)
					global->interfaces[i]->def = j = 1;
				UNLOCK(global->interfaces[i]);
			}
		}
		in->def = 0;
	}
		
	if(in->deleted != 0) { // already deleted
		UNLOCK(in);
		UNLOCK(global);
		return 0; 
	}
	in->deleted = xtime();
	validity_delete(in);
	if(thread_is_running(&(in->thread)))
		thread_end(&(in->thread));
	
	UNLOCK(in);
	UNLOCK(global);
	return 0;
}

void interface_purge(const time_t now) {
	interface_t **old = NULL;
	int no_old = 0;
	int i,j,deleted;
	
	//D("Interface Purge Begins");
	
	LOCK(global);
	for(i = 0, deleted = 0; i < global->no_interfaces; ++i) {
		LOCK(global->interfaces[i]);
		if(IS_PURGE_TIME(global->interfaces[i]->deleted,now))
			deleted++;
		UNLOCK(global->interfaces[i]);
	}
	if(deleted) {
		old	= global->interfaces;
		no_old	= global->no_interfaces;
		PTRARR_ALLOC(&(global->interfaces),&(global->no_interfaces),
				no_old - deleted);
		for(i = 0, j = 0; i < no_old; ++i) {
			LOCK(old[i]);
			if(!IS_PURGE_TIME(old[i]->deleted,now)) {
				global->interfaces[j++] = old[i];
				UNLOCK(old[i]);
				old[i] = NULL;
			} else
				UNLOCK(old[i]);
		}
	}
	UNLOCK(global);
	if(deleted) {
		for(i = 0; i < no_old; ++i) {
			if(old[i] != NULL) {
				thread_join(&(old[i]->thread),NULL);
				free_interface(old[i]);
			}
		}
	}
	if(old != NULL)
		xfree(old);

	//D("Interface Purge Ends");
}

void interface_nick_change(interface_t *in, network_t *net, 
		const char *onick, const char *nnick) {
	int i;

	LOCK(in);
	if(in->type == INTERFACE_SEND) {
		for(i = 0; i < in->send.no_pools; ++i)
			pool_nick_change(in->send.pools[i],net,onick,nnick);
	} else if(in->type == INTERFACE_RECV) {
		for(i = 0; i < in->recv.no_uploads; ++i) {
			send_t *u = in->recv.uploads[i];
			LOCK(u);
			if(u->network == net && 
					irc_strcasecmp(u->nick,onick) == 0) {
				xfree(u->nick);
				u->nick = xstrdup(nnick);
			}
			UNLOCK(u);
		}		
	} else if(in->type == INTERFACE_CHAT) {
		for(i = 0; i < in->chat.no_chats; ++i) {
			chat_t *c = in->chat.chats[i];
			LOCK(c);
			if(c->network == net &&
					irc_strcasecmp(c->nick,onick) == 0) {
				xfree(c->nick);
				c->nick = xstrdup(nnick);
			}
			UNLOCK(c);
		}
	}
	UNLOCK(in);
}

int interface_rename(interface_t *in, const char *name) {
	int i, ret = 0;

	if(in == NULL || name == NULL)
		return -1;
	
	LOCK(global);
	for(i = 0; i < global->no_interfaces && ret == 0; ++i) {
		LOCK(global->interfaces[i]);
		if(global->interfaces[i]->deleted == 0 &&
				strcmp(global->interfaces[i]->name,name) == 0)
			ret = -1;
		UNLOCK(global->interfaces[i]);
	}
	if(ret == 0) {
		LOCK(in);
		xfree(in->name);
		in->name = xstrdup(name);
		UNLOCK(in);
	}
	UNLOCK(global);
	
	return ret;
}

int interface_set_default(interface_t *in) {
	interfacetype_t type;
	int i;

	LOCK(global);
	
	LOCK(in);
	type = in->type;
	if(in->deleted != 0) {
		UNLOCK(in);
		UNLOCK(global);
		return -1;
	}
	UNLOCK(in);
	
	for(i = 0; i < global->no_interfaces; ++i) {
		LOCK(global->interfaces[i]);
		if(global->interfaces[i]->type == type) {
			if(global->interfaces[i] != in)
				global->interfaces[i]->def = 0;
			else
				global->interfaces[i]->def = 1;
		}
		UNLOCK(global->interfaces[i]);
	}
	
	UNLOCK(global);
	
	return 0;
}

int interface_valid(interface_t *in) {
	int i;
	
	if(in == NULL)
		return 0;
	
	if(validity_test(in))
		return 1;
	
	LOCK(global);
	for(i = 0; i < global->no_interfaces; ++i) {
		if(global->interfaces[i] == in) {
			LOCK(in);
			i = in->deleted;
			if(in->deleted == 0)
				validity_insert(in);
			UNLOCK(in);
			UNLOCK(global);
			if(i == 0) // in->deleted == 0
				return 1;
			else
				return 0;
		}
	}
	UNLOCK(global);
	
	return 0;
}

int interface_chat_close(interface_t *in, const char *hostmask, 
		network_t *net, const char *nick, chat_t *ptr, 
		const char *reason) {
	int i,count;

	LOCK(in);
	if(in->type != INTERFACE_CHAT) {
		UNLOCK(in);
		return 0;
	}
	for(i = 0,count = 0; i < in->chat.no_chats; ++i) {
		chat_t *c = in->chat.chats[i];
		LOCK(c);
		if(ptr != NULL) {
			if(c == ptr && c->state != STATE_COMPLETE
					&& c->state != STATE_DELETED
					&& c->state != STATE_ERROR) {
				c->state = STATE_DELETED;
				if(reason != NULL)
					c->reason = xstrdup(reason);
				count++;
			}
		} else if(irc_hostmask_match(c->host,hostmask)
				&& c->state != STATE_COMPLETE
				&& c->state != STATE_DELETED
				&& c->state != STATE_ERROR) {
			if(nick == NULL && (net == NULL || c->network == net)) {
				c->state = STATE_DELETED;
				if(reason != NULL)
					c->reason = xstrdup(reason);
				count++;
			} else if(nick != NULL) {
				if(irc_strcasecmp(c->nick,nick) == 0 &&
					(net == NULL || c->network == net)) {
					c->state = STATE_DELETED;
					if(reason != NULL)
						c->reason = xstrdup(reason);
					count++;
				}
			}
		}
		UNLOCK(c);
	}
	UNLOCK(in);

	return count;
}

int interface_upload_close(interface_t *in, const char *hostmask,
		network_t *net, const char *nick, const char *file,
		send_t *ptr, const char *reason) {
	int i,count;

	LOCK(in);
	if(in->type != INTERFACE_RECV) {
		UNLOCK(in);
		return 0;
	}
	for(i = 0, count = 0; i < in->recv.no_uploads; ++i) {
		send_t *u = in->recv.uploads[i];
		LOCK(u);
		if((ptr == NULL && send_matchs(u,hostmask,net,nick,file,NULL)) || u == ptr) {
			u->state = STATE_DELETED;
			if(reason != NULL) {
				if(u->reason != NULL)
					xfree(u->reason);
				u->reason = xstrdup(reason);
			}	
			count++;
		}
		UNLOCK(u);
	}
	UNLOCK(in);

	return count;
}

int interface_reset_counters(interface_t *in) {
	LOCK(in);
	in->record_rate	= 0;
	in->stat_mib	= 0;
	in->stat_carry	= 0;
	UNLOCK(in);
	return 0;
}

/** Settings Data **/
static setting_t interface_generic_data[] = {
	{"mode",	ST_ENUM,	OFFSETOF(interface_t,mode),
		0,NULL,NULL,interfacemode_to_str,str_to_interfacemode},
	{"bandwidth",	ST_UINT,	OFFSETOF(interface_t,bandwidth),
		0,"0",NULL,NULL,NULL},
	{"snd-bufsize",	ST_UINT,	OFFSETOF(interface_t,snd_bufsize),
		0,"0",NULL,NULL,NULL},
	{"rcv-bufsize",	ST_UINT,	OFFSETOF(interface_t,rcv_bufsize),
		0,"0",NULL,NULL,NULL},
	{"advertise-ip",ST_NSTR,	OFFSETOF(interface_t,advertise_ip),
		0,NULL,NULL,NULL,NULL},
	{"bind-ip",	ST_NSTR,	OFFSETOF(interface_t,bind_ip),
		0,NULL,NULL,NULL,NULL},
	{"accept-per-sec",ST_INT,	OFFSETOF(interface_t,accept_per_sec),
		0,"1","5",NULL,NULL},
	{"secure-mode",	ST_BOOL,	OFFSETOF(interface_t,secure_mode),
		VALUE_BOOL_ONOFF,NULL,NULL,NULL,NULL},
	{"dcc-port",	ST_UINT,	OFFSETOF(interface_t,dcc_port),
		0,"0","65535",NULL,NULL},
	{"no-ports",	ST_UINT,	OFFSETOF(interface_t,no_ports),
		0,"0","100",NULL,NULL},
	{"dccserver-port",ST_UINT,	OFFSETOF(interface_t,dccserver_port),
		0,"0","65535",NULL,NULL},
	{"dcc-timeout",	ST_UINT,	OFFSETOF(interface_t,dcc_timeout),
		0,"0",NULL,NULL,NULL},
	{"dcc-packet",	ST_UINT,	OFFSETOF(interface_t,dcc_packet),
		0,"512","65535",NULL,NULL},
	{NULL}
};

static setting_t interface_recv_data[] = {
	{"dir",		ST_NSTR,	OFFSETOF(interface_t,recv.dir),
		0,NULL,NULL,NULL,NULL},
	{"max-no-uploads",ST_INT,	OFFSETOF(interface_t,recv.max_no_uploads),
		0,"0",NULL,NULL,NULL},
	{"max-size",	ST_OFF,		OFFSETOF(interface_t,recv.max_size),
		0,"0",NULL,NULL,NULL},
	{NULL}
};

static setting_t interface_chat_data[] = {
	{"max-no-chats",ST_INT,		OFFSETOF(interface_t,chat.max_no),
		0,"0",NULL,NULL,NULL},
	{NULL}
};

int interface_apply_setting(interface_t *i, 
		const char *name, const char *value) {
	value_t *v = value_string(name,value);
	int ret = -1;
	LOCK(i);
	if(i->type == INTERFACE_RECV)
		ret = apply_setting(interface_recv_data,i,v);
	else if(i->type == INTERFACE_CHAT)
		ret = apply_setting(interface_chat_data,i,v);
	if(ret == -1)
		ret = apply_setting(interface_generic_data,i,v);
	UNLOCK(i);
	xfree(v);
	return ret;
}

int interface_read_settings(interface_t *i, pqueue_t *out) {
	int ret;
	LOCK(i);
	ret = read_settings(interface_generic_data,i,out);
	if(ret != -1) {
		if(i->type == INTERFACE_RECV)
			ret = read_settings(interface_recv_data,i,out);
		else if(i->type == INTERFACE_CHAT)
			ret = read_settings(interface_chat_data,i,out);
	}
	UNLOCK(i);
	return ret;
}
