//=========================================================================
// Summary  : Driver parent class for socket-based devices
// Filename : SocketDriver.cc
// Author   : Marsh
// Project  : 
// Revision : 1
// Created  : 2001/01/8
// Modified : 2000/08/21
//=========================================================================
// Description :
//=========================================================================

#include <sys/socket.h>
#include <sys/types.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <errno.h>
#include <stdlib.h>
#include <netdb.h>
#include <ioctl.h>

#include "SocketDriver.h"
#include "Syslog.h"

# define IN_CLASSD_NSHIFT 0
//# define MAKE_IN_ADDR(A,B,C,D) (A<<IN_CLASSA_NSHIFT | B<<IN_CLASSB_NSHIFT | C<<IN_CLASSC_NSHIFT | D<<IN_CLASSD_NSHIFT)
# define MAKE_IN_ADDR(A,B,C,D) (A | B<<8 | C<<16 | D<<24)

int SignalTable<SocketDriver::SignalCallbackMethod>::_lastSignal = -1;

//===============================================================
SocketDriver::SocketDriver( const char *name )
     : Task( name ), socketList(1)
{

     socketListMax = -1;
}

SocketDriver::~SocketDriver()
{

     // Clean up the dynamic array
     struct SocketListEntry *cleanMe = NULL;
     for( int i=0; i < socketList.size(); i++ ) {
	  // Why does this cause a segfault?
	  socketList.get( i, &cleanMe );
	  // The actual list entries are self-cleaning
	  if( cleanMe ) {
	       if( cleanMe->socketNum > 0 ) {
		    close( cleanMe->socketNum );
		    cleanMe->socketNum = -1;
	       }
	       clean( cleanMe );
	  }
     }

}

//===============================================================
// Add a new connection

int SocketDriver::registerSocket( const char *name,
				  const char *hostname, int port,
				  CallbackMethod callback,
				  int triggerByteCount,
				  Boolean openImmediately )
{
     struct SocketListEntry *newSocket = new SocketListEntry();
  
     // Do everything at once, if possible
     newSocket->socketNum = -1;
     newSocket->name = strdup( name );
     newSocket->hostname = strdup( hostname );
     newSocket->port = port;
     newSocket->callback = callback;
     newSocket->buffSize = triggerByteCount;
     newSocket->state = Disconnected;
     socketList.add( &newSocket );
  
     // This is not strictly kosher, but DynamicArray.add doesn't return
     // the index of a new element.  Instead, it just adds it to the end,
     // so it should now be valid.
     int index = socketList.size() - 1;
     socketListMax = max( index, socketListMax );
  
     // I think technically, this is incorrect.  Is it possible for DynamicArray
     // to .add() an element somewhere other than the end of the list?
     if( openImmediately ) {
	  openSocket( index );
     }

     return index;
}

int SocketDriver::openSocket( int socketIndex )
{
     // First look up the socket
     struct SocketListEntry *listEntry;

     socketList.get( socketIndex, &listEntry );
     
     // Make sure it's within known bounds
     if( (socketIndex < 0) || (socketIndex > socketListMax) )
	  return -1;
     
     return openSocket( listEntry );
}

int SocketDriver::openSocket( struct SocketListEntry *listEntry )
{

     if( (listEntry->socketNum >= 0) && (listEntry->state == Connected) ) {
	  // If socketNum is already non-zero, we're already connected.
	  return 0;
     }

     // To get this far, something must be amiss in the connection state info.
     // Either the socket number is invalid, or the state is recorded as disconnected
     // In either case, explicitly close the connection and reset the state.
     if( listEntry->socketNum >= 0 ) closeSocket( listEntry );
     listEntry->socketNum = -1;
     listEntry->state = Disconnected;

     Syslog::write("opening socket: %s, %s", listEntry->name, listEntry->hostname);

     int newSock;
     struct sockaddr_in server;
     struct hostent *hp;
  
     newSock = socket( PF_INET, SOCK_STREAM, 0 );
     if( newSock < 0 ) {
	  Syslog::write("%s::addSocket -- Error creating new socket (%d):%d",
			_name, errno, strerror( errno ) );
	  return -1;
     }
     server.sin_family = AF_INET;
     hp = gethostbyname( (char *)listEntry->hostname );
  
     if( hp == 0 ) {
    
	  Syslog::write("Unable to resolve host %s: %s",
			listEntry->name, listEntry->hostname );
	  herror( NULL );
	  return -1;
     }
  
     memcpy( &server.sin_addr, hp->h_addr, hp->h_length );
     //     server.sin_addr.s_addr = MAKE_IN_ADDR(192,168,0,67);
     server.sin_port = htons( listEntry->port );
 
     // connect returns a zero if it succeeds.
     if( connect(newSock, (struct sockaddr *)&server,
		 sizeof( server ) ) != 0 ) {
	  Syslog::write("%s::addSocket -- Unable to connect to %s",
			listEntry->name, listEntry->hostname );
	  return -1;
     }
  
     // Success.

     listEntry->socketNum = newSock;
     listEntry->state = Connected;

     return 0;
     
}

