/*

  This is a mbot module based on C-ALICE. You should see http://alicebot.org
  in order to understand ALICE's purpose and configuration. The required
  aiml and other data files can also be found there, but i strongly
  recommend the ones i have at:
    http://alunos.uevora.pt/~l12697/prog/mbot/alice.tgz
  Just unpack the file inside mbot's directory and edit at least
  alice/log/localuser.txt to your needs. Without those files, this module is
  useless.

  Error and conversation logging is done through mbot's own services, thus,
  "errorfile" and "logfile" in alice.ini are ignored.

  I added 2 tags to ALICE, <seen> and <word>, which integrate the
  functionality of the "!seen" and "??" commands into aiml (see mbot.aiml).

  A great deal of bugs from C-ALICE were fixed, but some still lurk around
  (I'm amazed how they distribute such buggy software...), particulary some
  related to <srai> tags.

  Original credits:

  Program C: an implementation of ALICE and AIML in C.
  Copyright (C) 2000 Jacco Bikker

  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.     

  This program has been modified significantly since Jacco's
  original release, please see THANKS in the text directory 
  for contributions.

*/

#include "mbot.h"

#define SOURCE s->script.source
#define DEST s->script.dest
#define SEND_TEXT s->script.send_text

#define MAX_VARNAME_SIZE 40		// max size the name of a var can be
#define MAX_VARVAL_SIZE MSG_SIZE	// max size the value of a var can be
#define MAX_LINE_SIZE 16384		// max size a line of text can be
#define MAX_RANDLIST_SIZE 200		// max # of <li></li> in a random
#define MAX_SRAI 20			// max <srai> followed in a response

/////////////////////
// class definition
/////////////////////

class CAlice {
public:

  CAlice (CNetServer *, c_char, c_char, CLog *);
  ~CAlice (void);

  // Breaks lines, cleans up text and logs, then sends to respond2
  char *respond (char *text);
  int setvar (int protect, char *varname, c_char value);
  // saves internal variables to file
  void savevars (char *varfile);
  // saves default vars to a new file. Also loads defaults into memory
  void loadvars (char *varfile );

  CNetServer *s;
  bool initialized;

private:

  c_char getvar (c_char varname, c_char vardefault);
  char *get_pattern_matched (void);
  // checks a line of AIML for syntax errors
  int checkline (char *line);
  // splits the aiml files into patterns.txt and templates.txt
  int prepare ();

  // makes alice's answer pretty. (cleans up text)
  void print (char *line);
  // makes substitutions on text
  void substitute (CText *subst, char *text);
  // gets a response
  char* respond2 (char *text);

  void replacer (char *line);
  int splitaiml (c_char src);
  int match (c_char text, c_char pattern, int final);
  void randomize (char *text);
  void reevaluate (char *line );

  typedef struct _var_array { 
    int protect;
    int def;
    char *varname;
    char *value;
    struct _var_array *next;
  } var_array;

  /* star holds "*" and "_", that holds our that, pbuffer is sort of
     a general scrap variable, although it does eventually hold our answer. */
  char *star, *that, *pbuffer;

  // best holds the pattern matched
  char *best;

  // char's for our init file
  FILE *aimlfile, *errorfile, *templatefile, *patternfile;
  CText *personfile, *person2file, *substitutefile, *personffile;
  char *datafile, *unsortedfile;

  // should we log?
  bool logging, log_error;
  CLog *log;

  // new variables for the getvar setvar code
  var_array *root_var;

  long chunk[28];

  /* cc is checkcount,
     hp is have we had a pattern?  If we have it's set to one.
     hh is had that?
     he is had template. */
  int cc, hp, hh, he;

  /* topic holds our topic which we convert to conditional tags. */
  /* this isn't coded yet */
  char topic[MAX_VARVAL_SIZE+1];

  FILE *unsorted;
  char *vars;

  int srai_num;		// number of <srai> tags followed in a response

};

CList *alice_list;

///////////////
// prototypes
///////////////

extern "C" {

// removes all spaces including '\t' '\v' ...
static void spacetrim (char *line);
// finds string from end
static char *strlstr (char *line, c_char token);
static void stripurl (char *line );
// replaces string1 with string2 in line
static int replace (char *line, c_char string1, c_char string2);
// removes all the text from first to last
static void strremove (char *text, c_char first, c_char last);
// cleans up text
static void cleaner (char *text);

static CAlice *server2alice (CNetServer *);
static void alice_event (CNetServer *);
static void alice_conf (CNetServer *, c_char);
static void alice_stop (CModule *);
static void alice_start (CModule *);

}

//////////////////
// alice helpers
//////////////////

static char *
cleanproc (char *line, int step)
{
  char symb;
  while (1)
    {
      symb = *line;
      if (isspace (symb))
        {
          line += step;
          continue;
        }
      break;
    }
  return line;
}

static void
spacetrim (char *line)
{
  char *scrap;
  int len;

  len = strlen (line);
  if (!len)
    return;

  scrap = cleanproc (line + len - 1, -1);
  scrap[1] = 0;

  scrap = cleanproc (line, 1);
  if (scrap == line)
    return;
  memmove (line, scrap, strlen (scrap) + 1);
}

static char *
strlstr (char *line, c_char token)
{
  char *pos = NULL;
  while ((line = strstr (line, token)))
    {
      pos = line;
      line++;
    }
  return pos;
}

static void
stripurl (char *line)
{
  strremove (line, "<a href=", ">");
  replace (line, "</a>", "");
  strremove (line, "<A HREF=", ">");
  replace (line, "</A>", "");
  while (replace (line, "<br>", ""));
  while (replace (line, "<BR>", ""));
  replace (line, "<ul>", "");
  while (replace (line, "<li>", ""));
  while (replace (line, "</li>", ""));
  replace (line, "</ul>", "");
  strremove (line, "<APPLET", "</APPLET>");
  strremove (line, "<applet", "</applet>");        
  replace (line, "&lt;", "<");
  replace (line, "&gt;", ">");
  replace (line, "\n", "");
}

static void
strremove (char *text, c_char first, c_char last)
{
  char *pos1, *pos2;

  if (!first || !last || !text)
    return;
  if (!(pos1 = strstr (text, first)))
    return;
  if (!(pos2 = strstr (pos1, last)))
    return;
  if (pos1 >= pos2)
    return;
  pos2 += strlen (last);
  memmove (pos1, pos2, strlen (pos2) + 1);
}

static int
replace (char *string, c_char strfrom, c_char strto)
{
  char *pos, *tmp;
  size_t len, len2;

  if (!string || !strfrom || !strto)
    return 0;
  pos = strstr (string, strfrom);
  if (!pos)
    return 0;

  len = strlen (strfrom);
  len2 = strlen (strto);

  if (len > len2)
    {
      tmp = pos + len - len2;
      memmove (pos, tmp, strlen (tmp) + 1);
    }
  if (len < len2)
    {
      char buf[MAX_LINE_SIZE+1];
      int buflen;

      tmp = pos + len;
      buflen = strlen (tmp) + 1;
      memcpy (buf, tmp, buflen);
      memcpy (pos + len2, buf, buflen);
    }

  strncpy (pos, strto, len2);
  return 1;
}

