// An IRC thing

// based on the terminal.c and telnet.c from TMI-2.
// 0.2 had first colors
// 0.201 works with actions via /me

// original comments follow...

// written by Dwayne Fontenot (Jacques)
// last modified: 1992 October 19 (runs on the Basis mudlib)

// This object implements a telnet client (providing a subset of the telnet
// protocol) using STREAM mode of MudOS 0.9 LPC sockets.  See the init()
// function // to find out the commands this terminal understands.
// This object may be used from within a MudOS mud to connect to any
// networked server that understands the telnet protocol (including
// another LPmud).
#include <mudlib.h>
inherit OBJECT;

#include <config.h>
#include <net/socket.h>
#include <driver/origin.h>

// Previously defined colors from /include/ansi.h:
#include <ansi.h>
//  BLK, RED, GRN, YEL, BLU, MAG, CYN, WHT
#define RESET_COLOR "%^RESET%^"

#define VER "0.201"

#define SHORTDESC "computer with tIRC"
#define DISCONNECTED SHORTDESC+" ("+RED+"disconnected"+RESET_COLOR+")"
#define CONNECTED SHORTDESC+" ("+GRN+"connected"+RESET_COLOR+")"

// #define LOGFILEDIR "/u/t/tim/irclogs/"
#define BACKLOG_LENGTH 10

#define WRITE_WAIT_CALLBACK 0
#define WRITE_GO_AHEAD      1

#define STD_TELNET 23

#define IAC  255
#define DONT 254
#define DO   253
#define WONT 252
#define WILL 251

#define TELOPT_ECHO   1
#define TELOPT_SGA    3
#define TELOPT_TTYPE 24
#define TELOPT_NAWS  31

static string *telopts = ({"BINARY", "ECHO", "RCP", "SUPPRESS GO AHEAD",
                        "NAME", "STATUS", "TIMING MARK", "RCTE", "NAOL", "NAOP",
                    "NAOCRD", "NAOHTS", "NAOHTD", "NAOFFD", "NAOVTS",
                    "NAOVTD", "NAOLFD", "EXTEND ASCII", "LOGOUT", "BYTE MACRO",
                    "DATA ENTRY TERMINAL", "SUPDUP", "SUPDUP OUTPUT",
                    "SEND LOCATION", "TERMINAL TYPE", "END OF RECORD",
                    "TACACS UID", "OUTPUT MARKING", "TTYLOC",
                    "3270 REGIME", "X.3 PAD", "NAWS", "TSPEED", "LFLOW",
                    "LINEMODE"});

static string s_iac_dont_echo;
static string s_iac_do_echo;
static string s_iac_wont_echo;
static string s_iac_will_echo;
static string s_iac_dont_sga;
static string s_iac_do_sga;
static string s_iac_wont_sga;
static string s_iac_will_sga;
static string s_iac_wont_ttype;
static string s_iac_wont_naws;
static string s_iac;
static string s_dont_echo;
static string s_do_echo;
private string callback;private int conn_fd;
private string conn_host;
private int conn_port;
private int connected;
private int verbose;

private int write_state = WRITE_WAIT_CALLBACK;
private string write_message = "";

static void init_tel_neg()
{
  s_iac_dont_echo  = sprintf("%c%c%c",IAC,DONT,TELOPT_ECHO);
  s_iac_do_echo    = sprintf("%c%c%c",IAC,DO  ,TELOPT_ECHO);
  s_iac_wont_echo  = sprintf("%c%c%c",IAC,WONT,TELOPT_ECHO);
  s_iac_will_echo  = sprintf("%c%c%c",IAC,WILL,TELOPT_ECHO);
  s_iac_dont_sga   = sprintf("%c%c%c",IAC,DONT,TELOPT_SGA);
  s_iac_do_sga     = sprintf("%c%c%c",IAC,DO  ,TELOPT_SGA);
  s_iac_wont_sga   = sprintf("%c%c%c",IAC,WONT,TELOPT_SGA);
  s_iac_will_sga   = sprintf("%c%c%c",IAC,WILL,TELOPT_SGA);
  s_iac_wont_ttype = sprintf("%c%c%c",IAC,WONT,TELOPT_TTYPE);
  s_iac_wont_naws  = sprintf("%c%c%c",IAC,WONT,TELOPT_NAWS);
  s_iac            = sprintf("%c",    IAC);
  s_dont_echo      = sprintf("%c%c",  DONT,TELOPT_ECHO);
  s_do_echo        = sprintf("%c%c",  DO,  TELOPT_ECHO);
}

