#include "sockets.h"
#include "signal.h"
#include "main.h"
extern "C" {
#include <errno.h>
#include <fcntl.h>
#include <sys/time.h>  // For timeval and timerclear()
#pragma implementation

#ifdef _AIX
  #include <sys/select.h>
  #define _BSD
#else
#ifdef LINUX
  #define FNDELAY O_NDELAY
#endif
  #include <sys/types.h>
  #include <sys/time.h>
#endif

  char* inet_ntoa (struct in_addr);
  void bzero (char*, int);
  int getdtablesize ();
}

int quitting_object;

Socket_Class::Socket_Class (int num_sockets){
  size = num_sockets;
  socketlist = new int [size];
  destinations = new unsigned long [size];
  lowest_available = 0;
  for (num_sockets=0 ; num_sockets<size ; num_sockets++)
    {socketlist[num_sockets] = -1;
     destinations[num_sockets] = 0;}
};

/** grow increases the number of sockets allocated for. **/
void Socket_Class::grow(int by){
  int* templist, i;
  unsigned long* dlist;
  templist = new int [size + by];
  dlist = new unsigned long [size + by];
  for (i=0 ; i<size ; i++)
    {templist[i] = socketlist[i];
     dlist[i] = destinations[i];}
  for ( ; i < (size + by) ; i++)
    {templist[i] = -1;
     destinations[i] = 0;}
  size += by;
  delete socketlist;
  delete destinations;
  socketlist = templist;
  destinations = dlist;
};

int Socket_Class::add_connection (char* address, char* port){
  int portnum = -1;
  sscanf (port , "%i", &portnum);
  if (portnum < 0)  return (-1);
  return (add_connection (address, portnum));
};

void Socket_Class::next_available (){
  for (lowest_available=0 ; lowest_available < size ; lowest_available++)
    if (socketlist[lowest_available] < 0)
      return;
  grow();
  return;
}

int Socket_Class::add_connection (char* address, int port){
  long sd, on, index;
  struct sockaddr_in server;
  if ((sd = socket (AF_INET , SOCK_STREAM , 0)) < 0)
    {perror("Socket_Class::add_connection - socket");  return (-1);}
  server.sin_family = AF_INET;
/*  server.sin_len = sizeof (server);  */
  server.sin_port = htons(port);
  server.sin_addr.s_addr = inet_addr (address);
  printf ("CONNECTING TO ADDRESS: %s %i\n", address, port);
  if (connect (sd , (struct sockaddr*) &server , sizeof(server)) < 0)
    {perror("Socket_Class::add_connection - connect"); return -1;}
  socketlist[index = lowest_available] = sd;
  destinations[index] = server.sin_addr.s_addr;
#ifdef _AIX
  ioctl (sd , FIONBIO , (void*)1);       /** sets socket non-blocking **/
#else
  fcntl (sd , F_SETFL, FNDELAY);
#endif
  next_available();
  return index;
};

/** try to get a new connection -- this machine being the host. **/
int Socket_Class::add_connection (int port){
  struct sockaddr_in from;
  int newsock, from_size, on, index;
  struct fd_set readers;
  struct timeval timeout;
  static long sd = setup_host_socket(port);

  if (sd < 0)  return (-1);
  // Do a select on the socket with timeout set to 0.
  // Select returns the number of ready files so if somebody is waiting
  // then it should return 1.
  FD_ZERO (&readers);
  FD_SET (sd, &readers);
  timerclear(&timeout);
  if (select (getdtablesize(), &readers, NULL, NULL, &timeout) < 1)
    return -1;
  from_size = sizeof(from);
  newsock = accept (sd , (struct sockaddr*)&from , &from_size);
  if (newsock < 0)  /* no connection there */
    {/*perror("Socket_Class::add_connection - accept");*/
     return -1;}
  on = 1;
  setsockopt (newsock , SOL_SOCKET , SO_REUSEADDR , &on, sizeof (on));
#ifdef _AIX
  ioctl (newsock , FIONBIO , (void*)1);       /** sets socket non-blocking **/
#else
  fcntl (newsock, F_SETFL, FNDELAY);
#endif
  socketlist[index = lowest_available] = newsock;
  destinations[index] = from.sin_addr.s_addr;
  next_available();
  return index;
}

