#include "SocketStream.h"
#include "BaseException.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#include <errno.h>

using namespace std;

namespace FD {

const int network_socket::BROADCAST_TYPE = 0;
const int network_socket::TCP_STREAM_TYPE = 1;

#define FLOWDESIGNER_IDENT_STRLEN 8
#define FLOWDESIGNER_IDENT "OVERFLOW"
#define FLOWDESIGNER_BROADCAST_IP "255.255.255.255"

network_socket::network_socket(int type, int port) 
: m_read_socket(0)
  , m_listen_socket(0)
, m_write_socket(0), m_port(port), m_type(type) {

  switch (m_type) {
  case BROADCAST_TYPE:
    init_broadcast();
    break;

  case TCP_STREAM_TYPE:
    break;

  default:
    throw new GeneralException("Unknown packet type",__FILE__,__LINE__);
    break;
  }


}

network_socket::~network_socket() {

  //shutting down communication
  shutdown();
}

void network_socket::printOn (ostream &out) const {

  out<<"<network_socket";
  out<<" Type : "<<m_type;
  out<<" Port : "<<m_port<<" >"<<endl;

}

void network_socket::init_broadcast() {

  printf("Broadcast device initialising...");
    
  // Set up the write socket (datagram type)
  //
  m_write_socket = socket(AF_INET, SOCK_DGRAM, 0);

  if (m_write_socket == -1) {
    perror(__PRETTY_FUNCTION__);
    throw new GeneralException("Unable to create write_socket",__FILE__,__LINE__);
  }

  
  //initialising write structure
  memset(&m_write_addr, 0, sizeof(m_write_addr));
  
  m_write_addr.sin_family = AF_INET;
  m_write_addr.sin_addr.s_addr = inet_addr(FLOWDESIGNER_BROADCAST_IP);
  m_write_addr.sin_port = htons(m_port);
  
  // Set write socket options to allow broadcasting
  //
  u_int broadcast = 1;
  if (setsockopt(m_write_socket, SOL_SOCKET, SO_BROADCAST,(const char*)&broadcast, sizeof(broadcast)) < 0) {
    perror(__PRETTY_FUNCTION__);
    throw new GeneralException("Unable to allow broadcasting for write socket",__FILE__,__LINE__);
  }
    
  // Set up the read socket
  //
  m_read_socket = socket(PF_INET, SOCK_DGRAM, 0);
  if (m_read_socket == -1) {
    perror(__PRETTY_FUNCTION__);
    throw new GeneralException("Unable to create read_socket",__FILE__,__LINE__);
  }

  // Set socket options to allow sharing of port
  //
  u_int share = 1;
  if (setsockopt(m_read_socket, SOL_SOCKET, SO_REUSEADDR,(const char*)&share, sizeof(share)) < 0) {
    perror(__PRETTY_FUNCTION__);
    throw new GeneralException("Unable to share port for read_socket",__FILE__,__LINE__);
  }
    
  // Bind socket to port (any address)
  //
  memset(&m_read_addr, 0, sizeof(m_read_addr));
  m_read_addr.sin_family = AF_INET;
  m_read_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  m_read_addr.sin_port = htons(m_port);
  if (bind(m_read_socket, (sockaddr*) &m_read_addr, sizeof(m_read_addr)) < 0) {
    perror(__PRETTY_FUNCTION__);
    throw new GeneralException("Unable to bind read_socket",__FILE__,__LINE__);
  }

  // Set socket to non-blocking
  //
  if (fcntl(m_read_socket, F_SETFL, O_NONBLOCK) < 0) {
    perror(__PRETTY_FUNCTION__);
    throw new GeneralException("Unable set read_socket non-blocking",__FILE__,__LINE__);
  }

  //non blocking connexion
  m_blocking = false;

  printf("done\n");
}


void network_socket::shutdown() {

    printf("Shuting down...");
    
    // Close sockets
    //

    if (m_write_socket == m_read_socket) {
      close(m_write_socket);
    }
    else {
      close(m_write_socket);
      close(m_read_socket);
    }

    if (m_listen_socket) {
       close(m_listen_socket);
    }

    printf("done\n");
    
}

///////////////////////////////////////////////////////////////////////////
// Send a packet
//
size_t network_socket::send_packet(const unsigned char *packet, size_t size) {
    
  unsigned int flags = 0;

  switch (m_type) {
  case BROADCAST_TYPE:
    
    if (sendto(m_write_socket, (const char*)packet, size,
	       0, (sockaddr*) &m_write_addr, sizeof(m_write_addr)) < 0) {
      perror(__PRETTY_FUNCTION__);
      throw new GeneralException("Unable to send packet",__FILE__,__LINE__);
      return 0;
    }
    
    break;

  case TCP_STREAM_TYPE:
    
    if (send(m_write_socket, (const char*)packet, size,flags) < 0) {
      perror(__PRETTY_FUNCTION__);
      throw new GeneralException("Unable to send packet",__FILE__,__LINE__);
      return 0;
    }
    break;

  default:
    throw new GeneralException("Unknown packet type",__FILE__,__LINE__);
    break;
  }

  return size;
}


///////////////////////////////////////////////////////////////////////////
// Receive a packet
//
size_t network_socket::recv_packet(unsigned char *packet, size_t size) {


  size_t packet_len = 0;
  unsigned int flags = 0;
  socklen_t addr_len = sizeof(m_read_addr);

  switch (m_type) {
  case BROADCAST_TYPE:
    #ifndef __CYGWIN__
    packet_len = recvfrom(m_read_socket, (char*)packet, (size_t)size, 0, (sockaddr*) &m_read_addr, &addr_len);
    #else
    #warning CYGWIN not yet supported for broadcast network_socket
    #endif
    if ((int) packet_len < 0) {
      if (errno == EAGAIN) {
	return 0;
      }
      else {
	perror(__PRETTY_FUNCTION__);
	throw new GeneralException("Unable to recv packet",__FILE__,__LINE__);
	return 0;
      }
    }
    break;

  case TCP_STREAM_TYPE:

	  //Will wait for all data (if not error occurs)
                #ifndef __CYGWIN__
                flags = MSG_WAITALL;
		#else
		#warning CYGWIN not fully supported for TCP_STREAM_TYPE
		#endif
		packet_len = recv(m_read_socket, packet, size,flags);

		if (packet_len < 0) {
		  perror(__PRETTY_FUNCTION__);
		  throw new GeneralException("Unable to recv packet",__FILE__,__LINE__);
		  return 0;
		}
    break;
  default:
    throw new GeneralException("Unknown packet type",__FILE__,__LINE__);
    break;
  }

  //printf("read packet len = %d \n", (int) packet_len);
  return packet_len;
}

void network_socket::init_tcp_stream(bool blocking) {

  struct sockaddr_in serverp;
  int address_size;// size of server address struct 
  struct hostent* entp;
  char host[256];
  int flags;// temp for old socket access flags 
  int one = 1;
  char* first_dot;

  
  m_blocking = blocking;

  
  address_size = sizeof(serverp);

  if(gethostname(host,256) == -1) {
    throw new GeneralException("network_socket::init_tcp_stream : couldn't get hostname.",__FILE__,__LINE__);
  }

  /* now, strip down to just the hostname */
  if((first_dot = strchr(host,'.'))) {
    *first_dot = '\0';
  }

  cerr<<"current host : "<<host<<endl;

  if((entp = gethostbyname(host)) == NULL) {
    cerr<<"Did not find host : "<<host<<endl;
    throw new GeneralException("network_socket::init_tcp_stream : host unknown.",__FILE__,__LINE__);
  }

  memcpy(&(serverp.sin_addr), entp->h_addr_list[0], entp->h_length);


  serverp.sin_port = htons(m_port);

  /* 
   * Create the INET socket.  
   * 
   */
  if((m_listen_socket = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
    perror("network_socket::init_tcp_stream : call to socket() failed; socket not created.");
    throw new GeneralException("network_socket::init_tcp_stream : socket not created.",__FILE__,__LINE__);
  }

  /*
   * let's say that our process should get the SIGIO's when it's
   * readable
   */
   //Carle : Remove set ownership, error on my system
   /*
  if(fcntl(m_listen_socket, F_SETOWN, getpid()) == -1) {

    perror("network_socket::init_tcp_stream : call to fcntl() failed while setting socket "
	   "pid ownership; socket not created.");
    shutdown();

    throw new GeneralException("network_socket::init_tcp_stream : could not change ownership of the socket."
			       ,__FILE__,__LINE__);    
  }
  */

  /*
   * get the current access flags
   */
  if((flags = fcntl(m_listen_socket, F_GETFL)) == -1) {
    
    perror("network_socket::init_tcp_stream : call to fcntl failed while getting socket "
	   "access flags; socket not created.");
    shutdown();
    
    throw new GeneralException("network_socket::init_tcp_stream : could not get flags of the socket."
			       ,__FILE__,__LINE__);    
  }

  if(!blocking) {
      /*
       * OR the current flags with 
       * O_NONBLOCK (so we won't block), and write them back
       */
      
      if(fcntl(m_listen_socket, F_SETFL, flags | O_NONBLOCK ) == -1) {
	
	perror("network_socket::init_tcp_stream : call to :fcntl() failed while setting socket "
	       "access flags; socket not created.");
	shutdown();
	
	throw new GeneralException("network_socket::init_tcp_stream : could not set flags (O_NONBLOCK) of the socket."
				 ,__FILE__,__LINE__);   
	
      }
  }//non blocking
    

  /* make sure we can reuse the port soon after */
 
  if(setsockopt(m_listen_socket, SOL_SOCKET, SO_REUSEADDR, (const char*)&one, sizeof(one))) {
    
    perror("network_socket::init_tcp_stream : setsockopt(2) failed");
    
    throw new GeneralException("network_socket::init_tcp_stream : setsocktopt failed."
				 ,__FILE__,__LINE__);   
    
  }
  

  /* 
   * Bind it to the port indicated
   * INADDR_ANY indicates that any network interface (IP address)
   * for the local host may be used (presumably the OS will choose the 
   * right * one).
   *
   * Specifying sin_port = 0 would allow the system to choose the port.
   */
  serverp.sin_family = PF_INET;
  serverp.sin_addr.s_addr = INADDR_ANY;

  if(bind(m_listen_socket, (struct sockaddr*) &serverp, sizeof(serverp)) == -1) {

    perror ("network_socket::init_tcp_stream : bind() failed; socket not created.");
    shutdown();

    throw new GeneralException("network_socket::init_tcp_stream : bind failed."
				 ,__FILE__,__LINE__); 
  }


  cerr<<"init_tcp_stream done!"<<endl;
  
}

void network_socket::socket_listen(int backlog, bool blocking) {

  init_tcp_stream(blocking);

  cerr<<"listening"<<endl;
  
  if(listen(m_listen_socket,backlog)) {
    
    perror("network_socket::init_tcp_stream : listen(2) failed:");
    shutdown();
    
    throw new GeneralException("network_socket::init_tcp_stream : listen failed."
			       ,__FILE__,__LINE__); 
    
  }
}

void network_socket::socket_accept() {

  if (m_blocking) {
    cerr<<"accept (blocking)"<<endl;
  }
  else {
    cerr<<"accept (non-blocking)"<<endl;
  }

  //int accept(int s, struct sockaddr *addr, int *addrlen);
  socklen_t length;

  if((m_read_socket = accept(m_listen_socket,(struct sockaddr *)NULL, &length)) == -1) {
    perror("network_socket::server_accept error when calling accept()");
    shutdown();
    throw new GeneralException("network_socket::server_accept error when calling accept()",__FILE__,__LINE__);
  }
    
  //write & read sockets are the same
  m_write_socket = m_read_socket;

  //set the socket non blocking?
  
  //sending Overflow identification banner
  /*
  cerr<<"send banner"<<endl;

  unsigned char banner[] = {'O','V','E','R','F','L','O','W'};

  send_packet(&banner[0],FLOWDESIGNER_IDENT_STRLEN);
  */
  cerr<<"accept done!"<<endl;
}

void network_socket::socket_connect(const char *host) {

  struct sockaddr_in server;
  struct hostent* entp;
  int sock;
  unsigned char banner[FLOWDESIGNER_IDENT_STRLEN];
  int numread;

  /* fill in server structure */
  server.sin_family = PF_INET;

  /* 
   * this is okay to do, because gethostbyname(3) does no lookup if the 
   * 'host' * arg is already an IP addr
   */
  if((entp = gethostbyname(host)) == NULL) {
    char message[256];
    sprintf(message, "player_connect() \"%s\" is an unknown host", host);
    throw new GeneralException(message,__FILE__,__LINE__);
  }

  memcpy(&server.sin_addr, entp->h_addr_list[0], entp->h_length);

  //setting port
  server.sin_port = htons(m_port);

  /* make our socket (and leave it blocking) */
  if((m_write_socket = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
    perror("network_socket::connect(): socket() failed");
    throw new GeneralException("network_socket::connect connect() failed",__FILE__,__LINE__);
  }

  //same socket for read & write
  m_read_socket = m_write_socket;

  /* 
   * hook it up
   */
  if(connect(m_write_socket, (struct sockaddr*)&server, sizeof(server)) == -1) {
    perror("network_socket::connect(): connect() failed");
    shutdown();
    throw new GeneralException("network_socket::connect(): connect() failed",__FILE__,__LINE__);
  }

// CC- Disable banner features. 01/12/2003
//  /*
//   * read the banner from the server
//   */
//  if((numread = recv_packet(&banner[0],FLOWDESIGNER_IDENT_STRLEN)) != FLOWDESIGNER_IDENT_STRLEN) {
//    perror("network_socket::connect(): read() failed");
//    shutdown();
//    throw new GeneralException("network_socket::connect(): read() failed",__FILE__,__LINE__);
//  }
//
//  /*
//   * verify the banner (if it contains "OVERFLOW")
//   */
//  
//  if (banner[0] != 'O' || banner[1] != 'V' || banner[2] != 'E' || banner[3] != 'R' ||
//      banner[4] != 'F' || banner[5] != 'L' || banner[6] != 'O' || banner[7] != 'W') {
//    shutdown(); 
//    throw new GeneralException("network_socket::connect() does not contain the banner",__FILE__,__LINE__); 
//  } 


}




/******************************************************************************
 Stream part!
*******************************************************************************/

socket_streambuf::socket_streambuf(int type, int port)
  : network_socket(type,port)
   , takeFromBuf(false) {

   
}

int socket_streambuf::overflow(int c)
{

   send_packet((unsigned char*) &c, 1);

   //FIXME: Find EOF?

   return 0;
}


streamsize socket_streambuf::xsputn(const char *s, streamsize n) {

  return send_packet((unsigned char*) s, (size_t) n);

}

int socket_streambuf::uflow() {

  if (takeFromBuf) {
    takeFromBuf = false;
    return charBuf;
  } else {
    recv_packet((unsigned char*) &charBuf, 1);
    return charBuf;      
  }
}

int socket_streambuf::underflow() {

   if (takeFromBuf) {
     return charBuf;
   } 
   else {
     recv_packet((unsigned char*) &charBuf, 1);
     takeFromBuf = true;
     return charBuf;
   }
}

int socket_streambuf::pbackfail(int c) {

  if (!takeFromBuf) {

    if (c != EOF)
      charBuf = c;
      
    takeFromBuf = true;
    return charBuf;
  } else {
    return EOF;
  }
}

streamsize socket_streambuf::xsgetn(char *s, streamsize n) {

  return recv_packet((unsigned char*) s, (size_t) n);

}


}//namespace FD


syntax highlighted by Code2HTML, v. 0.9.1