#include <kernel/kernel.h>
#include <kernel/user.h>
#include <type.h>
#include <status.h>

inherit LIB_MUDMODE;
inherit user API_USER;

/*
  A very basic Intermud-3 daemon for those MUDs running on the DGD driver
  with network support.  It is not meant to run out of the box, but rather
  to show how to receive and send data over the Intermud network using
  DGD's network capabilities.

  Originally created by Jason Cone (Qi@CornerStone)
  
  Ported to WOTF by Aidil in oct 2007.

  Extended into a fully functional I3 inband client with message routing
  support

  And then sortof ported to Phantasmal, also by Aidil.

*/

#define SAVEFILE "intermud3-data"

#define IMUD_NAME "Phantasmal I3 demo"
#define ADMIN_EMAIL "aidil@WOTF"

mixed *mpRouterList;
static private mapping aServices;
static int connected;
mapping chanlist;
int chanlist_id;
mapping mudlist;
int mudlist_id;
int password;
static int trace_protocol;
int current_router;
static int force_reconnect;
static int bpcount;
static int shutting_down;
string * listening;

void switch_router();
void create();


void save_me( void ) {
  save_object( SAVEFILE );
}

void restore_me( void ) {
  restore_object( SAVEFILE );
}

private void write_imud_stream(string sType, mixed sTargetMUD,
                               mixed sTargetUser, mixed *mpMessage)
{
  mixed sOrigUser;
  string sSend;
  int nSize;
  mixed *mpPacket;

  if(!sTargetUser) sTargetUser = 0;
  if(!sTargetMUD) sTargetMUD = 0;

  if( this_user() != nil ) {
    if( this_user() != nil )
      sOrigUser = this_user()->get_name();
  }

/*
 * The spec says 0, not nil, so thats what we'll change those to if needed.
 */
  if(!sOrigUser) sOrigUser = 0;
  if(!sTargetMUD) sTargetMUD = 0;
  if(!sTargetUser) sTargetUser = 0;

  /*  The first 6 entries into the outgoing array will always be the
      same. 
  */

  mpPacket = ({
    sType,  /*The packet type.  Should correspond to one of the services*/
    5,      /*Always 5. */
    IMUD_NAME,  /*Name of the MUD as it should be known over the I-3 network*/
    sOrigUser,  /*Name of the local user involved in this transaction*/
    sTargetMUD, /*Name of the target MUD*/
    sTargetUser,  /*Name of the target user on the target MUD*/
  }) + mpMessage; /*Service-specific information*/
  

  send_mudmode(mpPacket);
  return;
}


mixed query_mud_names() {
  return mudlist ? map_indices(mudlist) : nil;
}

mixed query_listening() {
  return listening;
}

int valid_mud_name(string str, varargs string service) {
  return connected && str && mudlist && mudlist[str] && mudlist[str][0] == -1;
}

/*
  **NOTE**
  The following group of functions allow for easier sending of data over
  the I-3 network according to the nature of the action.
  The logic for this setup was borrowed from the LIMA MUDlib.
*/

/*  Use this function to send a packet to the router */
static void send_to_router(string sType, mixed *mpMessage)
{
  write_imud_stream(sType, mpRouterList[current_router][0], 0, mpMessage);
}


/*  Use this function to send a packet to a certain MUD */
static void send_to_mud(string sType, mixed sMudName, mixed *mpMessage)
{
  write_imud_stream(sType, sMudName, 0, mpMessage);
}


/*  Use this function to send a packet to a certain MUD -and- direct */
/*  the action to a specific user. */
static void send_to_user(string sType, mixed sMudName, mixed sUserName,
                         mixed *mpMessage)
{
  write_imud_stream(sType, (sMudName ? sMudName:0) , (sUserName ? sUserName:0) , mpMessage);
}


/*  Use this function to send a packet to all the MUDs connected to the */
/*  I-3 network. */
static void send_to_all(string sType, mixed *mpMessage)
{
  write_imud_stream(sType, 0, 0, mpMessage);
}

