/* ARISA - Admin Interface
 * 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"
#include "admin.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 "admin-interface.h"

/*** Global Variables ***/
menu_t   *global_menu_root;
menu_t   *main_menu_root;

/*** Menu Functions ***/

void free_menu(menu_t *m) {
	LOCK_FREE(&m->lock);
	
	if(m->name != NULL)
		xfree(m->name);
	
	if(m->type == ADMIN_MENU && m->ents != NULL) {
		int i;
		for(i = 0; i < m->no_ents; ++i)
			menu_deref(m->ents[i]);
		xfree(m->ents);
	}

	if(m->help != NULL)
		strarr_free(&m->help,&m->no_help);
	
	xfree(m);	
}

menu_t *alloc_menu(enum menu_type_t type) {
	menu_t *m = xalloc(sizeof(menu_t));
	
	LOCK_INIT(&m->lock);
	m->type 	= type;
	m->ref_count 	= 1;
	m->func		= NULL;
	m->min_args	= 0;
	m->max_args	= 0;
	m->log		= 0;
	m->hide		= 0;
	m->privs	= PRIVS_EMPTY;
	m->helper	= NULL;
	m->help		= NULL;
	m->no_help	= 0;
	m->ents		= NULL;
	m->no_ents	= 0;

	return m;
}

void menu_ref(menu_t *m) {
	assert(m != NULL);
	LOCK(m);
	m->ref_count++;
	UNLOCK(m);
}

void menu_deref(menu_t *m) {
	int ref_count;
	
	assert(m != NULL);
	LOCK(m);
	m->ref_count--;
	ref_count = m->ref_count;
	UNLOCK(m);
	if(ref_count <= 0)
		free_menu(m);
}

void menu_add(menu_t *m, menu_t *ent) {
	menu_ref(ent);
	
	LOCK(m);
	PTRARR_ADD(&m->ents,&m->no_ents,ent);
	UNLOCK(m);
}

void menu_del(menu_t *m, menu_t *ent) {
	LOCK(m);
	PTRARR_DEL(&m->ents,&m->no_ents,ent);
	UNLOCK(m);

	menu_deref(ent);
}

/* some pre-declarations for menu_resolv */
static instance_t *alloc_instance(instance_t *orig,
		const char *prompt, menu_t *menu);
static void free_instance(instance_t *instance);
static menu_t **find_cmds(instance_t *ins, const char *prefix, int len,
		unsigned long privs, int *no_cmds, int use_hide, int fbg);

menu_t *menu_resolv(const char *path) {
	instance_t *ins = alloc_instance(NULL,NULL,main_menu_root);
	int i,ppos = 0,npos = 0,no_cmds = 0;
	menu_t *cmd = NULL,**cmds;
	
	ins->gmenu = global_menu_root;

	if(path[ppos] != '\0' && path[ppos] == '/')
		ppos++;
	
	do {
		npos = ppos;
		while(path[npos] != '\0' && path[npos] != '/') 
			npos++;
		
		cmd = NULL;
		cmds = find_cmds(ins,path+ppos,(npos-ppos),
				PRIVS_FULL,&no_cmds,0,1);
		
		if(no_cmds == 1) {
			cmd = cmds[0];
			xfree(cmds);
		} else if(no_cmds > 1) {
			for(i = 0; i < no_cmds && cmd == NULL; ++i) {
				if(strlen(cmds[i]->name) == (npos-ppos))
					cmd = cmds[i];
				else
					menu_deref(cmds[i]);
			}
			xfree(cmds);
		} else
			break;
		
		if(path[npos] == '/' && cmd->type != ADMIN_MENU) {
			menu_deref(cmd);
			cmd = NULL;
			break;
		} else if(path[npos] == '\0') {
			break;
		} else {
			menu_deref(ins->lmenu);
			ins->lmenu = cmd;
		}

		if(path[npos] != '\0')
			ppos = npos+1;
		else
			ppos = npos;
	} while(path[ppos] != '\0');
	
	free_instance(ins);
	return cmd;
}

