/*
 * parse.c
 */

#include <ctype.h>
#include <sys/time.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <string.h>
#include <memory.h>
#include <malloc.h>
#include <stdlib.h>

#include "config.h"
#include "player.h"
#include "fix.h"
#include "clist.h"

/* externs */

extern char    *end_string();
extern int      nhash_update[], update[];
extern file     load_file(), load_file_verbose(char *, int);
extern void     sync_notes();
extern void     log(char *, char *);
extern char    *full_name(player *);
extern void     tell_player(player *, char *);
extern void     tell_room(room *, char *);
extern void     save_player(player *);
extern void     do_inform(player *, char *);
extern void     destroy_player(player *);
extern void     do_prompt(player *, char *);
extern void     sync_to_file(char, int);
extern void     handle_error(char *);
extern char    *convert_time(time_t);
extern void     move_to(player *, char *, int);
extern player  *find_player_global(char *);
extern char    shutdown_reason[];

/* interns */

struct command *last_com;
char           *stack_check;
int             nsync = 0, synct = 0, sync_counter = 0, note_sync = NOTE_SYNC_TIME;
int     mem_use_log_count = 0;
int             account_wobble = 1;
int             performance_timer = 0;
struct command *help_list = 0;
file            help_file = {0, 0};
time_t          shutdown_count = -1;

/* returns the first char of the input buffer */

char           *first_char(player * p)
{
   char           *scan;
   scan = p->ibuffer;
   while (*scan && isspace(*scan))
      *scan++;
   return scan;
}

/* what happens if bad stack detected */

void            bad_stack()
{
   int             missing;
   missing = (int) stack - (int) stack_check;
   if (last_com)
      sprintf(stack_check, "Bad stack in function %s, missing %d bytes",
         last_com->text, missing);
   else
      sprintf(stack_check, "Bad stack somewhere, missing %d bytes", missing);
   stack = end_string(stack_check);
   log("stack", stack_check);
   stack = stack_check;
}


/* flag changing routines */


/* returns the value of a flag from the flag list */

int             get_flag(flag_list * list, char *str)
{
   for (; list->text; list++)
      if (!strcmp(list->text, str))
    return list->change;
   return 0;
}


/* routine to get the next part of an arg */

char           *next_space(char *str)
{
   while (*str && *str != ' ')
      str++;
   if (*str == ' ')
   {
      while (*str == ' ')
    str++;
      str--;
   }
   return str;
}


/* view command lists */

void view_commands(player * p, char *str)
{
   struct command *comlist;
   char *oldstack;
   int s;
   player *p2;

   oldstack = stack;
   if (*str && p->residency & ADMIN)
   {
      p2 = find_player_global(str);
      if (!p2)
      {
         return;
      }
      sprintf(stack, " Commands available to %s ...\n", p2->name);
      stack = end_string(stack);
      tell_player(p, oldstack);
      stack = oldstack;
   } else
   {
      strcpy(stack, " Commands available to you ...\n");
      stack = strchr(stack, 0);
      p2 = p;
   }

   for (s = 0; s < 27; s++)
   {
      for (comlist = coms[s]; comlist->text; comlist++)
      {
         if ((!comlist->level || ((p2->residency) & comlist->level)) &&
	     (!comlist->andlevel || ((p2->residency) & comlist->andlevel)))
         {
            sprintf(stack, "%s, ", comlist->text);
            stack = strchr(stack, 0);
         }
      }
   }
   stack -= 2;
   *stack++ = '\n';
   *stack++ = 0;
   tell_player(p, oldstack);
   stack = oldstack;
}


void            view_sub_commands(player * p, struct command * comlist)
{
   char           *oldstack;
   oldstack = stack;

   strcpy(stack, " Available sub commands ...\n");
   stack = strchr(stack, 0);

   for (; comlist->text; comlist++)
      if (((!comlist->level) || ((p->residency) & (comlist->level))) &&
	  ((!comlist->andlevel) || ((p->residency) & (comlist->andlevel))))
      {
    sprintf(stack, "%s, ", comlist->text);
    stack = strchr(stack, 0);
      }
   stack -= 2;
   *stack++ = '\n';
   *stack++ = 0;
   tell_player(p, oldstack);
   stack = oldstack;
}

