lpc4/lib/
lpc4/lib/doc/efun/
lpc4/lib/doc/lfun/
lpc4/lib/doc/operators/
lpc4/lib/doc/simul_efuns/
lpc4/lib/doc/types/
lpc4/lib/etc/
lpc4/lib/include/
lpc4/lib/include/arpa/
lpc4/lib/obj/d/
lpc4/lib/save/
lpc4/lib/secure/
lpc4/lib/std/
lpc4/lib/std/living/
#!/usr/local/bin/lpc -Cstay

#pragma all_inline
#include <arpa/telnet.h>
#include <socket.h>
#include <socket_errors.h>

#define DEBUG 1

string file;
string address;
string name;
string password;
int auto_login=300;

static int socket;

void stdout(string foo);
void stdin(string s);
void login();
void NULL() {}

void m_stdin(string s)
{
  s=replace(s,({",","\\n"}),({"\n","\n"}));
  map_array(s/"\n",stdin);
}

string my_gets()
{
  string s;
  s=gets();
  return s[0..strlen(s)-2];
}

void save()
{
  if(!strlen(file)) return;
  write("msh: saving....\n");
  rm(file);
  write_file(file,save_object());
  popen("chmod 600 "+file);
}

static mapping movement_verbs=([
	"n":"s",
	"s":"n",
	"e":"w",
	"w":"e",
	"u":"d",
	"d":"u",
	"nw":"se",
	"ne":"sw",
	"se":"nw",
	"sw":"ne",
	"north":"south",
	"south":"north",
	"east":"west",
	"west":"east",
	"up":"down",
	"down":"up",
	"northwest":"southeast",
	"northeast":"southwest",
	"southeast":"northwest",
	"southwest":"northeast",
	"enter mirror":"climb",
	"enter ship":"ashore",
	"pull plug":"up",
	"enter tree":"out",
	"aft":"forward",
	"forward":"aft",
	"out":"in",
	"in":"out",
	]);

void send(string s)
{
  socket_write(socket,s+"\n"); 
#if DEBUG
  write("sending: "+code_value(s)+"\n");
#endif
}

/* from-to database */

mapping dbase=([]);

void remove_entry(string from,string to)
{
  if(dbase[to])
    dbase[to]=m_delete(dbase[to],from);
}

void add_entry(string from,string to,string how)
{
#if DEBUG
  write("msh: From:"+from+" to:"+to+" "+how+"\n");
#endif
  if(dbase[to])
  {
    dbase[to][from]=how;
  }else{
    dbase[to]=([from:how]);
  }
}

/* 0 means no path.
 * -1 means already there
 * otherwise the returned string is a command to take you one
 * step on the way
 */
mixed howgetto(string from,string to)
{
  mapping whereto,done;
  int e,d;
  string foo;
  string *alpha,*beta,*gamma,*delta;

  whereto=([to:""]);
  done=([]);
  while(!(foo=whereto[from]) && m_sizeof(whereto))
  {
    done|=whereto;
    whereto=dbase & whereto;
    alpha=m_indices(whereto);
    beta=m_values(whereto);
    whereto=([]);
    for(e=0;e<sizeof(alpha);e++)
    {
      gamma=m_indices(dbase[alpha[e]]);
      delta=m_values(dbase[alpha[e]]);
      foo=beta[e];
      for(d=0;d<sizeof(gamma);d++)
	whereto[gamma[d]]=delta[d]+"\\n"+foo;
    }
    whereto-=done;
  }
  return foo;
}

static string current_pos;
void goto(string where)
{
  string cmd;
  if(stringp(cmd=howgetto(current_pos,where)))
  {
    send(replace(cmd,"\\n","\n"));
    write("Msh: Ok.\n");
  }else{
    write("Msh: don't know the way or no such room.\n");
  }
}

/* Autoactions */

mixed *autoactions=({});

void add_autoaction(string match,mixed action)
{
  autoactions+=({ ({match,sizeof(match/"%"),action}) });
}

void remove_autoaction(string a)
{
  autoactions=filter_array(autoactions,
		   lambda(mixed *a,string b)
			 {
			   return a[0]!=b;
			 },a);
}