menu_t *menu_from_cmds(const char *name, 
		int min_args, int max_args, int log, int hide, 
		uint32_t privs, 
		int (*func)(instance_t *,acontext_t *,menu_t*,char **,int),
		void (*helper)(instance_t *,acontext_t *,char **,int),
		cmd_t *cmds) {
	menu_t *ent,*m = alloc_menu(ADMIN_MENU);
	int i;

	m->name		= xstrdup(name);
	m->min_args	= min_args;
	m->max_args	= max_args;
	m->log		= log;
	m->hide		= hide;
	m->privs	= privs;
	m->func		= func;
	m->helper	= helper;

	for(i = 0; cmds[i].name != NULL; ++i) {
		ent = alloc_menu(ADMIN_COMMAND);
		ent->name	= xstrdup(cmds[i].name);
		ent->min_args	= cmds[i].min_args;
		ent->max_args	= cmds[i].max_args;
		ent->log	= cmds[i].log;
		ent->hide	= cmds[i].hide;
		ent->privs	= cmds[i].privs;
		ent->func	= cmds[i].func;
		ent->helper	= cmds[i].helper;
		PTRARR_ADD(&m->ents,&m->no_ents,ent);
	}

	return m;
}

/*** Instance Functions ***/

/** alloc_instance
 *	Initialises a new instance_t, orig should be the level below.
 */
static instance_t *alloc_instance(instance_t *orig, 
				const char *prompt, menu_t *menu) {
	instance_t *i = xalloc(sizeof(instance_t));
	if(prompt != NULL) {
		i->prompt = xstrdup(prompt);
		i->free_prompt = 1;
	} else {
		i->prompt = NULL;
		i->free_prompt = 0;
	}
	if(orig != NULL) {
		i->gmenu = orig->gmenu;
		i->prev = orig;
	} else {
		i->gmenu = NULL;
		i->prev = NULL;
	}
	
	i->lmenu = menu;
	if(menu != NULL)
		menu_ref(i->lmenu);
	
	i->data	= NULL;
	i->check= NULL;
	i->free = NULL;
	return i;
}

/** free_instance
 *	Deallocates an instance_t and associated data.
 */
static void free_instance(instance_t *i) {
	if(i->lmenu != NULL)
		menu_deref(i->lmenu);
	if(i->prompt != NULL && i->free_prompt)
		xfree(i->prompt);
	if(i->free != NULL && i->data != NULL)
		i->free(i->data);
	xfree(i);
}

/** free_instances
 *	Deallocates an instance_t and all instances below it.
 */
static void free_instances(instance_t *i) {
	instance_t *p;
	while(i != NULL) {
		p = i->prev;
		free_instance(i);
		i = p;
	}
}

/*** Party Line ***/

/** admin_party_line_msg
 * 	Sends msg to the bots party line, excluding pqueue_t *ex.
 *	The *ex argument is generally used to exclude the sender.
 */
void admin_party_line_msg(const char *msg, pqueue_t *ex) {
	char buffer[512];
	int i;

	snprintf(buffer,sizeof(buffer),"%sPL%s %s",
			COLOUR_BOLD,COLOUR_BOLD,msg);
	
	LOCKR(&(global->party_line.lock));
	for(i = 0; i < global->party_line.no_subs; ++i) {
		if(global->party_line.subs[i] != ex)
			pqueue_push_back(global->party_line.subs[i],
					xstrdup(buffer));
	}
	UNLOCKR(&(global->party_line.lock));
}

/** admin_party_line_subscribe
 *	Attaches the message queue for an admin session to the party line,
 *	and annouces that fact to the others on it.
 */
void admin_party_line_subscribe(acontext_t *ac) {
	char buffer[64];
	
	LOCKR(&(global->party_line.lock));
	PTRARR_ADD(&(global->party_line.subs),&(global->party_line.no_subs),
			ac->msgqueue);
	UNLOCKR(&(global->party_line.lock));
	
	if(ac->user != NULL) {
		LOCK(ac->user);
		snprintf(buffer,sizeof(buffer),"%s joined",
				ac->user->username);
		UNLOCK(ac->user);
		admin_party_line_msg(buffer,NULL);
	}
}