/* initialise the hash array */

void            init_parser()
{
   int             i;
   struct command *scan;
   scan = complete_list;
   for (i = 0; i < 27; i++)
   {
      coms[i] = scan;
      while (scan->text)
    scan++;
      scan++;
   }
}

/* see if any commands fit the bill */

char           *do_match(char *str, struct command * com_entry)
{
   char           *t;
   for (t = com_entry->text; *t; t++, str++)
      if (tolower(*str) != *t)
    return 0;
   if ((com_entry->space) && (*str) && (!isspace(*str)))
      return 0;
   while (*str && isspace(*str))
      str++;
   return str;
}

/* execute a function from a sub command list */

void            sub_command(player * p, char *str, struct command * comlist)
{
   char           *oldstack, *rol;
   void            (*fn) ();
   oldstack = stack;

   while (comlist->text)
   {
      if (((!comlist->level) || ((p->residency) & (comlist->level))) &&
	  ((!comlist->andlevel) || ((p->residency) & (comlist->andlevel))))
      {
    rol = do_match(str, comlist);
    if (rol)
    {
       last_com = comlist;
       stack_check = stack;
       fn = comlist->function;
       (*fn) (p, rol);
       if (stack != stack_check)
          bad_stack();
       sys_flags &= ~(FAILED_COMMAND | PIPE | ROOM_TAG | FRIEND_TAG | EVERYONE_TAG);
       command_type = 0;
       return;
    }
      }
      comlist++;
   }
   rol = str;
   while (*rol && !isspace(*rol))
      rol++;
   *rol = 0;
   sprintf(oldstack, " Cannot find sub command '%s'\n", str);
   stack = end_string(oldstack);
   tell_player(p, oldstack);
   stack = oldstack;
}


/* match commands to the main command lists */

void            match_commands(player * p, char *str)
{
   struct command *comlist;
   char           *rol, *oldstack, *space;
   void            (*fn) ();
   oldstack = stack;

   while (*str && *str == ' ')
      str++;
   space = strchr(str, 0);
   space--;
   while (*space == ' ')
      *space-- = 0;
   if (!*str)
      return;
   if (isalpha(*str))
      comlist = coms[((int) (tolower(*str)) - (int) 'a' + 1)];
   else
      comlist = coms[0];

   while (comlist->text)
   {
      if (((!comlist->level) || ((p->residency) & (comlist->level))) &&
	  ((!comlist->andlevel) || ((p->residency) & (comlist->andlevel))))
      {
    rol = do_match(str, comlist);
    if (rol)
    {
       last_com = comlist;
       stack_check = stack;
       fn = comlist->function;
       (*fn) (p, rol);
       if (stack != stack_check)
          bad_stack();
       sys_flags &= ~(FAILED_COMMAND | PIPE | ROOM_TAG | FRIEND_TAG | EVERYONE_TAG);
       command_type = 0;
       return;
    }
      }
      comlist++;
   }
   p->antipipe++;

/*
   if (p->antipipe > 30)
   {
      quit(p, 0);
      return;
   }
*/
   rol = str;
   while (*rol && !isspace(*rol))
      rol++;
   *rol = 0;
   sprintf(oldstack, " Cannot find command '%s'\n", str);
   stack = end_string(oldstack);
   tell_player(p, oldstack);
   stack = oldstack;
}

/* handle input from one player */

void            input_for_one(player * p)
{
   char           *pick;
   void            (*fn) ();

   if (p->input_to_fn &&
			!(current_room == prison && !(p->residency & (ADMIN | SU))))
   {
      p->idle = 0;
      p->idle_msg[0] = 0;
      last_com = &input_to;
      stack_check = stack;
      fn = p->input_to_fn;
      (*fn) (p, p->ibuffer);
      if (stack != stack_check)
         bad_stack();
      sys_flags &= ~(FAILED_COMMAND | PIPE | ROOM_TAG | FRIEND_TAG
                     | EVERYONE_TAG);
      command_type = 0;
      return;
   }
   if (!p->ibuffer[0])
      return;
   p->idle = 0;
   p->idle_msg[0] = 0;
   action = "doing command";
   if (p->ibuffer[0] != '#')
   {
      if (p->saved_flags & CONVERSE)
      {
         pick = p->ibuffer;
         while (*pick && isspace(*pick))
            pick++;
         if (*pick)
            if (*pick == '/')
               if (current_room == prison &&
                   !(p->residency & (ADMIN | SU)))
                  sub_command(p, pick + 1, restricted_list);
               else
                  match_commands(p, pick + 1);
            else
               say(p, pick);
      } else if (current_room == prison && !(p->residency & (ADMIN | SU)))
         sub_command(p, p->ibuffer, restricted_list);
      else
         match_commands(p, p->ibuffer);
   }
}


