/* ARISA - Main pack and packlist 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 <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <fnmatch.h>
#include <math.h>

#define MAX_SCAN_DEPTH		32
#define MAX_SCAN_DEPTH_STR 	"32"

static pack_t *in_list(pack_t **packs, int no_packs, 
		pack_t *p, const char *file) {
	pack_t *ret = NULL;
	int i;
	for(i = 0; i < no_packs && ret == NULL; ++i) {
		if(packs[i] == p) {
			ret = p;
		} else {
			RLOCK(packs[i]);
			// strcmp, might need to be strcasecmp if doze port
			if(strcmp(packs[i]->file,file) == 0)
				ret = packs[i];
			RUNLOCK(packs[i]);
		}
	}
	return ret;
}

int packlist_add(packlist_t *p) {
	int i,ret = -1;
	LOCK(global);
	LOCK(p);
	for(i = 0; i < global->no_packlists; ++i) {
		LOCK(global->packlists[i]);
		if(strcasecmp(global->packlists[i]->name,p->name) == 0
			&& global->packlists[i]->deleted == 0) {
			LOGP(L_ERR,
				"Error, attempted to add duplicate packlist %s",
				global->packlists[i]->name);
			UNLOCK(global->packlists[i]);
			break;
		}
		UNLOCK(global->packlists[i]);
	}
	if(i == global->no_packlists) {
		LOGP(L_INF,"Added packlist %s",p->name);
		PTRARR_ADD(&global->packlists,&global->no_packlists,p);
		validity_insert(p);
		se_notify_packlist_add(p);
		ret = 0;
	}
	UNLOCK(global);
	if(ret == 0)
		access_verify_entries(p->access);
	UNLOCK(p);
	return ret;
}

void packlist_purge(const time_t now) {
	packlist_t **old = NULL;
	int no_old = 0;
	int i,j,deleted;
	
	//D("Packlist Purge Begins");
	
	LOCK(global);
	for(i = 0, deleted = 0; i < global->no_packlists; ++i) {
		LOCK(global->packlists[i]);
		if(IS_PURGE_TIME(global->packlists[i]->deleted,now))
			deleted++;
		UNLOCK(global->packlists[i]);
	}
	if(deleted) {
		old 	= global->packlists;
		no_old 	= global->no_packlists;
		PTRARR_ALLOC(&(global->packlists),&(global->no_packlists),
				no_old - deleted);
		for(i = 0,j = 0; i < no_old; ++i) {
			LOCK(old[i]);
			if(!IS_PURGE_TIME(old[i]->deleted,now)) {
				global->packlists[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)
				free_packlist(old[i]);
		}
	}
	if(old != NULL)
		xfree(old);

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

int packlist_del(packlist_t *p) {
	int i;
	LOCK(global);
	for(i = 0; i < global->no_users; ++i) {
		LOCK(global->users[i]);
		if(global->users[i]->packlists != NULL)
			PTRARR_DEL(&global->users[i]->packlists,
				&global->users[i]->no_packlists,p);
		UNLOCK(global->users[i]);
	}
	LOCK(p);
	if(p->deleted == 0) {
		LOGP(L_INF,"Deleted packlist %s",p->name);
		p->deleted = xtime();
		validity_delete(p);
	}
	UNLOCK(p);
	UNLOCK(global);
	return 0;
}

packlist_t *packlist_find(const char *name) {
	packlist_t *p = NULL;
	int i;
	LOCK(global);
	for(i = 0; i < global->no_packlists && p == NULL; ++i) {
		LOCK(global->packlists[i]);
		if(strcasecmp(global->packlists[i]->name,name) == 0 
				&& global->packlists[i]->deleted == 0) {
			p = global->packlists[i];
			validity_insert(p);
		}
		UNLOCK(global->packlists[i]);
	}
	UNLOCK(global);
	return p;
}

pack_t *pack_clone(pack_t *p) {
	pack_ref(p);
	return p;
}

static int qsort_packcmp_none(const void *a, const void *b) {
	return 0;
}

static int qsort_packcmp_az(const void *a, const void *b) {
	pack_t *ap = *((pack_t **)a), *bp = *((pack_t **)b);
	return strcmp(ap->label,bp->label);
}

static int qsort_packcmp_za(const void *a, const void *b) {
	pack_t *ap = *((pack_t **)a), *bp = *((pack_t **)b);
	return strcmp(bp->label,ap->label);
}

static int qsort_packcmp_i_az(const void *a, const void *b) {
	pack_t *ap = *((pack_t **)a), *bp = *((pack_t **)b);
	return strcasecmp(ap->label,bp->label);
}

static int qsort_packcmp_i_za(const void *a, const void *b) {
	pack_t *ap = *((pack_t **)a), *bp = *((pack_t **)b);
	return strcasecmp(bp->label,ap->label);
}

typedef int (*packcmp_func_t)(const void *,const void *);

static const packcmp_func_t packcmp[] = {
	qsort_packcmp_none,	/* SORT_NONE = 0 */
	qsort_packcmp_az,	/* SORT_AZ   = 1 */
	qsort_packcmp_za,	/* SORT_ZA   = 2 */
	qsort_packcmp_i_az,	/* SORT_I_AZ = 3 */
	qsort_packcmp_i_za	/* SORT_I_ZA = 4 */
};