string query_auto_load(){return base_name(this_object())+":";}

void
set_callback(string arg)
{
        callback = arg;
}

void create()
{
  init_tel_neg();
  connected = 0;
  verbose = 0;
  set_callback("handler");
  set("short", "@@query_short");
  set("long", "@@query_long");
  set("id", ({"term", "terminal", "tirc", "tIRC"}));
  set("prevent_drop", "@@query_connected");
  set("nick", "tIRCuser");
  seteuid(getuid()); // I guess this is needed to save to files
  init_tel_neg();
}

void
set_verbosity(int v)
{
        verbose = v;
}

int
query_connected()
{
        return connected;
}

void disconnected()
{
  call_other(this_object(), callback, "close");
  connected = 0;
}

int connected()
{
  call_other(this_object(), callback, "open");
  connected++;
}

void my_socket_write(int fd, string message)
{
  int ret;

  write_message = write_message + message;
  if(write_state == WRITE_GO_AHEAD){
    ret = socket_write(fd, write_message);
    write_message = "";
    if(ret == EESUCCESS) write_state = WRITE_GO_AHEAD;
    else if(ret == EECALLBACK) write_state = WRITE_WAIT_CALLBACK;
    else if(ret == EEWOULDBLOCK || ret == EEALREADY)
      call_out("my_socket_write", 10, fd, "");
  }
}

int line(string str)
{
  if(connected == 2){
    my_socket_write(conn_fd,s_iac_dont_sga+s_iac_dont_echo);
    write("SENT dont SUPPRESS GO AHEAD\nSENT dont ECHO\n");
    return(1);
  }
  return(0);
}

int char(string str)
{
  if(connected == 2){
    my_socket_write(conn_fd,s_iac_do_sga+s_iac_do_echo);
    write("SENT do SUPPRESS GO AHEAD\nSENT do ECHO\n");
    return(1);
  }
  return(0);
}

int connect(string str)
{
  int ret;

  if(!str) return(0);
  if (connected == 2){
    notify_fail("Already connected to " + socket_address(conn_fd) + ".\n");
    return(0);
  } else if (connected == 1){
    notify_fail("Connection in progress.\n");
    return(0);
  }
  conn_fd = socket_create(STREAM,"socket_shutdown");
  if (sscanf(str, "%s %d", conn_host, conn_port) != 2) {
    conn_host = str;
    conn_port = STD_TELNET;
  }
  connected();
  resolve(conn_host, "resolve_connect");
  return(1);
}

int tellme(string str)
{
  message("telnet",str,environment(this_object()),this_object());
/*
  if(find_player("tim")){
    tell_object(find_player("tim"), str);
    return 1;
  }
*/
}

void add_chan_backlog(string which_chan, string whichname, string msg)
{
    string whichchan;
    string *backlog;
    int count ;

    whichchan = lower_case(which_chan);
    if(undefinedp(query("chan_backlog"+"/"+whichchan)))
      set("chan_backlog"+"/"+whichchan,({}));
    backlog = query("chan_backlog"+"/"+whichchan);
    count = sizeof(backlog);
    if(count >= BACKLOG_LENGTH)
      backlog = backlog[1..BACKLOG_LENGTH];
//    msg = replace_string(msg,"tells","told",1);
    msg = BLU+whichchan+"<"+whichname+"> "+RESET_COLOR+msg;
    if(!backlog)
      backlog = ({ msg }) ;
    else
      backlog = backlog + ({ msg }) ;
    set("chan_backlog"+"/"+whichchan,backlog);
    return;
}