/** admin_party_line_unsubscribe
 *	Removes the message queue for an admin session from the party line,
 *	and annouces the users departure to others on it.
 */
void admin_party_line_unsubscribe(acontext_t *ac) {
	char buffer[64];
	
	if(ac->user != NULL) {
		LOCK(ac->user);
		snprintf(buffer,sizeof(buffer),"%s left",
				ac->user->username);
		UNLOCK(ac->user);
		admin_party_line_msg(buffer,NULL);
	}
	
	LOCKR(&(global->party_line.lock));
	PTRARR_DEL(&(global->party_line.subs),&(global->party_line.no_subs),
			ac->msgqueue);
	UNLOCKR(&(global->party_line.lock));
}

/*******************
 * Admin Interface *
 *******************/

/** _admin_msg
 *	Internal handler for admin_msg and admin_cmsg.
 *	Adds a formatted message to the mesage queue of an script context.
 */
static void _admin_msg(acontext_t *ac, int colour, 
		const char *format, va_list va) {
	if(ac->msgqueue != NULL) {
		char buffer[512];

		vsnprintf(buffer,sizeof(buffer),format,va);

		if(!colour) {
			char cbuffer[512];
			quote_colours(cbuffer,sizeof(cbuffer),buffer);
			pqueue_push_back(ac->msgqueue,xstrdup(cbuffer));
		} else
			pqueue_push_back(ac->msgqueue,xstrdup(buffer));
	}
}

void admin_msg(acontext_t *ac, const char *format, ...) {
	va_list va;

	va_start(va,format);
	_admin_msg(ac,0,format,va);
	va_end(va);
}

void admin_cmsg(acontext_t *ac, const char *format, ...) {
	va_list va;

	va_start(va,format);
	_admin_msg(ac,1,format,va);
	va_end(va);
}

/** cmd_strarr
 *	Generic command for manipulating arrays of strings,
 *	used in many places in the menu system to reduce code duplication.
 */
static int cmd_strarr(acontext_t *ac, char ***strs, int *no_strs,
		char **args, int no_args) {
	char *tmp;
	int i,j,k;
	if(no_args < 2) {
		for(i = 0; i < *no_strs; ++i)
			admin_msg(ac,"%2d: %s",i+1,(*strs)[i]);
		return 0;
	}
	if(tolower(args[1][0]) == 'a') {
		if(no_args >= 3)
			PTRARR_ADD(strs,no_strs,xstrdup(args[2]));
		else {
			admin_msg(ac,"%s add <line>",args[0]);
		}
	} else if(tolower(args[1][0]) == 'd') {
		if(no_args >= 3) {
			i = atoi(args[2]);
			if(i >= 1 && i <= *no_strs) {
				xfree((*strs)[i-1]);
				PTRARR_DEL(strs,no_strs,(*strs)[i-1]);
			} else {
				admin_msg(ac,"Invalid Number: %d",i);
				return RET_INV_ARG;
			}
		} else {
			admin_msg(ac,"%s del <number>",args[0]);
		}
	} else if(tolower(args[1][0]) == 'w') {
		strarr_free(strs,no_strs);
	} else if(tolower(args[1][0]) == 'm' || 
			strncasecmp(args[1],"sw",2) == 0) {
		if(no_args >= 4) {
			i = atoi(args[2]);
			j = atoi(args[3]);
			if(i < 1 || i > *no_strs) {
				admin_msg(ac,"Invalid Number: %d",i);
			} else if(j < 1 || j > *no_strs) {
				admin_msg(ac,"Invalid Number: %d",j);
			} else if(i != j) {
				if(tolower(args[1][0]) == 'm') { // move
					tmp = (*strs)[i-1];
					if(j < i) {
						for(k = i-1; k > (j-1); --k)
							(*strs)[k] = (*strs)[k-1];
					} else {
						for(k = i-1; k < (j-1); ++j)
							(*strs)[k] = (*strs)[k+1];
					}
					(*strs)[j-1] = tmp;
				} else { // swap
					tmp = (*strs)[j-1];
					(*strs)[j-1] = (*strs)[i-1];
					(*strs)[i-1] = tmp;
				}
			}
		} else {
			admin_msg(ac,"%s %s <src no> <dst no>",args[0],args[1]);
			return RET_ISF_ARG;
		}
	} else if(strncasecmp(args[1],"se",2) == 0) {
		if(no_args >= 4) {
			i = atoi(args[2]);
			if(i < 1 || i > *no_strs) {
				admin_msg(ac,"Invalid Number: %d",i);
				return RET_INV_ARG;
			} else {
				if((*strs)[i-1] != NULL)
					xfree((*strs)[i-1]);
				(*strs)[i-1] = xstrdup(args[3]);
			}
		} else {
			admin_msg(ac,"%s set <number> <value>",args[0]);
			return RET_ISF_ARG;
		}
	} else {
		admin_msg(ac,"%s [add|del|move|set|swap|wipe]",args[0]);
		return RET_ISF_ARG;
	}
	return 0;
}