static void
cleaner (char *text)
{
  char symb;
  char *oldtext = text;

  while ((symb = *text))
    if (isalnum (symb) || symb == ' ' || symb == '-')
      text++;
    else
      memmove (text, text + 1, strlen (text) + 1);
  spacetrim (oldtext);
}

////////////////////
// class functions
////////////////////

CAlice::CAlice (CNetServer *server, c_char init_file, c_char v, CLog *l)
{
  cc = hp = hh = he = 0;
  initialized = 0;
  s = server;
  log = l;
  templatefile = patternfile = NULL;
  substitutefile = new CText ();
  personfile = new CText ();
  personffile = new CText ();
  person2file = new CText ();

  // Buffers for '*', the last response and the print routine
  star = (char *)my_malloc (MAX_LINE_SIZE+1);
  that = (char *)my_malloc (MAX_LINE_SIZE+1);
  best = (char *)my_malloc (MAX_LINE_SIZE+1);
  pbuffer = (char *)my_malloc (MAX_LINE_SIZE+1);
  datafile = unsortedfile = NULL;

  root_var=NULL;

  char *epos;

  char aimlscrap[PATH_MAX+1];
  char patternscrap[PATH_MAX+1];
  char templatescrap[PATH_MAX+1];
  char substitutescrap[PATH_MAX+1];
  char personscrap[PATH_MAX+1];
  char person2scrap[PATH_MAX+1];
  char personfscrap[PATH_MAX+1];

  vars = NULL;
  strset (&vars, v, PATH_MAX);

  // start the radomizer for the <random> tags
  srand (time (0));
  // load up the init file
  FILE *init = fopen (init_file, "r");
  if (!init)
    {
      s->write_botlog ("can't open init file %s: %s", init_file, strerror (errno));
      return;
    }
  while (!feof (init))
    {
      // we use pbuffer as a scrap variable.
      fgets (pbuffer, MAX_LINE_SIZE, init);
      // eliminate comments.
      if (strchr (pbuffer,'#'))
        *strchr(pbuffer,'#') = 0;
      // delete all spaces
      while (replace(pbuffer," ",""));
      // remove EOL chars
      while (replace(pbuffer,"\n",""));
      epos = strchr(pbuffer,'=');
      epos++;
      // these really should be strncmp()'s
      if (strstr (pbuffer, "aimlfile"))
        strcpy (aimlscrap, epos);
      if (strstr (pbuffer, "datafile"))
        strset (&datafile, epos, PATH_MAX);
      if (strstr (pbuffer, "personfile"))
        strcpy (personscrap,epos);
      if (strstr (pbuffer, "person2file"))
        strcpy (person2scrap,epos);
      if (strstr (pbuffer, "personffile"))
        strcpy (personfscrap,epos);
      if (strstr (pbuffer, "substitutefile"))
        strcpy (substitutescrap,epos);
      if (strstr (pbuffer, "patternfile"))
        strcpy (patternscrap,epos);
      if (strstr (pbuffer, "templatefile"))
        strcpy (templatescrap,epos);
      if (strstr (pbuffer, "unsortedfile"))
        strset (&unsortedfile, epos, PATH_MAX);

      if (strstr (pbuffer, "logging"))
        {
          if (*epos == 'y' || *epos == 'Y' || *epos == '1')
            logging = 1;
          else 
            logging = 0;
        }

      if (strstr (pbuffer, "log_error"))
        {
          if (*epos == 'y' || *epos == 'Y' || *epos == '1')
            log_error = 1; 
          else 
            log_error = 0;
        }
    }
  fclose (init);

  if (!personfile->read_file (personscrap))
    {
      s->write_botlog ("error opening personfile %s: %s", personfile, strerror (errno));
      return;
    }
  if (!person2file->read_file (person2scrap))
    {
      s->write_botlog ("error opening person2file %s: %s", person2file, strerror (errno));
      return;
    } 
  if (!personffile->read_file (personfscrap))
    {
      s->write_botlog ("error opening personffile %s: %s", personffile, strerror (errno));
      return;
    } 
  if (!substitutefile->read_file (substitutescrap))
    {
      s->write_botlog ("error opening substitutefile %s: %s", substitutefile, strerror (errno));
      return;
    } 
  
  // check to see if it needs to create new patterns.txt and templates.txt

  patternfile = fopen (patternscrap, "r");
  templatefile = fopen (templatescrap, "r");

  if (!patternfile || !templatefile)
    {
      if (patternfile)
        fclose (patternfile); 
      if (templatefile)
        fclose (templatefile);
      patternfile = fopen (patternscrap,"w");
      if (!patternfile)
        {
          s->write_botlog ("error opening patternfile %s: %s", patternscrap, strerror (errno));
          return;
        } 
      templatefile = fopen (templatescrap, "w");
      if (!templatefile)
        {
          s->write_botlog ("error opening templatefile %s: %s", templatescrap, strerror (errno));
          return;
        } 
      aimlfile = fopen (aimlscrap, "r");
      if (!aimlfile)
        {
          s->write_botlog ("error opening aimlfile %s: %s", aimlfile, strerror (errno));
          return;
        } 
      if (!prepare ())
        return;
      fclose (aimlfile);
      fclose (patternfile);
      fclose (templatefile);
      patternfile = fopen (patternscrap, "r");
      if (!patternfile)
        {
          s->write_botlog ("error opening patternfile %s: %s", patternscrap, strerror (errno));
          return;
        } 
      templatefile = fopen (templatescrap, "r");
      if (!templatefile)
        {
          s->write_botlog ("error opening templatefile %s: %s", templatescrap, strerror (errno));
          return;
        } 
    }
  else
    {
      // ok, this part really should be saved to a file upon preparation
      char last = 0;
      while (!feof (patternfile))
        {
          long line;
          line = ftell (patternfile);
          fgets (pbuffer, MAX_LINE_SIZE, patternfile);
          char first = pbuffer[0];
          if ((first < 'A') || (first > 'Z')) first = 'Z' + 1;
          if (first != last)
            {
              last = first;
              if ((last < 'A') || (last > 'Z')) last = 'Z' + 1;
              chunk[last - 'A'] = line;
            }
        }
    }
  loadvars (vars);
  initialized = 1;
}

CAlice::~CAlice (void)
{
  savevars (vars);
  free (star);
  free (that);
  free (best);
  free (pbuffer);
  free (datafile);
  free (unsortedfile);
  free (vars);
  if (substitutefile != NULL)
    delete substitutefile;
  if (templatefile != NULL)
    fclose (templatefile);
  if (patternfile != NULL)
    fclose (patternfile);
  if (personfile != NULL)
    delete personfile;
  if (personffile != NULL)
    delete personffile;
  if (person2file != NULL)
    delete person2file;
  var_array *varsnext;
  while (root_var != NULL)
    {
      varsnext = root_var->next;
      free (root_var->varname);
      free (root_var->value);
      delete root_var;
      root_var = varsnext;
    }
}