int get_chan_msg(string whichchan, string whichname, string msg){
  tellme(BLU+whichchan+"<"+whichname+"> "+RESET_COLOR+msg);
  add_chan_backlog(whichchan, whichname, msg);
  return 1;
}

void add_privmsg_backlog(string which_name, string msg, int sent)
{
    string whichname;
    string *backlog;
    int count ;

    whichname = lower_case(which_name);
    if(undefinedp(query("pmsg_backlog"+"/"+whichname)))
      set("pmsg_backlog"+"/"+whichname,({}));
    backlog = query("pmsg_backlog"+"/"+whichname);
    count = sizeof(backlog);
    if(count >= BACKLOG_LENGTH)
      backlog = backlog[1..BACKLOG_LENGTH];
//    msg = replace_string(msg,"tells","told",1);
    if(sent){
      msg = BLU+"You msg <"+whichname+"> "+RESET_COLOR+msg;
    }
    else{
      msg = BLU+"<"+whichname+"> "+RESET_COLOR+msg;
    }
    if(!backlog)
      backlog = ({ msg }) ;
    else
      backlog = backlog + ({ msg }) ;
    set("pmsg_backlog"+"/"+whichname,backlog);
    return;
}

int get_priv_msg(string whichname, string msg){
  tellme(BLU+"<"+whichname+"> "+RESET_COLOR+msg);
  add_privmsg_backlog(whichname, msg, 0);
  return 1;
}

//int recordmsg(string file, string str){
//  write_file(file, str, 0);
//  return 1;
//}

int send(string str)
{
  if(connected==2){
    if(!str){
      write("Sending CR.\n");
      my_socket_write(conn_fd,"\n");
      return(1);
    }
    tellme("Sending to server:"+str);
    my_socket_write(conn_fd,str + "\n");
    return(1);
  }
  return(0);
}

int nick(string str)
{
  if(!str){
    notify_fail("No parameters! Syntax: /nick Billybob\n");
    return 0;
  }
  send("nick"+" "+str);
  tellme("You change your nick to "+GRN+str+RESET_COLOR);
  set("nick", str);
//  recordmsg(LOGFILEDIR+"nicks", "Changed nick at "+ctime(time())+" to: "+str+"\n");
  return 1;
}

int ison(string str)
{
  if(!str){
    notify_fail("No parameters! Syntax: /ison Billybob\n");
    return 0;
  }
  send("ison"+" "+str);
  tellme("You check to see if "+GRN+str+RESET_COLOR+" is on.\n");
  return 1;
}

int join(string str)
{
  if(!str){
    notify_fail("You did no parameters! Syntax: /join #Takuhis_goat_sex_farm\n");
    return 0;
  }
  send("join"+" "+str);
//  recordmsg(LOGFILEDIR+"chanjoins", "Joined "+str+" at "+ctime(time())+"\n");
  return 1;
}

int quit(string str)
{
  if(!str){
    send("quit");
    tellme("You quit with no quit message");
    return 1;
  }
  send("quit"+" :"+str);
  tellme("You quit with the quit message: "+str);
//  recordmsg(LOGFILEDIR+"quits", "Quit at "+ctime(time())+": "+str+"\n");
  return 1;
}

int pong(string str)
{
  send("pong"+" "+str);
//  tellme("Sending a PONG to: ("+str+")");
//  recordmsg(LOGFILEDIR+"pongs", "Sent pong at "+ctime(time())+" to: "+str+"\n");
  return 1;
}