static int cmd_access_list(acontext_t *ac, access_t *acc, char *title,
				char **args, int no_args) {
	int i,j,ret = 0;
	
	if(no_args == 1 && acc == NULL) {
		admin_msg(ac,"-- Access List for %s --",title);
		admin_msg(ac,"Empty");
	} else if(no_args == 1) {
		char netname[13],channame[21],mode[6];
		LOCK(acc);
		admin_msg(ac,"-- Access List for %s --",title);
		admin_msg(ac,"Mode: %s",
				(acc->flags & ACCESS_SMART) == ACCESS_SMART ? "Smart" :
				 (acc->flags & ACCESS_ALLOW ? "Allow, Deny" :
				  "Deny, Allow"));
				
		admin_msg(ac,"%-2s %-8s %-12s %-20s %-5s %s",
				"No","Type","Network","Channel","Mode","Host");
		for(i = 0; i < acc->no_entries; ++i) {
			if(acc->entries[i]->network != NULL) {
				LOCK(acc->entries[i]->network);
				xstrncpy(netname,acc->entries[i]->network->name,
						sizeof(netname));
				UNLOCK(acc->entries[i]->network);
			} else
				xstrncpy(netname,"",sizeof(netname));
			
			if(acc->entries[i]->channel != NULL) {
				LOCK(acc->entries[i]->channel);
				xstrncpy(channame,
					acc->entries[i]->channel->name,
					sizeof(channame));
				UNLOCK(acc->entries[i]->channel);
			} else
				xstrncpy(channame,"",sizeof(channame));
			
			if(acc->entries[i]->mode != 0) {
				j = 0;
				if(acc->entries[i]->mode & MODE_OP)
					mode[j++] = 'o';
				if(acc->entries[i]->mode & MODE_VOICE)
					mode[j++] = 'v';
				if(acc->entries[i]->mode & MODE_HOP)
					mode[j++] = 'h';
				mode[j] = '\0';
			} else
				xstrncpy(mode,"",sizeof(mode));
			
			admin_msg(ac,"% -2d %-8s %-12s %-20s %-5s %s",
					i+1,
					acc->entries[i]->flags & ACCESS_ALLOW ?
					 "ALLOW" : "DENY",
					netname,channame,mode,
					acc->entries[i]->host != NULL ?
					 acc->entries[i]->host : "");
		}
		if(i == 0)
			admin_msg(ac,"Empty");
		UNLOCK(acc);
	} else if(tolower(args[1][0]) == 'a' || tolower(args[1][0]) == 'd') {
		// allow || deny
		if(no_args >= 4) {
			network_t	*net	= NULL;
			channel_t	*chan	= NULL;
			char		*host	= NULL;
			int		mode	= MODE_REG;
			int		err	= 0;
			for(i = 2; i < no_args && !err;) {
				if(strncasecmp(args[i],"-n",2) == 0 && 
						(i+1) < no_args) {
					// network
					if(chan == NULL) {
						net = irc_network_find(
								args[i+1]);
						if(net == NULL)
							err = i;
					}
					i += 2;
				} else if(strncasecmp(args[i],"-c",2) == 0 &&
						(i+2) < no_args) {
					// channel
					net = irc_network_find(args[i+1]);
					if(net != NULL) {
						chan = irc_channel_find(net,
								args[i+2]);
						if(chan == NULL)
							err = i;
					} else
						err = i;
					i += 3;
				} else if(strncasecmp(args[i],"-h",2) == 0 &&
						(i+1) < no_args) {
					host = args[i+1];
					i += 2;
				} else if(strncasecmp(args[i],"-m",2) == 0 &&
						(i+1) < no_args) {
					for(j = 0; args[i+1][j] != '\0'; ++j) {
						switch(tolower(args[i+1][j])) {
							case 'o':
							 mode |= MODE_OP;
							 break;
							case 'v':
							 mode |= MODE_VOICE;
							 break;
							case 'h':
							 mode |= MODE_HOP;
							 break;
							default:
							 err = i;
							 break;
						}
					}
					i += 2;
				} else {
					err = i;
					i++;
				}
			}
			if(err != 0) {
				admin_msg(ac,"Error with argument or parameters for: %s",args[err]);
				ret = RET_INV_ARG;
			} else {
				access_add_entry(acc,
					tolower(args[1][0]) == 'a' ?
					 ACCESS_ALLOW : ACCESS_DENY,
					net,host,chan,mode);
			}
		} else {
			admin_msg(ac,"%s %s [-net <network>] [-channel <network> <channel>] [-host <hostmask>] [-mode <mode>]",args[0],args[1]);
			ret = RET_ISF_ARG;
		}
	} else if(tolower(args[1][0]) == 'c') { // clone
		if(no_args >= 3) {
			packlist_t *list = packlist_find(args[2]);
			access_t *oacc;
			
			if(list != NULL) {
				LOCK(list);
				oacc = list->access;
				UNLOCK(list);
				access_copy(acc,oacc);
			} else {
				admin_msg(ac,"Unknown packlist: %s",args[2]);
				ret = RET_INV_ARG;
			}
		} else {
			admin_msg(ac,"%s %s <packlist>",args[0],args[1]);
			ret = RET_ISF_ARG;
		}
	} else if(tolower(args[1][0]) == 'm') { // mode
		if(no_args >= 3) {
			LOCK(acc);
			switch (tolower(args[2][0])) {
				case 'a':
					acc->flags = ACCESS_ALLOW;
					break;
				case 'd':
					acc->flags = ACCESS_DENY;
					break;
				case 's':
					acc->flags = ACCESS_SMART;
					break;
			}
			UNLOCK(acc);
		} else {
			admin_msg(ac,"%s %s [Allow,Deny|Deny,Allow,Smart]",
					args[0],args[1]);
			ret = RET_ISF_ARG;
		}
	} else if(tolower(args[1][0]) == 'r') { // remove
		if(no_args >= 3) {
			int no = atoi(args[2]) - 1;
			if(access_del_entry_no(acc,no) != 0) {
				admin_msg(ac,"Unable to remove entry %s",
						args[2]);
				ret = RET_INV_ARG;
			}
		} else {
			admin_msg(ac,"%s %s <number>",args[0],args[1]);
			ret = RET_ISF_ARG;
		}
	} else {
		admin_msg(ac,"%s [allow|clone|deny|mode|remove]",args[0]);
		ret = RET_ISF_ARG;
	}

	return ret;
}

