/* cque.c */

#include <stdio.h>
#include <ctype.h>
#include <fcntl.h>
#include <string.h>
#ifdef WANT_ANSI
#ifdef __STDC__
#include <stdlib.h>
#endif /* __STDC */
#endif /* WANT_ANSI */
#ifdef XENIX
#include <sys/signal.h>
#else
#include <signal.h>
#endif				/* xenix */

#include "mudconf.h"
#include "config.h"
#include "db.h"
#include "htab.h"
#include "interface.h"
#include "match.h"
#include "externs.h"
#include "flags.h"

void process_command(dbref player, dbref cause, int interactive,
	char *command, char *args[], int nargs);

/* ---------------------------------------------------------------------------
 * clean_que: Free command and arguments passed to queue routines, if needed
 */

static void clean_que(int key, char *arg1, char *argv[], int nargs)
{
int	i;

	if (key & RU_ARG1_TAKE) {
		if (arg1 != NULL) free_lbuf(arg1);
	}
	if (key & RU_ARG2_TAKE) {
		for (i=0; i<nargs; i++) {
			if (argv[i] != NULL) free_lbuf(argv[i]);
		}
	}
}

/* ---------------------------------------------------------------------------
 * add_to: Adjust an object's queue or semaphore count.
 */

static int add_to(dbref player, int am, int attrnum)
{
int	num, aflags;
dbref	aowner;
char	buff[20];
char    *atr_gotten;

	num = atoi(atr_gotten = atr_get(player, attrnum, &aowner, &aflags));
        free_lbuf(atr_gotten);
	num += am;
	if (num)
		sprintf(buff, "%d", num);
	else
		*buff = '\0';
	atr_add_raw(player, attrnum, buff);
	return (num);
}

/* ---------------------------------------------------------------------------
 * give_que: Thread a queue block onto the high or low priority queue
 */

static void give_que (BQUE *tmp)
{
	tmp->next = NULL;
	tmp->left = -1;

	/* Thread the command into the correct queue */

	if (Typeof(tmp->cause) == TYPE_PLAYER) {
		if (mudstate.qlast != NULL) {
			mudstate.qlast->next = tmp;
			mudstate.qlast = tmp;
		} else
			mudstate.qlast = mudstate.qfirst = tmp;
	} else {
		if (mudstate.qllast) {
			mudstate.qllast->next = tmp;
			mudstate.qllast = tmp;
		} else
			mudstate.qllast = mudstate.qlfirst = tmp;
	}
}

/* ---------------------------------------------------------------------------
 * que_want: Do we want this queue entry?
 */

static int que_want (BQUE *entry, dbref ptarg, dbref otarg)
{
	if ((ptarg != NOTHING) && (ptarg != Owner(entry->player))) return 0;
	if ((otarg != NOTHING) && (otarg != entry->player)) return 0;
	return 1;
}

/* ---------------------------------------------------------------------------
 * halt_que: Remove all queued commands from a certain player
 */

int halt_que(dbref player, dbref object)
{
BQUE	*trail, *point, *next;
int	numhalted;

	numhalted = 0;
	if ((player == NOTHING) && (object == NOTHING))
		return 0;

	/* Player queue */

	for (point = mudstate.qfirst; point; point = point->next)
		if (que_want(point, player, object)) {
			numhalted++;
			point->player = 0;
		}

	/* Object queue */

	for (point = mudstate.qlfirst; point; point = point->next)
		if (que_want(point, player, object)) {
			numhalted++;
			point->player = 0;
		}

	/* Wait queue */

	for (point=mudstate.qwait,trail=NULL; point; point = next)
		if (que_want(point, player, object)) {
			numhalted++;
			if (trail)
				trail->next = next = point->next;
			else
				mudstate.qwait = next = point->next;
			clean_que(RU_ARG1_TAKE|RU_ARG2_TAKE, point->comm,
				point->env, point->nargs);
			free_qentry(point);
		} else
			next = (trail = point)->next;

	/* Semaphore queue */

	for (point=mudstate.qsemfirst,trail=NULL; point; point = next)
		if (que_want(point, player, object)) {
			numhalted++;
			if (trail)
				trail->next = next = point->next;
			else
				mudstate.qsemfirst = next = point->next;
			if (point == mudstate.qsemlast)
				mudstate.qsemlast = trail;
			add_to(point->sem, -1, A_SEMAPHORE);
			clean_que(RU_ARG1_TAKE|RU_ARG2_TAKE, point->comm,
				point->env, point->nargs);
			free_qentry(point);
		} else
			next = (trail = point)->next;

	if (player == NOTHING)
		player = Owner(object);
	giveto(player, (mudconf.waitcost * numhalted));
	if (object == NOTHING)
		atr_clr(player, A_QUEUE);
	else
		add_to(player, -numhalted, A_QUEUE);
	return numhalted;
}

