/* synchronous sockets

basically, a moderately tidier interface to standard BSD sockets */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <netdb.h>
#include <strings.h>
#include <string.h>
#include <signal.h>

#include "SSocket.h"

#define SSOCKET_UNKNOWN_TYPE 0
#define SSOCKET_SERVER_TYPE 1
#define SSOCKET_CLIENT_TYPE 2

SSocket::~SSocket()
{
}

SSocket::SSocket()
{
  _type=SSOCKET_UNKNOWN_TYPE;
}

int SSocket::connect(const char *hostname, int port)
{
  struct hostent *host;
  struct sockaddr_in sa;
  int thesocket;

  /* let's find out about this host */
  host=gethostbyname(hostname);
  if (host==NULL)
    {
      //      perror(hostname);
      return -1;
    }

  /* create the socket */
  thesocket=socket(AF_INET,SOCK_STREAM,0);

  /* fill in the fields */
  bzero(&sa,sizeof(sa));
  sa.sin_family=AF_INET;
  sa.sin_port=htons(0);
  sa.sin_addr.s_addr=htonl(INADDR_ANY);

  /* bind it to the port */
  if (bind (thesocket, (struct sockaddr *) &sa, sizeof (sa)) <0)
    {
      ::close(thesocket);
      //      perror ("bind failed");
      return -1;
    }

  sa.sin_port=htons(port);
  sa.sin_addr=*(struct in_addr *) host->h_addr;

  if (::connect(thesocket, (struct sockaddr *) &sa, sizeof (sa)))
    {
      if (errno!=EINPROGRESS)
	{
	  //	  perror("connect failed");
	  ::close(thesocket);
	  return -1;
	}
    }

  _type=SSOCKET_CLIENT_TYPE;
  _socket=thesocket;

  // prevent "broken pipe" signals.
  signal(SIGPIPE, SIG_IGN);

  return 0;
}

int SSocket::disableNagle()
{
  int n=1;

  if (setsockopt (_socket, SOL_SOCKET, TCP_NODELAY, 
		  (char *) &n, sizeof(n))<0)
    {
      //      perror("could not setsockopt");
      //      ::close(thesocket);
      return -1;
    }

  return 0;
}

int SSocket::listen(int port, int listenqueue, int localhostOnly)
{
  int thesocket,n;
  struct sockaddr_in sa;

  thesocket=socket(AF_INET,SOCK_STREAM,0);
  if (thesocket==-1)
    return -1;

  /* avoid address already in use errors */
  n=1;
  if (setsockopt (thesocket, SOL_SOCKET, SO_REUSEADDR, 
		  (char *) &n, sizeof(n))<0)
    {
      //      perror("could not setsockopt");
      ::close(thesocket);
      return -1;
    }
  
  /* fill in the fields */
  bzero(&sa,sizeof(sa));
  sa.sin_family=AF_INET;
  sa.sin_port=htons(port);
  sa.sin_addr.s_addr=htonl(localhostOnly ? INADDR_LOOPBACK : INADDR_ANY);
  
  /* try to bind to the port */

  if (bind (thesocket, (struct sockaddr *) &sa, sizeof (sa)) <0)
    {
      ::close(thesocket);
      //      perror ("bind failed\n");
      return -1;
    }

  /* start listening */
  if (::listen(thesocket,listenqueue))
    {
      ::close(thesocket);
      perror("listen");
      return -1;
    }

  _type=SSOCKET_SERVER_TYPE;
  _socket=thesocket;

  return 0;
}

SSocket *SSocket::accept()
{
  int thesocket;
  SSocket *newssocket;

  _addrlen=sizeof(struct sockaddr);

  if (_type!=SSOCKET_SERVER_TYPE)
    {
      ::printf("not server type\n");
      return NULL;
    }

  /* actually accept the connection */
  if ((thesocket=::accept(_socket,&_addr,&_addrlen))<0)
    {
      //      perror("accept failed");
      return NULL;
    }

  newssocket=new SSocket();
  newssocket->_type=SSOCKET_CLIENT_TYPE;
  newssocket->_socket=thesocket;
  
  return (newssocket);
}

int SSocket::read(char *buffer, int requestedlen)
{
  int actuallyread;

  actuallyread=::read(_socket,buffer,requestedlen);

  return actuallyread;
}

int SSocket::write(const char *buffer, int requestedlen)
{
  int actuallywritten;

  actuallywritten=::write(_socket,buffer,requestedlen);
  
  return actuallywritten;
}

void SSocket::puts(const char *buffer)
{
  writeAll(buffer, (int) strlen(buffer));
}

void SSocket::printf(const char *format, ...)
{
  va_list ap;

  char *buf=NULL;
  int size=0;

  int res;
  do
  {
    size=size*2;
    if (size<512)
      size=512;
    
    buf=(char*) realloc(buf,size);
    
    va_start(ap,format);
    res=vsnprintf(buf,size,format,ap);
    va_end(ap);
  } while (res==-1);    

  writeAll(buf, res);
  free(buf);
}

/* returns <0 on error */
int SSocket::writeAll(const char *buffer, int requestedlen)
{
  int actuallywritten;
  int writtenthistime;

  actuallywritten=0;

  while (actuallywritten<requestedlen)
    {
      writtenthistime=::write(_socket,&buffer[actuallywritten],
			    requestedlen-actuallywritten);

      /* error? */
      if (writtenthistime<0)
	return writtenthistime;

      actuallywritten=actuallywritten+writtenthistime;
    }
  return actuallywritten;
}

/* returns len read */
int SSocket::gets(char *buffer, int maxlen)
{
  int pos;
  int readlen;
  
  pos=0;
  
  while(pos<maxlen-1)
    {
      if ((readlen=read(&buffer[pos],1))<0)
	goto eof;
      
      if (readlen==0 && pos==0)
	goto eof;
      
      if (readlen==0)
	break;

      if (buffer[pos]==0xd || buffer[pos]==0xa)
	{
	  buffer[pos]='\0';
	  return 1;
	}
      pos+=readlen;
      
    }

  buffer[pos]='\0';
  return 1;
  
 eof:
  buffer[pos]='\0';
  return 0;
  
}

void SSocket::close()
{
  ::close(_socket);
}

void SSocket::getRemoteIP(char *ipstring)
{
  struct sockaddr addr;
  socklen_t addrlen;
  
  getpeername(_socket,&addr,&addrlen);

  sprintf(ipstring,"%u.%u.%u.%u",
	  (int) (addr.sa_data[2])&0xff,	 
	  (int) (addr.sa_data[3])&0xff,	 
	  (int) (addr.sa_data[4])&0xff,	 
	  (int) (addr.sa_data[5])&0xff);
  
}

int SSocket::getFD()
{
  return _socket;
}

FILE *SSocket::getFile()
{
  return fdopen(_socket,"r+");
}