void handle_autoactions(string s)
{
  mixed foo;
  string *a;
  a=allocate(10);
  foreach(autoactions,foo)
  {
    if(sscanf(s,foo[0]+"%s",a[0],a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8],a[9])==foo[1])
    {
      if(stringp(foo[2]))
      {
	write("Autoaction: doing "+foo[2]+"\n");
	s=foo[2];
	s=replace(s,a[0..foo[1]-1],
		  ("$1,$2,$2,$4,$5,$6,$7,$8,$9"/",")[0..foo[1]-1]);
	m_stdin(s);
      }else if(functionp(foo[2])){
	foo[2](a);
      }
    }
  }
}

void list_autoactions()
{
  mixed foo;
  foreach(autoactions,foo)
  {
    write("\""+foo[0]+"\" -> ");
    if(stringp(foo[2]))
    {
      write(foo[2]);
    }else if(functionp(foo[2])){
      write(function_name(foo[2])+"()");
    }
    write("\n");
  }
}

/* Aliases */


void handle_line(string foo) { handle_autoactions(foo); }

static string from_mud_buffer="";

void from_mud(int sock,string foo)
{
  foo-="\r";
  write(foo);
  from_mud_buffer+=foo;

  while(sscanf(from_mud_buffer,"%s\n%s",foo,from_mud_buffer))
  {
    while(foo[0..1]=="> ")
    {
/*    handle_prompt(); */
      foo=foo[2..strlen(foo)];
    }
    handle_line(foo);
  }
  while(from_mud_buffer[0..1]=="> ")
  {
/*    handle_prompt(); */
    from_mud_buffer=from_mud_buffer[2..strlen(foo)];
  }
}

string invert_dir(string x)
{
  while(sscanf(x," %s",x));
  while(x[-1]==' ') x=x[0..strlen(x)-2];
  if(x=movement_verbs[x])
    return x;
  write("Failed to invert direction "+x+"\n");
  return "FAILED INVERT";
}

static mapping variables=([]);

string eval(string s)
{
  mixed a,b;
  if(!stringp(s)) return s;
  if(sscanf(s,"%s==%s",a,b)) return eval(a)==eval(b);
  if(sscanf(s,"%s!=%s",a,b)) return eval(a)!=eval(b);
  if(sscanf(s,"%s+%s",a,b)) return eval(a)+eval(b);
  if(sscanf(s,"!%s",a)) return !eval(a);
  if(sscanf(s,"%*[ ]%d%*[ ]%s",a,b)==4 && b=="") return a;
  return s;
}

mixed exec(string b)
{
  return clone_object(make_object("#pragma unpragma_strict_types\n"+
				  "void create() { update(file_name()); expunge(); }\n"+
				  "mixed foo() { "+b+" ;}\n"))->foo();
}

mapping aliases=([]);

string *get_then_else(string s)
{
  int cnt,e,i;
  string *words;

  words=s/" ";
  cnt=1;
  for(i=e=0;e<sizeof(words);e++)
  {
    switch(words[e])
    {
    case "\\if":
      if(i)
      {
	write("error: no then after if.\n");
	return 0;
      }
      cnt++;
      i=1;
      break;

    case "then":
      if(!i)
      {
	write("error: if without then.\n");
	return 0;
      }
      i=0;
      break;

    case "else":
      if(i)
      {
	write("error: else without then.\n");
	return 1;
      }
      if(!--cnt)
      {
	return ({words[0..e-1]*" ",words[e+1..sizeof(words)]*" "});
      }
      break;
    }
  }
  return s;
}