/* ---------------------------------------------------------------------------
 * do_halt: Command interface to halt_que.
 */

void do_halt (dbref player, dbref cause, int key, char *target)
{
dbref	player_targ, obj_targ;
int	numhalted;

	/* Figure out what to halt */

	if (!target || !*target) {
		obj_targ = NOTHING;
		if (key & HALT_ALL) {
			player_targ = NOTHING;
		} else {
			player_targ = Owner(player);
			if (Typeof(player) != TYPE_PLAYER)
				obj_targ = player;
		}
	} else {
		obj_targ = match_controlled(player, target);
		if (obj_targ == NOTHING) return;
		if (key & HALT_ALL) {
			notify(player, "Can't specify a target and /all");
			return;
		}
		if (Typeof(obj_targ) == TYPE_PLAYER) {
			player_targ = obj_targ;
			obj_targ = NOTHING;
		} else {
			player_targ = NOTHING;
		}
	}

	numhalted = halt_que(player_targ, obj_targ);
	if (Quiet(player)) return;
	if (numhalted == 1)
		notify(Owner(player), "1 queue entries removed.");
	else
		notify(Owner(player),
			tprintf("%d queue entries removed.", numhalted));
}

/* ---------------------------------------------------------------------------
 * nfy_que: Notify commands from the queue and perform or discard them.
 */

static int nfy_que(dbref sem, int key, int count)
{
BQUE	*point, *trail, *next;
int	num, aflags;
dbref	aowner;
char	*str;

	str = atr_get(sem, A_SEMAPHORE, &aowner, &aflags);
	num = atoi(str);
	free_lbuf(str);
	if (num > 0) {	
		num = 0;
		for (point=mudstate.qsemfirst,trail=NULL; point; point = next) {
			if (point->sem == sem) {
				num++;
				if (trail)
					trail->next = next = point->next;
				else
					mudstate.qsemfirst = next = point->next;
				if (point == mudstate.qsemlast)
					mudstate.qsemlast = trail;

				/* Either run or discard the command */

				if (key != NFY_DRAIN) {
					give_que(point);
				} else {
					giveto(point->player,
						mudconf.waitcost);
					clean_que(RU_ARG1_TAKE|RU_ARG2_TAKE,
						point->comm, point->env,
						point->nargs);
					free_qentry(point);
				}
			} else {
				next = (trail = point)->next;
			}

			/* If we've notified enough, exit */

			if ((key == NFY_NFY) && (num >= count))
				next = NULL;
		}
	} else {
		num = 0;
	}

	/* Update the sem waiters count */

	if (key == NFY_NFY)
		add_to(sem, -count, A_SEMAPHORE);
	else
		atr_clr(sem, A_SEMAPHORE);

	return num;
}

/* ---------------------------------------------------------------------------
 * do_notify: Command interface to nfy_que
 */