/* scan through the players and see if anything needs doing */

void            process_players()
{
   player         *scan;
   char           *oldstack;

   for (scan = flatlist_start; scan; scan = scan->flat_next)
   {
     if (scan->flat_next)
       if (((player *)scan->flat_next)->flat_previous != scan)
	 {
	   raw_wall("\n\n   -=> Non-terminated flatlist <=-\n\n");
	   raw_wall("\n\n   -=> Dumping end off of list <=-\n\n");
	   scan->flat_next=NULL;
	 }

      if ((scan->fd < 0) || (scan->flags & PANIC) ||
          (scan->flags & CHUCKOUT))
      {

         oldstack = stack;
         current_player = scan;

         if (scan->location && scan->name[0] && !(scan->flags & RECONNECTION))
         {
            sprintf(stack, " %s suddenly dissolve%s into a squintillion dots"
                           " that quickly disperse.\n",
                    scan->name, single_s(scan));
            stack = end_string(stack);
            tell_room(scan->location, oldstack);
            stack = oldstack;
            save_player(scan);
         }
         if (!(scan->flags & RECONNECTION))
         {
            command_type = 0;
	    if (scan->gender==PLURAL)
	      do_inform(scan, "[%s have disconnected] %s");
	    else
	      do_inform(scan, "[%s has disconnected] %s");
            if (scan->saved && !(scan->flags & NO_SAVE_LAST_ON))
               scan->saved->last_on = time(0);
         }
         if (sys_flags & VERBOSE || scan->residency == 0)
         {
            if (scan->name[0])
               sprintf(oldstack, "%s has disconnected from %s", scan->name,
                       scan->inet_addr);
            else
               sprintf(oldstack, "Disconnect from login. [%s]",
                       scan->inet_addr);
            stack = end_string(oldstack);
            log("newconn", oldstack);
         }
         destroy_player(scan);
         current_player = 0;
         stack = oldstack;
      } else if (scan->flags & INPUT_READY)
      {
/* there used to be this here...
            if (!(scan->lagged) && !(scan->flags & PERM_LAG))
   for reference... */

         if (!(scan->lagged))
         {
            current_player = scan;
            current_room = scan->location;
            input_for_one(scan);
            action = "processing players";
            current_player = 0;
            current_room = 0;

#ifdef PC
            if (scan->flags & PROMPT && scan == input_player)
#else
            if (scan->flags & PROMPT)
#endif
            {
               if (scan->saved_flags & CONVERSE)
                  do_prompt(scan, scan->converse_prompt);
               else
                  do_prompt(scan, scan->prompt);
            }
         }
         memset(scan->ibuffer, 0, IBUFFER_LENGTH);
         scan->flags &= ~INPUT_READY;
      }
   }
}




/* timer things */


/* automessages */

void            do_automessage(room * r)
{
   int             count = 0, type;
   char           *scan, *oldstack;
   oldstack = stack;
   scan = r->automessage.where;
   if (!scan)
   {
      r->flags &= ~AUTO_MESSAGE;
      return;
   }
   for (; *scan; scan++)
      if (*scan == '\n')
    count++;
   if (!count)
   {
      FREE(r->automessage.where);
      r->automessage.where = 0;
      r->automessage.length = 0;
      r->flags &= ~AUTO_MESSAGE;
      stack = oldstack;
      return;
   }
   count = rand() % count;
   for (scan = r->automessage.where; count; count--, scan++)
      while (*scan != '\n')
    scan++;
   while (*scan != '\n')
      *stack++ = *scan++;
   *stack++ = '\n';
   *stack++ = 0;
   type = command_type;
   command_type = AUTO;
   tell_room(r, oldstack);
   command_type = type;
   r->auto_count = r->auto_base + (rand() & 63);
   stack = oldstack;
}