char *
CAlice::get_pattern_matched ()
{
  return best;
}

int
CAlice::setvar (int protect, char *varname, c_char value)
{
  var_array *conductor, *one_before;
  lower (varname);
  if (root_var == NULL)	// the variable list is empty, create the first
    {
      root_var = new var_array;
      root_var->varname = root_var->value = NULL;
      root_var->protect = protect;
      strset (&root_var->varname, varname, MAX_VARNAME_SIZE);
      if (value[0] == '*')
        { 
          root_var->def = 1;
          strset (&root_var->value, value + 1, MAX_VARVAL_SIZE);
        }
      else
        {
          strset (&root_var->value, value, MAX_VARVAL_SIZE);
          root_var->def = 0;
        }
      root_var->next = NULL;
      if (root_var->def == 0)
        return 1;
      return 2;
    } 
  conductor = one_before = root_var;

  // we search through the array for the name
  while (conductor != NULL)
    {
      if (!(strcmp (varname, conductor->varname)))
        {
          if ((protect == 1) || (conductor->protect == protect))
            strset (&conductor->value, value, MAX_VARVAL_SIZE);
          else
            return 0;
          if (conductor->def == 0)
            return 1;
          return 2;
        }
      one_before = conductor;
      conductor = conductor->next;
    }
  /* if we get here, the variable doesn't exist. */
  /* create a new entry. */
  one_before->next = new var_array;
  one_before = one_before->next;
  one_before->varname = one_before->value = NULL;
  one_before->protect = protect;
  strset (&one_before->varname, varname, MAX_VARNAME_SIZE);
  if (value[0] == '*')
    { 
      one_before->def = 1;
      strset (&one_before->value, value + 1, MAX_VARVAL_SIZE);
    }
  else
    {
      strset (&one_before->value, value, MAX_VARVAL_SIZE);
      one_before->def = 0;
    }
  one_before->next = NULL;
  if (one_before->def == 0)
    return 1;
  return 2;     
}  

c_char
CAlice::getvar (c_char var, c_char vardefault)
{
  char *value = NULL;
  var_array *conductor;
  char varname[MAX_VARNAME_SIZE+1];
  my_strncpy (varname, var, MAX_VARNAME_SIZE);
  lower (varname);
  if (strlen(varname) > MAX_VARNAME_SIZE)
    return value;
  // we begin searching through the linked list
  conductor = root_var;
  while (conductor != NULL)
    {
      if (!strcmp (varname, conductor->varname))
        {
          value = conductor->value;
          return value;
        }
      conductor = conductor->next;
    }  
  /* if we made it to here, the variable doesn't exsist. */
  if (vardefault == NULL)
    return value;
  if (strlen (vardefault) > MAX_VARVAL_SIZE)
    return value;
  setvar (0, varname, vardefault);
  return vardefault;
}

char *
CAlice::respond (char *text)
{
  /* Thanks to Dr. Wallace for writing this into the CGI,
     it was stripped out of there, modified slightly, 
     then added to here. */
  char scrap[MAX_LINE_SIZE+1];
  char *ltimestr, *uip, *strtmp;
  struct tm *ltime;
  time_t clock;

  time (&clock);
  ltime = localtime (&clock); 
  ltimestr = asctime (ltime);

  srai_num = 0;
  my_strncpy (scrap, text, MAX_LINE_SIZE);
  substitute (substitutefile, scrap);
  /* perhaps some code to deal with remaining periods that are not
     sentence breaks.  (Like 5.02 or H.I.P.P.i.e.) */
  while (replace (scrap, "?", "."));
  while (replace (scrap, "!", "."));
  while (replace (scrap, "\n", ""));
  // we need to make sure our sentence ends in a period
  if (scrap[strlen (scrap)-2] != '.')
    strncat (scrap, ".", MAX_LINE_SIZE-strlen (scrap));
  uip = scrap;
  char oneline[MAX_LINE_SIZE+1];
  char output[MAX_LINE_SIZE+1];
  
  output[0] = 0;
  do
    {
      memset (oneline, 0, MAX_LINE_SIZE);
      // skip white space:
      while(uip[0] == ' ')
        uip++;
      my_strncpy (oneline, uip, MAX_LINE_SIZE);
      if (strstr (uip, ".") > 0)
        {
          // advance to next sentence:
          uip = strstr (uip, ".")+1;
          // strip out first sentence:
          strtmp = strstr (oneline, ".");
          if (strtmp != NULL)
            strtmp[0] = 0;
        }   
      if (oneline != NULL && strlen (oneline) > 0)
        {
          // Robot Respose:
          cleaner (oneline);
          strncat (output, respond2 (oneline), MAX_LINE_SIZE-strlen (output));
          if (logging && log != NULL)
            {
              *log << CMD[0] << ": " << oneline << EOL;
              *log << s->nick << ": " << output << EOL;
            }
          strncat (output, "  ", MAX_LINE_SIZE-strlen (output));
        }
    }
  while (strstr (uip, ".") > 0); // any more sentences?
  strcpy (pbuffer, output);
  print (pbuffer);
  return pbuffer;
}

void
CAlice::savevars (char *varfile)
{
  FILE *vars = fopen (varfile, "w");
  var_array *conductor = root_var;
  // first we print out that
  fprintf (vars, "%s\n", that);
  // we print out each variable
  while (conductor != NULL)
    {
      if (conductor->def == 1)
        fprintf (vars, "%i %s=*%s\n", conductor->protect, conductor->varname, conductor->value);
      else
        fprintf (vars, "%i %s=%s\n", conductor->protect, conductor->varname, conductor->value);
      conductor = conductor->next;
    }
  fclose (vars);
}

void
CAlice::loadvars (char *varfile)
{
  char scrap[MAX_LINE_SIZE+1], scrap2[MAX_LINE_SIZE+1];
  var_array *conductor = root_var;
  // first wipe out our existing variables
  while (conductor != NULL)
    {
      conductor = conductor->next;
      free (root_var->varname);
      free (root_var->value);
      delete root_var;
      root_var = conductor;
    }
 
  // next we see if varfile exists
  FILE *vars = fopen (varfile, "r");
  if (vars)
    {
      // if so, we load up variables from it into memory
      // first we grab that
      fgets (that, MAX_LINE_SIZE, vars);
      // remove the EOL char
      replace (that, "\n", "");
      // now we load up our vars
      while (!feof (vars))
        {
          fgets (scrap, MAX_LINE_SIZE, vars);
          replace (scrap, "\n", "");
          if (!(strstr (scrap, "=")))
            continue;
          my_strncpy (scrap2, scrap + 2, MAX_LINE_SIZE);
          scrap[1] = 0;
          int protect = atoi (scrap);
          my_strncpy (scrap, scrap2, MAX_LINE_SIZE);
          *strchr (scrap, '=') = 0;
          char *varname = scrap;
          char *value = strstr (scrap2, "=")+1;
          setvar (protect, varname, value);
        }
      fclose (vars);
    }
  else
    {
      // if not, we load up defvars.txt into our varfile, and into memory
      loadvars (datafile);
      savevars (varfile);
    }
}