void do_notify (dbref player, dbref cause, int key, char *what, char *count)
{
dbref	thing;
int	loccount;

	init_match(player, what, NOTYPE);
	match_exit();
	match_neighbor();
	match_possession();
	match_me();
	match_here();
	match_absolute();
	match_player();

	if ((thing = noisy_match_result()) < 0) {
		notify(player, "No match.");
	} else if (!controls(player, thing) && !(Flags(thing) & LINK_OK)) {
		notify(player, "Permission denied.");
	} else {
		if (count && *count)
			loccount = atoi(count);
		else
			loccount = 1;
		if (loccount > 0)
			nfy_que(thing, key, loccount);
	}
}

/* ---------------------------------------------------------------------------
 * setup_que: Set up a queue entry.
 */

static BQUE *setup_que(dbref player, dbref cause, int key, char *command,
	char *args[], int nargs)
{
int	a;
BQUE	*tmp;

	/* Can we run commands at all? */

	if (Halted(player))
		return NULL;

	/* make sure player can afford to do it */

	a = mudconf.waitcost;
	if ((random() % mudconf.machinecost) == 0) a++;
	if (!payfor(player, a)) {
		notify(Owner(player), "Not enough money to queue command.");
		return NULL;
	}

	/* Wizards and their objs may queue up to db_top+1 cmds. Players are
	 * limited to QUEUE_QUOTA. -mnp */

	a = mudconf.queuemax;
	if (Wizard(player))
		a += mudstate.db_top + 1;

	if (add_to(Owner(player), 1, A_QUEUE) > a) {
		notify(Owner(player),
			"Run away objects: too many commands queued.  Halted.");
		halt_que(Owner(player), NOTHING);

		/* halt also means no command execution allowed */
		s_Flags(player, Flags(player) | HALT);
		return NULL;
	}

	/* We passed all the tests.  Create the queue entry. */

	tmp = alloc_qentry("setup_que.qblock");
	if (key & RU_ARG1_TAKE) {
		tmp->comm = command;	/* save the lbuf */
	} else {
		tmp->comm = alloc_lbuf("setup_que.arg1");
		strcpy(tmp->comm, command);
	}
	tmp->player = player;
	tmp->left = -1;
	tmp->next = NULL;
	tmp->sem = NOTHING;
	tmp->cause = cause;

	/* Set up the environment */

	if (nargs > NUM_ENV_VARS) {
		if (key & RU_ARG2_TAKE) {
			for (a=NUM_ENV_VARS; a<nargs; a++) {
				free_lbuf(args[a]);
			}
		}
		nargs = NUM_ENV_VARS;
	}
	for (a=0; a<nargs; a++) {
		if (args[a] != NULL) {
			if (key & (RU_ARG2_TAKE|RU_ARG2_TMPL)) {
				tmp->env[a] = args[a];
			} else {
				tmp->env[a] = alloc_lbuf("setup_que.argv");
				strcpy(tmp->env[a], args[a]);
			}
		} else {
			tmp->env[a] = NULL;
		}
	}
	tmp->nargs = nargs;
	return tmp;
}

/* ---------------------------------------------------------------------------
 * wait_que: Add commands to the wait or semaphore queues.
 */

void wait_que(dbref player, dbref cause, int key, int wait, dbref sem,
	char *command, char *args[], int nargs)
{
BQUE	*tmp;

	if (mudconf.control_flags & CF_INTERP)
		tmp = setup_que(player, cause, key, command, args, nargs);
	else
		tmp = NULL;
	if (tmp == NULL) {
		clean_que(key, command, args, nargs);
		return;
	}
	tmp->left = wait;
	tmp->sem = sem;
	if (sem == NOTHING) {

		/* No semaphore, put on wait queue if wait value specified.
		 * Otherwise put on the normal queue. */

		if (wait <= 0) {
			give_que(tmp);
		} else {
			tmp->next = mudstate.qwait;
			mudstate.qwait = tmp;
		}
	} else {
		tmp->next = NULL;
		if (mudstate.qsemlast != NULL)
			mudstate.qsemlast->next = tmp;
		else
			mudstate.qsemfirst = tmp;
		mudstate.qsemlast = tmp;
	}
}