void stdin(string bar)
{
  mixed a,b,c,d,*f;
  int foo;

  sscanf(bar,"%s\n",bar);
  if(sscanf(bar,"\\%s",a))
  {
    sscanf(a,"%s %s",a,b);
    switch(a)
    {
    case "1": case "2": case "3": case "4": case "5":
    case "6": case "7": case "8": case "9": case "10":
    case "11": case "12": case "13": case "14": case "15":
    case "16": case "17": case "18": case "19": case "20":
      sscanf(a,"%d",foo);
      for(foo++;foo--;) m_stdin(b);
      break;

    case "help":
      write("\\on - list autoaction\n");
      write("\\on \"<string>\" <cmd list> - add autoaction\n");
      write("\\on <string> - remove autoaction\n");
      write("\\at <pos> - tell msh where you are\n");

      write("\\from <pos> to <pos> do <cmd list> - Tell msh how to get from pos to pos\n");
      write("\\route <pos> to <pos> do <cmd list> - like \\from but adds in both directions\n");
      write("\\from <pos> to <pos> - Show how to get from pos to pos\n");
      write("\\remove <pos> to <pos> - Remove entry in pos-to-pos database\n");
      write("\\save - save to savefile (no automatic saves are done)\n");
      write("\\goto <pos> - Send commands to go to pos\n");
      write("\\auto <seconds> - Set autologin timeout\n");
      write("\\wait <seconds> <cmd list> - Delay a command\n");
      write("\\alias - List aliases\n");
      write("\\alias <alias> - Remove alias\n");
      write("\\alias <alias> <cmd list> - Add an alias\n");
      write("\\login - try to login again\n");
      break;

    case "wait":
      sscanf(b,"%d %s",foo,b);
      call_out(m_stdin,foo,b);
      write("msh: Will do "+b+" in "+foo+" seconds.\n");
      break;

    case "login":
      login();
      break;

    case "alias":
      if(!b)
      {
	sum_arrays(lambda(string foo,string bar)
		 {
		   write(foo+"           "[strlen(foo)..1000]+":"+bar+"\n");
		 },m_indices(aliases),m_values(aliases));
	break;
      }
      if(sscanf(b,"%s %s",b,c))
      {
	c=replace(c,",","\\n");
	aliases[b]=c;
	write("msh: Ok.\n");
      }else{
	if(aliases[b])
	{
	  aliases=m_delete(aliases,b);
	  write("msh: Ok.\n");
	}else{
	  write("msh: No sush alias.\n");
	}
      }
      break;

    case "beep":
      write(sprintf("%c",7));
      break;

    case "on":
      if(!b)
      {
	list_autoactions();
	break;
      }
      if(sscanf(b,"\"%s\" %s",a,b))
      {
	b=replace(b,",","\\n");
	add_autoaction(a,b);
	write("msh: ok.\n");
      }else{
	remove_autoaction(b);
	write("msh: ok.\n");
      }
      break;

    case "at":
      current_pos=b;
      write("msh: Ok, You're now in: "+current_pos+"\n");
      break;

    case "auto":
      sscanf(b,"%d",auto_login);
      write("msh: Autologin set to "+auto_login+" seconds.\n");
      break;

    case "remove":
      if(!sscanf(b,"%s to %s",a,b))
      {
	write("msh: Usage: \remove <pos> to <pos>\n");
	break;
      }
      remove_entry(a,b);
      write("msh: Ok.\n");
      break;

    case "r":
    case "route":
      if(!sscanf(b,"%s to %s do %s",a,b,c))
      {
	write("msh: Usage: \route <pos> to <pos> do <list>\n");
	break;
      }
      c=replace(c,",","\\n");
      c=(c/"\\n"-({""}))*"\\n";
      f=map_array(c/"\\n",invert_dir);
      for(foo=0;foo<sizeof(f)/2;foo++)
      {
	d=f[foo];
	f[foo]=f[sizeof(f)-foo-1];
	f[sizeof(f)-foo-1]=d;
      }
      d=f*"\\n";
      if(sizeof(d/"FAILED INVERT")>1)
	break;
      
      add_entry(a,b,c);
      add_entry(b,a,d);
      write("msh: Ok.\n");
      break;

    case "from":
    case "f":
      if(!sscanf(b,"%s to %s",a,b))
      {
	write("msh: Usage: \from <pos> to <pos> [do <list>]\n");
	break;
      }
      if(sscanf(b,"%s do %s",b,c))
      {
	c=replace(c,",","\\n");
	if(c[-1]!='\n') c+="\n";
	add_entry(a,b,c);
	write("msh: Ok.\n");
      }else{
	c=howgetto(a,b);
	if(!stringp(c))
	{
	  write("msh: no known way.\n");
	}else{
	  write("msh: path = "+replace(c,"\\n",",")+"\n");
	}
      }
      break;

    case "goto":
    case "g":
      goto(b);
      break;

    case "clean":
      autoactions=({});
      dbase=([]);
      write("msh: Ok.\n");
      break;

    case "data":
      write(code_value(dbase));
      write("\n");
      break;

    case "dumpauto":
      write(code_value(autoactions));
      write("\n");
      break;

    case "dump":
      write("===============================================================\n");
      write("Current location: "+current_pos+"\n");
      write("===============================================================\n");
      break;


    case ".":
      from_mud(socket,"> ");
      break;
	  
    case "save":
      save();
      write("msh: saved.\n");
      break;

    case "shutdown":
      shutdown();
      break;

    case "if":
      if(!sscanf(b,"%s then %s",a,b))
      {
	write("Usage: \\if <stmnt> then <commands> else <commands>\n");
	break;
      }
      a=eval(a);
      b=get_then_else(b);
      if(b) m_stdin(b[!(a && (!stringp(a) || strlen(a)))]);
      break;

    case "print":
      if(!b)
      {
	write("Usage: \\print <expression>\n");
	break;
      }
      a=eval(b);
      write("Result: "+code_value(a,1)+"\n");
      break;

    case "eval":
    case "exec":
      if(a=exec(b))
	write("Result: "+code_value(a,1)+"\n");
      break;

    default:
      if(sscanf(a+(b?" "+b:""),"%s=%s",b,c))
      {
	sscanf(b,"%s ",b);
	while(sscanf(b," %s",b));
	write("Setting $"+b+" to "+c+".\n");
	variables[b]=c;
	break;
      }
	
      write("msh: no such action.\n");
      break;
      
    case "quote":
    case "q":
      if(movement_verbs[b]) current_pos=0;
      send(b);
      break;
    }
  }else{
    c=bar/" ";
    if(a=aliases[c[0]])
    {
      c[0]=c[1..10000]*" ";
      b=replace(a,
		explode("#@,#1,#2,#3,#4,#5,#6,#7,#8,#9",",")[0..sizeof(c)-1],
		c[0..10]);
      map_array(b/"\\n",stdin);
    }else{
      if(movement_verbs[bar]) current_pos=0;
      send(bar);
    }
  }
}