void
CAlice::replacer (char *line)
{
  char *pos;
  
  replace (line, "<alice>", "");
  replace (line, "<category>", "");
  replace (line, "</category>", "");
  replace (line, "</alice>", "");
  if ((pos = strstr (line, "<topic name=\"")))
    {
      my_strncpy (topic, pos, MAX_VARVAL_SIZE);
      *strstr (topic, "\"") = 0;
    }
  if (strstr (line, "</topic>"))
    topic[0] = 0;
  strremove (line, "<topic", ">");
  replace (line, "</topic>", "");
  while (replace (line, "<getversion/>", "<getvar name=\"version\"/>"));
  while (replace (line, "<set_animagent/>", "<setvar name=\"animagent\">on</setvar>"));
  while (replace (line, "<person/>", "<person><star/></person>"));
  while (replace (line, "<person2/>", "<person2><star/></person2>"));
  while (replace (line, "<personf/>", "<personf><star/></personf>"));
  while (replace (line, "<setname/>", "<setvar name=\"name\"/><star/></setvar>"));
  while (replace (line, "<birthday/>", "<getvar name=\"botbirthday\"/>"));
  while (replace (line, "<birthplace/>", "<getvar name=\"botbirthplace\"/>"));
  while (replace (line, "<botasmter/>", "<getvar name=\"botasmter\"/>"));
  while (replace (line, "<botmaster/>", "<getvar name=\"botmaster\"/>"));
  while (replace (line, "<boyfriend/>", "<getvar name=\"botboyfriend\"/>"));
  while (replace (line, "<favorite_band/>", "<getvar name=\"botband\"/>"));
  while (replace (line, "<favorite_book/>", "<getvar name=\"botbook\"/>"));
  while (replace (line, "<favorite_color/>", "<getvar name=\"botcolor\"/>"));
  while (replace (line, "<favorite_food/>", "<getvar name=\"botfood\"/>"));
  while (replace (line, "<favorite_movie/>", "<getvar name=\"botmovie\"/>"));
  while (replace (line, "<favorite_song/>", "<getvar name=\"botsong\"/>"));
  while (replace (line, "<for_fun/>", "<getvar name=\"botfun\"/>"));
  while (replace (line, "<friends/>", "<getvar name=\"botfriends\"/>"));
  while (replace (line, "<gender/>", "<getvar name=\"botgender\"/>"));
  while (replace (line, "<getname/>", "<getvar name=\"name\"/>"));
  while (replace (line, "<get_gender/>", "<getvar name=\"gender\"/>"));
  while (replace (line, "<getsize/>", "<getvar name=\"botsize\"/>"));
  while (replace (line, "<gettopic/>", "<getvar name=\"topic\"/>"));
  while (replace (line, "<girlfriend/>", "<getvar name=\"botgirlfriend\"/>"));
  while (replace (line, "<location/>", "<getvar name=\"botlocation\"/>"));
  while (replace (line, "<look_like/>", "<getvar name=\"botlooks\"/>"));
  while (replace (line, "<name/>", "<getvar name=\"botname\"/>"));
  while (replace (line, "<kind_music/>", "<getvar name=\"botmusic\"/>"));
  while (replace (line, "<question/>", "<getvar name=\"question\"/>"));
  while (replace (line, "<sign/>", "<getvar name=\"botsign\"/>"));
  while (replace (line, "<talk_about/>", "<getvar name=\"bottalk\"/>"));
  while (replace (line, "<they/>", "<getvar name=\"they\"/>"));
  while (replace (line, "<wear/>", "<getvar name=\"botwear\"/>"));
  while (replace (line, "<setname>", "<setvar name=\"name\">"));
  while (replace (line, "</setname>", "</setvar>"));
  while (replace (line, "<settopic>", "<setvar name=\"topic\">"));
  while (replace (line, "</settopic>", "</setvar>"));
  while (replace (line, "<set_it>", "<setvar name=\"it\">"));
  while (replace (line, "</set_it>", "</setvar>"));
  while (replace (line, "<set_personality>", "<setvar name=\"personality\">"));
  while (replace (line, "</set_personality>", "</setvar>"));
  while (replace (line, "<set_female/>","<setvar name=\"gender\">she</setvar>"));
  while (replace (line, "<set_male/>","<setvar name=\"gender\">he</setvar>"));
  // need code to handle get_??? and set_???
  while ((pos = strstr(line, "<get_")))
    {
      replace (pos, "<get_", "<getvar name=\"");
      replace (pos, "/>", "\"/>");
    }
  while ((pos = strstr (line, "<set_")))
    {
      replace (pos, "<set_", "<setvar name=\"");
      replace (pos, ">", "\">");
      if (replace (pos, "</se", "</setvar>"))
        {
          pos = strstr (pos, "</setvar>") + 9;
          strremove (pos, "t_", ">");
        }
    }
  while (replace (line, "<sr/>", "<srai><star/></srai>"));
  while (replace (line, "\n", ""));
}  

int
CAlice::checkline (char *line)
{
/* this performs syntax checking on the AIML.

ok, what we've got is a global int called cc.  If we have a
tag we add the appropiate value.  If we have a /tag we subtract that
value.
The values go as follows.
<alice> = ba = +1  </alice> = ea = -1
<topic> = bt = 2
category = bc = 4
pattern = bp = 8
that = bh = 16
template = be = 32
*/
  int ba = 0, ea = 0; // <alice> begin and end
  int bt = 0, et = 0; // <topic>
  int bc = 0, ec = 0; // <category>
  int bp = 0, ep = 0; // <pattern>
  int bh = 0, eh = 0; // <that>
  int be = 0, ee = 0; // <template>
  if (strstr (line, "<alice>"))
    ba = 1;
  if (strstr (line, "</alice>"))
    ea = 1;
  if (strstr (line, "<topic"))
    bt = 1;
  if (strstr (line, "</topic>"))
    et = 1;
  if (strstr (line, "<category>"))
    bc = 1;
  if (strstr (line, "</category>"))
    ec = 1;
  if (strstr (line, "<pattern>"))
    bp = 1;
  if (strstr (line, "</pattern>"))
    ep = 1;
  if (strstr (line, "<that>"))
    bh = 1;
  if (strstr (line, "</that>"))
    eh = 1;
  if (strstr (line, "<template>"))
    be = 1;
  if (strstr (line, "</template>"))
    ee = 1;
  if (ba)
    {
      if (cc != 0)
        return 0;
      cc += 1;
    }
  if (bt)
    {
      if (cc != 1)
        return 0;
      cc += 2;
    }
  if (bc)
    {
      if (cc !=1 && cc != 3)
        return 0;
      cc += 4;
    }
  if (bp)
    {
      if (!(cc & 1) || !(cc & 4))
        return 0;
      cc += 8;
    }
  if (ep)
    {
      if (!(cc & 1) || !(cc & 4) || hh || he || !(cc & 8))
        return 0;
      cc -= 8;
      hp = 1;
    }
  if (bh)
    {
      if (!(cc & 1) || !(cc & 4) || !(hp) || he)
        return 0;
      cc += 16;
    }
  if (eh)
    {
      if (!(cc & 1) || !(cc & 4) || !(hp) || he || !(cc & 16))
        return 0;
      cc -= 16;
      hh = 1;
    }
  if (be)
    {
      if (!(cc & 1) || !(cc & 4) || !(hp))
        return 0;
      cc += 32;
    }
  if (ee)
    {
      if (!(cc & 1) || !(cc & 4) || !(hp) || !(cc & 32))
        return 0;
      cc -= 32;
      he = 1;
    }
  if (ec)
    {
      if (!(cc & 1) || !(cc & 4) || !(hp) || !(he))
        return 0;
      cc -= 4;
      hp = 0;
      hh = 0;
      he = 0;
    }
  if (et)
    {
      if (cc !=3)
        return 0;
      cc -= 2;
    }
  if (ea)
    cc -= 1;
  return 1;
}

