/* ARISA - XDCC 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 <ctype.h>

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

void xdcc_info(network_t *net, channel_t *chan, 
		const char *host, const char *hostmask, const char *nick) {
	char 		**names = NULL, **msgs;
	pqueue_t 	to_check;
	packlist_t	*p;
	int		i,j, no_names = 0;
	time_t		now = xtime();

	//D("net: %p, chan: %p, host: \"%s\", nick: \"%s\"",
	//		net,chan,host,nick);
	
	if(nick == NULL)
		return;
	
	pqueue_init(&to_check,0);
	
	LOCK(global);
	for(i = 0; i < global->no_packlists; ++i) {
		LOCK(global->packlists[i]);
		if(global->packlists[i]->deleted == 0)
			pqueue_push_back(&to_check,global->packlists[i]);
		UNLOCK(global->packlists[i]);
	}
	UNLOCK(global);
	
	i = 0;
	while((p = pqueue_pop_front(&to_check)) != NULL) {
		LOCK(p);
		if(p->deleted != 0 || p->nolist > now) {
			UNLOCK(p);
			continue;
		}
		UNLOCK(p);
		if(packlist_check_access(p,net,host,hostmask,nick)) {
			LOCK(p);
			PTRARR_ADD(&names,&no_names,xstrdup(p->name));
			UNLOCK(p);
			i++;
		}
	}
	if(i == 0) // don't bothered
		return;

	LOCK(net);
	msgs = xdcc_info_msgs(names,no_names,&j,net->settings->info_line);
	UNLOCK(net);
	
	
	strarr_free(&names,&no_names);
	for(i = 0; i < j; ++i) {
		irc_send_notice(net,nick,msgs[i]);
		xfree(msgs[i]);
	}
	if(msgs != NULL)
		xfree(msgs);
}

char **xdcc_info_msgs(char **names, int no_names, int *no_ret, 
		const char *info) {
	char buffer[512],info_line[382],**ret = NULL;
	int pos = 0, info_size = 0;
	int i,j;
	
	*no_ret = 0;

	if(info != NULL) {
		colourise(COLOUR_IRC,info_line,sizeof(info_line),info);
		info_size = strlen(info_line);
	}
	
	if(no_names > 1)
		qsort(names,no_names,sizeof(char *),qsort_strcmp);
	
	for(j = 0; j < no_names; j = i) {
		pos = IMIN(
			snprintf(buffer,sizeof(buffer),
				"%s(%sXDCC%s)%s Trigger:%s(%s/msg $BOT xdcc list <list name>%s)%s Lists:%s(%s",
				IRC_BOLD,IRC_BOLD,IRC_BOLD,IRC_BOLD,
				IRC_BOLD,IRC_BOLD,IRC_BOLD,IRC_BOLD,
				IRC_BOLD,IRC_BOLD),
			sizeof(buffer)-1
		);
		for(i = j; i < no_names && (pos < sizeof(buffer) - 100); ++i) {
			pos += IMIN(
				snprintf(buffer+pos,sizeof(buffer)-pos,
					"%s%s",
					(i != j ? ", " : ""),names[i]),
				sizeof(buffer)-pos-1
			);
		}
		if(i == no_names) {
			if(info_size == 0 || 
				(pos + info_size) > (sizeof(buffer) - 100)) {
				snprintf(buffer+pos,sizeof(buffer)-pos,	
					"%s)%s =arisa=",IRC_BOLD,IRC_BOLD);
			} else {
				snprintf(buffer+pos,sizeof(buffer)-pos,
					"%s)%s Info:%s(%s%s%s)%s =arisa=",
					IRC_BOLD,IRC_BOLD,IRC_BOLD,IRC_BOLD,
					info_line,
					IRC_BOLD,IRC_BOLD
				);
			}
		}
		PTRARR_ADD(&ret,no_ret,xstrdup(buffer));
	}
	
	if(info_size != 0 && (pos + info_size) > (sizeof(buffer)-100)) {
		snprintf(buffer,sizeof(buffer),
			"%s(%sXDCC%s)%s Info:%s(%s%s%s)%s =arisa=",
			IRC_BOLD,IRC_BOLD,IRC_BOLD,IRC_BOLD,
			IRC_BOLD,IRC_BOLD,
			info_line,
			IRC_BOLD,IRC_BOLD
		);
		PTRARR_ADD(&ret,no_ret,xstrdup(buffer));
	}
	
	return ret;
}

void xdcc_remove(network_t *net, const char *nick, const char *label) {
	/* Here we use a 256 buffer, so if someone sends a maliciously long 
	   label it won't affect any IRC things below. */
	char buffer[256]; 
	int rem = 0;
	rem = queue_remove(NULL,NULL,net,nick,NULL,label,NULL);
	LOCK(net);
	if(rem == 1 && label != NULL) {
		LOGP(L_XDC,"XDCC REMOVE by [%s:%s] Success: for %s",
				net->name,nick,label);
		snprintf(buffer,sizeof(buffer),
				"Removed queued send for %s",label);
	} else if(rem > 0) {
		LOGP(L_XDC,"XDCC REMOVE by [%s:%s] Success: %d queued sends removed",
				net->name,nick,rem);
		snprintf(buffer,sizeof(buffer),
				"Removed %d queued sends",rem);
	} else if(label != NULL) {
		LOGP(L_XDC,"XDCC REMOVE by [%s:%s] Failed: for %s",
				net->name,nick,label);
		snprintf(buffer,sizeof(buffer),
				"You have no queued sends for %s",label);
	} else {
		LOGP(L_XDC,"XDCC REMOVE by [%s:%s] Failed: no queued sends",
				net->name,nick);
		snprintf(buffer,sizeof(buffer),
				"You have no queued sends to remove");
	}
	UNLOCK(net);
	irc_send_notice(net,nick,buffer);
}