static void _packlist_sort(packlist_t *l, int type) {
	int i;
	
	for(i = 0; i < l->no_packs; ++i)
		RLOCK(l->packs[i]);
	qsort(l->packs,l->no_packs,sizeof(pack_t *),packcmp[type]);
	for(i = 0; i < l->no_packs; ++i)
		RUNLOCK(l->packs[i]);
}

void packlist_sort(packlist_t *l, int type) {
	LOCK(l);
	_packlist_sort(l,type);
	UNLOCK(l);
}

static int pack_add(packlist_t *l, pack_t *p) {
	int tmp, req_hash = 0;
	
	LOCK(l);
	
	tmp = l->no_packs;
	PTRARR_ADD(&l->packs,&l->no_packs,p);
	if(l->no_packs == tmp) {
		// was already in the list
		UNLOCK(l);
		return -1;
	}
	
	RLOCK(p);
	LOGP(L_INF,"Added %s to packlist %s",p->file,l->name);
	if(p->hashed == 0)
		req_hash = 1;
	RUNLOCK(p);
	
	if(req_hash)
		hash_request(p);
	
	se_notify_pack_add(0,l,p);
	
	if(l->auto_sort != SORT_NONE)
		_packlist_sort(l,l->auto_sort);
	
	UNLOCK(l);
	
	return 0;
}

int pack_add_file(packlist_t *l, const char *file, const char *label, 
		void *evoid, pack_t **ret) {
	struct stat	*edata = (struct stat *)evoid;
	struct stat 	sdata;
	pack_t 		*p;
	int		i;
	
	LOCK(l);
	if(in_list(l->packs,l->no_packs,NULL,file)) {
		UNLOCK(l);
		return -1;
	} else
		UNLOCK(l);
	
	if(edata == NULL) {
		i = stat(file,&sdata);
		if(i != 0) {
			char errbuf[96];
			LOGP(L_ERR,"Failed to stat \"%s\" during pack add (%s)",
					file,
					lstrerror_r(errno,errbuf,sizeof(errbuf)) == 0 ?
					errbuf : "Unknown");
			return -1;
		}
		edata = &sdata;
	}
	if(!S_ISREG(edata->st_mode)) {
		LOGP(L_ERR,"Failed to add pack \"%s\" (not regular file)",file);
		return -1;
	}
	
	p = pack_cache_get(global->pack_cache,file,PACK_CACHE_CREATE);
	WLOCK(p);
	if(p->size != edata->st_size) {
		p->size	= edata->st_size;
		if(p->hashed) {
			p->hashed = 0;
			xfree(p->md5);
			xfree(p->crc);
			p->md5 = NULL;
			p->crc = NULL;
		}
	}
	if(label != NULL) {
		xfree(p->label);
		p->label = xstrdup(label);
	}
	WUNLOCK(p);
	
	if(pack_add(l,p) == 0) {
		if(ret != NULL) {
			pack_ref(p);
			*ret = p;
		}
		return 0;
	} else {
		free_pack(p);
		return -1;
	}
}