int SocketDriver::closeSocket( int socketIndex )
{
     struct SocketListEntry *listEntry;

     socketList.get( socketIndex, &listEntry );
     
     // Make sure it's within known bounds
     if( (socketIndex < 0) || (socketIndex > socketListMax) )
	  return -1;

     return closeSocket( listEntry );
}

int SocketDriver::closeSocket( struct SocketListEntry *listEntry )
{
     if( listEntry->socketNum > 0 ) {
	  close( listEntry->socketNum );
	  listEntry->socketNum = -1;
	  listEntry->state = Disconnected;
	  // Don't really clean up the entry...sigh
	  return 0;
     }

     // If you got here, it's an error 
     return -1;
}

int SocketDriver::getSocketFd( int socketIndex )
{
     struct SocketListEntry *listEntry;
     // Make sure it's within known bounds
     if( (socketIndex < 0) || (socketIndex > socketListMax) )
	  return -1;

     socketList.get( socketIndex, &listEntry );
     return listEntry->socketNum;
}

void SocketDriver::dumpSocketInfo( int socketIndex )
{
     struct SocketListEntry *listEntry;
     // Make sure it's within known bounds
     if( (socketIndex < 0) || (socketIndex > socketListMax) ) {
	  Syslog::write("Unable to find socket number %s",
			socketIndex );
	  return;
     }

     socketList.get( socketIndex, &listEntry );
     
     // Dump a little info
     Syslog::write("%s::dumpSocketInfo -- Dumping entry %d, fd %d",
		   _name, socketIndex, listEntry->socketNum );
     Syslog::write("Name:           %s",
		   listEntry->name );
     Syslog::write("Hostname[port]: %s[%d]",
		   listEntry->hostname, listEntry->port );
     Syslog::write("Trigger size   : %d",
		   listEntry->buffSize );

}

//===============================================================
int SocketDriver::addSignalCallback( int signo,
				     SignalCallbackMethod callback )
{
    Syslog::write("Registering function to signal %d",
		   signo );
     return _sigTable.addCallback( signo, callback );
}

//===============================================================
int SocketDriver::readSocket( struct SocketListEntry *theEntry )
{
     //int flags = (theEntry->peekAtData) ? MSG_PEEK : 0;
//      int flags = 0;
//      int nRecvd = 0;
//      unsigned int nBytesWaiting = 0;
//      // Clear out buffer
//      memset( theEntry->buffer, 0, theEntry->buffSize );
 
//      nBytesWaiting = ioctl(theEntry->socketNum, FIONREAD ,&nBytesWaiting);
//      Syslog::write("bytes waiting: %d\n",nBytesWaiting);
//      nRecvd = recv( theEntry->socketNum, theEntry->buffer, theEntry->buffSize, flags); 
//      if (nRecvd < 0)
//      {
// 	  Syslog::write("recv returned error (%d): %s", errno, strerror(errno ) );
// 	  return -1;
//      }
//      return nRecvd;
     return 0;
}

//===============================================================
int SocketDriver::writeSocket( int socketIndex, char *buffer, int buffSize )
{

     int nSent = 0;
     int send_flags = 0;
     int false_flag = 0;
     
     int fd = getSocketFd( socketIndex );
     if( fd < 0 ) {
	  // Invalid index
	  return -1;
     }

//       Syslog::write("DEBUG:socketDriver(writeSocket): Index=%2d, Fd=%2d.",
//  		   socketIndex, fd);

     ioctl(fd,FIONBIO,&false_flag);
     if( (nSent = send( fd, (char *)buffer, buffSize, send_flags )) < 0 ) {
	  //if( nSent = send( fd, (char *)"testing", 7,send_flags ) < 0 ) {
	  Syslog::write("EdgetechMessage::sendMsg failed to send (%d): %s",
 			errno, strerror(errno) ) ;
	  return -1;
     }

//     Syslog::write("I have presumably succeeded in sending %d bytes of a message of size: %d to fd: %d\n",nSent,buffSize,fd);
     return nSent;
}

