/* ARISA - pool handling 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"

static const char *err_dup	= "Duplicate Send";
static const char *err_max	= "At Per User Send Limit";
static const char *err_full	= "All Sends Full";
static const char *err_unknown	= "Unknown Error";

int pool_add(interface_t *interface, pool_t *p) {
	int i,ret = -1;
	LOCK(interface);
	LOCK(p);
	for(i = 0; i < interface->send.no_pools; ++i) {
		LOCK(interface->send.pools[i]);
		if(strcasecmp(interface->send.pools[i]->name,p->name) == 0
			&& interface->send.pools[i]->deleted == 0) {
			LOGP(L_ERR,"Error, attempted to add duplicate pool %s to Interface \"%s\"",
				interface->send.pools[i]->name,
				interface->name);
			UNLOCK(interface->send.pools[i]);
			break;
		}
		UNLOCK(interface->send.pools[i]);
	}
	if(i == interface->send.no_pools) {
		LOGP(L_INF,"Added pool %s to Interface \"%s\"",
				p->name,
				interface->name);
		PTRARR_ADD(&interface->send.pools,&interface->send.no_pools,p);
		validity_pair_insert(interface,p);
		ret = 0;
	}
	UNLOCK(p);
	UNLOCK(interface);
	if(ret == 0)
		se_notify_pool_add(interface,p);
	return ret;
}

void pool_purge(interface_t *interface, const time_t now) {
	pool_t **old = NULL;
	int no_old = 0;
	int i,j,deleted;
	LOCK(interface);
	for(i = 0, deleted = 0; i < interface->send.no_pools; ++i) {
		LOCK(interface->send.pools[i]);
		if(IS_PURGE_TIME(interface->send.pools[i]->deleted,now) && 
				interface->send.pools[i]->no_csends == 0)
			deleted++;
		UNLOCK(interface->send.pools[i]);
	}
	if(deleted) {
		old	= interface->send.pools;
		no_old	= interface->send.no_pools;
		interface->send.pools	= xalloc(sizeof(pool_t *) *
				(no_old - deleted));
		interface->send.no_pools= (no_old - deleted);
		for(i = 0, j = 0; i < no_old; ++i) {
			LOCK(old[i]);
			if(!IS_PURGE_TIME(old[i]->deleted,now)) {
				interface->send.pools[j++] = old[i];
				UNLOCK(old[i]);
				old[i] = NULL;
			} else 
				UNLOCK(old[i]);
		}
	}
	UNLOCK(interface);
	if(deleted) {
		for(i = 0; i < no_old; ++i) {
			if(old[i] != NULL)
				free_pool(old[i]);
		}
	}
	if(old != NULL)
		xfree(old);
}

int pool_del(interface_t *interface, pool_t *p) {
	int i,okay = 1;
	LOCK(global);
	for(i = 0; i < global->no_queues && okay; ++i) {
		LOCK(global->queues[i]);
		if(global->queues[i]->deleted == 0
			&& global->queues[i]->pool == p) {
			LOGP(L_ERR,"Error trying to remove pool with queues attached");
			okay = 0;
		}
		UNLOCK(global->queues[i]);
	}
	if(okay) {
		LOCK(interface);
		LOCK(p);
		LOGP(L_INF,"Deleted pool %s",p->name);
		p->deleted = xtime();
		validity_delete(p);
		for(i = 0; i < p->no_csends; ++i) {
			LOCK(p->csends[i]);
			p->csends[i]->state = STATE_DELETED;
			UNLOCK(p->csends[i]);
		}
		UNLOCK(p);
		UNLOCK(interface);
		UNLOCK(global);
		return 0;
	} else {
		UNLOCK(global);
		return -1;
	}
}

pool_t *pool_find(interface_t *interface, const char *name) {
	pool_t *ret = NULL;
	int i;
	
	LOCK(interface);
	if(interface->type != INTERFACE_SEND) {
		UNLOCK(interface);
		return NULL;
	}
	for(i = 0; i < interface->send.no_pools && ret == NULL; ++i) {
		pool_t *p = interface->send.pools[i];
		LOCK(p);
		if(strcasecmp(p->name,name) == 0 && p->deleted == 0) {
			ret = p;
			validity_pair_insert(interface,ret);
		}
		UNLOCK(p);
	}
	UNLOCK(interface);
	
	return ret;
}

int pool_add_send(pool_t *p, send_t *s, int flags, char **err) {
	int i,j,ret = 0;

	if(err != NULL)
		*err = (char *)err_unknown;
	
	LOCK(p);
	if(p->deleted != 0) {
		UNLOCK(p);
		return -1;
	}
	LOCK(s);
	if(p->sends_per_user > 0 && !(flags & SEND_NO_ULIMIT)) {
		for(i = 0, j = 0; i < p->no_csends && ret == 0; ++i) {
			LOCK(p->csends[i]);
			if(p->csends[i]->network == s->network && 
				irc_strcasecmp(p->csends[i]->nick, s->nick) == 0) {
				if(p->csends[i]->pack == s->pack)
					ret = -1;
				j++;
			}
			UNLOCK(p->csends[i]);
		}
		if(ret == -1) {
			if(err != NULL)
				*err = (char *)err_dup;
		} else if(j >= p->sends_per_user) {
			if(err != NULL)
				*err = (char *)err_max;
			ret = -1;
		}
	}
	if(ret != -1) {
		if(p->no_csends < p->sends || (flags & SEND_NO_CLIMIT)) {
			if(err != NULL)
				*err = NULL;
			PTRARR_ADD(&p->csends,&p->no_csends,s);
			s->state 	= STATE_QUEUED;
			s->pool		= p;
			ret		= 0;
		} else {
			if(err != NULL)
				*err = (char *)err_full;
			ret 		= -1;
		}
	}
	UNLOCK(s);
	UNLOCK(p);
	
	return ret;		
}

/*
int pool_close_send_no(pool_t *p, const int no) {
	int ret = -1;
	LOCK(p);
	if(no <= p->no_csends && no > 0) {
		LOCK(p->csends[no]);
		p->csends[no]->state = STATE_DELETED;
		UNLOCK(p->csends[no]);
		ret = 0;
	}
	UNLOCK(p);
	return ret;
}
*/