int getmsg(string str)
{
  string str1, str2, str3; // parts of the text
  string blah1, blah2, blah3; // temp strings
  string me_thing; // blah, see how to define it later, char for now
  me_thing = sprintf("%c", 1); // eliminate later

  sscanf(str,"%s\n",str1); // cut off the nextline thing
  sscanf(str,"%s\n",str1); // cut off the nextline thing
  sscanf(str1,"%s %s",str1, str2); // split first word from rest
  sscanf(str2,"%s %s",str2, str3); // split second word from rest
  if(str1=="PING"){
    // respond to pings...
    sscanf(str2,"%s\n", str2);
    sscanf(str2,":%s", str2);
//    tellme("Got a ping from "+str2+"!");
    pong(str2);
    return 1;
  }


  if(str2=="PRIVMSG"){
    // got chan message, parse and use function
    sscanf(str1,":%s", str1); // cut off : in front
    sscanf(str1,"%s!%s", blah1, blah2);
      // that makes blah1 the nick, blah2 the IP
    sscanf(str3,"%s %s", blah3, str3);
      // that cuts off chan name and makes blah3 the chan
    sscanf(str3,":%s",str3);
    if(blah3[0]==35){  // 35 is ascii number for #
      if((str3[0..0] == me_thing) && (str3[sizeof(str3)-1..sizeof(str3)-1] == me_thing))
        get_chan_msg(blah3, blah1, "*"+blah1+str3+"*"); // this is a /me
      else{
        get_chan_msg(blah3, blah1, str3); // blah2, the IP is unused
      }
    }
    else{
      if((str3[0..0] == me_thing) && (str3[sizeof(str3)-1..sizeof(str3)-1] == me_thing))
        get_priv_msg(blah1, "*"+blah1+str3+"*"); // this is a /me
      else{
        get_priv_msg(blah1, str3);
      }
    }
    return 1;
  }

  if(str2=="JOIN"){
//    tellme("join function isn't in yet");
    sscanf(str1,"%s!%s", blah1, blah2);
    // that makes blah1 the nick, blah2 the IP
    sscanf(str3,":%s", blah3, str3);
      // that cuts off colon and makes str3 the chan
    // ok, person named blah1 joined str3
    sscanf(blah1,":%s",blah1);
      get_chan_msg(str3, blah1, MAG+blah1+" has joined the channel.");
    return 1;
  }

  if(str2=="303"){
    sscanf(str3,"%s :%s",blah1, str3);
    tellme(GRN+"These people are online: "+str3+RESET_COLOR);

    return 1;
  }

  tellme("This wasn't processed:>"+str+"<"+"str1 is: "+str1+" and str2 is:"+str2);
  return 1;
}

int msg(string str)
{
  string msg; // message
  string who; // target
  string syntax;
  syntax = "Syntax: /msg Aspect I want you ba-a-ad.\n";
  if(!str){
    notify_fail(syntax);
    return 0;
  }
  if (sscanf(str,"%s %s",who,msg) != 2) {
    notify_fail(syntax);
    return 0;
  }
  if(!msg || !who){
    notify_fail(syntax);
    return 0;
  }
  send("privmsg "+who+" :"+msg);
  tellme("You message "+who+": "+msg);
  if(who[0]==35){  // 35 is ascii number for #
    add_chan_backlog(who,query("nick"),msg); // blah2, the IP is unused
  }
  else{
    add_privmsg_backlog(who, msg,1);
  }
//  add_privmsg_backlog(string whichname, string msg)
//  add_chan_backlog(string whichchan, string whichname, string msg)

  return 1;
}

int me(string str)
{
  string msg; // message
  string who; // target
  string syntax;
  syntax = "Syntax: /me aspect swoons at you... 'I want you ba-a-ad.'\n";
  if(!str){
    notify_fail(syntax);
    return 0;
  }
  if (sscanf(str,"%s %s",who,msg) != 2) {
    notify_fail(syntax);
    return 0;
  }
  if(!msg || !who){
    notify_fail(syntax);
    return 0;
  }
  send("privmsg "+who+" :"+ sprintf("%c%s%c", 1, "ACTION "+msg, 1));
  tellme(who+"*You "+msg+"*");
  if(who[0]==35){  // 35 is ascii number for #
    add_chan_backlog(who,query("nick"),"*"+query("nick")+msg+"*"); // blah2, the IP is unused
  }
  else{
    add_privmsg_backlog(who, "*"+query("nick")+msg+"*",1);
  }
//  add_privmsg_backlog(string whichname, string msg)
//  add_chan_backlog(string whichchan, string whichname, string msg)

  return 1;
}