int pack_del(packlist_t *l, pack_t *p) {
	int tmp;
	
	LOCK(l);
	tmp = l->no_packs;
	PTRARR_DEL(&l->packs,&l->no_packs,p);
	if(tmp == l->no_packs) {
		UNLOCK(l);
		return -1;
	}
	
	RLOCK(p);
	LOGP(L_INF,"Deleted %s from packlist %s",p->file,l->name);
	RUNLOCK(p);
	
	se_notify_pack_del(0,l,p);
	UNLOCK(l);

	free_pack(p);
	
	return 0;
}

void packlist_check(packlist_t *l) {
	struct stat 	sdata;
	pqueue_t 	to_check;
	pack_t		*p;
	int 		i,ret;
	
	pqueue_init(&to_check,0);
	
	LOCK(l);
	LOGP(L_INF,"Checking packlist %s",l->name);
	for(i = 0; i < l->no_packs; ++i) {
		pack_ref(l->packs[i]);
		pqueue_push_back(&to_check,l->packs[i]);
	}
	UNLOCK(l);

	while((p = pqueue_pop_front(&to_check)) != NULL) {
		RLOCK(p);
		ret = stat(p->file,&sdata);
		if(ret != 0) {
			// Error so we assume the file is gone
			RUNLOCK(p);
			pack_del(l,p);
		} else {
			if(!S_ISREG(sdata.st_mode)) {
				// File no longer regular?
				RUNLOCK(p);
				pack_del(l,p);
			} else if(p->size != sdata.st_size ||
					sdata.st_mtime > p->hashed) {
				
				if(p->size != sdata.st_size)
					LOGP(L_ERR,"File \"%s\" changed size.",
						p->file);
				else if(p->hashed != 0)
					LOGP(L_ERR,"File \"%s\" was modified since last hash, rehashing.",
						p->file);
				RUNLOCK(p);
				
				WLOCK(p);
				p->size = sdata.st_size;
				p->hashed = 0;
				if(p->md5 != NULL) {
					xfree(p->md5);
					p->md5 = NULL;
				}
				if(p->crc != NULL) {
					xfree(p->crc);
					p->crc = NULL;
				}
				WUNLOCK(p);
				hash_request(p);
			} else {
				RUNLOCK(p);
			}
		}
		pack_deref(p);
	}
}

static int test_filters(const char *file, char **filters, int no_filters) {
	char buffer[2048], *to_test;
	int i,ret;
	
	pathtofn(file,buffer,sizeof(buffer));
	
	for(i = 0, ret = 0; i < no_filters && ret == 0; ++i) {
		if(strchr(filters[i],'/') == NULL)
			to_test = buffer;
		else
			to_test = (char *)file;
		if(filters[i][0] == '-'
				&& fnmatch(filters[i]+1,to_test,0) == 0)
			ret = -1;
		else if(filters[i][0] == '+' 
				&& fnmatch(filters[i]+1,to_test,0) == 0)
			ret = 1;
		else if(fnmatch(filters[i],to_test,0) == 0)
			ret = 1;
	}
		
	return ret;
}

static int qsort_strcmp(const void *a, const void *b) {
	return strcmp(*((char **)a),*((char **)b));
}