void test1() { write(function_name(this_function())+"\n"); }
void test3() { write(function_name(this_function())+"\n"); }
void test4() { write(function_name(this_function())+"\n"); }

static int login_state;
void login_from_mud(int fd,string s)
{
  string tmp;
  from_mud(fd,s);
  s=lower_case(s);
  switch(login_state)
  {
  case 0:
    if(sscanf(s,"%sname%s",tmp,tmp))
    {
      send(name);
      login_state++;
    }
    break;
  case 1:
    if(sscanf(s,"%snew%s",tmp,tmp)) login_state++;
  case 2:
  case 3:
    if(sscanf(s,"%spassword%s",s,s))
    {
      socket_write(socket,password+"\n");  
      if(login_state!=2)
	set_socket_read(fd,from_mud);
      login_state++;
    }
  }
}

void connection_closed()
{
  write("Connection closed by foreign host.\n");
  if(auto_login)
  {
    remove_call_out(login);
    remove_call_out(login);
    write("msh: Will try again in "+auto_login+" seconds.\n");
    call_out(login,auto_login);
  }
}

void login()
{
  int change=0;
  int err;
  login_state=0;
  remove_call_out(login);
  remove_call_out(login);
  if(!name)
  {
    change++;
    write("Insert name: ");
    name=my_gets();
  }
  if(!password)
  {
    change++;
    write("Insert password: ");
    password=popen("stty -echo ; read 'foo' ; stty echo ; echo $foo");
    sscanf(password,"%s\n",password);
  }
  if(!address)
  {
    change++;
    write("Insert address: ");
    address=my_gets();
    while(sscanf(address," %s",address));
    if(sscanf(address,"%*d.%*d.%*d.%*s")!=4)
    {
      string a;
      int b;
      if(!sscanf(address,"%s %d",a,b))
      {
	write("Error in address.\n");
	exit(1);
      }
      write("Looking up address.\n");
      a=popen("nslookup "+a);
      sscanf(a,"%*sName:%*sAddress: %s\n",address);
      while(sscanf(address," %s",address));
      write("Ip number is: "+address+"\n");
      address+=" "+b;
    }
  }
  if(change)
    save();
  write("Trying "+address+" ...\n");
  socket=socket_create(STREAM | BUFFERED_INPUT | BUFFERED_OUTPUT ,
		       test1,connection_closed);
  socket_connect(socket,address,test3,test4);
  set_socket_read(socket,login_from_mud);

  /* check out why this is needed */
/*  set_socket_mode(socket,STREAM | BUFFERED_INPUT | BUFFERED_OUTPUT); */
  
}


int main(int argc,string *argv,string *env)
{
  string s;
  if(argc>1)
  {
    file=argv[1];
  }else{
    write("Save file (press return for no saves) ?: ");
    file=my_gets();
  }
  
  
  if(strlen(file))
    if(s=read_bytes(file))
      restore_object(s);

  login();
}