/*
 * MudOS mudmode connection emulator for DGD
 *
 * Based on fragments from Gurbalib and the DGD Kernel lib.
 *
 * This type of connection allows you to send LPC variables
 * between MUDs. 
 *
 * by Aidil of Way of the Force.
 */

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

inherit LIB_USER;
inherit LIB_SAVERESTOREVAR;

void queue_data(mixed data);

string buffer;          /* receive buffer */
mixed * packets;        /* packet queue   */
int rawmode;            /* rawmode flag   */
int packet_len;         /* expected length of the current inbound packet */
static int closing;


static void receive_mudmode(mixed data) { }

/*
 * NAME:        set_rawmode()
 * DESCRIPTION: enable/disable raw tcp mode
 */
void set_rawmode(int flag) {
  rawmode = (flag != 0);
}

/*
 * NAME:        query_rawmode()
 * DESCRIPTION: return raw tcp mode status
 */
int query_rawmode() {
  return rawmode;
}

/*
 * NAME:	message()
 * DESCRIPTION:	convert variable to bytestream and send it to the network
 */
static int send_mudmode(mixed data) {
  string str;
  int size;

  if (data == nil) {
    return 0;
  }

  size = strlen(str = save_variable(data)) + 1;

  str = "    " + str + " ";

  str[0] = (size & 0xff000000) >> 24;
  str[1] = (size & 0xff0000) >> 16;
  str[2] = (size & 0xff00) >> 8;
  str[3] = size & 0xff;

  str[strlen(str)-1] = 0;
  return ::message(str);
}

/*
 * NAME:	process_message()
 * DESCRIPTION:	process received bytestream from the network, restore variables in it
 *              and add them to the queue for the user object
 */
static void process_message(string str) {

  string packet;
  int size;

  /*
   * If we had no input, return here;
   */
  if(str == nil) return;
 
  /*
   * Initialize the buffer if it doesn't exist yet.
   */
  if(!buffer) {
    buffer = "";
    packet_len = 0; /* We had no data so reset this as well */
  }

  /*
   * Decode the packets, note that sending each seperate packet to the
   * user object is handled by send_data which runs with its own
   * rlimits
   */

    /*
     * Make sure the combined input doesn't exceed our max string size.
     * This also serves to resync the connection after a while in case
     * we failed to detect the end of the previous package.
     * Buffer and expected packet size are reset here.
     */
    if( strlen( buffer ) + strlen( str ) > MAX_STRING_SIZE ) {
      buffer = "";
      packet_len = 0;
      return;
    }

    /*
     * add the input data to our receive buffer.
     */
    buffer += str;
    size = strlen(buffer);

    /* Do we have the 4 bytes for the packet size and possibly the entire packet? */
    while(size >= packet_len + 4) {
      packet_len = buffer[3] + (buffer[2] << 8) + (buffer[1] << 16) + (buffer[0] << 24);

      /* Do we really have a complete packet? */
      if(packet_len + 4 <= size) {
        /*
         * extract packet from the buffer, excluding the size and
         * trailing 0
         */
        packet = buffer[4..packet_len+2];
        /*
         * remove current packet from the buffer
         * note that we skip the trailing zero
         * from the previous packet here.
         */
        if(size > packet_len + 4) {
          buffer = buffer[packet_len+4..];
        } else {
          buffer = "";
        }
        /*
         * reset size to the current length of the buffer
         */
        size = strlen(buffer);
        /*
         * set packet length to zero, will be set to something more useful during next
         * iteration of this loop, provided there are at least 4 bytes in the buffer.
         */
        packet_len = 0; 
        /*
         * restore the variable in the packet and send it to the user object.
         */
        receive_mudmode(restore_variable(packet));
      } 
    }
 
}

/*
 * NAME: receive_message()
 * DESCRIPTION: receive a packet from the network
 */
nomask int receive_message(string str) {
  process_message(str);
  return MODE_RAW;
}

/*
 * retrieve a converted packet from the queue
 */
static mixed next_packet() {
  mixed packet;

  if(!packets || !sizeof(packets)) {
    error("Queue is empty.\n");
  } else {
    packet = packets[0];
  }

  if(sizeof(packets) > 1) {
    packets = packets[1..];
  } else {
    packets = ({});
  }
  return packet;
}

/*
 * NAME:        send_data()
 * DESCRIPTION: call_out loop to send packets to the user object one at a time, and
 *              each in its own thread.
 */
void send_data() {
  mixed packet;

  if(packets && sizeof(packets)) {
    packet = next_packet();
    /*
     * Start the call_out for the next packet before sending the
     * current one to the user, this to make sure that a runtime
     * error won't prevent sending the remaining data.
     */
    if(sizeof(packets)) call_out("send_data",0);
    receive_mudmode(packet);
  }
}

/*
 * NAME:        queue_data()
 * DESCRIPTION: queue a variable for the user object.
 */
void queue_data(mixed data) {
  if(data) {
    if(!packets) {
      packets = ({data});
      call_out("send_data",0);
    } else {
      packets += ({data});
    }
  }
}

void open() { }

int login(string str) {
    open();
    query_conn()->set_mode(MODE_RAW);
    return MODE_RAW;
}