/* file syncing */

void            do_sync()
{
   int             origin;
   action = "doing sync";
   sync_counter = SYNC_TIME;
   origin = synct;
   while (!update[synct])
   {
      synct = (synct + 1) % 26;
      if (synct == origin)
    break;
   }
   if (update[synct])
   {
      sync_to_file(synct + 'a', 0);
      synct = (synct + 1) % 26;
   }
}

/* this is the actual timer pling */

void            actual_timer()
{
   static int      pling = TIMER_CLICK;
   player         *scan, *wibble;
   time_t t;

   if (sys_flags & PANIC)
      return;

#if !defined(hpux) && !defined(linux)
   if ((int) signal(SIGALRM, actual_timer) < 0)
      handle_error("Can't set timer signal.");
#endif /* hpux */

   t = time(0);
   if ((splat_timeout - t) <= 0)
      splat1 = splat2 = 0;
   pling--;
   if (pling)
      return;

   pling = TIMER_CLICK;

   sys_flags |= DO_TIMER;

   if (mem_use_log_count > 0)
      mem_use_log_count--;
   if (shutdown_count > 0)
      shutdown_count--;

   for (scan = flatlist_start; scan; scan = scan->flat_next)
      if (!(scan->flags & PANIC))
      {
    scan->idle++;
    scan->total_login++;
    if (scan->script && scan->script > 1)
       scan->script--;
    if (scan->timer_fn && scan->timer_count > 0)
       scan->timer_count--;
    if (scan->no_shout > 0)
       scan->no_shout--;
    if (scan->no_move > 0)
       scan->no_move--;
    if (scan->lagged > 0)
       scan->lagged--;
    if (scan->shout_index > 0)
       scan->shout_index--;

    if (scan->jail_timeout > 0)
       scan->jail_timeout--;
      }
   net_count--;
   if (!net_count)
   {
      net_count = 10;
      in_total += in_current;
      out_total += out_current;
      in_pack_total += in_pack_current;
      out_pack_total += out_pack_current;
      in_bps = in_current / 10;
      out_bps = out_current / 10;
      in_pps = in_pack_current / 10;
      out_pps = out_pack_current / 10;
      in_average = (in_average + in_bps) >> 1;
      out_average = (out_average + out_bps) >> 1;
      in_pack_average = (in_pack_average + in_pps) >> 1;
      out_pack_average = (out_pack_average + out_pps) >> 1;
      in_current = 0;
      out_current = 0;
      in_pack_current = 0;
      out_pack_current = 0;
   }
}


/* the timer function */