int
CAlice::splitaiml (c_char src)
{
  cc = 0;
  // it now prepares the temp file into the template and pattern files
  int patterns = 0, templates = 0, bytecount = 0;

  FILE *aiml = fopen (src, "r");
  if (!aiml)
    {
      s->write_botlog ("skipping file %s: %s", src, strerror (errno));
      return 1;
    }

  int linenr = -1;
  char line[MAX_LINE_SIZE+1], next[MAX_LINE_SIZE+1], buffer[MAX_LINE_SIZE+1];
  fgets (line, MAX_LINE_SIZE, aiml);
  if (!checkline (line))
    {
      s->write_botlog ("error %i encountered in file %s, at line %i", cc, src, linenr);
      return 0;
    }
  memset (buffer, 0, MAX_LINE_SIZE);
  /* we now make the temp.txt into unsorted.txt and templates.txt
     also we exchange some tags for shorthand tags. */
  while (!feof (aiml))
    {
      linenr++;
      fgets (next, MAX_LINE_SIZE, aiml); 
      if (!checkline (next))
        {
          s->write_botlog ("error %i encountered in file %s, at line %i", cc, src, linenr);
          return 0;
        }
      strip_crlf (line);
      replacer (line);
      if (strstr (line, "<pattern>" ) && (strstr(line, "<that>")) || strstr(next, "<that>"))
        {
          while (!strstr (line, "</that>"))
            {
              if (strlen (line) > MAX_LINE_SIZE)
                {
                  s->write_botlog ("line too long: %s", line);
                  break;
                }
              strcpy (line + strlen (line), next);
              replacer (line);
              linenr++;
              fgets (next, MAX_LINE_SIZE, aiml);
              if (!checkline (next))
                {
                  s->write_botlog ("error %i encountered in file %s, at line %i", cc, src, linenr);
                  return 0;
                }
            }
          patterns++;
          // what we need to print out.. is line till that
          strcpy (buffer, strstr (line, "</that>") + 7);
          *(strstr (line,"</that>") + 7) = 0;
          replace (line, "<pattern>", "");
          replace (line, "</pattern>", ""); 
          fprintf (unsorted, "%s", line);
          bytecount += strlen (line) - 3;
        }
      else if (strstr (line, "<pattern>" ))
        {
          while (!strstr (line, "</pattern>"))
            {
              if (strlen (line) > MAX_LINE_SIZE)
                {
                  s->write_botlog ("line too long: %s", line);
                  break;
                }
              strcpy (line + strlen (line), next);
              replacer (line);
              linenr++;
              fgets (next, MAX_LINE_SIZE, aiml);
              if (!checkline (next))
                {
                  s->write_botlog ("error %i encountered in file %s, at line %i", cc, src, linenr);
                  return 0;
                }
            }
          patterns++;
          strcpy (buffer, strstr (line, "</pattern>") + 10); 
          *(strstr (line,"</pattern>") + 10) = 0;
          replace (line, "<pattern>", "");
          replace (line, "</pattern>", "");
          fprintf (unsorted, "%s", line);
          bytecount += strlen (line) - 3;
        }
      if (strstr (line, "<template>"))
        {
          while (!strstr (line, "</template>"))
            {
              if (strlen (line) > MAX_LINE_SIZE)
                {
                  s->write_botlog ("line too long: %s", line);
                  break;
                }
              strcpy (line + strlen (line), next);
              replacer (line);
              linenr++;
              fgets (next, MAX_LINE_SIZE, aiml);
              if (!checkline (next))
                {
                  s->write_botlog ("error %i encountered in file %s, at line %i", cc, src, linenr);
                  return 0;
                }
            }
          long tpos = ftell (templatefile);
          templates++;
          if (templates != patterns)
            {
              s->write_botlog ("pattern/template count mismatch");
              mbot_exit ();
            }
          strcpy (buffer, strstr (line, "</template>") + 11); 
          *(strstr (line, "</template>") + 11) = 0;
          replace (line, "<template>", "");    
          fprintf (templatefile, "%s\n", line);
          fprintf (unsorted, "<tpos=%li>\n", tpos);
          strcpy (buffer, strstr (line, "</template>") + 11);
          bytecount += strlen (line) - 3;	    
        }
      strcpy (line, buffer);
      strcpy (line + strlen (line), next);

    }
  // this shouldn't be needed
  fprintf (templatefile, "<end of file>\n");
  fclose (aiml);
  return 1;
}

int
CAlice::prepare (void)
{
  // routine looks through C.aiml for what files to load
  char aline[256+1];
  fgets (aline, 256, aimlfile);
  unsorted = fopen (unsortedfile, "w");
  while (!feof (aimlfile))
    {
      aline[strcspn (aline, "\n")] = 0;
      if (!splitaiml (aline))
        return 0;
      memset (aline, 0, 256);
      fgets (aline, 256, aimlfile);
    }
  /* now we alphabatize unsorted.txt by the first letter.  
     we put this in patterns.txt
     we also set chunk, which will tell us where the first letter
     is in patterns.txt */
  fclose (unsorted);
  unsorted = fopen (unsortedfile, "r");
  char buffer[MAX_LINE_SIZE+1];
  for (int i = 'A'; i <= ('Z' + 1); i++)
    {
      chunk[i - 'A'] = ftell (patternfile);
      buffer[0] = i;
      buffer[1] = 0;
      rewind (unsorted);
      while (!feof (unsorted))
        {
          fgets (buffer, MAX_LINE_SIZE, unsorted);
          if (i <= 'Z')
            {
              if (buffer[0] ==i)
                fprintf (patternfile, "%s", buffer);
            }
          else
            if ((buffer[0] < 'A') || (buffer[0] > 'Z'))
              fprintf (patternfile, "%s", buffer);
        }
    } 
  fclose (unsorted);
  remove (unsortedfile); 
  return 1;
}