void pool_purge_sends(pool_t *p) {
	send_t **old_sends = NULL;
	int i,j,k,no_old_sends = 0;
	
	//D("p: %p",p);
	
	LOCK(p);
	for(j = 0,k = 0; j < p->no_csends; ++j) {
		LOCK(p->csends[j]);
		if(p->csends[j]->state == STATE_PURGING ||
			p->csends[j]->state == STATE_PURGEREQUEUE)
			k++;
		UNLOCK(p->csends[j]);
	}
	//D("%d to purge/requeue",k);
	if(k > 0) {
		old_sends = p->csends;
		no_old_sends = p->no_csends;
		PTRARR_ALLOC(&(p->csends),&(p->no_csends),no_old_sends - k);
		for(j = 0, k = 0; j < no_old_sends; ++j) {
			LOCK(old_sends[j]);
			if(old_sends[j]->state != STATE_PURGING &&
					old_sends[j]->state != STATE_PURGEREQUEUE) {
				p->csends[k++] = old_sends[j];
				UNLOCK(old_sends[j]);
				old_sends[j] = NULL;
			}
		}
	}
	UNLOCK(p);
	
	if(old_sends != NULL) {
		for(i = 0; i < no_old_sends; ++i) {
			if(old_sends[i] != NULL) {
				// requeue, requeuable sends
				if(old_sends[i]->state == STATE_PURGEREQUEUE) {
					UNLOCK(old_sends[i]);
					if(queue_requeue(old_sends[i]) != 0)
						free_send(old_sends[i]);
				} else {
					UNLOCK(old_sends[i]);
					free_send(old_sends[i]);
				}
			}
		}
		xfree(old_sends);
	}
}

/* send is assumed to be locked externally to this function if required. */
int send_matchs(send_t *s, const char *host, network_t *net,
		const char *nick, const char *file, const char *label) {
	int ret = 1;
	
	if(s->state == STATE_COMPLETE
			|| s->state == STATE_DELETED
			|| s->state == STATE_ERROR)
		return 0;
	
	if(host != NULL) {
		if(s->host != NULL)
			ret &= irc_hostmask_match(s->host,host);
		else
			ret = 0;
	}
	
	if(net != NULL)
		ret &= (s->network == net);
		
	if(nick != NULL) {
		if(s->nick != NULL)
			ret &= (irc_strcasecmp(s->nick,nick) == 0);
		else
			ret = 0;
	}
	
	if(file != NULL) {
		char buffer[256];
		RLOCK(s->pack);
		pathtofn(s->pack->file,buffer,sizeof(buffer));
		RUNLOCK(s->pack);
		ret &= (strcmp(buffer,file) == 0);
	}
	
	if(label != NULL) {
		RLOCK(s->pack);
		if(s->pack->label != NULL) {
			char buffer[512];
			colourise(COLOUR_NONE,buffer,sizeof(buffer),
					s->pack->label);
			ret &= (strcasecmp(buffer,label) == 0);
		} else
			ret = 0;
		RUNLOCK(s->pack);
	}
	
	return (ret != 0) ? 1 : 0;
}

void pool_nick_change(pool_t *p, network_t *net,
		const char *onick, const char *nnick) {
	int i;

	LOCK(p);
	for(i = 0; i < p->no_csends; ++i) {
		send_t *s = p->csends[i];
		LOCK(s);
		if(s->network == net &&
				irc_strcasecmp(s->nick,onick) == 0) {
			xfree(s->nick);
			s->nick = xstrdup(nnick);
		}
		UNLOCK(s);
	}
	UNLOCK(p);
}

