dbsc/clans/
dbsc/deity/
dbsc/houses/
dbsc/player/a/
dbsc/space/
//Christopher Busch
//(c) 1993 all rights reserved.
//eliza.cpp


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
// #include <search.h>

#include "eliza.hpp"





bool eliza::addname(char* n,int d)  //add one name and number
{
  if(numnames<maxnames) {
    for(int i=strlen(n)-1;i>=0;i--) if(n[i]=='_') n[i]=' ';
//    printf("found person '%s' at %d \n",n,d);
    return thenames[numnames++].set(n,d) != NULL;
  }
  return false;
}


int namecompare(const void* a,const void* b)
{ 
  return strcasecmp( 
                    ((eliza::nametype*)a)->name,
                    ((eliza::nametype*)b)->name); 
}

void eliza::sortnames()
{  
  qsort((char*)thenames,numnames,sizeof(eliza::nametype),namecompare);
}
int eliza::getname(char* n)
{
  nametype finder;
  if(finder.set(n,0) == NULL) return 0;
  nametype* nt=(nametype*)
    bsearch((char*)&finder,(char*)thenames,numnames,
            sizeof(nametype),namecompare);
  if(nt==NULL) return 0;
  return nt->dbase;
}


void eliza::reducespaces(char *m)
{
  int len=strlen(m),space= m[0]<' ',count=0;
  for(int i=0;i<len;i++)    {
    if(!space || m[i]>' ' )      {
      m[count]= (m[i]<' ' ? ' ' : m[i]);
      count++;
    }
    space= m[i]<' ';
  }
  if(count) m[count]='\0';
}


int subst(char start[],char sub1[],char sub2[])
{
  int ln1=strlen(sub1),changed=0;
  char s[MAXSIZE];
  while((start=strstr(start,sub1))!=NULL)
    {
      changed=1;
      for(int i=0;i<ln1;i++)
        start[i]=' ';
      strcpy(s,start);
      //printf("1'%s'",start);
      sprintf(start,"%s%s",sub2,s);
      //printf("2'%s'",start);
    }
  return changed;
}

//Gets a word out of a string.
int fggetword(char*& input,char* outword,char& outother) //0 if done
{
  outword[0]=0;
  char *outword0=outword;
  int curchar;
  curchar=input[0];

  while( isalnum(curchar) || curchar=='_' )
    {
      *outword=curchar;
      outword++;
      input++;
      //                printf(",%c;",*input); //debug
      curchar=*input;
    }
  if(*input!= '\0') input++;
  *outword='\0';
  outother=curchar;
  if( curchar == 0 && outword0[0]==0 ) return 0;
  return 1;
}

//add more then one name to some number
bool eliza::addbunch(char* names,int dbase) 
{ 
  char aword[MAXSIZE+3];
  char otherch;
  char* theinput=names;
  while( fggetword(theinput,aword,otherch)) {
    if(addname(aword,dbase) == false) return false;
  }
  return true;
}

//This tries more exhaustively to find a name out of the database
//for example if 'Bobby Jo' fails, it will try Bobby then Jo.
int eliza::getanyname(char* n)
{
  int dbase=getname(n);
  if(dbase==0) {
    char aword[MAXSIZE+3];
    char otherch;
    char* theinput=n;
    while( fggetword(theinput,aword,otherch)) {
      dbase=getname(aword);
      if(dbase!=0) return dbase;
    } 
  }
  return dbase;
}



char* substit(char *in)
{
  static const char pairs[][10]=
    {"am","are",
     "I","you",
     "mine","yours",
     "my","your",
     "me","you",
     "myself","yourself",
     //swapped order:
     "you","I",
     "yours","mine",
     "your","my",
     "yourself","myself",
     "",""};

  for(int i=0;pairs[i*2][0]!='\0';i++)
    {
      if(strcasecmp(in,pairs[i*2])==0)
        {
          return (char*)pairs[i*2+1];
        }
    }
  return in;
}       

#include <unistd.h>