void
CAlice::substitute (CText *subst, char *text)
{
  if (subst == NULL)
    return;
  char spaces[MAX_LINE_SIZE+1], string1[MAX_LINE_SIZE+1],
       string2[MAX_LINE_SIZE+1], line[MAX_LINE_SIZE+1], *strtmp;
  int pos1, pos2, pos3, pos4, l;
  subst->rewind_text ();
  upper (text);
  // replace "'" with "`"
  for (size_t i = 0; i < strlen (text); i++)
    if (text[i] == '\'')
      text[i] = '`';
  my_strncpy (spaces + 1, text, MAX_LINE_SIZE-1);
  spaces[0] = ' ';
  l = strlen (spaces);
  spaces[l] = ' ';
  spaces[l + 1] = 0;
  // do replacing from our filename
  for (int i = subst->line_num; i > 0; i--)
    {
      my_strncpy (line, subst->get_line (), MAX_LINE_SIZE);
      // allow comments
      strtmp = strstr (line, "#");
      if (strtmp != NULL)
        strtmp[0] = 0;
      if ((pos1 = strstr (line, "'") - line) < 0
          || (char *)0-line == (int)pos1)
        continue;
      if ((pos2 = strstr (line + pos1 + 1, "'") - line) < 0
          || (char *)0-line == (int)pos2)
        continue;
      if ((pos3 = strstr (line + pos2 + 1, "'") - line) < 0
          || (char *)0-line == (int)pos3)
        continue;
      if ((pos4 = strstr (line + pos3 + 1, "'") - line) < 0
          || (char *)0-line == (int)pos4)
        continue;
      my_strncpy (string1, line + pos1 + 1, MAX_LINE_SIZE);
      string1[pos2 - pos1] = 0;
      upper (string1);
      my_strncpy (string2, line + pos3 + 1, MAX_LINE_SIZE);
      string2[pos4 - pos3] = 0;
      string1[pos2 - pos1 - 1] = string2[pos4 - pos3 - 1] = 0;
      while (replace (spaces, string1, string2));
    }
  strcpy (text, spaces + 1);
}

int
CAlice::match (c_char text, c_char pattern, int final)
{
  int starpos, pos2;
  // if it's a perfect match, return with a YIPPE!
  if (!strcmp (pattern, text))
    return 1;
  // if we just have a '*'
  if ((pattern[0] == '*') && (!pattern[1]))
    {
      if (final)
        strcpy (star, text);
      return 1;
    }
  // check to see if we have a star.. if we don't return false
  if (((starpos = strstr( pattern, "*" ) - pattern) < 0) &&
      ((starpos = strstr( pattern, "_" ) - pattern) < 0))
    return 0;
  // make sure the star isn't in the front
  if (starpos > 0)
    {
      // if the stuff in the front of the star doesn't match, return false
      if (strncmp( pattern, text, starpos ))
        return 0;
      // if we have nothing after the star, return true
      if (!pattern[starpos + 1])
        {
          if (final)
            strcpy (star, text + starpos);
          return 1; 
        }
    }
  /* at this point, we either have a star in the front and text
     * HELLO
     or a star in the middle and the text in front matches.
     HELLO * COMPUTER
     this code should be simplified using strcspn()'s and strspn()
     actually.. a better idea..  we reverse both of them
     then we do the same thing we did to begin with.
     nonetheless.. for now.. this code works.. :) */
  char pluseoln[MAX_LINE_SIZE+1], texteoln[MAX_LINE_SIZE+1];
  my_strncpy (texteoln, text, MAX_LINE_SIZE);
  int len = strlen (text);
  texteoln[len] = 10;
  texteoln[len + 1] = 0;
  my_strncpy (pluseoln, pattern + starpos + 1, MAX_LINE_SIZE);
  len = strlen (pluseoln);
  pluseoln[len] = 10;
  pluseoln[len + 1] = 0;
  if ((pos2 = strstr (texteoln, pluseoln) - texteoln) > -1
      && (char *)0-texteoln != pos2)
    {
      if (text[pos2 + strlen (pattern + starpos + 1)] == 0)
        {
          if (final)
            {
              strcpy (star, text + starpos);
              star[pos2 - starpos] = 0;
            }
          return 1;
        }
    }
  return 0;
}

void
CAlice::print (char *line)
{
  int start = 1, space = 0, bpos = 0;
  char *pos = pbuffer, *scrap;
  while (*pos)
    {
      if (*pos == ' ')
        space = 1;
      else
        {
          /* this capitilizes I and I'm */
          if ((space) && ((*pos == 'i') || (*pos == 'I')) && 
              ((*(pos + 1) == ' ') || (*(pos + 1) == '\'')))
            {
              line[bpos++] = ' ';
              line[bpos++] = 'I';
              space = 0;
            }	
          /* capitilizes the first word in a sentence. */
          else if ((start) && (*pos >= 'a') && (*pos <= 'z'))
            {
              if (space)
                line[bpos++] = ' ';
              line[bpos++] += 'A' - 'a';
              start = space = 0;
            } 
          else 
            {
              if ((space) && (*(pos +1) != '.'))
                line[bpos++] = ' ';
              line[bpos++] = *pos;
              space = 0;
            }
          start = 0;
        }
      if ((*pos == '.') && (*pos+1 == ' '))
        start = 1;
      pos++;
    }
  // Capitalize current user name
  scrap = strstr (line, getvar ("name", "Human"));
  if ((scrap) && (*scrap >= 'a') && (*scrap <= 'z'))
    *scrap += 'A' - 'a';
  line[bpos] = 0;
  int last = strlen( line ) - 1;
  while ((line[last] == ' ') && (last >= 0))
    line[last--] = 0;
  if ((last >= 0) && (line[last] >= 'a') && (line[last] <= 'z'))
    {
      line[last + 1] = '.';
      line[last + 2] = 0;
    }
}

void
CAlice::randomize (char *text)
{
  // this function handles our random tags
  size_t pos1;
  int list[MAX_RANDLIST_SIZE];
  char rstring[MAX_LINE_SIZE+1], buffer[MAX_LINE_SIZE+1], *strtmp;

  while ((int)(pos1 = (size_t)strstr (text, "<random>") - (size_t)text) > -1
         && (char *)0-text != (int)pos1)
    {
      my_strncpy (rstring, text + pos1 + 8, MAX_LINE_SIZE);
      strtmp = strstr (rstring, "</random>");
      if (strtmp != NULL)
        {
          strtmp[0] = 0;
          int items = 0;
          int pos, last = -1;
          while ((pos = (size_t)strstr (rstring + last + 1, "<li>") - (size_t)rstring) > -1
                 && (char *)0-rstring != (int)pos)
            {
              list[items++] = pos + 4;
              last = pos;
            }
          int rnd = random_num (items);
          my_strncpy (buffer, text, MAX_LINE_SIZE);
          strcpy (buffer + pos1, rstring + list[rnd]);
          if (strstr (buffer, "</li>"))
            *strstr (buffer, "</li>") = 0;
          strcpy (buffer + strlen (buffer), " ");
          strcpy (buffer + strlen (buffer), strstr (text, "</random>") + 9);
          strcpy (text, buffer);
        }
      else
        {
          // Handle erroneous randoms
          my_strncpy (rstring, text + pos1 + 8, MAX_LINE_SIZE);
          strcpy (text, rstring);
        }
    }
}