void do_event(string event, mixed destination, mixed data) {
  send_to_mud( "event-notify", destination, ({ event,data }) );
}

void do_beep(string targetuser, string targetmud) {
  send_to_user( "beep",targetmud,targetuser,({this_user()->query_name()}));
}
  
void do_channel_m( string chan, string who, string what ) {
 
  send_to_all( "channel-m",({ chan, who, what }) );
}

void do_channel_e( string chan, string who, string what ) {
 
  send_to_all( "channel-e",({ chan, who, what }) );
}

void do_tell( string name, string who, string where, string what ) {
  send_to_user( "tell", where, who, ({ name , what }) );
}

void do_chan_who(string chan, string where) {
  send_to_mud( "chan-who-req", where, ({chan}) );
}

void do_who( string where ) {
  send_to_mud( "who-req", where, ({ 0 }) );
}

void do_locate( string who ) {
  send_to_all( "locate-req", ({ who }) );
}

/*
  When a whole packet has been received, call the associated function
  for the given function.  If none exists, send an error packet back
  to the sender saying that we don't support that service.
*/
private void handle_router_read(mixed *mpMessage)
{
/*
  If you don't have support for this service, return an error.  You
  shouldn't receive packets for unsupported services if you didn't
  define them in your startup-request-3 packet.
*/

  if(!aServices[mpMessage[0]])
  {
    if(mpMessage[4] != 0 && mpMessage[0] != "ucache-update") {
      write_imud_stream("error", mpMessage[2], mpMessage[3],
        ({ "unk-type",
        "Packet-type '"+mpMessage[0]+"' is not defined",
        mpMessage }));
    }
    return;
  }

/*
  This call assumes that you've either #include'ed or inherited the
  various services into this object.  If not, you'll have to modify
  this call to reflect your setup.
*/
  call_other(this_object(), aServices[mpMessage[0]],
    mpMessage[2], mpMessage[3], mpMessage[5], mpMessage[6..]);
}

void rcv_locate_reply( string origmud, mixed origuser, mixed destuser, 
		    mixed rest ) {
  object p;

  if(origuser == 0) origuser = nil;
  if(destuser == 0) destuser = nil;

  p = find_user( destuser );
  if( p != nil && typeof(p) == T_OBJECT) {
    p->message( "[locate] " + rest[1] + 
		" has been found on '" + rest[0] + "\n");
  }
}

void rcv_locate_req( string origmud, mixed origuser, mixed destuser, 
		    mixed rest ) {
  object u;

  if(origuser == 0) origuser = nil;
  if(destuser == 0) destuser = nil;
  DRIVER->message("Locate request from "+(origuser ? origuser:"<NONE>")+"@"+(origmud ? origmud:"<NONE>")+
    " for " + rest[0] + "\n");
  u = find_user( rest[0] );
  if( u != nil) {
    DRIVER->message("Found.\n");
    send_to_user( "locate-reply", origmud, (origuser ? origuser:0), 
		  ({ IMUD_NAME, rest[0] ,
		       0, 0 
		  }) );
  } else {
    DRIVER->message("Not found.\n");
  }
}

void rcv_tell( string origmud, mixed origuser, mixed destuser, 
			mixed rest ) {
  object user;

  if(origuser == 0) origuser = nil;
  if(destuser == 0) destuser = nil;

   
  if(!destuser || !find_user(destuser)) {
    DRIVER->message("Destination user not found.\n");
    send_to_user("error",origmud,origuser, ({"unk-user","The user "+destuser+" is currently not available on "+IMUD_NAME,0}));
  } else {
    DRIVER->message("Found destination user.\n");
    find_user(destuser)->message(origuser+"@"+origmud+" tells you: "+rest[1]+"\n");
  }
}