int Socket_Class::setup_host_socket (int portnum){
  int sd, on;
  short sindex;
  struct sockaddr_in host;

  if ((sd = socket (AF_INET , SOCK_STREAM , 0)) == -1)
    {perror("Socket_Class::setup_host_socket - socket"); return -1;}
  on = 1;
  setsockopt (sd , SOL_SOCKET , SO_REUSEADDR , &on, sizeof (on));
  host.sin_family = AF_INET;
/*  host.sin_len = sizeof(host); */
  host.sin_port = htons(portnum);
  host.sin_addr.s_addr = INADDR_ANY;
  if (bind (sd , (struct sockaddr*) &host , sizeof(host)))
    {perror ("Socket_Class::setup_host_socket - bind"); close (sd); return -1;}
  if (listen (sd , max_waiting_connections) == -1)
    {perror("Socket_Class::setup_host_socket - listen"); close(sd); return -1;}
  ioctl (sd, FIONBIO, (void*)1);
  return sd;
}

void Socket_Class::close_connection (int index){
  if (index >= 0 && index <= size)
    {close (socketlist[index]);
     socketlist[index] = 0;
     if (index < lowest_available)  lowest_available = index;
     socketlist[index] = -1;}
}


int Socket_Class::send (int index, char* buffer, int size){
  return write(socketlist[index] , buffer , size);
};

String* Socket_Class::receive (int index){
  int received;
  static char temp [max_read_chunk];
  String* outstring = NULL;
  String* tempstring;
  do
    {received = read (socketlist[index] , temp , max_read_chunk);
     if (! received)
       {close_connection (index);
	quitting_object = index;
	return NULL;}
     if (!outstring)
       {if (received <= 0)
	  return NULL;
	else
	  outstring = new String (temp , received);}
     else
       {tempstring = new String (temp , received);
	(*outstring) += (*tempstring);
        delete tempstring;
        tempstring = NULL;}}
  while (received == max_read_chunk);
  return outstring;
}

String* Socket_Class::wait_receive (int index){
  String* tempstring;
  ioctl (socketlist[index] , FIONBIO , (void*)0); /** sets socket blocking **/
  while (! (tempstring = receive (index)));
  ioctl (socketlist[index] , FIONBIO , (void*)1); /*sets socket non-blocking*/
  return tempstring;
}

int broken_pipe;
void socket_sigcatcher (int unused){
  broken_pipe = 1;
};

Mud_Sockets::Mud_Sockets (int num_sockets) : Socket_Class (num_sockets){
  int i;
  signal ((SIGPIPE), socket_sigcatcher);
  objectlist = new int [num_sockets];
  incoming_data = new String* [num_sockets];
  for (i = 0 ; i<num_sockets ; i++)
    incoming_data[i] = NULL;
}

Mud_Sockets::~Mud_Sockets (){
  int i;
  delete objectlist;     objectlist = NULL;
  for (i=0 ; i<size ; i++)
    {if (incoming_data[i])
       {delete incoming_data[i];
	incoming_data[i] = NULL;}}
}

void Mud_Sockets::grow (int by){
  int i, *newlist = new int [size + by];
  String** newdata = new String* [size + by];
  for (i = 0; i < size ; i++)
    {newlist[i] = objectlist[i];
     newdata[i] = incoming_data[i];}
  delete objectlist;
  delete incoming_data;
  objectlist = newlist;
  incoming_data = newdata;
  Socket_Class::grow (by);
}

int Mud_Sockets::add_connection (int port){
  int i;
  if ((i = Socket_Class::add_connection(port)) >= 0)
    {objectlist[i] = 0;
     incoming_data [i] = new String ("");}
  return i;
}
int Mud_Sockets::add_connection (char* address, char* port, int object){
  int i;
  if ((i = Socket_Class::add_connection(address, port)) >= 0)
    {incoming_data [i] = new String ("");
     objectlist[i] = object;}
  return i;
}
int Mud_Sockets::add_connection (char* address, int port, int object){
  int i;
  if ((i = Socket_Class::add_connection(address, port)) >= 0)
    {incoming_data [i] = new String ("");
     objectlist[i] = object;}
  return i;
}
int Mud_Sockets::add_connection (String* full_address, int object){
  String s1, s2;
  if ((full_address->freq(" ") != 1) ||
      (full_address->index(" ") > 16) || (full_address->index(" ") < 8))
    return -1;
  s1 = full_address->before(" ");
  s2 = full_address->after(" ");
  if (!(s1.length() && s2.length()))
    return -1;
  return add_connection ((char*)(s1.chars()), (char*)(s2.chars()), object);
}