void
CAlice::reevaluate (char *line)
{
  char varname[MAX_VARNAME_SIZE+1];
  char value[MAX_VARVAL_SIZE+1];
  char scrap[MAX_LINE_SIZE+1];
  char *pos;
  lower (star);
  randomize (line);

  while ((pos = strstr (line, "<think>")))
    {
      my_strncpy (scrap, pos + 7, MAX_LINE_SIZE);
      *strstr (scrap, "</think>") = 0;
      reevaluate (scrap);
      strremove (line, "<think>", "</think>");
    }

  while (replace (line, "<star/>", star));
  while (replace (line, "<that/>", that));

  while ((pos = strstr(line,"<person>")))
    {
      my_strncpy (scrap, pos + 8, MAX_LINE_SIZE);
      *strstr (scrap, "</person>") = 0;
      substitute (personfile, scrap);
      lower (scrap);
      strremove (line, "son>", "</person>"); 
      replace (line, "<per", scrap);
    }

  while ((pos = strstr (line, "<person2>")))
    {
      my_strncpy (scrap, pos + 9, MAX_LINE_SIZE);
      *strstr (scrap, "</person2>") = 0;
      substitute (person2file, scrap);
      lower (scrap);
      strremove (line, "son2>", "</person2>"); 
      replace (line, "<per", scrap);
    }

  while ((pos = strstr (line, "<personf>")))
    {
      my_strncpy (scrap, pos + 9, MAX_LINE_SIZE);
      *strstr (scrap, "</personf>") = 0;
      substitute (personffile, scrap);
      lower (scrap);
      strremove (line, "sonf>", "</personf>"); 
      replace (line, "<per", scrap);
    }

  while ((pos = strstr (line, "<getvar")))
    {
      if (!(pos =strstr (pos + 8, "name")))
        {
          strremove (line, "<getvar", "/>");
          continue;
        }
      pos = strstr (pos, "\"") + 1;
      // ok.. now pos = foo" default="bar" /> or... pos = foo"
      my_strncpy (scrap, pos, MAX_LINE_SIZE);
      char *lix = strstr (scrap, "\"");
      if (lix != NULL)
        lix[0] = 0;
      my_strncpy (varname, scrap, MAX_VARNAME_SIZE);
      pos = strstr (line, "<getvar");
      // this is if we don't have a default
      if (!(strstr (pos + 8, "default")))
        {
          my_strncpy (scrap, pos, MAX_LINE_SIZE);
          *strstr (line, "<getvar") = 0;
          strremove (scrap, "<getvar","/>");
          strncat (line, getvar (varname, "weird"), MAX_LINE_SIZE-strlen (line));
          strncat (line, scrap, MAX_LINE_SIZE-strlen (line));
          continue;
        }
      // insert code here to deal with defaults
    }

  while ((pos = strlstr (line, "<setvar")))
    {
      char *holder = pos;
      int setreturn;
      pos = strstr (pos, "name=\"") + 6;
      my_strncpy (scrap, pos, MAX_LINE_SIZE);
      *strstr (scrap, "\"") = 0;
      my_strncpy (varname, scrap, MAX_VARNAME_SIZE);
      pos = strstr (pos, ">") +1;
      strcpy (scrap, pos);
      char *lix = strstr (scrap, "</setvar>");
      if (lix != NULL)
        *lix = 0;
      my_strncpy (value, scrap, MAX_VARVAL_SIZE);
      setreturn = setvar (0, varname, value);
      if (setreturn == 1)
        {
          // print name
          strremove (holder, "<setvar", "\"");
          strremove (holder, "\"", "</setvar>");
        }
      if (setreturn == 2)
        {
          strremove (holder, "<setvar", ">");
          replace (holder, "</setvar>", "");
        }
    }

  while ((pos = strstr (line, "<srai>")))
    {
      if (++srai_num > MAX_SRAI)
        break;
      my_strncpy (scrap, pos + 6, MAX_LINE_SIZE);
      *strstr (scrap, "</srai>") = 0;
      upper (scrap);
      my_strncpy (scrap, respond2 (scrap), MAX_LINE_SIZE);
      // is this too cheap?
      strremove (line, "ai>", "</srai>"); 
      replace (line, "<sr", scrap);
    }

  while ((pos = strstr (line, "<system>")))
    {
      my_strncpy (scrap, pos + 8, MAX_LINE_SIZE);
      *strstr (scrap, "</system>") = 0;
      system (scrap);
      strremove (line, "<system>", "</system>");
    }

  while ((pos = strstr (line, "<seen>")))
    {
      my_strncpy (scrap, pos + 6, MAX_LINE_SIZE);
      *strstr (scrap, "</seen>") = 0;

      // if it's in a channel with the bot
      for (int i = 0; i < s->channel_num; i++)
        if (CHANNELS[i]->user_index (scrap) != -1)
          {
            snprintf (line, MAX_LINE_SIZE, "%s is currently online.", scrap);
            return;
          }

      // else get the seen module and ask it
      CModule *m;
      s->bot->modules.rewind ();
      while ((m = (CModule *)s->bot->modules.next ()) != NULL)
        if (strcmp (m->module->name, "seen") == 0)
          break;
      s->bot->modules.restore_current ();
      time_t t = -1;
      if (m != NULL)
        {
          time_t (*server2seentime)(CNetServer *, c_char) = (time_t(*)(CNetServer *, c_char))dlsym (m->handle, "server2seentime");
          t = server2seentime (s, scrap);
        }
      if (t == -1)
        switch (random_num (2))
          {
            case 0: snprintf (line, MAX_LINE_SIZE, "Humm, I don't remember %s.", scrap); break;
            case 1: snprintf (line, MAX_LINE_SIZE, "I don't remember %s... Tell %s to buy me more RAM.", scrap, getvar ("botmaster", "mirage")); break;
          }
      else
        {
          char timestr[MSG_SIZE+1];
          my_strncpy (timestr, asctime (localtime (&t)), MSG_SIZE);
          timestr[strlen (timestr) - 1] = 0;
          snprintf (line, MAX_LINE_SIZE, "I last saw %s on %s.", scrap, timestr);
        }
    }

  while ((pos = strstr (line, "<word>")))
    {
      my_strncpy (scrap, pos+6, MAX_LINE_SIZE);
      *strstr (scrap, "</word>") = 0;
      CModule *m;
      s->bot->modules.rewind ();
      while ((m = (CModule *)s->bot->modules.next ()) != NULL)
        {
          if (strcmp (m->module->name, "word") == 0)
            break;
        }
      s->bot->modules.restore_current ();
      char text[MSG_SIZE+1];
      text[0] = 0;
      if (m != NULL)
        {
          void (*server2wordtext)(CNetServer *, c_char, char *, size_t) = (void(*)(CNetServer *, c_char, char *, size_t))dlsym (m->handle, "server2wordtext");
          server2wordtext (s, scrap, text, MSG_SIZE);
        }
      if (text[0] == 0)
        snprintf (line, MAX_LINE_SIZE, "Not defined... lousy word anyway :D");
      else
        {
          switch (random_num (5))
            {
              case 0: snprintf (line, MAX_LINE_SIZE, "I heard that %s is %s", scrap, text); break;
              case 1: snprintf (line, MAX_LINE_SIZE, "Someone said %s is %s", scrap, text); break;
              case 2: snprintf (line, MAX_LINE_SIZE, "%s is %s", scrap, text); break;
              case 3: snprintf (line, MAX_LINE_SIZE, "I know that %s is %s", scrap, text); break;
              case 4: snprintf (line, MAX_LINE_SIZE, "It's public that %s is %s", scrap, text); break;
            }
        }
    }
  strcpy (that, line);
}

