/* Cque.c */

#include "os.h"
#include "config.h"
#include "db.h"
#include "interface.h"
#include "match.h"
#include "externs.h"

#ifdef MEM_CHECK
#include "mem_check.h"
#endif


static void big_que (dbref player, char *command, dbref cause);

typedef struct bque BQUE;

struct bque {
  BQUE *next;
  dbref player;                 /* player who will do command */
  dbref cause;                  /* player causing command (for %N) */
  int left;                     /* seconds left until execution */
  char *env[10];                /* environment, from wild match */
  char *comm;                   /* command to be executed */
};

static BQUE *qfirst = NULL, *qlast = NULL, *qwait = NULL;
static BQUE *qlfirst = NULL, *qllast = NULL;

void parse_que (dbref player, char *command, dbref cause        /* cause is needed to determine priority */
  )
{
  char buff[BUFFER_LEN], *s, *r;
  s = buff;
  strcpy (buff, command);
  while (r = parse_up (&s, ';')) {
    big_que (player, r, cause);
  }
}

static int add_to (dbref player, int am)
{
  int num = 0;
  ATTR *a;
  char buff[MAX_COMMAND_LEN];

  player = db[player].owner;
  a = atr_get (player, "QUEUE");
  if (a)
    num = atoi (a->value);
  num += am;
  if (num > 0)
    sprintf (buff, "%d", num);
  else
    *buff = 0;
  (void) atr_add (player, "QUEUE", buff, GOD, NOTHING);
  return (num);
}

static void big_que (dbref player, char *command, dbref cause)
{
  int a;
  BQUE *tmp;

  if ((Typeof (player) != TYPE_PLAYER) && (db[player].flags & HALT))
    return;
  /* make sure player can afford to do it */
  if (!payfor (player,
      QUEUE_COST + (((OS_RAND () & QUEUE_LOSS) == 0) ? 1 : 0))) {
    notify (db[player].owner, "Not enough money to queue command.");
    return;
  }
  if (add_to (player, 1) > QUEUE_QUOTA) {
    notify (db[player].owner, "Run away objects, commands halted");
    do_halt (db[player].owner, "");
    /* halt means no command execution allowed */
    db[player].flags |= HALT;
    return;
  }
  tmp = (BQUE *) malloc (sizeof (BQUE));
  strcpy ((tmp->comm = (char *) malloc (strlen (command) + 1)), command);
#ifdef MEM_CHECK
  add_check ("bqueue");
  add_check ("bqueue_comm");
#endif
  tmp->player = player;
  tmp->next = NULL;
  tmp->left = 0;
  tmp->cause = cause;
  for (a = 0; a < 10; a++)
    if (!wptr[a])
      tmp->env[a] = NULL;
    else {
      strcpy ((tmp->env[a] =
          (char *) malloc (strlen (wptr[a]) + 1)), wptr[a]);
#ifdef MEM_CHECK
      add_check ("bqueue_env");
#endif
    }
  if (Typeof (cause) == TYPE_PLAYER) {
    if (qlast) {
      qlast->next = tmp;
      qlast = tmp;
    } else
      qlast = qfirst = tmp;
  } else {
    if (qllast) {
      qllast->next = tmp;
      qllast = tmp;
    } else
      qllast = qlfirst = tmp;
  }
}

void wait_que (dbref player, int wait, char *command, dbref cause)
{
  BQUE *tmp;
  int a;
  /* make sure player can afford to do it */
  if (!payfor (player,
      QUEUE_COST + (((OS_RAND () & QUEUE_LOSS) == 0) ? 1 : 0))) {
    notify (player, "Not enough money to queue command.");
    return;
  }
  tmp = (BQUE *) malloc (sizeof (BQUE));
  strcpy ((tmp->comm = (char *) malloc (strlen (command) + 1)), command);
#ifdef MEM_CHECK
  add_check ("bqueue");
  add_check ("bqueue_comm");
#endif
  tmp->player = player;
  tmp->next = qwait;
  tmp->left = wait;
  tmp->cause = cause;
  for (a = 0; a < 10; a++)
    if (!wptr[a])
      tmp->env[a] = NULL;
    else {
      strcpy ((tmp->env[a] =
          (char *) malloc (strlen (wptr[a]) + 1)), wptr[a]);
#ifdef MEM_CHECK
      add_check ("bqueue_env");
#endif
    }
  qwait = tmp;
}