static void _scan_dir(packlist_t *l, const char *path, time_t now,
		pack_t **packs, int no_packs, char **filters, int no_filters,
		ino_t *history, int depth) {
	char 		buffer[2048];
	struct dirent	*dent;
	struct stat	sdata;
	DIR		*dir;
	int		i, filter;

	if(stat(path,&sdata) != 0)
		return;
	history[depth] = sdata.st_ino;
	
	dir = opendir(path);
	if(dir == NULL) {
		char errbuf[96];
		LOGP(L_ERR,"Failed to open directory \"%s\" (%s)",
			path,lstrerror_r(errno,errbuf,sizeof(errbuf)));
		return;
	}
	
	while((dent = readdir(dir)) != NULL) {
		if(strcmp(dent->d_name,".") == 0 
				|| strcmp(dent->d_name,"..") == 0)
			continue;
		snprintf(buffer,sizeof(buffer),"%s%s%s",
				path,
				path[strlen(path)-1] != '/' ? "/" : "",
				dent->d_name);
		if(stat(buffer,&sdata) != 0)
			continue;
		if(filters != NULL)
			filter = test_filters(buffer,filters,no_filters);
		else
			filter = 1;
		if(S_ISREG(sdata.st_mode) && filter == 1 
				&& !in_list(packs,no_packs,NULL,buffer)) {
			if(abs(now - sdata.st_mtime) < 5)
				LOGP(L_INF,"Not adding file \"%s\" (modified in last 5 seconds)",buffer);
			else
				pack_add_file(l,buffer,NULL,&sdata,NULL);
		} else if(S_ISDIR(sdata.st_mode) && depth > 0 && filter >= 0) {
			for(i = depth; i < MAX_SCAN_DEPTH; ++i) {
				if(history[i] == sdata.st_ino)
					break;
			}
			if(i != MAX_SCAN_DEPTH)
				continue;
			_scan_dir(l,buffer,now,packs,no_packs,
				filters,no_filters,history,depth-1);
		}
	}

	closedir(dir);
}

void packlist_scan(packlist_t *l, int scan_depth) {
	sorttype_t	auto_sort;
	time_t		now = xtime();
	pack_t		**packs = NULL;
	ino_t		history[MAX_SCAN_DEPTH];
	char		**filters = NULL, **dirs = NULL;
	int		no_packs = 0, no_filters = 0, no_dirs = 0;
	int		i;
	
	packlist_check(l);
	
	/* The main thing to note here is sorting is disabled during the scan
	 * so as to save overhead on many consecutive pack additions.
	 */
	
	
	// Copy all required data
	LOCK(l);
	LOGP(L_INF,"Scanning packlist %s",l->name);
	
	if((auto_sort = l->auto_sort) != SORT_NONE)
		l->auto_sort = SORT_NONE;
	if(scan_depth <= 0)
		scan_depth = l->scan_depth;
	if(scan_depth < 1)
		scan_depth = 1;
	if(scan_depth > MAX_SCAN_DEPTH)
		scan_depth = MAX_SCAN_DEPTH;
	
	packs = PTRARR_DUP(l->packs,l->no_packs,&no_packs);
	for(i = 0; i < no_packs; ++i)
		pack_ref(packs[i]);

	filters = strarr_dup(l->filters,l->no_filters,&no_filters);
	dirs = strarr_dup(l->dirs,l->no_dirs,&no_dirs);
	UNLOCK(l);

	// Do scanning in unlocked state
	for(i = 0; i < no_dirs; ++i)
		_scan_dir(l,dirs[i],now,packs,no_packs,filters,no_filters,
			history,scan_depth-1);
	
	// Free Data
	strarr_free(&dirs,&no_dirs);
	strarr_free(&filters,&no_filters);
	for(i = 0; i < no_packs; ++i)
		pack_deref(packs[i]);
	if(packs != NULL)
		xfree(packs);
	
	// Update list variables, and sort the new packs if required
	LOCK(l);
	l->auto_sort = auto_sort;
	l->last_scan = now;
	if(auto_sort != SORT_NONE)
		_packlist_sort(l,auto_sort);
	UNLOCK(l);
}

void packlists_scan(const time_t now) {
	pqueue_t to_scan;
	packlist_t *p;
	int i;
	
	pqueue_init(&to_scan,0);
	
	LOCK(global);
	for(i = 0; i < global->no_packlists; ++i) {
		packlist_t *l = global->packlists[i];
		LOCK(l);
		if(l->deleted == 0 
			&& test_timer(now,l->last_scan,l->dir_scan_interval))
			pqueue_push_back(&to_scan,l);
		UNLOCK(l);
	}
	UNLOCK(global);
	while((p = pqueue_pop_front(&to_scan)) != NULL)
		packlist_scan(p,-1);
}