void xdcc_list(network_t *net, packlist_t *list, 
		const char *host, const char *hostmask, const char *nick) {
	char buffer[512];
	char list_name[128], nolistr[256], *p;
	time_t nolist, now = xtime();
	responsetype_t response;
	listtype_t list_type;

	LOCK(list);
	xstrncpy(list_name,list->name,sizeof(list_name));
	nolist 		= list->nolist;
	xstrncpy(nolistr,list->nolist_reason != NULL ? list->nolist_reason : "",
		sizeof(nolistr));
	list_type 	= list->type;
	response	= list->response;
	UNLOCK(list);
	
	if(!packlist_check_access(list,net,host,hostmask,nick)) {
		LOCK(net);
		LOGP(L_XDC,"XDCC LIST of %s to [%s:%s] Denied: Insufficient Access",
				list_name,net->name,nick);
		UNLOCK(net);
		xdcc_info(net,NULL,host,hostmask,nick);
		//snprintf(buffer,sizeof(buffer),"Invalid list: %s",list_name);
		//irc_send_notice(net,nick,buffer);
		return;
	}
	
	if(nolist >= now) {
		LOCK(net);
		LOGP(L_XDC,"XDCC LIST of %s to [%s:%s] Denied: Listing Disabled",
				list_name,net->name,nick);
		UNLOCK(net);
		snprintf(buffer,sizeof(buffer),
			"XDCC LIST is presently disabled for list: %s%s%s%s",
			list_name,
			nolistr[0] != '\0' ? " (" : "",
			nolistr,
			nolistr[0] != '\0' ? ")" : "");
		irc_send_notice(net,nick,buffer);
		return;
	}
		
	LOCK(list);
	if(ignore_test(list->ignore_list,hostmask)) {
		UNLOCK(list);
		LOCK(net);
		LOGP(L_XDC,"XDCC LIST of %s to [%s:%s] Denied: By Ignore List",
				list_name,net->name,nick);
		UNLOCK(net);
		LOCK(list);
		LOCK(list->ignore_list);
		snprintf(buffer,sizeof(buffer),
			"You may not list %s right now; either you have exceeded the list frequency of %d list(s) per %d seconds, or the operator has explicitly Denied you from listing this packlist.",
			list_name,
			list->ignore_list->trigger,
			list->ignore_list->period);
		UNLOCK(list->ignore_list);
		UNLOCK(list);
		irc_send_notice(net,nick,buffer);
		return;
	}
	UNLOCK(list);
			
	if(list_type != LIST_CHAT) {
		pqueue_t listing;
		
		pqueue_init(&listing,0);
		packlist_xdl_generate(list,&listing,list_type,COLOUR_IRC,
				net,NULL,"","");
		LOCK(net);
		LOGP(L_XDC,"XDCC LIST of %s sent to [%s:%s]",
				list_name,net->name,nick);
		UNLOCK(net);
		while((p = pqueue_pop_front(&listing)) != NULL) {
			if(response == RESPONSE_NOTICE)
				irc_send_notice(net,nick,p);
			else if(response == RESPONSE_PRIVMSG)
				irc_send_msg(net,nick,p);
			xfree(p);
		}
	} else if(list_type == LIST_CHAT) {
		if(chat_list_open(net,host,hostmask,nick,list) != 0) {
			LOCK(net);
			LOGP(L_XDC,"XDCC LIST of %s to [%s:%s] Failed: No Chat Slots available",
					list_name,net->name,nick);
			UNLOCK(net);
			irc_send_notice(net,nick,"Sorry, there are no available DCC chat slots available at this time.");
		} /* chat_list_open logs the initialization of a chat */
	}
}