static void output_settings(acontext_t *ac, 
		const char *title, const char *var, pqueue_t *settings) {
	char buffer[400];
	value_t *v;
	if(title != NULL)
		admin_msg(ac,title,var);
	while((v = pqueue_pop_front(settings)) != NULL) {
		char *str = value_as_string(v,
				VALUE_ALLOW_NULL | VALUE_NICE_FLOATS,
				buffer,sizeof(buffer),NULL);
		admin_msg(ac,"%s: %s",v->name,str != NULL ? str : "Not Set");
		xfree(v);
	}
}

static menu_t **find_cmds(instance_t *ins, const char *prefix, int len,
		unsigned long privs, int *no_cmds, int use_hide, int fbg) {
	menu_t *cmd,**cmds = NULL;
	int i;
	*no_cmds = 0;
	
	if(ins->lmenu != NULL) {
		LOCK(ins->lmenu);
		for(i = 0; i < ins->lmenu->no_ents; ++i) {
			cmd = ins->lmenu->ents[i];
			if((strncmp(cmd->name,prefix,len) == 0 || len == 0)
				&& PRIV_M_ISSET(privs,cmd->privs)
				&& (!cmd->hide || !use_hide)) {
				PTRARR_ADD(&cmds,no_cmds,cmd);
				menu_ref(cmd);
			}
		}
		UNLOCK(ins->lmenu);
	}
	if((*no_cmds == 0 || !fbg) && ins->gmenu != NULL) {
		LOCK(ins->gmenu);
		for(i = 0; i < ins->gmenu->no_ents; ++i) {
			cmd = ins->gmenu->ents[i];
			if((strncmp(cmd->name,prefix,len) == 0 || len == 0)
				&& PRIV_M_ISSET(privs,cmd->privs)
				&& (!cmd->hide || !use_hide)) {
				PTRARR_ADD(&cmds,no_cmds,cmd);
				menu_ref(cmd);
			}
		}
		UNLOCK(ins->gmenu);
	}

	return cmds;
}