void fixgrammer(char s[])
{
  char newsent[MAXSIZE+20];
  newsent[0]='\0';
  
  char aword[MAXSIZE+3];
  char* theinput;       
  theinput=s;
  char otherch;
  char ministr[]=" ";   


  while( fggetword(theinput,aword,otherch))
    {
#ifdef DEBUG
      printf(">%s'%s' : '%s' , %d\n",newsent,theinput,aword,otherch);
#endif

      ministr[0]=otherch;
      strcat(newsent,substit(aword));           
      strcat(newsent,ministr);

    }
  strcpy(s,newsent);

}


//enables $variable translation
void eliza::addrest(char* replied, char* talker,
		    char* rep,char* target,char* rest)
{
  trim(rest);
  char* i;
  char* point   = replied;
  char* str     = rep;
  char* toofar = replied+repsize-1;
  while ( *str != '\0')    {
    if ( *str != '$' )      {
      *point++ = *str++;
      if(point==toofar) { *point='\0'; return; }
      continue;
    }
    ++str;
    switch( *str )         /*add $Variable commands here*/
      {
      case 's':
      	i="$s";
      	break;
      case 'n':         
        i=talker;
        break;
      case 't': 
        i=target;
        break;
      case 'r':
        i=rest;
        break;
      case '$':
        i="$";
        break;
      case 0:
        continue;
      case 'A':  //dont change THIS - Chris Busch  //author
	i=(char*)eliza_title;
	break; 
      case 'V': //dont change THIS!   //Version
	i=(char*)eliza_version;
	break;
      case 'C': //dont change this!  //Compile time
	i= __DATE__ " " __TIME__;
	break;
      default:
        i="$UNDEFINED";
        break;
      }
    ++str;
    while ( ( *point = *i ) != '\0' ) {
      ++point;
      ++i;
      if(point==toofar) { *point='\0'; return; }
    }
  }
  *point++ = '\0';
}


//nothing can be NULL!!!
char* eliza::processdbase(char* talker,char* message,char* target,int dbase)
{
  static char replied[repsize];
  char *rep=NULL;

  if( dbase<0 || dbase>numdbases) return "dbase error in processnumber";

  if(strlen(message)>MAXSIZE) return "You talk too much.";

  strcpy(replied,"I dont really know much about that, say more.");


  reducespaces(message);
  trim(message);
  int overflow=10; //runtime check so we dont have circular cont jumps
  do{
    thekeys[dbase].reset();
    while(thekeys[dbase].curr()!=NULL)    {
      int i=0,rest=0;
      if(match(thekeys[dbase].curr()->key.getlogic(),message,i,rest))
        {
          rep=thekeys[dbase].curr()->key.getrndreply();
          fixgrammer(&message[rest]);
          addrest(replied,talker,rep,target,&message[rest]);
          reducespaces(replied);
          return replied;
        }
      thekeys[dbase].advance();
    }
    dbase=thekeys[dbase].contdbase;
    overflow--;
  }while(dbase>=0 && overflow>0);
  if(overflow <=0) {
    return "potential circular cont (@) jump in databases";
  }
  return replied;
}


bool eliza::loaddata(char file[],char recurflag)
{
  //NOTE!!!! numdbases is the count of dbases!!!
  if(numdbases>=maxdbases) return false;

  FILE *data;
  char str[MAXSIZE];
  if((data=fopen(file,"rt"))==NULL)
    {
#ifdef DEBUG
      puts("File error!");
#endif
      return false;
    }
  int linecount=0;

  while(fgets(str,MAXSIZE-1,data)!=NULL)
    {
      linecount++;
#ifdef DEBUG
      puts(str);
#endif
      trim(str);
      if(strlen(str)>0)
        {
          if(str[0]>='1' && str[0]<='9')            {
            thekeys[numdbases].curr()->key.addreply(str[0]-'0',&(str[1]));
#ifdef DEBUG
            //puts("reply found");
#endif
          } else switch(str[0])    {
          case '\0' :
            break;
          case '(':
            thekeys[numdbases].addkey();
            thekeys[numdbases].curr()->key.addlogic(str);
#ifdef DEBUG
            //puts("logic found");
#endif
            break;
          case '#':
#ifdef DEBUG
            puts("rem");
#endif
            break;
          case '"':
            fprintf(stderr,"%s\n",&str[1]);
            break;
          case '\'':
            fprintf(stdout,"%s\n",&str[1]);
            break;
          case '>': //add another database
            if(numdbases<maxdbases-1) numdbases++;
            else 
	      fputs("response database is full. numdbases>maxdbases",stderr);
            if(!addbunch(&str[1],numdbases) ) {
              puts("name database is full.");
            }
            break;
	  case '%': //include another file inline
	    trim(str+1);
	    if(!loaddata(str+1,1)) //recurflag set
	      printf("including inline '%s' failed at %d!\n",str+1,linecount);
	    break;
	  case '@': //add continue to another database jump
	    if(thekeys[numdbases].contdbase==allkeys::contnotset) {
	      trim(str+1);
	      for(char* i=str+1;*i!=0;i++) if(*i=='_') *i=' ';
	      sortnames();
	      int contd=getname(str+1);
	      thekeys[numdbases].contdbase=contd;
//	      printf("found cont link %s to %d\n",str,contd);
	    } else 
	      printf(" @ continue already used for database %d at %d\n"
		     ,numdbases,linecount);
	    break;
	  default:
            printf("extraneous line: '%s' at %d\n",str,linecount);
          }
        }
    }
  fclose(data);
  if(!recurflag) {
    numdbases++;
    sortnames();
  }
  return true;
}