static int pick_pack(packlist_t *list, const char *pack, int *matches_ptr) {
	int i, matches, matched, len;
	
	if(pack[0] == '#') {
		for(i = 1; pack[i] != '\0'; ++i) {
			if(!(isdigit(pack[i]) || isspace(pack[i])))
				break;
		}
		if(pack[i] == '\0') {
			matched = atoi(pack+1);
			return matched > 0 ? matched : 0;
		}
	}
		
	/* Cut any white space off the end of the identifier, and find the
	 * length for partial matching.
	 */
	
	for(len = strlen(pack) - 1; len > 0; --len) {
		if(!(isspace(pack[len])))
			break;
	}
	len += 1;
	
	for(i = 0, matches = 0, matched = 0; i < list->no_packs; ++i) {
		pack_t *p = list->packs[i];
		RLOCK(p);
		if(strncasecmp(p->label,pack,len) == 0) {
			matched = i+1;
			matches++;
		} else if(p->hashed && (len == 8 || len == 32)) {
			if(strncasecmp(p->crc,pack,IMAX(8,len)) == 0 ||
					strncasecmp(p->md5,pack,32) == 0) {
				matched = i+1;
				matches++;
			}
		}
		RUNLOCK(p);
	}

	if(matches > 1) {
		if(matches_ptr != NULL)
			*matches_ptr = matches;
		matched = atoi(pack);
		matched = matched > 0 ? matched : -1;
	} else if(matches == 0) {
		matched = atoi(pack);
		matched = matched > 0 ? matched : 0;
	}

	return matched;
}