/*** Global Menu ***/
#include "admin/global_menu.c"
/*** Channel Menu ***/
#include "admin/channel_menu.c"
/*** Network Menu ***/
#include "admin/network_menu.c"
/*** Pool Menu ***/
#include "admin/pool_menu.c"
/*** Interface Menu ***/
#include "admin/interface_menu.c"
/*** Queue Menu ***/
#include "admin/queue_menu.c"
/*** Packlist Menu ***/
#include "admin/packlist_menu.c"
/*** Settings Menu ***/
#include "admin/settings_menu.c"
/*** User(s) Menu ***/
#include "admin/user_menu.c"
/*** Files Menu ***/
#include "admin/files_menu.c"
/*** Scripting Menu ***/
#include "admin/scripting_menu.c"
/*** Main Menu ***/
#include "admin/main_menu.c"
/*** Help for all Menus ***/
#include "admin/help.c"

/** qsort_strcmp
 *	Linkage to strcmp for use with qsort.
 */
static int qsort_strcmp(const void *a, const void *b) {
	return strcmp(*((char **)a),*((char **)b));
}

/** qsort_cmdcmp
 *	For qsort to sort commands by name.
 */
static int qsort_cmdcmp(const void *a, const void *b) {
	return strcmp((*((menu_t **)a))->name,(*((menu_t **)b))->name);
}

/** build_prompt
 *	Constructs a full prompt string from the prompts of all the instances.
 */
static void build_prompt(acontext_t *ac, char *buffer, size_t bufsize) {
	instance_t *i,*p;
	char c;
	int j = 0,k;
	
	/* 10 points if you figure out how it does its stuff ^_~ */
	
	buffer[j++] = ']';
	//buffer[j++] = '>';
	i = (instance_t *)ac->instance;
	do {
		p = i->prev;
		if(i->prompt != NULL && j < (bufsize-4)) {
			for(k = strlen(i->prompt); 
				j < (bufsize-5) && k > 0; --k)
				buffer[j++] = i->prompt[k-1];
			buffer[j++] = '/';
		}
		i = p;
	} while(i != NULL);
	if(((instance_t *)ac->instance)->prev == NULL) // i.e. root
		buffer[j++] = '/';
	buffer[j++] = '[';
	buffer[j] = '\0';
	
	for(j = 0, k = strlen(buffer)-1; k > j; ++j,--k) {
		c = buffer[j];
		buffer[j] = buffer[k];
		buffer[k] = c;
	}
}

/** admin_output_prompt
 *	Builds prompt and places it on the message queue of a session.
 */