int disconnect(string str)
{
  int ret;

  if (!connected){
    notify_fail("Not connected anywhere.\n");
    return(0);
  }
  ret = socket_close(conn_fd);
  conn_fd = -1;
  disconnected();
  if(ret < 0){
    notify_fail("unable to disconnect cleanly.\n");
    return(0);
  }
  return(1);
}

void receive_data(int rec_fd,string msg)
{
  string *chunks;
  int i;
  object hearer;

  hearer = environment(this_object());
  if(getmsg(msg)) return;  // stops here, interprets the text
  chunks = explode(msg,s_iac);
  for(i=0;i<sizeof(chunks);i++){
    switch(chunks[i][0]){
    case DONT:
      if (verbose)
          message("telnet","RCVD dont "+telopts[chunks[i][1]]+"\n",hearer,
              this_object());
      switch(chunks[i][1]){
      case TELOPT_ECHO:
        message("telnet_neg",s_iac_dont_echo,hearer,this_object());
        break;
      }
      if(strlen(chunks[i]) > 2)
        message("telnet",chunks[i][2..strlen(chunks[i])-1],hearer,
                this_object());
      break;
    case DO:
      if (verbose)
         message("telnet","RCVD do "+telopts[chunks[i][1]]+"\n",hearer,
              this_object());
      switch(chunks[i][1]){
      case TELOPT_ECHO:
        my_socket_write(rec_fd,s_iac_wont_echo);
    if (verbose)
           message("telnet","SENT wont ECHO\n",hearer,this_object());
        break;
      case TELOPT_TTYPE:
        my_socket_write(rec_fd,s_iac_wont_ttype);
    if (verbose)
        message("telnet","SENT wont TERMINAL TYPE\n",hearer,this_object());
        break;
      case TELOPT_NAWS:
        my_socket_write(rec_fd,s_iac_wont_naws);
            if (verbose)
        message("telnet","SENT wont NAWS\n",hearer,this_object());
        break;
      default:
    my_socket_write(rec_fd,sprintf("%c%c%c",IAC,WONT,TELOPT_ECHO));
        if (verbose)
        message("telnet","SENT wont " + chunks[i][1] + "\n",hearer,
                this_object());
        break;
      }
      if(strlen(chunks[i]) > 2)
        message("telnet",chunks[i][2..strlen(chunks[i])-1],hearer,
                this_object());
      break;
    case WONT:
          if (verbose)
      message("telnet","RCVD wont "+telopts[chunks[i][1]]+"\n",hearer,
              this_object());
      switch(chunks[i][1]){
      case TELOPT_ECHO:
        message("telnet_neg",s_iac_wont_echo,hearer,this_object());
        break;
      case TELOPT_SGA:        message("telnet_neg",s_iac_wont_sga,hearer,this_object());
        break;      }      if(strlen(chunks[i]) > 2)        message("telnet",chunks[i][2..strlen(chunks[i])-1],hearer,                this_object());      break;    case WILL:
          if (verbose)
      message("telnet","RCVD will "+telopts[chunks[i][1]]+"\n",hearer,
              this_object());
      switch(chunks[i][1]){
      case TELOPT_ECHO:
        message("telnet_neg",s_iac_will_echo,hearer,this_object());
        my_socket_write(rec_fd,s_iac_do_echo);
        if (verbose)
        message("telnet","SENT do ECHO\n",hearer,this_object());
        break;
      case TELOPT_SGA:
        message("telnet_neg",s_iac_will_sga,hearer,this_object());
        break;
      }
      if(strlen(chunks[i]) > 2)
        message("telnet",chunks[i][2..strlen(chunks[i])-1],hearer,
                this_object());
      break;
    default:
      message("telnet",chunks[i],hearer,this_object());
      break;
    }
  }
}

void write_data(int fd)
{
  write_state = WRITE_GO_AHEAD;
  my_socket_write(fd,"");
}

varargs void socket_shutdown(int fd)
{
  object hearer;

  if ((origin() == ORIGIN_LOCAL || origin() == ORIGIN_DRIVER) ||
        fd == conn_fd) {
    hearer = environment(this_object());
    socket_close(fd);
    conn_fd = -1;
    disconnected();
    return;
  }
}