void xdcc_send(network_t *net, packlist_t *list, 
		const char *host, const char *hostmask,
		const char *nick, const char *pack) {
	char	buffer[512];
	char 	list_name[128], nosendr[256], *p;
	time_t 	nosend, now = xtime();
	pack_t 	*spack;
	send_t	*ssend;
	queue_t	*queue;
	int 	pack_no,tmp,retries;

	if(pack == NULL)
		return;
	
	LOCK(list);
	xstrncpy(list_name,list->name,sizeof(list_name));
	nosend = list->nosend;
	xstrncpy(nosendr,list->nosend_reason != NULL ? list->nosend_reason : "",
			sizeof(nosendr));
	UNLOCK(list);
	
	if(!packlist_check_access(list,net,host,hostmask,nick)) {
		LOCK(net);
		LOGP(L_XDC,"XDCC SEND from %s to [%s:%s] Denied: Insufficient Access",
				list_name,net->name,nick);
		UNLOCK(net);
		snprintf(buffer,sizeof(buffer),"Invalid list: %s",list_name);
		irc_send_notice(net,nick,buffer);
		return;
	}
	
	if(nosend >= now) {
		LOCK(net);
		LOGP(L_XDC,"XDCC SEND from %s to [%s:%s] Denied: Sending Disabled",
				list_name,net->name,nick);
		UNLOCK(net);
		snprintf(buffer,sizeof(buffer),
			"XDCC SEND is presently disabled for list: %s%s%s%s",
			list_name,
			nosendr[0] != '\0' ? " (" : "",
			nosendr,
			nosendr[0] != '\0' ? ")" : "");
		irc_send_notice(net,nick,buffer);
		return;
	}
	
	LOCK(list);
	if((pack_no = pick_pack(list,pack,&tmp)) == -1) {
		UNLOCK(list);
		LOCK(net);
		LOGP(L_XDC,"XDCC SEND from %s to [%s:%s] Denied: Ambiguous Pack \"%s\"",
				list_name,net->name,nick,pack);
		UNLOCK(net);
		snprintf(buffer,sizeof(buffer),
			"The identifier \"%s\" matches %d packs in the list %s. Please use a more specific identifier.",
			pack,tmp,list_name);
		irc_send_notice(net,nick,buffer);
		return;
	}
		
	if(pack_no < 1 || pack_no > list->no_packs) {
		UNLOCK(list);
		LOCK(net);
		LOGP(L_XDC,"XDCC SEND from %s to [%s:%s] Denied: Invalid Pack \"%s\"",
				list_name,net->name,nick,pack);
		UNLOCK(net);
		snprintf(buffer,sizeof(buffer),
				"\"%s\" is not a valid pack for list: %s.",
				pack,list_name);
		irc_send_notice(net,nick,buffer);
		return;
	}

	spack = pack_clone(list->packs[pack_no - 1]);
	queue = list->queue;
	retries = list->retries;
	
	UNLOCK(list);
	
	ssend 		= alloc_send();
	ssend->pack 	= spack;
	ssend->network 	= net;
	ssend->host	= xstrdup(host);
	ssend->nick 	= xstrdup(nick);
	ssend->queue	= queue;
	ssend->retries	= retries;
	
	pack_ref(spack);
	tmp = queue_enqueue(queue,ssend,0,&p);
	if(tmp == 0) {
		LOCK(net);
		LOGP(L_XDC,"XDCC SEND %s:%d to [%s:%s]",
				list_name,pack_no,net->name,nick);
		UNLOCK(net);
		/* No need to notify user since the send will be active
		 * in a moment, or so we hope.
		 */
	} else if(tmp > 0) {
		LOCK(net);
		LOGP(L_XDC,"XDCC SEND %s:%d queued for [%s:%s], postion: %d",
			list_name,pack_no,net->name,nick,tmp);
		UNLOCK(net);
		RLOCK(spack);
		snprintf(buffer,sizeof(buffer),
			"You are queued in position %d for \"%s\", to remove this queue type \"/msg $BOT XDCC REMOVE %s\".  To check the status of your queues on this list type \"/msg $BOT XDCC QUERY\".  This send will be attempted %d time%s.",
			tmp,
			spack->label,spack->label,
			retries+1,
			(retries+1) > 1 ? "s" : "");
		RUNLOCK(spack);
		irc_send_notice(net,nick,buffer);
	} else {
		LOCK(net);
		LOGP(L_XDC,"XDCC SEND %s:%d to [%s:%s] Denied: %s",
				list_name,pack_no,
				net->name,nick,p);
		UNLOCK(net);
		
		RLOCK(spack);
		snprintf(buffer,sizeof(buffer),
				"Send of \"%s\" Denied: %s",
				spack->label,p);
		RUNLOCK(spack);
		free_send(ssend);
		irc_send_notice(net,nick,buffer);
	}
	pack_deref(spack);
}