void admin_output_prompt(acontext_t *ac) {
	char buffer[512];
	build_prompt(ac,buffer,sizeof(buffer));
	admin_msg(ac,"%s",buffer);
}

static void run_cmd(acontext_t *ac, menu_t *cmd, 
		char **args, int no_args, const char *logmsg) {
	instance_t *ins = ac->instance;
	int ret_val = RET_OK;

	if(cmd->log != 0) {
		LOCK(ac->user);
		LOGP(L_ADM,"%s %s",
			ac->user->username,logmsg);
		UNLOCK(ac->user);
	}
		
	if(cmd->type == ADMIN_COMMAND) {
		ret_val = cmd->func(ins,ac,cmd,args,no_args);
		
		if(ret_val == RET_UP && ins->prev != NULL) { // request going up a level
			ac->instance = ins->prev;
			free_instance(ins);
			ins = ac->instance;
			admin_output_prompt(ac);
		} else if(ret_val == RET_RTN) { // request to return to root
			instance_t *p;
			while(ins->prev != NULL) {
				p = ins->prev;
				free_instance(ins);
				ins = p;
			}
			ac->instance = ins;
			admin_output_prompt(ac);
		} else if(ret_val == RET_QIT) {
			ac->complete = 1;
		}
	} else if(cmd->type == ADMIN_MENU) {
		instance_t *nins = alloc_instance(ins,cmd->name,cmd);
		
		if(cmd->func != NULL)
			ret_val = cmd->func(nins,ac,cmd,args,no_args);
				
		if(ret_val == RET_OK) {
			ac->instance = nins;
			admin_output_prompt(ac);
		} else
			free_instance(nins);
	}
			
	if(ac->verbose) {
		if(ret_val == RET_OK)
			admin_msg(ac,"OK");
		else
			admin_msg(ac,"ERR (%d)",ret_val);
	}
}

/** admin_process_cmd
 * 	Determines what command if any to execute, splits arguments,
 *	calls function or helper and logs execution.
 */
void admin_process_cmd(acontext_t *ac, char *buffer, size_t bufsize) {
	instance_t 	*ins = (instance_t *)ac->instance;
	menu_t *	cmd = NULL,**cmds = NULL;
	unsigned long 	privs;
	char		logbuf[1024];
	char 		**args = NULL;
	char 		c;
	int 		no_cmds,no_args;
	int 		i,len;
		
	if(buffer[0] == '\0')
		return;

	LOCK(ac->user);
	privs = ac->user->uprivs;
	UNLOCK(ac->user);
	
	for(i = 0; buffer[i] != '\0'; ++i) {
		if(buffer[i] == ' ' || buffer[i] == '?')
			break;
	}
	
	if(buffer[i] == '?') {
		/* ? character in the command itself, prompt user with
		 * possibilities.
		 */
		len	= i;
		cmds	= find_cmds(ins,buffer,i,privs,&no_cmds,1,0);
		if(no_cmds > 0) {
			if(no_cmds > 1)
				qsort(cmds,no_cmds,sizeof(char *),qsort_cmdcmp);
			for(i = 0; i < no_cmds; ++i)
				admin_msg(ac,"%s",cmds[i]->name);
		}
		if(ac->verbose) // FIXME: remove me?
			admin_msg(ac,"OK");
	} else {
		len = i;
		c = buffer[len];
		buffer[len] = '\0';
		cmds = find_cmds(ins,buffer,len,privs,&no_cmds,0,1);
		if(no_cmds == 1)
			cmd = cmds[0];
		else if(no_cmds > 1) {
			for(i = 0; i < no_cmds && cmd == NULL; ++i) {
				if(strcmp(cmds[i]->name,buffer) == 0)
					cmd = cmds[i];
			}
		}
		
		buffer[len] = c;
		if(cmd != NULL) {
			if(cmd->max_args > 1)
				no_args = cmd->max_args + 2;
			else if(cmd->max_args == 0)
				no_args = 2;
			else if(cmd->max_args == -2)
				no_args = cmd->min_args + 1;
			else
				no_args = 0;
			
			if(cmd->log != 0) {
				char pbuf[512];
				build_prompt(ac,pbuf,sizeof(pbuf));
				snprintf(logbuf,sizeof(logbuf),"%s \"%s\"",
						pbuf,buffer);
			}

			args = split_opt(buffer,buffer,&no_args);
			if(cmd->max_args >= 0 &&
				no_args > cmd->max_args+1) {
				admin_msg(ac,"Too many parameters for command: %s",cmd->name);
				if(ac->verbose) // XXX
					admin_msg(ac,"ERR (%d)",RET_EXS_ARG);
			} else if(cmd->helper != NULL && 
				(args[no_args-1][0] == '?' || 
				args[no_args-1][strlen(args[no_args-1])-1] == '?')) {
				cmd->helper(ins,ac,args,no_args);
				if(ac->verbose) // XXX
					admin_msg(ac,"OK");
			} else if((no_args-1) < cmd->min_args) {
				admin_msg(ac,"Too few parameters for command: %s",cmd->name);
				if(ac->verbose) // XXX
					admin_msg(ac,"ERR (%d)",RET_ISF_ARG);
			} else
				run_cmd(ac,cmd,args,no_args,logbuf);
		} else {
			admin_msg(ac,"Unknown Command");
			if(ac->verbose)
				admin_msg(ac,"ERR (%d)",RET_INV_CMD);
		}					
	}
	if(cmds != NULL) {
		for(i = 0; i < no_cmds; ++i)
			menu_deref(cmds[i]);
		xfree(cmds);
	}
	if(args != NULL)
		xfree(args);
}	
	