void rcv_chanlist_reply( string origmud, mixed origuser, mixed destuser, 
			 mixed rest ) {
  string *chans;
  int i;

  if(!chanlist) chanlist = ([ ]);

  chanlist_id = rest[0];

  /*  chans = map_indices( chanlist );
  for( i = 0; i < sizeof( chans ); i++ ) {
    if( rest[1][chans[i]] == 0 ) {
      chanlist[chans[i]] = 0;
    }
  }
  */
  chans = map_indices( rest[1] );
  if(!listening) listening = ({});
  for( i = 0; i < sizeof( chans ); i++ ) {
    chanlist[chans[i]] = (rest[1][chans[i]] != 0? rest[1][chans[i]]:nil);
    send_to_router( "channel-listen", ({ chans[i], 1 }) );
  }
  save_me();
}

void rcv_mudlist( string origmud, mixed origuser, mixed destuser, 
			mixed rest ) {
  string *muds;
  int i;

  if( mudlist == nil ) {
    mudlist = ([ ]);
  }

  mudlist_id = rest[0];

/*
  muds = map_indices( mudlist );

  for( i = 0; i < sizeof( muds ); i++ ) {
    if( rest[1][muds[i]] == 0 ) {
      mudlist[muds[i]] = 0;
    }
  }
*/
  muds = map_indices( rest[1] );

  for( i = 0; i < sizeof( muds ); i++ ) {
    mudlist[muds[i]] = (rest[1][muds[i]] != 0 ? rest[1][muds[i]] : nil);
  }
  save_me();
}

void rcv_startup_reply( string origmud, mixed origuser, mixed destuser, 
			mixed rest ) {

  
/*
  mpRouterList = rest[0];
  mpRouterList = ({ ({ "*yatmim", "149.152.218.102 23"}), ({ "*i4", "204.209.44.3 8080" }) });
*/

  if(rest[1]) {
    password = rest[1];
  }
  connected = 1;
  save_me();
}

void rcv_error( string origmud, mixed origuser, mixed destuser, 
			mixed rest ) {
}

mapping query_mudlist( void ) {
  return( mudlist );
}

mapping query_chanlist( void ) {
  return( chanlist );
}

void receive_mudmode(mixed data) {
  if(data) handle_router_read(data);
}

/*
  This is called when the connection is lost, closed, or when the daemon
  itself is destructed.  Possible code that could go in here could
  include, but is not limited to, automatic reconnecting, save current
  settings, notify a group that may be logged in, log event to a file,
  etc.
*/
void close() {
  connected = 0;
  if(shutting_down) {
    current_router--;
    return;
  }
  if(force_reconnect) {
    force_reconnect = 0;
    call_out("reconnect",3);
  } else {
    call_out( "reconnect", 60 );
  }
}

void reconnect( void ) {
  string *spAddress;

  if(!sizeof(mpRouterList)) {
    return;
  }


  current_router++;
  if(current_router >= sizeof(mpRouterList)) {
    current_router = 0;
  }

  if( connected == 0 ) {
    string r;
    call_out("timeout_connection",60);
    spAddress = explode(mpRouterList[current_router][1], " ");
    connect(spAddress[0], (int)spAddress[1],"tcp");
  }
}


int query_mudlist_id( void ) {
  return( mudlist_id );
}
int query_chanlist_id( void ) {
  return( chanlist_id );
}

void open()
{
  mudlist = ([]);
  chanlist = ([]);
  mudlist_id = 0;
  chanlist_id = 0;
  DRIVER->message("Sending startup request...\n");
  send_to_router("startup-req-3", ({
    password, /* Password - Send 0 if you're new */
    query_mudlist_id(), /* Mudlist ID - Send 0 if you're new */
    query_chanlist_id(),  /* Channel List ID - Send 0 if you're new */
    9999, /* What port people use to connect to your MUD */
    0   ,  /* The MUD's designated port for Imud TCP comm */
    0   ,  /* The MUD's designated port for Imud UDP/OOB comm */
    "Phantasmal", /*Name of current lib*/
    "LPMud",      /*Name of base lib*/
    "" + status()[ST_VERSION]+"-WOTF-NET",  /*Driver version*/
    "LPMud",  /*MUD Type*/
    "beta testing", /*MUD Status*/
    ADMIN_EMAIL,  /*Email address of the MUD's admin*/
      /*The following is a list of SUPPORTED services with appropriate values.
	Information regarding the various services can be found in the I-3
	specification.
      */
    ([
      /*      "auth"    : 1, */
      "channel" : 1,      
      /*      "emoteto" : 1, */
      /*      "file"    : 1, */
      /*      "finger"  : 1, */
      "locate"  : 1,
      /*      "mail"    : 1, */
      "tell"    : 1,
      "who"     : 1,
      /*      "ucache"  : 1, */
    ]),
      /*This is a list of custom services that are support by your MUD, but
	not by the I-3 protocol.
      */
    ([]),
  }));
}