void xdcc_query(network_t *net, packlist_t *list, 
		const char *host, const char *hostmask, const char *nick) {
	char 		buffer[512];
	char 		list_name[128], *p;
	queue_t		*q;
	pqueue_t	queues;
	int		i,j;

	pqueue_init(&queues,0);
	
	if(list != NULL) {
		LOCK(list);
		xstrncpy(list_name,list->name,sizeof(list_name));
		UNLOCK(list);
		if(!packlist_check_access(list,net,host,hostmask,nick)) {
			LOCK(net);
			LOGP(L_XDC,"XDCC QUERY for %s by [%s:%s] Denied: Insufficient Access",
					list_name,net->name,nick);
			UNLOCK(net);
			snprintf(buffer,sizeof(buffer),"Invalid list: %s",list_name);
			irc_send_notice(net,nick,buffer);
			return;
		}
		
		LOCK(list);
		q = list->queue;
		UNLOCK(list);
		
		LOCK(q);
		for(i = 0; i < q->no_qsends; ++i) {
			LOCK(q->qsends[i]);
			if(q->qsends[i]->network == net && 
				irc_strcasecmp(q->qsends[i]->nick,nick) == 0) {
				RLOCK(q->qsends[i]->pack);
				snprintf(buffer,sizeof(buffer),
					"You are queued for \"%s\", present position %d of %d, \"/msg $BOT XDCC REMOVE %s\" to remove this queue.  This queue has %d attempt%s remaining.",
					q->qsends[i]->pack->label,
					i+1,q->no_qsends,
					q->qsends[i]->pack->label,
					q->qsends[i]->retries+1,
					(q->qsends[i]->retries+1) > 0 ? "s":""
				);
				RUNLOCK(q->qsends[i]->pack);
				pqueue_push_back(&queues,xstrdup(buffer));
			}
			UNLOCK(q->qsends[i]);
		}
		UNLOCK(q);
	} else {
		LOCK(global);
		for(i = 0; i < global->no_queues; ++i) {
			q = global->queues[i];
			LOCK(q);
			for(j = 0; j < q->no_qsends; ++j) {
				LOCK(q->qsends[j]);
				if(q->qsends[j]->network == net &&
					irc_strcasecmp(q->qsends[j]->nick,nick) == 0) {
					RLOCK(q->qsends[j]->pack);
					snprintf(buffer,sizeof(buffer),
						"You are queued for \"%s\", present position %d of %d, \"/msg $BOT XDCC REMOVE %s\" to remove this queue.  This queue has %d attempt%s remaining.",
						q->qsends[j]->pack->label,
						j+1,q->no_qsends,
						q->qsends[j]->pack->label,
						q->qsends[j]->retries+1,
						(q->qsends[j]->retries+1) > 0 ? 
						 "s":""
					);
					RUNLOCK(q->qsends[j]->pack);
					pqueue_push_back(&queues,xstrdup(buffer));
				}
				UNLOCK(q->qsends[j]);
			}
			UNLOCK(q);
		}
		UNLOCK(global);
	}
	
	LOCK(net);
	LOGP(L_XDC,"XDCC QUERY by [%s:%s], %d matchs",
			net->name,nick,pqueue_length(&queues));
	UNLOCK(net);
	
	if(pqueue_length(&queues) == 0) {
		irc_send_notice(net,nick,"No queues found");
	} else {
		while((p = pqueue_pop_front(&queues)) != NULL) {
			irc_send_notice(net,nick,p);
			xfree(p);
		}
	}
}

