//============================================================================
/// Summary  : Simple Socket
/// Filename : simpleSocket.h
/// Author   : reed@bluefinrobotics.com
/// Project  : Simple Socket Library
/// Revision : 0.0
/// Created  : 2002.02.26
/// Modified : 2002.02.26
//============================================================================

#ifndef _SIMPLESOCKET_H
#define _SIMPLESOCKET_H

//#define DEBUG_SS 1

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h> // IO Control of socket.
//#include <fcntl.h>     // IO Control of socket.
//#include <sys/unistd.h> // close
#include <sys/time.h>
#include <signal.h>
#include <netinet/in.h>
#include <errno.h>
#include <stdio.h>


#include <string.h> // strncpy

#include "../utils/Syslog.h"
#include "System.h"

// Some limits.
static const int MaxIpAddressStringSize = 16;
static const int MaxDatagramSize = 640;

// Address parameters from netinit/in.h.
static const u_long SS_ANY_ADDRESS = INADDR_ANY;
static const u_long SS_BROADCAST_ADDRESS = INADDR_BROADCAST;

// Other address parameters.
static const char SS_BROADCAST_STRING[] = "255.255.255.255\0";
static const char SS_LOCALHOST_STRING[] = "127.0.0.1\0";

class SimpleSocket
{
 public:
    // Constructors / Destructors
    SimpleSocket(debug_t debugLevel = 0,
		 int datagramBufferSize = MaxDatagramSize);
    virtual ~SimpleSocket();

    // ---------- Create Socket Items ----------
    // Socket Type enumerations
    // Taken from sys/socket.h
    enum Type{ SS_STREAM = SOCK_STREAM,        // stream socket
	       SS_DATAGRAM = SOCK_DGRAM,       // datagram socket
	       SS_RAW = SOCK_RAW,              // raw-protocol interface
	       SS_RDM = SOCK_RDM,              // reliably-delivered message
	       SS_SEQPACKET = SOCK_SEQPACKET };// sequenced packet stream
    // Socket Domain (address families) enumerations
    // Taken from sys/socket.h (minimal listing here)
    enum Domain{ SS_UNSPEC = AF_UNSPEC, // ( 0) unspecified
		 // ...
	         SS_INTERNET = AF_INET, // ( 2) internetwork: UDP, TCP, etc.
		 // ...
		 SS_MAX = AF_MAX };     // (20) maximum number of domains
    // Socket Option Flags per-socket 
    // Taken from sys/socket.h (minimal listing)
    enum OptionFlags{SS_DEBUG = SO_DEBUG,         // turn on debugging info recording
                     //...
                     SS_BROADCAST = SO_BROADCAST, // permit sending of broadcast messages
		     //...
		     SS_SENDTIMEOUT = SO_SNDTIMEO,    // send timeout
		     SS_RECEIVETIMEOUT = SO_RCVTIMEO, // receive timeout
		     SS_ERROR = SO_ERROR,             // get error status and clear
		     SS_TYPE = SO_TYPE };             // get socket type
    // Socket Level
    enum Levels{ SS_SOCKET = SOL_SOCKET };

    // Socket Protocol (from sys/socket.h ... same as address families for now).
    typedef Domain Protocol;
    /*     // Message IO Control */
    /*     enum MessageDirection{ SS_SEND = SO_SNDTIMEO, */
    /*     			   SS_RECEIVE = SO_RCVTIMEO}; */
    // IO Control
    enum IoControl{ BLOCKING = 0, NONBLOCKING = 1 };

    // Attach flags for connecting as client or binding as host.
    enum AttachMethods{ AS_CLIENT = 0, AS_HOST = 1 };

    /// create socket
    int create( Type type,
		Domain domain = SS_INTERNET,
		Protocol proctocol = SS_UNSPEC );
    /// create datagram socket
    int createInternetDatagram();
    /// create steam socket
    int createInternetStream();

    /// destroy socket
    int destroy();

    /// set socket address long value
    int setAddress(unsigned short port,
		   unsigned long ipAddress = SS_BROADCAST_ADDRESS,
		   unsigned short family = SS_INTERNET);
    /// set socket address IP address string
    int setAddress(unsigned short port,
		   const char *ipAddressString = SS_BROADCAST_STRING,
		   unsigned short family = SS_INTERNET);

    /// set socket options
    int setOptions(int level,
		   int options,
		   const void *optionValue,
		   int optionLength);
    /* /// set socket timeout */
    /*     int setOptionsTimeout(MessageDirection messageDirection,  */
    /* 			  int seconds = 0,   // seconds */
    /* 			  int useconds = 0); // micro-seconds */
    /// set socket options broadcast
    int setOptionsDatagramBroadcast();

    /// attach (connect) to an IP address and port
    int attach(const unsigned short attachFlag);

    /// bindTo (bind) a name to a socket
    int bindTo( int descriptor, 
		struct sockaddr_in *socketAddress = NULL,
		int sizeSocketAddress = sizeof(struct sockaddr) );


    // +++++++ Converters go here +++++++