int packlist_check_access(packlist_t *l, network_t *net, 
			const char *host, const char *hostmask,
			const char *nick) {
	access_t *access;
	
	LOCK(l);
	if(l->deleted != 0) { // we don't normally check this but here we do
		UNLOCK(l);
		return 0;
	}
	if(l->access == NULL) {
		UNLOCK(l);
		return 1;
	}
	access = l->access;
	UNLOCK(l);
	
	return access_check(access,net,host,hostmask,nick);
}

int packlist_check_access_channel(packlist_t *l, network_t *net, 
				channel_t *chan) {
	access_t *access;

	LOCK(l);
	if(l->deleted != 0) { // we don't normally check this but here we do
		UNLOCK(l);
		return 0;
	}
	if(l->access == NULL) {
		UNLOCK(l);
		return 1;
	}
	access = l->access;
	UNLOCK(l);
	
	return access_check_channel(access,net,chan);
}

long savgspeed(pool_t *p) {
	double total = 0.0;
	int i;
	for(i = 0; i < AVGSPEED; ++i) {
		total += p->avgspeed[i];
	}
	return (long)(total / (double)AVGSPEED);
}

/* append xdl head to queue specified by *q */
void packlist_xdl_head(packlist_t *l, pqueue_t *q, colourtype_t colour, 
			const char *prefix, const char *postfix, int addtail) {
	char buffer[512];
	char bqueue[32],bcap[32],bmin[32],bmax[32],btmp1[16],btmp2[16];
	char *bold,*unbold;
	unsigned long long xfer,size;
	int i;
	
	if(colour == COLOUR_IRC)
		bold = unbold = IRC_BOLD;
	else if(colour == COLOUR_INT)
		bold = unbold = COLOUR_BOLD;
	else
		bold = unbold = "";	
	
	LOCK(l);
	LOCK(l->queue);
	LOCK(l->queue->pool);
	if(l->queue->no_qsends > 0)
		snprintf(bqueue,sizeof(bqueue),
			"Queue: %d/%d, ",
			l->queue->no_qsends, l->queue->length);
	else
		strcpy(bqueue,"");
	if(l->queue->pool->bandwidth != 0)
		snprintf(bcap,sizeof(bcap),
			"Cap: %ldKiB/sec, ",
			(long)l->queue->pool->bandwidth);
	else
		strcpy(bcap,"");
	if(l->queue->pool->send_min_bandwidth != 0)
		snprintf(bmin,sizeof(bmin),
			"Min: %ldKiB/sec, ",
			(long)l->queue->pool->send_min_bandwidth);
	else
		strcpy(bmin,"");
	if(l->queue->pool->send_max_bandwidth != 0)
		snprintf(bmax,sizeof(bmax),
			"Max: %ldKiB/sec, ",
			(long)l->queue->pool->send_max_bandwidth);
	else
		strcpy(bmax,"");
	
		
	snprintf(buffer,sizeof(buffer),
		"%s%s**%s %d packs %s**%s %d of %d slots open, %s%s%sRecord: %ldKiB/s %s**%s%s",
		prefix,
		bold,unbold,l->no_packs,bold,unbold,
		l->queue->pool->sends - l->queue->pool->no_csends > 0 ?
		l->queue->pool->sends - l->queue->pool->no_csends : 0,
		l->queue->pool->sends,
		bqueue,bmin,bmax,
		(long)(l->queue->pool->record_send / 1024L),
		bold,unbold,
		postfix
	);
	pqueue_push_back(q,xstrdup(buffer));
	snprintf(buffer,sizeof(buffer),
		"%s%s**%s Bandwidth Usage %s**%s Current: %ldKiB/s, %sRecord: %ldKiB/s %s**%s%s",
		prefix,
		bold,unbold,bold,unbold,
		(long)(savgspeed(l->queue->pool)	/ 1024L),
		bcap,
		(long)(l->queue->pool->record_rate	/ 1024L),
		bold,unbold,
		postfix
	);
	pqueue_push_back(q,xstrdup(buffer));
	xfer = ((unsigned long long)l->queue->pool->stat_mib)*(1024*1024) +
		(unsigned long long)l->queue->pool->stat_carry;
	UNLOCK(l->queue->pool);
	UNLOCK(l->queue);
	
	for(i = 0, size = 0; i < l->no_packs; ++i) {
		RLOCK(l->packs[i]);
		size += (unsigned long long) l->packs[i]->size;
		RUNLOCK(l->packs[i]);
	}
	snprintf(buffer,sizeof(buffer),
		"%s%s**%s Total Offered: %s, Total Transferred: %s %s**%s%s",
		prefix,bold,unbold,
		format_bytes(btmp1,sizeof(btmp1),size,1),
		format_bytes(btmp2,sizeof(btmp2),xfer,1),
		bold,unbold,postfix);
	pqueue_push_back(q,xstrdup(buffer));
			
	if(addtail) {
		snprintf(buffer,sizeof(buffer),
			"%s%s**%s To request a file type: \"/msg $BOT xdcc send %s #x\" %s**%s%s",
			prefix,bold,unbold,l->name,bold,unbold,postfix
		);
		pqueue_push_back(q,xstrdup(buffer));
	}
	UNLOCK(l);
}