//===============================================================
void SocketDriver::run( void )
{
     int fooint;
     // Enter the main loop
     do {
	  int maxFd = 0, n;
	  fd_set readSet, exceptSet;
	  struct timeval timeOut;

	  FD_ZERO( &readSet );
	  FD_ZERO( &exceptSet );
    
	  // Fill the two sets with all sockets
	  for( int i=0; i < socketList.size(); i++ ) {
	       struct SocketListEntry *thisEntry;
	       socketList.get( i, &thisEntry );


	       // Error checking
	       if( thisEntry->state == Error ) {
		    // Nominally, you could have some control over attempts to 
		    // reconnect: have a Boolean flag doReconnect, or call a 
		    // function in user space to determine whether the 
		    // reconnect should be attempted
		    Syslog::write("Connection on port \"%s\" to \"%s\" lost.  Attempting reconnect.",
				  thisEntry->name, thisEntry->hostname );
		    openSocket( thisEntry );
				  
	       }

	       // Initialize the fd_sets for select()
	       if( (thisEntry->state == Connected) && (thisEntry->socketNum >=0 ) ) {
		    FD_SET( thisEntry->socketNum, &readSet );
		    FD_SET( thisEntry->socketNum, &exceptSet );
		    maxFd = max( maxFd, thisEntry->socketNum+1 );
	       }
	  }
    
    
	  // Reset the signal handler
	  _sigTable.resetSignal();
    
	  // Enter the select.  Note that this is set up
	  // to return only when there is incoming data,
	  // an exception, or some sort of interruption (signal).
	  // There is no timeout right now.
	  // n = select( maxFd,
	  //      &readSet,
	  //      NULL,
	  //      &exceptSet,
	  //      NULL );

//	  Syslog::write("Entering select...");
    
	  //n = select( maxFd, &readSet,NULL,NULL,(struct timeval *)NULL);

	  n = select( maxFd, &readSet,NULL,&exceptSet,(struct timeval *)NULL);

//	  Syslog::write("...exiting");

	  if( n < 0 ) 
	  {
//	       Syslog::write("Error exit");
	       // On an error condition
	       if( errno == EINTR ) 
	       {
//		    Syslog::write("select was interrupted by a signal! (%d)",
//				  _sigTable.lastSignal() );
		    // A signal has come in.  What to do?
		    // Call the appropriate callback
		    if( _sigTable.isCallback( _sigTable.lastSignal() ) ) {
			 // Uh-oh.  How do I do this if the table of callbacks
			 // is in _sigTable, but it's a member of SocketDriver
			 // ???
			 callMemberFunction( this,
					     _sigTable.callback( _sigTable.lastSignal() ) )();
		    } else {
			 ;
			 // No registered function?  Ignore it.
		    }
	       } else {
		    Syslog::write("select was interrupted for some reason!");
	       }
	  } 
	  else  if(n > 0) {
//	       Syslog::write("Normal exit");
	       // Normal exit.  At this point, select() has only set
	       // the fds which are actually read to be read (or
	       // had an exception)
	       for( int i=0; i < socketList.size(); i++ ) {
		    struct SocketListEntry *thisEntry;
		    socketList.get( i, &thisEntry );
		    if( thisEntry->socketNum > 0 ) {
			 if( FD_ISSET( thisEntry->socketNum, &readSet ) ) {
			      // If the child has requested a data
			      // block be read automatically, do so
			      if( thisEntry->buffSize > 0 ) 
			      {
				   unsigned int nBytesWaiting = 0;
				   unsigned int x = ioctl(
					thisEntry->socketNum,
					FIONREAD ,&nBytesWaiting);

//				   Syslog::write("Read %u character, waiting for %d", nBytesWaiting, thisEntry->buffSize );

				   if( (nBytesWaiting>=thisEntry->buffSize) &&
					(thisEntry->callback) ) {
//					Syslog::write("Calling member function");
					callMemberFunction( this, thisEntry->callback)( thisEntry, false );
				   } else if( nBytesWaiting == 0 ) {
					// If we're here, the connection was probably dropped.
					// Set the error flag
//					Syslog::write("Some error here");
					thisEntry->state = Error;

					
				   }
			      } 
			      else 
			      {
				// Hmmm, some sort of error?
				   thisEntry->lastErrNo = errno;
				   callMemberFunction( this, thisEntry->callback)( thisEntry, true );
			      }
		    
			 } 
			 if( FD_ISSET( thisEntry->socketNum, &exceptSet ) ) 
			 {
			      // What to do on an exception?
			      Syslog::write("%s::run -- Socket %s caught an exception",
					    _name, thisEntry->name );
			      thisEntry->lastErrNo = errno;
			      callMemberFunction( this, thisEntry->callback)( thisEntry, true );
			 } 
			 else {
			      //This FD wasn't set in either set
			      ;
			 }
		
		    }
	    
	       }
	  }
	  
     } while (1);
}