/** admin_context_check
 *	Executes the check functions defined in all the levels of the
 *	instance tree of the context.  If one returns -1 then the tree
 *	above and including that instance is freed, and the user prompt 
 *	re-issued.  Example:
 *		"A -> B -> C", if B returns -1, B and C are free'd and the new
 *		prompt is A.
 */
void admin_context_check(acontext_t *ac, time_t now) {
	instance_t *i,*p,*pp;
	i = (instance_t *)ac->instance;
	pp = i;
	do {
		p = i->prev;
		if(i->check != NULL) {
			if(i->check(i,ac,now) == -1) {
				i->prev = NULL;
				if(pp != NULL && pp != i)
					free_instances(pp);
				else
					free_instance(i);
				pp = p;
			}
		}
		i = p;
	} while(i != NULL);
	if(pp != (instance_t *)ac->instance) {
		ac->instance = pp;
		admin_output_prompt(ac);
	}
}

acontext_t *admin_new_context(pqueue_t *msgqueue) {
	acontext_t *ac = xalloc(sizeof(acontext_t));
	ac->verbose	= 0;
	ac->complete	= 0;
	ac->last_ret	= 0;
	ac->user	= NULL;
	ac->msgqueue	= msgqueue;
	ac->instance	= alloc_instance(NULL,NULL,main_menu_root);
	((instance_t *)(ac->instance))->gmenu = global_menu_root;
	ac->colour	= COLOUR_NONE;
	return ac;
}

void admin_free_context(acontext_t *ac) {
	if(ac->instance != NULL)
		free_instances(ac->instance);
	xfree(ac);
}

int admin_context_complete(acontext_t *ac) {
	if(ac->complete != 0 || ac->instance == NULL)
		return 1;
	else
		return 0;
}

void admin_global_init(void) {
	global_menu_root = menu_from_cmds("global",
			0,0,0,0,PRIVS_EMPTY,NULL,NULL,(cmd_t *)global_menu);
	main_menu_root = menu_from_cmds("",
			0,0,0,0,PRIVS_EMPTY,NULL,NULL,(cmd_t *)main_menu);

	register_network_menu(); 	// includes channel
	register_interface_menu(); 	// includes pool
	register_files_menu();
	register_packlist_menu();
	register_queue_menu();
	register_users_menu(); 		// includes self menu
	register_settings_menu();
	register_scripting_menu();

	admin_load_static_help();
}

void admin_global_free(void) {
	menu_deref(global_menu_root);
	menu_deref(main_menu_root);
}