void packlist_xdl_body(packlist_t *l, pqueue_t *q, colourtype_t colour,
				const char *prefix, const char *postfix) {
	char buffer1[512],buffer2[512],buffer3[16];
	char *ptr;
	size_t len;
	int i,pos;
	
	LOCK(l);
	for(i = 0; i < l->no_packs; ++i) {
		RLOCK(l->packs[i]);
		pos = IMIN(
			snprintf(buffer1,sizeof(buffer1),
				"%s%%9#%-3d%%9 %3dx [%s] %s",
				prefix,i+1,
				l->packs[i]->no_gets,
				three_digit_size(buffer3,l->packs[i]->size),
				l->packs[i]->label),
			sizeof(buffer1)-1
		);
		if(l->packs[i]->hashed && l->hash_display != HASH_NONE) {
			if(l->hash_display == HASH_CRC) {
				snprintf(buffer1+pos,sizeof(buffer1)-pos,
					" [CRC:%8s]%s",
					l->packs[i]->crc,
					postfix
				);
			} else if(l->hash_display == HASH_MD5) {
				snprintf(buffer1+pos,sizeof(buffer1)-pos,
					" [MD5:%32s]%s",
					l->packs[i]->md5,
					postfix
				);
			} else {
				snprintf(buffer1+pos,sizeof(buffer1)-pos,
					" [CRC:%8s] [MD5:%32s]%s",
					l->packs[i]->crc,l->packs[i]->md5,
					postfix
				);
			}
		} else {
			snprintf(buffer1+pos,sizeof(buffer1)-pos,"%s",postfix);
		}
		RUNLOCK(l->packs[i]);

		len = colourise(colour,buffer2,sizeof(buffer2),buffer1);
		ptr = xalloc(len+1);
		memcpy(ptr,buffer2,len+1);
		pqueue_push_back(q,ptr);
	}
	UNLOCK(l);
}

static int do_taglines(pqueue_t *q, colourtype_t colour,
		const char *name,
		char **lines, int no_lines,
		const char *prefix, const char *postfix) {
	char buffer1[512];
	char buffer2[512];
	int i,pos;
	if(lines == NULL || no_lines <= 0)
		return 0;
	
	for(i = 0; i < no_lines; ++i) {
		pos = snprintf(buffer1,sizeof(buffer1),
				"%s%s**%s ",prefix,COLOUR_BOLD,COLOUR_BOLD);
		pos += xstrncpy_rpl(buffer1+pos,lines[i],
				sizeof(buffer1)-pos,
				"$LIST",name);
		snprintf(buffer1+pos,sizeof(buffer1)-pos,
				" %s%s**%s%s",
				COLOUR_OFF,COLOUR_BOLD,COLOUR_BOLD,postfix);
		colourise(colour,buffer2,sizeof(buffer2),buffer1);
		pqueue_push_back(q,xstrdup(buffer2));
	}
	
	return 1;
}