/* ---------------------------------------------------------------------------
 * do_wait: Command interface to wait_que
 */

void do_wait (dbref player, dbref cause, int key, char *event, char *cmd,
	char *cargs[], int ncargs)
{
dbref	thing, aowner;
int	howlong, num, aflags;
char	*what;

	/* If arg1 is all numeric, do simple (non-sem) timed wait. */

	if (is_number(event)) {
		howlong = atol(event);
		wait_que (player, cause, key, howlong, NOTHING, cmd,
			cargs, ncargs);
		return;
	}

	/* Semaphore wait with optional timeout */

	what = parse_to(&event, '/', 0);
	init_match(player, what, NOTYPE);
	match_exit();
	match_neighbor();
	match_possession();
	match_me();
	match_here();
	match_absolute();
	match_player();

	if ((thing = noisy_match_result()) < 0) {
		notify(player, "No match.");
	} else if (!controls(player, thing) && !(Flags(thing) & LINK_OK)) {
		notify(player, "Permission denied.");
	} else {

		/* Get timeout, default -1 */

		if (event && *event)
			howlong = atol(event);
		else
			howlong = -1;

		add_to(thing, 1, A_SEMAPHORE);
		what = atr_get(thing, A_SEMAPHORE, &aowner, &aflags);
		num = atoi(what);
		free_lbuf(what);
		if (num <= 0)
			thing = NOTHING;
		wait_que(player, cause, key, howlong, thing, cmd,
			cargs, ncargs);
	}
}

/* ---------------------------------------------------------------------------
 * do_second: Check the wait and semaphore queues for commands to remove.
 */

void do_second(int nsecs)
{
BQUE	*trail, *point, *next;
char	*cmdsave;

	/* move contents of low priority queue onto end of normal one
	 * this helps to keep objects from getting out of control since
	 * its affects on other objects happen only after one seconds
	 * this should allow @halt to be type before getting blown away
	 * by scrolling text */

	if ((mudconf.control_flags & CF_DEQUEUE) == 0) return;

	cmdsave = mudstate.debug_cmd;
	mudstate.debug_cmd = (char *)"< do_second >";

	if (mudstate.qlfirst) {
		if (mudstate.qlast)
			mudstate.qlast->next = mudstate.qlfirst;
		else
			mudstate.qfirst = mudstate.qlfirst;
		mudstate.qlast = mudstate.qllast;
		mudstate.qllast = mudstate.qlfirst = NULL;
	}

	/* Note: the point->left test would be 0 except the command is being
	 * put in the low priority queue to be done in one second anyways
	 */

	/* Do the wait queue */

	for (point=mudstate.qwait,trail=NULL; point; point=next) {
		point->left -= nsecs;
		if (point->left <= 1) {
			if (trail != NULL)
				trail->next = next = point->next;
			else
				mudstate.qwait = next = point->next;
			give_que(point);
		} else
			next = (trail = point)->next;
	}

	/* Check the semaphore queue for expired timed-waits */

	for (point=mudstate.qsemfirst,trail=NULL; point; point=next) {
		if (point->left < 0) {
			next = (trail = point)->next;
			continue;	/* Skip if not timed-wait */
		}
		point->left -= nsecs;
		if (point->left <= 1) {
			if (trail != NULL)
				trail->next = next = point->next;
			else
				mudstate.qsemfirst = next = point->next;
			if (point == mudstate.qsemlast)
				mudstate.qsemlast = trail;
			add_to(point->sem, -1, A_SEMAPHORE);
			point->sem = NOTHING;
			give_que(point);
		} else
			next = (trail = point)->next;
	}
	mudstate.debug_cmd = cmdsave;
	return;
}

/* ---------------------------------------------------------------------------
 * do_top: Execute the command at the top of the queue
 */