/*
  start_net() is called when the daemon is first loaded 
  Things of note that take place here:

    * Router list is given a default address and name

    * The Services mapping is initialized to reflect supported
      call-back functions for the respective services.

    * The connection to the Intermud router is initialized.
*/
void start_net()
{
  string *spAddress;
  connected = 0;
  mudlist = ([ ]);
  chanlist = ([ ]);

/*
    I went ahead and included this segment to show how I set up the packet-
    type/function relationships.  Each key is the string that is sent
    in each packet and the values are the names of the actual functions
    that are called when packets are received.

    You should only define those functions that you have coded support
    for, though, as defining unsupported ones will lead to router/mud
    miscommunications.
*/

  restore_me();

  if(!mpRouterList) {
/*
    mpRouterList = ({ ({ "*yatmim", "149.152.218.102 23"}), ({ "*i4", "204.209.44.3 8080" }) });
*/
    mpRouterList = ({ ({ "*wir", "195.242.99.94 3004"}) });
  }

  aServices = ([
    "tell"            : "rcv_tell",
    "emoteto"         : "rcv_emoteto",
    "who-req"         : "rcv_who_req",
    "who-reply"       : "rcv_who_reply",
#ifdef I3_ENABLE_FINGER
    "finger-req"      : "rcv_finger_req",
    "finger-reply"    : "rcv_finger_reply",
#endif
    "locate-req"      : "rcv_locate_req",
    "locate-reply"    : "rcv_locate_reply",
    "chanlist-reply"  : "rcv_chanlist_reply",
    "chan-who-req"    : "rcv_chan_who_req",
    "chan-who-reply"  : "rcv_chan_who_reply",
    "channel-m"       : "rcv_channel_m",
    "channel-e"       : "rcv_channel_e",
    "channel-t"       : "rcv_channel_t",
    "chan-user-req"   : "rcv_chan_user_req",
    "chan-user-reply" : "rcv_chan_user_reply",
#ifdef I3_ENABLE_OOB
    "auth-mud-req"    : "rcv_auth_mud_req",
    "auth-mud-reply"  : "rcv_auth_mud_reply",
#endif
#ifdef I3_ENABLE_UCACHE
    "ucache-update"   : "rcv_ucache_update",
#endif
    "mudlist"         : "rcv_mudlist",
    "startup-reply"   : "rcv_startup_reply",
    "error"           : "rcv_error",
    "beep"            : "rcv_beep",
    "notice"          : "rcv_notice",
    "event"           : "rcv_event",
#ifdef I3_ENABLE_OOB
    "oob-req"         : "rcv_oob_req",
#endif
  ]);

  if(sizeof(mpRouterList)) {
    if(current_router >= sizeof(mpRouterList)) current_router = 0;
    spAddress = explode(mpRouterList[current_router][1], " ");
    call_out("connection_timeout",60);
    connect(spAddress[0], (int)spAddress[1],"tcp");
  }
}

void destroying() {
  if(connected) send_to_router("shutdown",({60}));
  close_user();
  shutting_down = 1;
}

int query_current_router() { return current_router; }
mixed query_current_router_data() { return mpRouterList[current_router]; }
mixed query_router_list() { return mpRouterList + ({}); }
int query_connected() { return connected; }

void create() {
  user::create();
  listening = ({});
  call_out("start_net",3);
}