void packlist_xdl_tail(packlist_t *l, pqueue_t *q, colourtype_t colour,
		network_t *net, channel_t *chan, 
		const char *prefix, const char *postfix) {
	char name[128];
	int done = 0;

	LOCK(l);
	xstrncpy(name,l->name,sizeof(name));
	UNLOCK(l);
	
	if(chan != NULL) {
		LOCK(chan);
		done = do_taglines(q,colour,name,
				chan->taglines,chan->no_taglines,
				prefix,postfix);
		UNLOCK(chan);
	}
	if(!done && l != NULL) {
		LOCK(l);
		done = do_taglines(q,colour,name,
				l->taglines,l->no_taglines,
				prefix,postfix);
		UNLOCK(l);
	}
	if(!done && net != NULL) {
		LOCK(net);
		done = do_taglines(q,colour,name,
				net->settings->taglines,
				net->settings->no_taglines,
				prefix,postfix);
		UNLOCK(net);
	}
	if(!done) {
		LOCK(global);
		done = do_taglines(q,colour,name,
				global->network_settings->taglines,
				global->network_settings->no_taglines,
				prefix,postfix);
		UNLOCK(global);
	}
}

int pack_ref(pack_t *p) {
	int ret;
	WLOCK(p);
	ret = ++p->ref_count;
	WUNLOCK(p);
	return ret;
}

int pack_deref(pack_t *p) {
	int ret;
	if(pack_cache_present(global->pack_cache,p)) {
		ret = pack_cache_deref(global->pack_cache,p);
	} else {
		WLOCK(p);
		ret = --p->ref_count;
		WUNLOCK(p);
	}
	if(ret == 0)
		free_pack_real(p);
	return ret;
}

int packlist_valid(packlist_t *l) {
	int i;

	if(l == NULL)
		return 0;

	if(validity_test(l))
		return 1;

	LOCK(global);
	for(i = 0; i < global->no_packlists; ++i) {
		if(global->packlists[i] == l) {
			LOCK(l);
			i = l->deleted;
			if(l->deleted == 0)
				validity_insert(l);
			UNLOCK(l);
			UNLOCK(global);
			if(i == 0) // l->deleted == 0
				return 1;
			else
				return 0;
		}
	}
	UNLOCK(global);

	return 0;
}

int pack_valid(packlist_t *l, pack_t *p) {
	if(l == NULL || p == NULL)
		return 0;
	if(packlist_valid(l)) {
		int i;
		LOCK(l);
		for(i = 0; i < l->no_packs; ++i)
			if(l->packs[i] == p) {
				pack_ref(p);
				UNLOCK(l);
				return 1;
			}
		UNLOCK(l);
	}
	return 0;
}

int packlist_rename(packlist_t *l, const char *name) {
	int i,ret = 0;

	if(l == NULL || name == NULL)
		return -1;

	LOCK(global);
	for(i = 0; i < global->no_packlists && ret == 0; ++i) {
		LOCK(global->packlists[i]);
		if(global->packlists[i]->deleted == 0 &&
				
				strcasecmp(global->packlists[i]->name,name) == 0)
			ret = -1;
		UNLOCK(global->packlists[i]);
	}
	if(ret == 0) {
		LOCK(l);
		if(l->name != NULL)
			xfree(l->name);
		l->name = xstrdup(name);
		UNLOCK(l);
	}
	UNLOCK(global);

	return ret;
}