int pool_rename(interface_t *in, pool_t *p, const char *name) {
	int i,j, ret = 0;
	
	if(p == NULL || name == NULL)
		return -1;
	
	if(in == NULL) {
		LOCK(global);
		for(i = 0; i < global->no_interfaces && in == NULL; ++i) {
			LOCK(global->interfaces[i]);
			if(global->interfaces[i]->type == INTERFACE_SEND && 
					global->interfaces[i]->deleted == 0) {
				for(j = 0; j < global->interfaces[i]->send.no_pools && in == NULL; ++j) {
					if(global->interfaces[i]->send.pools[j] == p)
						in = global->interfaces[i];
				}
			}
			UNLOCK(global->interfaces[i]);
		}
		if(in != NULL)
			LOCK(in);
		UNLOCK(global);
		if(in == NULL)
			return -1;
	} else
		LOCK(in);

	if(in->type != INTERFACE_SEND || in->deleted != 0) {
		UNLOCK(in);
		return -1;
	}

	for(i = 0; i < in->send.no_pools && ret == 0; ++i) {
		LOCK(in->send.pools[i]);
		if(in->send.pools[i]->deleted == 0 &&
				strcmp(in->send.pools[i]->name,name) == 0)
			ret = -1;
		UNLOCK(in->send.pools[i]);
	}
	if(ret == 0) {
		LOCK(p);
		xfree(p->name);
		p->name = xstrdup(name);
		UNLOCK(p);
	}
	UNLOCK(in);

	return ret;
}

int pool_valid(interface_t *in, pool_t *p) {
	if(in == NULL || p == NULL)
		return 0;

	if(validity_pair_test(in,p))
		return 1;
	
	if(interface_valid(in)) {
		int i;
		
		LOCK(in);
		if(in->type != INTERFACE_SEND || in->deleted != 0) {
			UNLOCK(in);
			return 0;
		}
		for(i = 0; i < in->send.no_pools; ++i) {
			if(in->send.pools[i] == p) {
				LOCK(p);
				i = p->deleted;
				if(p->deleted == 0)
					validity_pair_insert(in,p);
				UNLOCK(p);
				UNLOCK(in);
				if(i == 0) // p->deleted == 0
					return 1;
				else
					return 0;
			}
		}
		UNLOCK(in);
	}
	
	return 0;
}

int pool_send_close(pool_t *p, const char *hostmask, network_t *net, 
		const char *nick, const char *file, send_t *ptr, 
		const char *reason) {
	int i,count;

	LOCK(p);
	for(i = 0,count = 0; i < p->no_csends; ++i) {
		send_t *s = p->csends[i];
		LOCK(s);
		if((ptr == NULL && send_matchs(s,hostmask,net,nick,file,NULL)) || s == ptr) {
			// If a send is QUEUED, then it has not been picked up
			// by the interface and should be directly PURGED.
			switch(s->state) {
				case STATE_QUEUED:
					s->state = STATE_PURGING;
					break;
				case STATE_PURGING:
				case STATE_PURGEREQUEUE:
					s->retries = 0;
					break;
				default:
					s->state = STATE_DELETED;
					break;
			}
			if(s->reason != NULL)
				xfree(s->reason);
			s->reason = reason != NULL ? xstrdup(reason) : NULL;
			count++;
		}
		UNLOCK(p->csends[i]);
	}
	UNLOCK(p);

	return count;
}

int pool_reset_counters(pool_t *p) {
	LOCK(p);
	p->record_send	= 0;
	p->record_rate	= 0;
	p->stat_mib	= 0;
	p->stat_carry	= 0;
	UNLOCK(p);
	return 0;
}

/** Settings Data **/
static setting_t pool_data[] = {
	{"sends",		ST_UINT,OFFSETOF(pool_t,sends),
		0,"0",NULL,NULL,NULL},
	{"bandwidth",		ST_UINT,OFFSETOF(pool_t,bandwidth),
		0,"0",NULL,NULL,NULL},
	{"send-max-bandwidth",	ST_UINT,OFFSETOF(pool_t,send_max_bandwidth),
		0,"0",NULL,NULL,NULL},
	{"send-min-bandwidth",	ST_UINT,OFFSETOF(pool_t,send_min_bandwidth),
		0,"0",NULL,NULL,NULL},
	{"sends-per-user",	ST_INT,	OFFSETOF(pool_t,sends_per_user),
		0,"0",NULL,NULL,NULL},
	{"dequeue-algorithm",	ST_INT,	OFFSETOF(pool_t,dequeue_algorithm),
		0,"0","2",NULL,NULL},
	{NULL}
};

int pool_apply_setting(pool_t *p, const char *name, const char *value) {
	value_t *v = value_string(name,value);
	int ret;
	LOCK(p);
	ret = apply_setting(pool_data,p,v);
	UNLOCK(p);
	xfree(v);
	return ret;
}

int pool_read_settings(pool_t *p, pqueue_t *out) {
	int ret;
	LOCK(p);
	ret = read_settings(pool_data,p,out);
	UNLOCK(p);
	return ret;
}