void            timer_function()
{
   player         *scan, *old_current;
   void            (*fn) ();
   room           *r, **list;
   char           *oldstack, *text;
   int             count = 0, pcount = 0;
   char           *action_cpy;
   struct tm      *ts;
   time_t          t;
#if !defined(linux)
   struct mallinfo minfo;
#else
   struct mstats memstats;
#endif /* LINUX */

   if (!(sys_flags & DO_TIMER))
      return;
   sys_flags &= ~DO_TIMER;

   waitpid((pid_t) - 1, (int *) 0, WNOHANG);
   /* wait3(0,WNOHANG,0); */

   old_current = current_player;
   action_cpy = action;

   oldstack = stack;

   if (mem_use_log_count == 0)
   {
#if !defined(linux)
      minfo = mallinfo();
      sprintf(stack, "Total arena space - %d", minfo.arena);
#else
      memstats = mstats();
      sprintf(stack, "Total heap size - %d", memstats.bytes_total);
#endif /* LINUX */
      stack = end_string(stack);
      log("mem", oldstack);
      stack = oldstack;
      mem_use_log_count = 60;
   }

   if (shutdown_count > -1)
   {
      command_type |= HIGHLIGHT;
      switch (shutdown_count)
      {
      case 31536000:
	raw_wall("\n\n-=> We'll be shutting down this time next year <=-\n"
		 "-=> Anyone who is still on at this time wins a "
		 "prize <=-\n\n");
	break;
      case 86400:
	raw_wall("\n\n-=> We'll be shutting down this time tomorrow - "
		 "Helpful arent we! <=-\n\n");
	break;
      case 3600:
	 raw_wall("\n\n-=> We'll be rebooting in 1 hour, "
		  "pity the admin that sets it to such a "
		  "stupid time. <=-\n\n");
	break;
      case 900:
	raw_wall("\n\n-=> We'll be rebooting in 15 mins, "
		 "You can 5 eggs in that time! <=-\n\n");
	break;
      case 300:
	raw_wall("\n\n-=> We'll be rebooting in 5 mins, "
		 "go put the kettle on now <=-\n\n");
	break;
      case 180:
	raw_wall("\n\n-=> 3 minutes to go until the reboot, "
		 "time to make the tea <=-\n\n");
	break;
      case 60:
	raw_wall("\n\n-=> 1 minute left to the reboot, "
		 "time to say bye to people <=-\n\n");
	break;
      case 30:
	raw_wall("\n\n-=> 30 seconds until the reboot, "
		 "hold on to your drinks <=-\n\n");
	break;
      case 15:
	raw_wall("\n\n-=> 15 seconds to go to reboot, "
		 "see you in a little while! <=-\n\n");
	break;
      case 10:
	raw_wall("\n\n-=> 10 seconds to go to reboot, "
		 "The end is nigh! <=-\n\n");
	break;
      case 5:
	raw_wall("\n\n-=> 5 seconds to go to reboot, "
		 "The end is nigh(er)! <=-\n\n");
	break;
      case 1:
	raw_wall("\n\n-=> 1 second to go to reboot, "
		 "The end is REALLY nigh! <=-\n\n");
	break;
      case 0:
	log("shutdown", shutdown_reason);
	sys_flags |= SHUTDOWN;
	stack = oldstack;
	return;
      }
      command_type &= ~HIGHLIGHT;
    }

   if (sync_counter)
      sync_counter--;
   else
      do_sync();

   if (note_sync)
      note_sync--;
   else
   {
      note_sync = NOTE_SYNC_TIME;
      sync_notes(1);
   }

   align(stack);
   list = (room **) stack;

   for (scan = flatlist_start; scan; scan = scan->flat_next)
   {
      if (!(scan->flags & PANIC))
      {
         if (scan->script && scan->script == 1)
         {
            text = stack;
            sprintf(text, " Time is now %s.\n"
                          " Scripting stopped ...\n", convert_time(time(0)));
            stack = end_string(text);
            tell_player(scan, text);
            stack = text;
            scan->script = 0;
         }
         if (scan->timer_fn && !scan->timer_count)
         {
            current_player = scan;
            fn = scan->timer_fn;
            (*fn) (scan);
            scan->timer_fn = 0;
            scan->timer_count = -1;
         }
         current_player = old_current;
         action = "processing autos";
         r = scan->location;
         if (r)
         {
            pcount++;
            if (r->flags & AUTO_MESSAGE && !(r->flags & AUTOS_TAG))
            {
               if (!r->auto_count)
                  do_automessage(r);
               else
                  r->auto_count--;
               *(room **) stack = r;
               stack += sizeof(room *);
               count++;
               r->flags |= AUTOS_TAG;
            }
         }
/* Jail timeout thang */
         if (scan->jail_timeout == 0 && scan->location == prison)
         {
            command_type |= HIGHLIGHT;
            tell_player(scan, " After serving your sentence you are flung out"
                              " to society again.\n");
            command_type &= ~HIGHLIGHT;
            move_to(scan, ENTRANCE_ROOM, 0);
         }
      }
   }
   for (; count; count--, list++)
      (*list)->flags &= ~AUTOS_TAG;
   stack = oldstack;
   action = action_cpy;
   current_players = pcount;

   t = time(0);
   ts = localtime(&t);

   /*
    * if (!account_wobble && (ts->tm_hour)==0) { account_wobble=1;
    * do_birthdays(); }
    */
   if (account_wobble == 1 && ((ts->tm_hour) == 3))
   {
      account_wobble = 2;
      /* system("bin/account &"); */
   }
   if (account_wobble == 2 && ((ts->tm_hour) > 3))
      account_wobble = 1;
}

/* the help system (aargh argh argh) */


/* look through all possible places to find a bit of help */