int do_top(int ncmds)
{
BQUE	*tmp;
dbref	player;
int	count;
char	*command, *cp, *cmdsave;

	if ((mudconf.control_flags & CF_DEQUEUE) == 0) return 0;

	cmdsave = mudstate.debug_cmd;
	mudstate.debug_cmd = (char *)"< do_top >";
	
	for (count=0; count<ncmds; count++) {
		if (!test_top()) {
			mudstate.debug_cmd = cmdsave;
			return count;
		}
		player = mudstate.qfirst->player;
		if ((player > 0) && !(Flags(player) & GOING)) {
			giveto(player, mudconf.waitcost);
			mudstate.curr_enactor = mudstate.qfirst->cause;
			mudstate.curr_player = player;
			add_to(Owner(player), -1, A_QUEUE);
			mudstate.qfirst->player = 0;
			if (!Halted(player)) {
				command = mudstate.qfirst->comm;
				while (command) {
					cp = parse_to(&command, ';', 0);
					if (cp && *cp) {
						process_command(player,
							mudstate.qfirst->cause,
							0, cp,
							mudstate.qfirst->env,
							mudstate.qfirst->nargs);
					}
				}
			}
		}

		tmp = mudstate.qfirst->next;
		clean_que(RU_ARG1_TAKE|RU_ARG2_TAKE, mudstate.qfirst->comm,
			mudstate.qfirst->env, mudstate.qfirst->nargs);
		free_qentry(mudstate.qfirst);
		if (!(mudstate.qfirst = tmp))
			mudstate.qlast = NULL;
	}
	mudstate.debug_cmd = cmdsave;
	return count;
}

/* ---------------------------------------------------------------------------
 * do_ps: tell player what commands they have pending in the queue
 */

static void show_que(dbref player, int key, BQUE *queue,
	int *qtot, int *qent, int *qdel,
	dbref player_targ, dbref obj_targ, const char *header)
{
BQUE	*tmp;
char	*bp, *bufp;
int	i;

	*qtot = 0;
	*qent = 0;
	*qdel = 0;
	for (tmp = queue; tmp; tmp = tmp->next) {
		(*qtot)++;
		if (que_want(tmp, player_targ, obj_targ)) {
			(*qent)++;
			if (key == PS_SUMM) continue;
			if (*qent == 1)
				notify(player,
					tprintf("----- %s Queue -----",
						header));
			bufp = unparse_object(player, tmp->player);
			if ((tmp->left >= 0) && (Good_obj(tmp->sem)))
				notify(player,
					tprintf("[#%d/%d]%s:%s",
						tmp->sem, tmp->left,
						bufp, tmp->comm));
			else if (tmp->left >= 0)
				notify(player,
					tprintf("[%d]%s:%s", tmp->left,
						bufp, tmp->comm));
			else if (Good_obj(tmp->sem))
				notify(player,
					tprintf("[#%d]%s:%s", tmp->sem,
						bufp, tmp->comm));
			else
				notify(player,
					tprintf("%s:%s", bufp, tmp->comm));
			bp = bufp;
			if (key == PS_LONG) {
				for (i=0; i<(tmp->nargs); i++) {
					if (tmp->env[i] != NULL) {
						safe_str((char *)"; Arg",
						bufp, &bp);
						safe_chr(i+'0', bufp, &bp);
						safe_str((char *)"='",
							bufp, &bp);
						safe_str(tmp->env[i],
							bufp, &bp);
						safe_chr('\'', bufp, &bp);
					}
				}
				*bp = '\0';
				bp = unparse_object(player, tmp->cause);
				notify(player,
					tprintf("   Enactor: %s%s",
						bp, bufp));
				free_lbuf(bp);
			}	
			free_lbuf(bufp);
		} else if (tmp->player == 0) {
			(*qdel)++;
		}
	}
	return;
}