void packlist_plist_generate(packlist_t *l, pqueue_t *pq, 
		pformat_t pformat, colourtype_t colour_type, 
		network_t *net, channel_t *chan,
		const char *prefix, const char *postfix) {
	char buffer[512];
	
	switch (pformat) {
		case PFORMAT_MINIMAL:
			LOCK(l);
			snprintf(buffer,sizeof(buffer),
				"%s**%s XDCC %s**%s Packlist: %s\r\n",
				IRC_BOLD,IRC_BOLD,
				IRC_BOLD,IRC_BOLD,
				l->name);
			UNLOCK(l);
			pqueue_push_back(pq,xstrdup(buffer));
			packlist_xdl_body(l,pq,colour_type,prefix,postfix);
			break;
		case PFORMAT_SUMMARY:
			packlist_xdl_head(l,pq,colour_type,prefix,postfix,0);
			LOCK(l);
			snprintf(buffer,sizeof(buffer),
				"%s**%s For a Listing: \"/msg $BOT XDCC LIST %s\" %s**%s",
				IRC_BOLD,IRC_BOLD,
				l->name,
				IRC_BOLD,IRC_BOLD);
			UNLOCK(l);
			pqueue_push_back(pq,xstrdup(buffer));
			packlist_xdl_tail(l,pq,colour_type,net,chan,
					prefix,postfix);
			break;
		case PFORMAT_FULL:
			packlist_xdl_head(l,pq,colour_type,prefix,postfix,1);
			packlist_xdl_body(l,pq,colour_type,prefix,postfix);
			packlist_xdl_tail(l,pq,colour_type,net,chan,
					prefix,postfix);
			break;
		case PFORMAT_TAGONLY:
			packlist_xdl_tail(l,pq,colour_type,net,chan,
					prefix,postfix);
			break;
	}
}

void packlist_xdl_generate(packlist_t *l, pqueue_t *pq,
		listtype_t type, colourtype_t colour_type,
		network_t *net, channel_t *chan,
		const char *prefix, const char *postfix) {
	if(type == LIST_CHAT)
		return;
	
	if(type == LIST_NORMAL)
		packlist_xdl_head(l,pq,colour_type,prefix,postfix,1);
	else if(type == LIST_NOPACKS)
		packlist_xdl_head(l,pq,colour_type,prefix,postfix,0);
	if(type == LIST_NORMAL)
		packlist_xdl_body(l,pq,colour_type,prefix,postfix);

	packlist_xdl_tail(l,pq,colour_type,net,chan,prefix,postfix);
}

/** Settings Data **/
static setting_t packlist_data[] = {
	{"auto-sort",		ST_ENUM,OFFSETOF(packlist_t,auto_sort),
		0,NULL,NULL,sorttype_to_str,str_to_sorttype},
	{"command-limit",	ST_INT,	OFFSETOF(packlist_t,command_limit),
		0,"0",NULL,NULL,NULL},
	{"dir-scan-interval",	ST_UINT,OFFSETOF(packlist_t,dir_scan_interval),
		0,"0",NULL,NULL,NULL},
	{"hash-display",	ST_ENUM,OFFSETOF(packlist_t,hash_display),
		0,NULL,NULL,hashtype_to_str,str_to_hashtype},
	{"response",		ST_ENUM,OFFSETOF(packlist_t,response),
		0,NULL,NULL,responsetype_to_str,str_to_responsetype},
	{"retries",		ST_INT,	OFFSETOF(packlist_t,retries),
		0,"0",NULL,NULL,NULL},
	{"scan-depth",		ST_INT,OFFSETOF(packlist_t,scan_depth),
		0,"1",MAX_SCAN_DEPTH_STR,NULL,NULL},
	{"time-limit",		ST_INT,	OFFSETOF(packlist_t,time_limit),
		0,"0",NULL,NULL,NULL},
	{"type",		ST_ENUM,OFFSETOF(packlist_t,type),
		0,NULL,NULL,listtype_to_str,str_to_listtype},
	{NULL}
};

int packlist_apply_setting(packlist_t *p, const char *name, const char *value) {
	value_t *v = value_string(name,value);
	int ret;
	LOCK(p);
	ret = apply_setting(packlist_data,p,v);
	UNLOCK(p);
	xfree(v);
	return ret;
}

int packlist_read_settings(packlist_t *p, pqueue_t *out) {
	int ret;
	LOCK(p);
	ret = read_settings(packlist_data,p,out);
	UNLOCK(p);
	return ret;
}