struct command *find_help(char *str)
{
   struct command *comlist;
   if (isalpha(*str))
      comlist = coms[((int) (tolower(*str)) - (int) 'a' + 1)];
   else
      comlist = coms[0];

   for (; comlist->text; comlist++)
      if (do_match(str, comlist))
    return comlist;
   comlist = help_list;
   if (!comlist)
      return 0;
   for (; comlist->text; comlist++)
      if (do_match(str, comlist))
    return comlist;

   return 0;
}


void            next_line(file * hf)
{
   while (hf->length > 0 && *(hf->where) != '\n')
   {
      hf->where++;
      hf->length--;
   }
   if (hf->length > 0)
   {
      hf->where++;
      hf->length--;
   }
}

void            init_help()
{
   file            hf;
   struct command *found, *hstart;
   char           *oldstack, *start, *scan;
   int             length;
   oldstack = stack;


   if (sys_flags & VERBOSE)
      log("boot", "Loading help pages");


   if (help_list)
      FREE(help_list);
   help_list = 0;

   if (help_file.where)
      FREE(help_file.where);
   help_file = load_file("doc/help");
   hf = help_file;

   align(stack);
   hstart = (struct command *) stack;

   while (hf.length > 0)
   {
      while (hf.length > 0 && *(hf.where) != ':')
    next_line(&hf);
      if (hf.length > 0)
      {
    scan = hf.where;
    next_line(&hf);
    *scan++ = 0;
    while (scan != hf.where)
    {
       start = scan;
       while (*scan != ',' && *scan != '\n')
          scan++;
       *scan++ = 0;
       found = find_help(start);
       if (!found)
       {
          found = (struct command *) stack;
          stack += sizeof(struct command);
          found->text = start;
          found->function = 0;
          found->level = 0;
          found->andlevel = 0;
          found->space = 1;
       }
       found->help = hf.where;
    }
      }
   }
   *(hf.where - 1) = 0;
   found = (struct command *) stack;
   stack += sizeof(struct command);
   found->text = 0;
   found->function = 0;
   found->level = 0;
   found->andlevel = 0;
   found->space = 0;
   found->help = 0;
   length = (int) stack - (int) hstart;
   help_list = (struct command *) MALLOC(length);
   memcpy(help_list, hstart, length);
   stack = oldstack;
}


/* load that help file in  */

int             get_help(player * p, char *str)
{
   int             fail = 0;
   file            text;
   char           *oldstack;

   oldstack = stack;
   if (*str == '.')
      return 0;

   sprintf(stack, "doc/%s.help", str);
   stack = end_string(stack);
   text = load_file_verbose(oldstack, 0);
   if (text.where)
   {
      if (*(text.where))
      {
    stack = oldstack;
    sprintf(stack, "-- Help ----------------------------------------"
       "---------------------\n%s-----------------------------"
       "----------------------------------------\n", text.where);
    stack = end_string(stack);
    pager(p, oldstack, 1);
    fail = 1;
      } else
    fail = 0;
      free(text.where);
   }
   stack = oldstack;
   return fail;
}



int             get_victim(player * p, char *text)
{
   return 0;
}


/* the help command */

void help(player * p, char *str)
{
   char *oldstack;
   struct command *fn;
   oldstack = stack;

   if (!*str)
   {
      if (p->residency)
         str = "general";
      else
         str = "newbie";
   }
   fn = find_help(str);
   if (!fn || !(fn->help))
   {
      if (get_help(p, str))
         return;
      sprintf(stack, " Cannot find any help on the subject of '%s'\n", str);
      stack = end_string(stack);
      if (p->saved_flags & NO_PAGER)
         tell_player(p, oldstack);
      else
         pager(p, oldstack, 0);
      stack = oldstack;
      return;
   }
   if (!strcasecmp(str, "newbie"))
      if (get_victim(p, fn->help))
      {
         stack = oldstack;
         return;
      }
   sprintf(stack, "-- Help ---------------------------------------------"
           "----------------\n%s---------------------------------------"
           "------------------------------\n", fn->help);
   stack = end_string(stack);
   if (p->saved_flags & NO_PAGER)
      tell_player(p, oldstack);
   else
      pager(p, oldstack, 0);
   stack = oldstack;
}