void resolve_connect( string name, string number, int key ) {
    int ret;

    if (connected) {
      if (number)
        conn_host = number;
      ret = socket_connect(conn_fd, sprintf("%s %d", conn_host, conn_port),
          "receive_data", "write_data");
      if(ret == EESUCCESS) {
        connected();
        return;
      }
    }
    if (interactive(environment())) {
      message("error","unable to connect: " + socket_error(ret) + "\n",
          environment());
      disconnect(0);
    }
}
 



string query_short() {
  if (query_connected()) return CONNECTED;
  return DISCONNECTED;
}

string query_long() {
  if (query_connected())
    {
      return @EndText
This is a computer with the tIRC IRC client installed on it.
It is currently connected.
You can view the command list by typing: help tirc
EndText;
    }
  else
    {
      return @EndText
This is a computer with the tIRC IRC client installed on it.
It is NOT currently connected.
You can view the command list by typing: help tirc
EndText;
    } 
}

int clean_up() {
    socket_shutdown();
    return 0;
}

int help(string str){
    string msg;
    msg = "";
    if(!str){
        return 0;
    }
    if(str=="tirc"){
        msg = ("Help for the tIRC IRC client, version " + VER + 
@EndText - Created By Tim
________________________________________________________________________________
The following commands are recognized by tIRC:
/ison
/join
/me
/msg
/nick
/raw
/server
/quit
You can view help for each of these commands by typing 'help tirc <command>',
for example, 'help tirc /nick'
EndText);
    }

    if(str=="tirc /ison"){
        msg = (@EndText
The /ison command will let you check who is online out of a list of nicks.
Syntax:   /ison <names separated by spaces>
Example:  /ison John Paul George Ringo
EndText);
    }
    if(str=="tirc /join"){
        msg = (@EndText
The /join command will join a certain channel.
Syntax:   /join <name of channel>
Example:  /join #Takuhis_goat_sex_pix
EndText);
    }
    if(str=="tirc /me"){
        msg = (@EndText
The /me command will send an action to a channel or nickname.
Syntax:   /me <name|channel> <message>
Example:  /me Billybob punches you in the face for no apparent reason.
          /me #Takuhis_goat_sex_pix welcomes Raven to the channel!
EndText);
    }
    if(str=="tirc /msg"){
        msg = (@EndText
The /msg command will send a message to a channel or nickname.
Syntax:   /msg <name|channel> <message>
Example:  /msg Billybob Hi, I intend to kill you.
          /msg #Takuhis_goat_sex_pix Welcome to the channel, Raven!
EndText);
    }
    if(str=="tirc /nick"){
        msg = (@EndText
The /nick command will change your nickname.
Syntax:   /nick <name>
Example:  /nick Billy
EndText);
    }
    if(str=="tirc /raw"){
        msg = (@EndText
The /raw command will send raw commands to the server.
Syntax:   /raw <message>
Example:  /raw quit :Bye everybody!
EndText);
    }
    if(str=="tirc /server"){
        msg = (@EndText
The /server command will connect you to a server and use your
own name as the default nickname.
Syntax:   /server <server> <port>
Example:  /server irc.blah.com 1234
EndText);
    }
    if(str=="tirc /quit"){
        msg = (@EndText
The /quit command will disconnect you from the server.
You can put an optional message for when you leave.
Syntax:    /quit <optional message>
Examples:  /quit
           /quit Bye everybody!
EndText);
    }
    if(msg!=""){
      write(msg);
      return 1;
    }
    return 0;
}


void init()
{
  add_action("connect",({"connect", "/connect", "telnet"}));
  add_action("nick",({"nick", "/nick"}));
  add_action("ison",({"ison", "/ison"}));
  add_action("join",({"join", "/join"}));
  add_action("msg",({"msg", "/msg"}));
  add_action("me",({"me", "/me"}));
  add_action("send",({"send", "raw", "/raw"}));
  add_action("quit","/quit");
  add_action("disconnect","disconnect");
  add_action("help","help");
/*
need to figure out what line and char do :P
  add_action("line","line");
  add_action("char","char");
*/
}






