/*
 * Atom-4 Network Protocol utility functions
 * Implementation file
 *
 * $Id: net.cc,v 1.12 2003/04/15 02:48:11 hsteoh Exp hsteoh $
 */

#include <assert.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#include "net.h"


#define IS_TERMINATOR(x)	((x)>=0x00 && (x)<=0x1f)



/*
 *
 * CLASS netparser
 *
 */

void netparser::skip_spaces() {
  while (raw[curpos] && raw[curpos]==' ') curpos++;
}

netparser::netparser(char *message) {
  reset();
  if (message) parse(message);
}

netparser::~netparser() {
  reset();
}

void netparser::parse(char *message) {
  reset();				// kill any existing buffers

  // Find real message length (this is to weed out newlines and other crap
  // that we might get from the network socket)
  raw = message;
  for (rlen=0; raw[rlen] && !IS_TERMINATOR(raw[rlen]); rlen++);
  raw[rlen] = '\0';			// terminate message properly

  // Parse first word
  skip_spaces();
  type = next_word();
}

void netparser::reset() {
  raw=type=NULL;
  rlen=curpos=0;
}

char *netparser::next_word() {
  char *word = &raw[curpos];
  int end;

  // Find end of word
  while (raw[curpos] && raw[curpos]!=' ') curpos++;
  end=curpos;

  skip_spaces();			// must do this first, 'cos
					// skip_spaces() won't go past \0
  raw[end] = '\0';			// null-terminate word

  return word;
}

char *netparser::get_rest() {
  char *rest=&raw[curpos];
  curpos=rlen;				// bump curpos to end of buffer
  return rest;
}



/*
 *
 * CLASS netconn
 *
 */

void netconn::scan_for_packets() {
  int i=0;
  int packet_start;

  while (i<rcvbuf_end) {
    if (!truncate) {
      // Skip terminators
      while (i<rcvbuf_end && IS_TERMINATOR(rcvbuf[i])) i++;

      packet_start=i;			// mark start of packet

      // Scan for end of packet
      while (i<rcvbuf_end && !IS_TERMINATOR(rcvbuf[i])) i++;

      if (!IS_TERMINATOR(rcvbuf[i])) {
        if (i==NET_LINE_LIMIT) {
          truncate=1;			// packet too long; enter truncate mode
          rcvbuf[++i]='\0';		// truncate packet
          process_packet(&rcvbuf[packet_start]);
        } else {			// packet is incomplete
          wrap_buffer(packet_start);	// wrap buf to prepare for more data
          return;
        }
      } else {				// got a packet
        rcvbuf[i]='\0';			// null-terminate packet
        if (i > packet_start) {
          process_packet(&rcvbuf[packet_start]); // forward to derived class
        }				// (ignore if 0 length)
      }
    } else {				// truncate mode
      // Ignore everything until we find a terminator
      for (i=0; i<rcvbuf_end && !IS_TERMINATOR(rcvbuf[i]); i++);

      if (IS_TERMINATOR(rcvbuf[i])) {
        // Found terminator; leave truncate mode.
        truncate=0;
      } else {				// no terminator in buffer yet
        wrap_buffer(i);			// throw away everything
        return;
      }
    } // if (!truncate)
  } // while(i<rcvbuf_end)

  // If we reach here, it means we've exhausted the entire buffer. Clear it
  // and start over.
  rcvbuf_end=0;
}

void netconn::wrap_buffer(int start) {
  size_t wrapsize;

  assert(start>=0 && start<=NET_LINE_LIMIT);

  wrapsize = NET_LINE_LIMIT-start;
  if (wrapsize>0) {
    memmove(&rcvbuf[0], &rcvbuf[start], wrapsize*sizeof(char));
  }

  // Update end of buffer pointer
  rcvbuf_end -= start;
}

netconn::netconn(int sockfd, eventloop *eloop, int sendqueue_limit) :
	loop(eloop), sock(sockfd), sendlimit(sendqueue_limit) {
  rcvbuf_end = 0;
  truncate = 0;

  // Register with event loop
  loop->register_handler(eventloop::READER, sock, this);
}