/* call every second to check for wait queue commands */
void do_second (void)
{
  BQUE *trail = NULL, *point, *next;
  /* 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 (qlfirst) {
    if (qlast)
      qlast->next = qlfirst;
    else
      qfirst = qlfirst;
    qlast = qllast;
    qllast = qlfirst = NULL;
  }
  for (point = qwait; point; point = next)
    /*
     * Note: this would be 0 except the command is being put in the low
     * priority queue to be done in one second anyways
     */
    if (point->left-- <= 1) {
      int a;
      giveto (point->player, QUEUE_COST);
      for (a = 0; a < 10; a++) {
        wptr[a] = point->env[a];
      }
      parse_que (point->player, point->comm, point->cause);
      if (trail)
        trail->next = next = point->next;
      else
        qwait = next = point->next;
      for (a = 0; a < 10; a++)
        if (point->env[a]) {
          free ((char *) point->env[a]);
#ifdef MEM_CHECK
          del_check ("bqueue_env");
#endif
        }
      if (point->comm)
        free ((char *) point->comm);
      free ((char *) point);
#ifdef MEM_CHECK
      del_check ("bqueue_comm");
      del_check ("bqueue");
#endif
    } else
      next = (trail = point)->next;
}

int test_top (void)
{
  return (qfirst ? 1 : 0);
}

/* execute one command off the top of the queue */
int do_top (void)
{
  int a;
  int i = 0;
  BQUE *tmp;
  dbref player;
  char tbuf1[BUFFER_LEN], tbuf2[BUFFER_LEN];
  char tbuf3[BUFFER_LEN], *s;

  if (!qfirst)
    return (0);
  if (qfirst->player && !(db[qfirst->player].flags & GOING)) {
    giveto (qfirst->player, QUEUE_COST);
    cplr = qfirst->player;
    strcpy (ccom, qfirst->comm);
    add_to (player = qfirst->player, -1);
    qfirst->player = 0;
    if ((Typeof (player) == TYPE_PLAYER) || !(db[player].flags & HALT)) {
      for (a = 0; a < 10; a++) {
        wptr[a] = qfirst->env[a];
      }
      strcpy (tbuf2, uncompress (qfirst->comm));
      tbuf3[0] = '\0';
      for (s = tbuf2; *s; tbuf3[i++] = toupper ((int)*s++));
      tbuf3[i++] = '\0';
      if ((strstr (tbuf3, "@DOL")) == NULL)
        strcpy (tbuf1, pronoun_substitute (qfirst->cause, tbuf2, player));
      else
        strcpy (tbuf1, tbuf2);
      if (IS (player, TYPE_THING, THING_VERBOSE))
        notify (db[player].owner, tprintf ("#%d] %s", player, tbuf1));
      process_command (player, tbuf1, qfirst->cause);
    }
  }
  tmp = qfirst->next;
  for (a = 0; a < 10; a++)
    if (qfirst->env[a]) {
      free ((char *) qfirst->env[a]);
#ifdef MEM_CHECK
      del_check ("bqueue_env");
#endif
    }
  if (qfirst->comm)
    free ((char *) qfirst->comm);
  free ((char *) qfirst);
#ifdef MEM_CHECK
  del_check ("bqueue_comm");
  del_check ("bqueue");
#endif
  if (!(qfirst = tmp))
    qlast = NULL;
  return (1);
}