void xdcc_admin(network_t *net, const char *host, const char *hostmask, 
		const char *nick, const char *data) {
	char buffer[512];
	int i,match;
	user_t *user;
	
	if(data == NULL)
		user = NULL;
	else
		user = user_find(data);

	if(user == NULL) {
		LOCK(net);
		LOGP(L_ERR,"XDCC ADMIN to [%s:%s] Denied: Invalid User",
					net->name,nick);
		UNLOCK(net);
		snprintf(buffer,sizeof(buffer),
			"You must specify a valid user to perform XDCC ADMIN");
		irc_send_notice(net,nick,buffer);
		return;
	}
	
	LOCK(user);
	for(i = 0, match = 0; i < user->no_hosts && !match; ++i) {
		if(irc_hostmask_match(host,user->hosts[i]))
			match = 1;
	}
	UNLOCK(user);
	
	if(!match) {
		LOCK(net);
		LOG(L_ERR,"XDCC ADMIN to [%s:%s] Denied: Invalid Host: %s",
				net->name,nick,host);
		UNLOCK(net);
		snprintf(buffer,sizeof(buffer),
			"Your host (%s) is not permitted to perform XDCC ADMIN",
			host);
		irc_send_notice(net,nick,buffer);
	} else {
		admin_open(-1,net,host,nick,user);
	}
}

void xdcc_cmd(network_t *net, const char *host, const char *hostmask, 
		const char *nick, const char *cmd) {
	char buffer[512], obuffer[256];
	char **tokens, *lcmd = NULL, *list_name = NULL, *data = NULL;
	packlist_t *list = NULL;
	int no_tokens;
	
	xstrncpy(buffer,cmd,sizeof(buffer));
	irc_strip_colours(buffer);

	no_tokens	= 3;
	tokens		= split_opt(buffer,buffer,&no_tokens);
	lcmd		= no_tokens >= 2 ? tokens[1] : NULL;
	data		= no_tokens >= 3 ? tokens[2] : NULL;
	
	if(tokens != NULL)
		xfree(tokens);
	if(no_tokens < 2)
		return;
	
	if(irc_strcasecmp(lcmd,"list") == 0 
			|| irc_strcasecmp(lcmd,"query") == 0) {
		list_name = data;
	} else if(irc_strcasecmp(lcmd,"send") == 0 && data != NULL) {
		no_tokens	= 2;
		tokens		= split_opt(data,data,&no_tokens);
		list_name	= no_tokens >= 1 ? tokens[0] : NULL;
		data		= no_tokens >= 2 ? tokens[1] : NULL;
		if(tokens != NULL)
			xfree(tokens);
	}	
	
	if(list_name != NULL)
		list = packlist_find(list_name);

	if(irc_strcasecmp(lcmd,"list") == 0) {
		if(list != NULL)
			xdcc_list(net,list,host,hostmask,nick);
		else
			xdcc_info(net,NULL,host,hostmask,nick);
	} else if(irc_strcasecmp(lcmd,"send") == 0) {
		if(list != NULL)
			xdcc_send(net,list,host,hostmask,nick,
					(data == NULL ? "0" : data));
		else {
			LOCK(net);
			LOGP(L_XDC,
				"XDCC SEND to [%s:%s] Denied: Invalid List",
				net->name,nick);
			UNLOCK(net);
			if(list_name != NULL)
				snprintf(obuffer,sizeof(obuffer),
						"Invalid list: %s",list_name);
			else
				snprintf(obuffer,sizeof(obuffer),
						"Invalid list");
			irc_send_notice(net,nick,obuffer);
		}
	} else if(irc_strcasecmp(lcmd,"remove") == 0) {
		xdcc_remove(net,nick,data);
	} else if(irc_strcasecmp(lcmd,"query") == 0) {
		xdcc_query(net,list,host,hostmask,nick);
	} else if(irc_strcasecmp(lcmd,"info") == 0) {
		xdcc_info(net,NULL,host,hostmask,nick);
	} else if(irc_strcasecmp(lcmd,"admin") == 0) {
		xdcc_admin(net,host,hostmask,nick,data);
	} else {
		LOCK(net);
		LOGP(L_XDC,"Invalid XDCC command %s from [%s:%s]",
				lcmd,net->name,nick);
		UNLOCK(net);
		snprintf(obuffer,sizeof(obuffer),
			"Invalid XDCC command \"%s\"",lcmd);
		irc_send_notice(net,nick,obuffer);
	}
}