netconn::~netconn() {
  loop->unregister_handler(eventloop::READER, sock);
  if (sendqueue.num_elem() > 0) {
    flush();
    // (flush() should've unregistered the writer after sending last packet)
  }
  close(sock);				// close client socket
fprintf(stderr, "Client socket (%d) closed\n", sock);
}

int netconn::send_packet(char *fmt, ...) {
  int rc;
  va_list args;

  va_start(args, fmt);
  rc = vsend_packet(fmt, args);
  va_end(args);
  return rc;
}

int netconn::vsend_packet(char *fmt, va_list args) {
  int i, count;
  char *sendbuf;

  // Don't bother if outgoing queue is full.
  if (sendqueue.num_elem() >= sendlimit) return 0;

  sendbuf = new char[NET_BUFFER_SIZE];
  if (!sendbuf) return 0;		// out of memory

  count=vsnprintf(sendbuf, NET_LINE_LIMIT, fmt, args);
  if (count >= NET_LINE_LIMIT) {
    // packet got truncated at 1024 bytes; append packet terminator.
    sendbuf[NET_LINE_LIMIT]='\0';
    count=NET_LINE_LIMIT;
  }

  // Verify packet validity
  for (i=0; i<count && !IS_TERMINATOR(sendbuf[i]); i++);
  if (i<count) {			// illegal characters in packet
    delete sendbuf;			// discard buffer
    return 0;
  }

  // Queue packet for sending
  if (sendqueue.num_elem()==0) {
fprintf(stderr, "Registering as writer...\n");
    // Register ourselves as a writer now that we have something to write
    loop->register_handler(eventloop::WRITER, sock, this);
  }
  sendqueue.append(sendbuf);

  return 1;
}

void netconn::read_ready(eventloop *src, int fd) {
  int avail_len=NET_LINE_LIMIT - rcvbuf_end;	// don't use NET_BUFFER_SIZE
					// so that we have room for '\0'.
  int len;				// number of bytes received

  assert(fd==sock);
  if (avail_len<=0) return;		// buffer full: nothing to do

  len=recv(sock, &rcvbuf[rcvbuf_end], avail_len, MSG_NOSIGNAL);
  if (len > 0) {
    rcvbuf_end += len;
    scan_for_packets();
  } else {
    // Note: we're assuming that recv() returns 0 bytes if we hit EOF
    if (len==0) {
      disconnected();
    } else {
      // FIXME: handle recv() errors
    }
  }
}

// FIXME: this function sometimes may block, if the system socket buffer is
// for whatever reason smaller than our max packet size. We could use send()'s
// MSG_DONTWAIT option, and keep track of partial buffers. But that's more
// trouble than it's worth for now.
void netconn::write_ready(eventloop *src, int fd) {
  char *packet, *cp;			// [O]
  int count;

//fprintf(stderr, "Write ready...\n");

  assert(fd==sock);
  assert(sendqueue.num_elem() > 0);

  // Retrieve packet for sending
  packet = cp = sendqueue.remove(sendqueue.headp());
fprintf(stderr, "Sending packet: %s\n", packet);
  count = strlen(packet);
  packet[count] = '\n';			// be nice to telnet
  count++;				// +1 to include packet terminator

  do {
    int sent;

    sent = send(sock, cp, count, MSG_NOSIGNAL);
    if (sent>0) {
      count -= sent;
      cp += sent;
    } else {
      if (errno==EPIPE) {
        disconnected();			// handle error state
        delete packet;
        return;
      } else {
        // FIXME: error occurred, throw exception
      }
    }
  } while (count);

  delete [] packet;			// done with packet buffer
  if (sendqueue.num_elem()==0) {
    // If no more pending packets to send, remove ourselves from the event
    // loop to avoid spinning
fprintf(stderr, "No more pending packets: unregistering as writer\n");
    loop->unregister_handler(eventloop::WRITER, sock);
  }
}

void netconn::flush() {
  while (sendqueue.num_elem() > 0) {
    // Simulate write-ready state until all packets are sent
    write_ready(loop, sock);
  }
}