    /// Convert string IP address to a hex address
    /// i.e. "255.255.255.255" --> 0xFFFFFFFF.
    unsigned long string2hexIpAddress(const char *ipAddressString);
    //char *hex2stringIpAddress(unsigned long);


    // +++++++ Heavy duty functions that do it all +++++++

    /// Create broadcast datagram socket
    /// @param socket port
    /// @param network address structure
    /// @return returns : -1 on failure
    /// @return returns :  0 on success
    int createBroadcastDatagramSend(unsigned short port, 
				    unsigned long ipAddress = SS_BROADCAST_ADDRESS,
				    unsigned short family = SS_INTERNET);
    int createBroadcastDatagramSend(unsigned short port, 
				    const char *ipAddressString = SS_BROADCAST_STRING,
				    unsigned short family = SS_INTERNET);
    int createBroadcastDatagramReceive(unsigned short port);
    /// Create a connected internet stream to a host computer.
    int createConnectedStreamClient(unsigned short port,
				    unsigned long ipAddress,
				    unsigned short family = SS_INTERNET);
    int createConnectedStreamClient(unsigned short port,
				    const char *ipAddressString = SS_LOCALHOST_STRING,
				    unsigned short family = SS_INTERNET);
    /// Create a connected internet stream for receiving computer.
    int createConnectedStreamHost(unsigned short port,
				  unsigned long address = SS_ANY_ADDRESS,
				  unsigned short family = SS_INTERNET);
/*     int createConnectedStreamHost(unsigned short port, */
/* 				  const char *ipAddressString = LOCALHOST_STRING); */

    // ---------- Transfer Over Socket Commands ----------

    /// send to socket explicit
    int broadcast(const void *message,
		  unsigned int messageLength,
		  IoControl ioControl = NONBLOCKING,
		  int flags = 0);
    /// send to socket simple
    int broadcastDatagram(const void *message,
			  unsigned int messageLength,
			  IoControl ioControl = NONBLOCKING);

    /// receive from socket simple
    int receiveFrom(void *dataBuffer,
		    unsigned int bufferLength,
		    int *receivedLength,
		    int flags = 0,
		    int socketDescriptor = -1,
		    struct sockaddr_in *receivedSocketAddress = NULL);
    int receiveDatagram(void *dataBuffer,
			unsigned int bufferLength);
    /// sending to connected socket
    int sending(const void *message,
		unsigned int messageLength,
		int descriptor = -1,
		IoControl ioControl = NONBLOCKING,
		int flags = 0);
    /// receiving from connected socket
    int receiving(const void *buffer,
		  unsigned int sizeOfBuffer,
		  int descriptor = -1,
		  IoControl = NONBLOCKING,
		  int flages = 0);

    /// listening for connection to socket
    int listening(const int backlog,
		  IoControl ioControl = NONBLOCKING);

    /// accepting connection from socket
    int accepting(IoControl ioControl = NONBLOCKING);
	  
    bool ready() const
	{ 
	    int error = 0;
	    switch(networkAddress.type)
	    {
	    case SS_STREAM:
		if(!networkAddress.exists)
		{
		    dwrite(3)("Socket hasn't been created, socket not ready!\n");
		    error++;
		}
		if(!networkAddress.addressSet)
		{
		    dwrite(3)("Socket address hasn't been set, socket not ready!\n");
		    error++;
		}
		if(!networkAddress.connected)
		{
		    dwrite(3)("Socket hasn't been connected, socket not ready!\n");
		    error++;
		}
		break;
	     case SS_DATAGRAM:
		 if(!networkAddress.exists)
		 {
		     dwrite(3)("Socket hasn't been created, socket not ready!\n");
		     error++;
		 }
		 if(!networkAddress.addressSet)
		 {
		     dwrite(3)("Socket address hasn't been set, socket not ready!\n");
		     error++;
		 }
		 if(!networkAddress.optionsSet)
		 {
		     dwrite(3)("Socket options haven't been set, socket not ready!\n");
		     error++;
		 }
		 break;
	     default:
		 dwrite(3)("Invalid socket type 0x%X, socket not ready!\n",networkAddress.type);
		 error++;
		 break;
	     }
	     
	     if(!error)
		 return(true);
	     else
		 return(false);
	 }
    bool connected() const { return(networkAddress.connected); }	
    bool optionSet() const { return(networkAddress.optionsSet); }
    bool addressSet() const { return(networkAddress.addressSet); }
    bool exists() const { return(networkAddress.exists); }

 protected:

 private:
    /// Network address.
    struct NetworkAddress 
    {
	bool connected;
	bool optionsSet;
	bool addressSet;
	bool exists;
	Type type;
	Domain domain;
	Protocol protocol;
	int descriptor;
	char ipAddressString[MaxIpAddressStringSize];
	unsigned long ipAddress;
	unsigned short port;
	struct sockaddr_in address;
	int addressLength;
	//int optionValue;
	//int optionValueLength;
    } networkAddress;

    debug_t debug;
};
#endif