void do_ps(dbref player, dbref cause, int key, char *target)
{
char	*bufp;
dbref	player_targ, obj_targ;
int	pqent, pqtot, pqdel, oqent, oqtot, oqdel, wqent, wqtot, sqent, sqtot, i;

	/* Figure out what to list the queue for */

	if (!target || !*target) {
		obj_targ = NOTHING;
		if (key & PS_ALL) {
			player_targ = NOTHING;
		} else {
			player_targ = Owner(player);
			if (Typeof(player) != TYPE_PLAYER)
				obj_targ = player;
		}
	} else {
		player_targ = Owner(player);
		obj_targ = match_controlled(player, target);
		if (obj_targ == NOTHING) return;
		if (key & PS_ALL) {
			notify(player, "Can't specify a target and /all");
			return;
		}
		if (Typeof(obj_targ) == TYPE_PLAYER) {
			player_targ = obj_targ;
			obj_targ = NOTHING;
		}
	}
	key = key & ~PS_ALL;

	switch (key) {
	case PS_BRIEF:
	case PS_SUMM:
	case PS_LONG:
		break;
	default:
		notify(player, "Illegal combination of switches.");
		return;
	}

	/* Go do it */

	show_que(player, key, mudstate.qfirst, &pqtot, &pqent, &pqdel,
		player_targ, obj_targ, "Player");
	show_que(player, key, mudstate.qlfirst, &oqtot, &oqent, &oqdel,
		player_targ, obj_targ, "Object");
	show_que(player, key, mudstate.qwait, &wqtot, &wqent, &i,
		player_targ, obj_targ, "Wait");
	show_que(player, key, mudstate.qsemfirst, &sqtot, &sqent, &i,
		player_targ, obj_targ, "Semaphore");

	/* Display stats */

	bufp = alloc_mbuf("do_ps");
	if (Wizard(player))
		sprintf(bufp,"Totals: Player...%d/%d[%ddel]  Object...%d/%d[%ddel]  Wait...%d/%d  Semaphore...%d/%d",
			pqent, pqtot, pqdel, oqent, oqtot, oqdel,
			wqent, wqtot, sqent, sqtot);
	else
		sprintf(bufp,"Totals: Player...%d/%d  Object...%d/%d  Wait...%d/%d  Semaphore...%d/%d",
			pqent, pqtot, oqent, oqtot, wqent, wqtot, sqent, sqtot);
	notify(player, bufp);
	free_mbuf(bufp);
}

/* ---------------------------------------------------------------------------
 * do_queue: Queue management
 */

void do_queue (dbref player, dbref cause, int key, char *arg)
{
int	i, ncmds, was_disabled;

	was_disabled = 0;
	if (key == QUEUE_KICK) {
		i = atol(arg);
		if ((mudconf.control_flags & CF_DEQUEUE) == 0) {
			was_disabled = 1;
			mudconf.control_flags |= CF_DEQUEUE;
			notify(player,"Warning: automatic dequeueing is disabled.");
		}
		ncmds = do_top(i);
		if (was_disabled)
			mudconf.control_flags &= ~CF_DEQUEUE;
		if (!Quiet(player))
			notify(player,
				tprintf("%d commands processed.", ncmds));
	} else if (key == QUEUE_WARP) {
		i = atol(arg);
		if ((mudconf.control_flags & CF_DEQUEUE) == 0) {
			was_disabled = 1;
			mudconf.control_flags |= CF_DEQUEUE;
			notify(player,"Warning: automatic dequeueing is disabled.");
		}
		do_second(i);
		if (was_disabled)
			mudconf.control_flags &= ~CF_DEQUEUE;
		if (Quiet(player)) return;
		if (i > 0)
			notify(player,
				tprintf("WaitQ timer advanced %d seconds.", i));
		else if (i < 0)
			notify(player,
				tprintf("WaitQ timer set back %d seconds.", i));
		else
			notify(player,
				"Object queue appended to player queue.");
				
	}
}