char* eliza::trim(char str[])
{
  int i,a,ln;
  for(ln=strlen(str);(ln>0) && (str[ln-1]<=' ');ln--);
  str[ln]=0;
  for(i=0;(i<ln) && (str[i]<=' ');i++);
  if(i>0)
    for(ln-=i,a=0;a<=ln;a++)
      str[a]=str[a+i];
  return str;
}




int eliza::doop(char op,int a,int b)
{
  switch(op)
    {
    case '\0':
    case '&': return a && b;
            case '|': return a || b;
            case '~': return a && !b;
            default:
#ifdef DEBUG
              printf("Errored Op! %c\n",op);
#endif
              return 0;
            }
}

int eliza::lowcase(char ch)
{
  if(ch>='A' && ch<='Z') return ch+32;
  return ch;
}

int eliza::strpos(char *s,char *sub)
{
  int i,a,lns=strlen(s),lnsub=strlen(sub),run,space=1;
  if(sub[0]=='=') {// = exact equality operator    
    return strcasecmp(&sub[1],s) ? 0 : lns;
  }
  if(sub[0]=='^'){  // match start operator    
    return strncasecmp(&sub[1],s,lnsub-1) ? 0 : lns;
  }
  if(lnsub>lns) return 0;

  run=lns-lnsub;
  for(i=0;i<=run;i++)
    {
      if(space)
        {
          for(a=0;a<lnsub && (lowcase(s[i+a])==sub[a]);a++);
#ifdef DEBUG
          //printf("hey %d r:%d",a,i+a);
#endif
          if(a==lnsub) return (i+a);
        }
      space=s[i]==' ';
    }

  return 0;
}

#define encounterop                  \
trim(ph);                    \
if(strlen(ph))               \
{                            \
        a=strpos(m,ph);      \
        if(a>0) rest=a;      \
        res=doop(op,res,a);  \
}                            \
len=ph[0]=0;                 



int eliza::match(char s[],char m[],int& in,int& rest)
{
  int l=strlen(s),res=1,op='\0',a;
  char ph[MAXSIZE];
  int len=0;
  ph[0]=0;
#ifdef DEBUG
  //puts(s);
  //puts(m);
#endif
  while(s[in]!='(') in++;
  in++;
  for(;in<l;in++)
    {
      switch(s[in])
        {
        case '(':
#ifdef DEBUG
          //printf("in %d into...\n",in);
#endif
          a=match(s,m,in,rest);
          if(op=='\0') res=a;
          else
            res=doop(op,res,a);
          break;
        case '&':
          encounterop
            op='&';
          break;
        case '|':
          encounterop
            op='|';
          break;
        case '~':
          encounterop
            op='~';
          break;
        case ')':
#ifdef DEBUG
          //printf("p:'%s'\n",ph);
#endif
          trim(ph);
          if(strlen(ph))
            {
              a=strpos(m,ph);
              if(a>0) rest=a;
              return doop(op,res,a);
            } else return res;

          break;
        default:
          ph[len++]=s[in];
          ph[len]='\0';
        }

    }
  return res;
}