/* tell player what commands they have pending in the queue */
void do_queue (dbref player, const char *what)
{
  BQUE *tmp;
  dbref victim = NOTHING;
  int everything = 0;
  int quick = 0;
  int pq = 0, oq = 0, wq = 0;
  int tpq = 0, toq = 0, twq = 0;

  if (!string_compare (what, "count")) {
    victim = player;
    everything = 1;
    quick = 1;
  } else if (Wizard (player)) {
    if (!what || !*what)
      victim = player;
    else if (!string_compare (what, "all")) {   /* do the whole thing */
      victim = player;
      everything = 1;
    } else {
      init_match (player, what, TYPE_PLAYER);
      match_player ();
      match_absolute ();
      match_me ();
      victim = match_result ();
    }
  } else {
    victim = player;
  }

  switch (victim) {
  case NOTHING:
    notify (player, "I couldn't find that player.");
    break;
  case AMBIGUOUS:
    notify (player, "I don't know who you mean!");
    break;
  default:
    if (everything == 1)
      notify (player, "Queue for : all");
    else
      notify (player, tprintf ("Queue for : %s", db[victim].name));

    if (!quick)
      notify (player, "Player Queue:");
    for (tmp = qfirst; tmp; tmp = tmp->next) {
      tpq++;
      if ((db[tmp->player].owner == db[victim].owner) || (everything)) {
        pq++;
        if (!quick)
          notify (player, tprintf ("%s:%s", unparse_object (player,
                tmp->player), tmp->comm));
      }
    }
    if (!quick)
      notify (player, "Object Queue:");
    for (tmp = qlfirst; tmp; tmp = tmp->next) {
      toq++;
      if ((db[tmp->player].owner == db[victim].owner) || (everything)) {
        oq++;
        if (!quick)
          notify (player, tprintf ("%s:%s", unparse_object (player,
                tmp->player), tmp->comm));
      }
    }
    if (!quick)
      notify (player, "Wait Queue:");
    for (tmp = qwait; tmp; tmp = tmp->next) {
      twq++;
      if ((db[tmp->player].owner == db[victim].owner) || (everything)) {
        wq++;
        if (!quick)
          notify (player, tprintf ("[%d]%s:%s", tmp->left,
              unparse_object (player, tmp->player), tmp->comm));
      }
    }
    notify (player, "------------  Queue Done  ------------");
    notify (player,
      tprintf ("Totals: Player...%d/%d   Object...%d/%d   Wait...%d/%d",
        pq, tpq, oq, toq, wq, twq));
  }
}

/* remove all queued commands from a certain player */
/* it cannot be called on specific objects */

void do_halt (dbref player, char *ncom)
{
  BQUE *tmp, *trail = NULL, *point, *next;
  int num = 0;
  notify (db[player].owner, "Halted.");
  for (tmp = qfirst; tmp; tmp = tmp->next)
    if ((tmp->player == player) || (db[tmp->player].owner == player)) {
      num--;
      giveto (player, QUEUE_COST);
      tmp->player = 0;
    }
  for (tmp = qlfirst; tmp; tmp = tmp->next)
    if ((tmp->player == player) || (db[tmp->player].owner == player)) {
      num--;
      giveto (player, QUEUE_COST);
      tmp->player = 0;
    }
  /* remove wait q stuff */
  for (point = qwait; point; point = next)
    if ((point->player == player) || (db[point->player].owner == player)) {
      int a;

      giveto (player, QUEUE_COST);
      if (trail)
        trail->next = next = point->next;
      else
        qwait = next = point->next;
      for (a = 0; a < 10; a++)
        if (point->env[a]) {
          free ((char *) point->env[a]);
#ifdef MEM_CHECK
          del_check ("bqueue_env");
#endif
        }
      if (point->comm)
        free ((char *) point->comm);
      free ((char *) point);
#ifdef MEM_CHECK
      del_check ("bqueue_comm");
      del_check ("bqueue");
#endif
    } else
      next = (trail = point)->next;

  if (db[player].owner == player)
    (void) atr_add (player, "QUEUE", "", GOD, NOTHING);
  else
    add_to (player, num);
  if (*ncom)
    parse_que (player, ncom, player);
}

void do_halt1 (dbref player, const char *arg1, const char *arg2)
{
  dbref victim;
  char temp[BUFFER_LEN];
  if (*arg1 == '\0')
    do_halt (player, "");
  else {
    init_match (player, arg1, NOTYPE);
    match_neighbor ();
    match_possession ();
    match_me ();
    match_absolute ();
    match_player ();
    if ((victim = noisy_match_result ()) == NOTHING)
      return;
    if ((db[victim].owner != player) && (!Wizard (player))) {
      notify (player, "Permission denied.");
      return;
    }
    if (Typeof (victim) != TYPE_PLAYER) {
      sprintf (temp, "#%d", victim);
      do_set (player, temp, "HALT");
    } else {
      if (victim != player)
        notify (player, "Halted.");
      do_halt (victim, (char*)arg2);
    }
  }
}

void do_allhalt (dbref player)
{
  dbref victim;
  if (!Wizard (player)) {
    notify (player, "Sorry, you can only halt your own items.");
    return;
  }
  for (victim = 0; victim < db_top; victim++) {
    if (Typeof (victim) == TYPE_PLAYER) {
      notify (victim, tprintf ("Your objects have been globally halted by %s",
          db[player].name));
      do_halt (victim, "");
    }
  }
}