char *
CAlice::respond2 (char *text)
{
  char *pos, line[MAX_LINE_SIZE+1], buffer[MAX_LINE_SIZE+1],
       capthat[MAX_LINE_SIZE+1];
  long tpos, bestpos = 0;
  int alpha, thatused;
  star[0] = pbuffer[0] = best[0] = 0;
  alpha = ((text[0] >= 'A') && (text[0] <= 'Z'));
  int tval, bestline = -1, linenr = -1;
  if (alpha) 
    fseek (patternfile, chunk[text[0] - 'A'], SEEK_SET);
  else 
    fseek (patternfile, chunk[26], SEEK_SET);
  while (!feof (patternfile))
    {
      linenr++;
      fgets (line, MAX_LINE_SIZE, patternfile);
      /* this gets fired if Alice didn't find an exact
         match in it's own letter */
      if ((alpha) && (line[0] != text[0]))
        {
          alpha = 0;
          fseek (patternfile, chunk[26], SEEK_SET);
          continue;
        }
      /* this strips the position out of the end of the pattern.
         if the line doesn't contain a <tops get the next line. */
      if ((tval = strstr (line, "<tpos=") - line) < 0
          || (char *)0-line == (int)tval)
        continue;
      my_strncpy (buffer, line + tval + 6, MAX_LINE_SIZE);
      if (strstr (buffer, ">"))
        *strstr (buffer, ">") = 0;
      line[tval] = 0;
      tpos = atol (buffer);
      thatused = 0;
      while (replace (line, "<getvar name=\"botname\"/>", getvar ("botname", "DarkSun")));
      // check to see if <that> was used
      if ((pos = strstr (line, "<that>")))
        {
          // we copy that into buffer
          my_strncpy (buffer, pos + 6, MAX_LINE_SIZE);
          // we chop that off our line
          *strstr (line, "<that>") = 0;
          if (strstr (buffer, "</that>"))
            *strstr (buffer, "</that>") = 0;
          /* now buffer holds our that from the pattern
             that holds our last statement. */
          my_strncpy (capthat, that, MAX_LINE_SIZE);
          upper (capthat);
          cleaner (capthat);
          if (!match (capthat, buffer, 0 ))
            continue;
          thatused = 1;
        }
      spacetrim (line);

      if (!match (text, line, 0))
        continue;
      if ((strcmp (text, line) == 0) || (thatused))
        {
          strcpy (best, line);
          bestline = linenr;
          bestpos = tpos;
          break;
        }
      if ((strcmp (line, best) < 0) && (bestline > -1))
        continue;
      strcpy (best, line);
      bestline = linenr;
      bestpos = tpos;
    }
  match (text, best, 1);
  fseek (templatefile, bestpos, SEEK_SET);
  fgets (line, MAX_LINE_SIZE, templatefile);
  buffer[0] = 10;
  buffer[1] = 0;
  if (strstr (line, "</template>"))
    *strstr (line, "</template>") = 0;
  reevaluate (line);
  strcpy (pbuffer, line);
  return pbuffer;
}

////////////////////
// module managing
////////////////////

extern "C" {

CAlice *
server2alice (CNetServer *s)
{
  CAlice *a;
  alice_list->rewind ();
  while ((a = (CAlice *)alice_list->next ()) != NULL)
    if (a->s == s)
      return a;
  return NULL;
}

// events watcher to parse privmsg
void
alice_event (CNetServer *s)
{
  char *alans;
  CAlice *a = server2alice (s);
  if (a == NULL)
    return;

  if (strcmp (CMD[1], "PRIVMSG") == 0)
    {
      if (CMD[3][0] == '')			// ctcp
        return;
      if (CMD[2][0] == '#')                     // if it's a channel
        {
          if (strncasecmp (CMD[3], s->nick, s->nick.getlen ()) == 0)
            {
              char *buf = strstr (CMD[3], " ");
              if (buf == NULL)
                return;
              alans = a->respond (buf);
              stripurl (alans);
              strip_crlf (alans);
              SEND_TEXT (DEST, "%s: %s", SOURCE, alans);
            }
        }
      else                                      // pvt with the bot
        {
          char buf[MSG_SIZE+1];
          my_strncpy (buf, CMD[3], MSG_SIZE);
          strtok (buf, " ");
          CScript::cmd_type *c;
          // only respond if it's not a mbot command
          for (int i = LEVEL_MAX; i >= 0; i--)
            {
              c = s->script.cmds[i];
              while (c != NULL)
                {
                  if (strcasecmp (c->cmd, buf) == 0)
                    return;
                  c = c->next;
                }
            }
          alans = a->respond (CMD[3]);
          stripurl (alans);
          strip_crlf (alans);
          SEND_TEXT (DEST, "%s", alans);
        }
    }
}

// configuration file's local parser
void
alice_conf (CNetServer *s, c_char bufread)
{
  char buf[5][MSG_SIZE+1];
  strsplit (bufread, buf, 4);

  if (strcasecmp (buf[0], "alice") == 0)
    {
      if (buf[2][0] == 0)
        s->bot->conf_error ("sintax error: use \"alice initfile varsfile [logname]\"");
      CLog *log = NULL;
      if (buf[3][0] != 0)
        {
          log = s->bot->log_get (buf[3]);
          if (log == NULL)
            s->bot->conf_error ("inexistant logname");
        }
      CAlice *a = server2alice (s);
      if (a != NULL)
        s->bot->conf_error ("alice already defined in this server");
      a = new CAlice (s, buf[1], buf[2], log);
      if (!a->initialized)
        s->bot->conf_error ("error initializing alice");
      alice_list->add ((void *)a);
      s->script.events.add ((void *)alice_event);
    }
}

void
alice_stop (CModule *m)
{
  CAlice *a;
  alice_list->rewind ();
  while ((a = (CAlice *)alice_list->next ()) != NULL)
    {
      a->s->script.events.del ((void *)alice_event);
      delete a;
    }
  alice_list->delall ();
}

// module initialization
void
alice_start (CModule *m)
{
  alice_list = new CList ();
}

struct CModule::module_type module = {
  MODULE_VERSION,
  "alice",
  alice_start,
  alice_stop,
  alice_conf,
  NULL
};

}