void Mud_Sockets::close_connection (int index){
  Socket_Class::close_connection (index);
  if (index >= 0 && index <= size)
    {delete incoming_data[index];
     incoming_data[index] = NULL;
     objectlist[index] = -1;}
}

int Mud_Sockets::break_connection (int o_num){
  int i;
  for (i=0 ; i<size ; i++)
    if (objectlist[i] == o_num)
      {close_connection (i);
       return 1;}
  return 0;
}

String* Mud_Sockets::get_destination (int o_num){
  int i;
  struct in_addr ina;
  struct hostent* dst;
  char* addrstr;
  char hex_addr [16];
  String* tempstring = NULL;
  for (i=0 ; i<size ; i++)
    if (objectlist[i] == o_num)
      {ina.s_addr = destinations[i];
       dst = gethostbyaddr ((void*) &ina,
			    sizeof(ina),
			    AF_INET);
       if (dst)
	 tempstring = new String (dst->h_name);
       else
	 {addrstr = inet_ntoa (ina);
	  if (addrstr != (char*)(-1))
	    tempstring = new String (addrstr);}
       return tempstring;}
  return NULL;
}

int Mud_Sockets::move_connection (int oldobj , int newobj){
  int i;
  for (i=0 ; i<size ; i++)
    if (objectlist[i] == oldobj)
      {objectlist[i] = newobj;
       return 1;}
  return 0;
}

String* Mud_Sockets::receive (int index){
  String *tempstring;
  if (socketlist[index] < 0)  return NULL;
  quitting_object = -1;
  if (!objectlist[index])   // #0 shouldn\'t process any input
    return NULL;
  tempstring = Socket_Class::receive (index);
  if (incoming_data[index]->empty() && !tempstring)
    return NULL;
  if (tempstring)
    {(*incoming_data[index]) += *tempstring;
     delete tempstring;
     tempstring = NULL;}
  if (incoming_data[index]->contains ("\n"))
    {if (incoming_data[index]->contains("\r"))
       tempstring = new String (incoming_data[index]->before ("\r"));
     else
       tempstring = new String (incoming_data[index]->before ("\n"));
     *incoming_data[index] = incoming_data[index]->after ("\n");}
  return tempstring;
}

String* Mud_Sockets::wait_receive (int index){
  String* tempstring;
  ioctl (socketlist[index] , FIONBIO , (void*)0); /* sets socket blocking */
  while (! (tempstring = receive (index)));
  ioctl (socketlist[index], FIONBIO , (void*)1);  /*sets socket non-blocking*/
  return tempstring;
}

int Mud_Sockets::send_line (int objectnum, String* s){
  int i;
  broken_pipe = 0;
  for (i=0 ; i<size ; i++)
    if (objectlist[i] == objectnum)
      {// s->gsub("\n", "\r\n");  // optimize this later.
       send (i , (char*)s->chars() , s->length());
       if (broken_pipe)
	 {close_connection (i);
	  broken_pipe = 0;
	  signal ((SIGPIPE), socket_sigcatcher);
	  return -1;}
       return 1;}
  return 0;
}

input_line Mud_Sockets::receive (){
  int i;
  String* read_string;
  Value* tempval;
  static struct One_Line incoming;
  for (i = 0 ; i < size ; i++)
    {if (socketlist[i] >= 0)
       {if (read_string = receive (i))
	  {incoming.instring = read_string;
	   incoming.object_num = objectlist[i];
	   return &incoming;}
        else
	  if (quitting_object >= 0)
	    {tempval = new Value (new String ("quit"), SYM);
	     all_processes.add_process (objectlist[quitting_object] ,
					tempval , NULL);
	     tempval->release();}}}
  return NULL;
};