/* --------------------------------------------------------------------------------
 #
 #	PluginMain.cpp (4DPlugin.c)
 #	source generated by 4D Plugin Wizard
 #	Project : FineLoad
 #	author : Pasi
 #	29.10.2001
 #
 # --------------------------------------------------------------------------------*/
// http://www.unixguide.net/network/socketfaq/

#include "Includes.h"


// Pollset
// http://apr.apache.org/docs/apr/1.4/group__apr__poll.html
// http://dev.ariel-networks.com/apr/apr-tutorial/sample/pollset-sample.c

// TTcp

TTcp::TTcp() 
{
	MA_apr_initialize(); 
	
	currentSocket = NULL;
  currentSocketNum = 0;
	currentServer = NULL;
	nextInConnectionId			= FIRST_IN_CONNECTION_ID;  // 1 000 001	// currentSocketType = SOCKET_TYPE_UNKNOWN;
	nextServerConnectionId	= FIRST_SERVER_CONNECTION_ID; // 2 000 001
	nextOutConnectionId			= FIRST_OUT_CONNECTION_ID; // 3 000 001
  
	lastInConnectionId      = LAST_IN_CONNECTION_ID; 
	lastServerConnectionId	= LAST_SERVER_CONNECTION_ID;
	lastOutConnectionId 		= LAST_OUT_CONNECTION_ID;
  
	tcpServers = new TPointerArray<TServer>();
	tcpSockets = new TPointerArray<TSocket>();
    
  // pollset
  pollsetIdNext        = FIRST_POLLSET_ID; // only in constructor
  pollsetAprId         = NULL; // must be before DestroyPollset();
  pollsetAprMemoryPool = NULL; // must be before DestroyPollset();
  // pollsetReturn_pfd    = NULL; must NEVER set this
  DestroyPollset();  // clear all the rest values
  
}

TTcp::~TTcp() 
{
  // apr_status_t returnValue;  // we can't use returnValue in destructor
  
	/*
   // this will always be some in list of tcpServers
   if( currentServer != NULL ) {
   delete currentServer; // = NULL;
   }
   
   // this will always be some in list of tcpServers
   if( currentSocket != NULL ) {
   delete currentSocket; // = NULL;
   }
   */
	
	delete tcpServers;
	delete tcpSockets;
    
  // pollset
  DestroyPollset();  
  
	MA_apr_terminate();
}


void TTcp::ClearPollset(void) 
{
  //if( pollsetId ) { 
  
  apr_status_t status;
  if( pollsetAprId ) { 
    // destroy even if param pollset was wrong, else would not be able to destroy if id was missing
    status = apr_pollset_destroy	(	pollsetAprId	);       // destroy apr pool  
    if ( status != APR_SUCCESS ){
      errTxtSocket = L"Pollset delete failed (ClearPollset)";  
    }
    pollsetAprId = NULL;
    /*
     apr_status_t apr_pollset_destroy	(	apr_pollset_t * 	pollset	)	
     Destroy a pollset object
     
     Parameters:
     pollset	The pollset to destroy
     */
  }
  if( pollsetAprMemoryPool ) { 
    apr_pool_destroy( pollsetAprMemoryPool );    // clear apr memory pool 
    pollsetAprMemoryPool = NULL;   
  }
  
  //}
}

long TTcp::AddSocketsToPollset() 
{        
  //long option = APR_POLLIN;
  long returnValue = kNoErr;
  apr_status_t status;
  apr_socket_t *socket;
  apr_int16_t option;
  
  ClearPollset();
  if( pollsetMaxSize <  1 ) { 
    returnValue = -1;
    errTxtSocket = L"Pollset maximum size is smaller than 1 (AddSocketsToPollset)";  
  } else {
    status =  apr_pool_create( &pollsetAprMemoryPool, NULL );  // create memory pool
    if ( status != APR_SUCCESS ){
      returnValue = -abs( status );
      errTxtSocket = L"Pollset memory pool could not be created (AddSocketsToPollset)";  
    } else {
      status = apr_pollset_create( &pollsetAprId, pollsetMaxSize, pollsetAprMemoryPool, pollsetOption );
      //status = apr_pollset_create( &pollsetAprId, 10, pollsetAprMemoryPool, 0 );
      if ( status != APR_SUCCESS ){
        returnValue = -abs( status );
        errTxtSocket = L"Pollset could not be created (AddSocketsToPollset)";
      }
    }  
  }
  
  if( returnValue == kNoErr ) { 
    for ( unsigned long arrIndex = 0; arrIndex < pollSocketApr.size() ; arrIndex++ ) {
      socket = pollSocketApr[arrIndex];
      option = pollSocketOption[arrIndex];
      // apr_pollfd_t pfd = { pollsetAprMemoryPool, APR_POLL_SOCKET, option, 0, { NULL }, NULL };
      apr_pollfd_t pfd = { pollsetAprMemoryPool, APR_POLL_SOCKET, option, 0, { NULL }, NULL };
      pfd.desc.s = socket;
      status = apr_pollset_add( pollsetAprId, &pfd );  
      if( status != APR_SUCCESS ){
        returnValue = -abs( status );
        break; // exit loop
      }
    }
  }
  
  if( returnValue != kNoErr ) { 
    ClearPollset();
  }
  return returnValue;
  
  /*
   man poll:
   
   The event bitmasks in events and revents have the following bits:
   
   POLLERR        An exceptional condition has occurred on the device or socket.  This flag is output
   only, and ignored if present in the input events bitmask.
   
   POLLHUP        The device or socket has been disconnected.  This flag is output only, and ignored if
   present in the input events bitmask.  Note that POLLHUP and POLLOUT are mutually exclu-
   sive and should never be present in the revents bitmask at the same time.
   
   POLLIN         Data other than high priority data may be read without blocking.  This is equivalent to
   ( POLLRDNORM | POLLRDBAND ).
   
   POLLNVAL       The file descriptor is not open.  This flag is output only, and ignored if present in
   the input events bitmask.
   
   POLLOUT        Normal data may be written without blocking.  This is equivalent to POLLWRNORM.
   
   POLLPRI        High priority data may be read without blocking.
   
   POLLRDBAND     Priority data may be read without blocking.
   
   POLLRDNORM     Normal data may be read without blocking.
   
   POLLWRBAND     Priority data may be written without blocking.
   
   POLLWRNORM     Normal data may be written without blocking.
   
   The distinction between normal, priority, and high-priority data is specific to particular file types
   or devices.
   
   ---
   
   apr_status_t apr_pollset_add	(	apr_pollset_t * 	pollset,
   const apr_pollfd_t * 	descriptor 
   )		
   Add a socket or file descriptor to a pollset
   
   Parameters:
   pollset	The pollset to which to add the descriptor
   descriptor	The descriptor to add
   Remarks:
   If you set client_data in the descriptor, that value will be returned in the client_data field whenever this descriptor is signalled in apr_pollset_poll().
   If the pollset has been created with APR_POLLSET_THREADSAFE and thread T1 is blocked in a call to apr_pollset_poll() for this same pollset that is being modified via apr_pollset_add() in thread T2, the currently executing apr_pollset_poll() call in T1 will either: (1) automatically include the newly added descriptor in the set of descriptors it is watching or (2) return immediately with APR_EINTR. Option (1) is recommended, but option (2) is allowed for implementations where option (1) is impossible or impractical.
   If the pollset has been created with APR_POLLSET_NOCOPY, the apr_pollfd_t structure referenced by descriptor will not be copied and must have a lifetime at least as long as the pollset.
   Do not add the same socket or file descriptor to the same pollset multiple times, even if the requested events differ for the different calls to apr_pollset_add(). If the events of interest for a descriptor change, you must first remove the descriptor from the pollset with apr_pollset_remove(), then add it again specifying all requested events.
   */
}

/*
 long TTcp::RemoveFromPollset( apr_socket_t *socket ) 
 {
 apr_pollfd_t pfd = { pollsetAprMemoryPool, APR_POLL_SOCKET, 0, 0, { NULL }, NULL }; // APR_POLL_SOCKET, APR_POLLIN?
 pfd.desc.s = socket;
 apr_status_t status = apr_pollset_remove( pollsetAprId, &pfd );
 /-*
 apr_status_t apr_pollset_remove	(	apr_pollset_t * 	pollset,
 const apr_pollfd_t * 	descriptor 
 )		
 Remove a descriptor from a pollset
 
 Parameters:
 pollset	The pollset from which to remove the descriptor
 descriptor	The descriptor to remove
 Remarks:
 If the pollset has been created with APR_POLLSET_THREADSAFE and thread T1 is blocked in a call to apr_pollset_poll() for this same pollset that is being modified via apr_pollset_remove() in thread T2, the currently executing apr_pollset_poll() call in T1 will either: (1) automatically exclude the newly added descriptor in the set of descriptors it is watching or (2) return immediately with APR_EINTR. Option (1) is recommended, but option (2) is allowed for implementations where option (1) is impossible or impractical.
 apr_pollset_remove() cannot be used to remove a subset of requested events for a descriptor. The reqevents field in the apr_pollfd_t parameter must contain the same value when removing as when adding.
 *-/
 return (long)status;
 }
 */

void TTcp::DestroyPollset() 
{
  ClearPollset();
  
  pollsetId         = 0; // unique running id
  // pollsetIdNext     = FIRST_POLLSET_ID; // only in constructor
  pollsetMaxSize    = 0;
  pollsetOption     = 0;
  
  pollCount         = 0;  // how many times has been polled
  pollReceiveCount  = 0;  // how many times has been been received events  
  // pollsetReturn_pfd    = NULL; must NEVER set this
}

long TTcp::CreatePollset( long pollsetMaxSizeIn, long optionIn ) 
{
  
  ClearPollset();
  pollsetMaxSize = pollsetMaxSizeIn;
  pollsetOption  = optionIn;
  pollsetId      = pollsetIdNext;
  pollsetIdNext++;
  // RecreatePollset(); // not until arrays has been set
  return pollsetId;
  
  /*
   apr_status_t apr_pollset_create	(	apr_pollset_t ** 	pollset,
   apr_uint32_t 	size,
   apr_pool_t * 	p,
   apr_uint32_t 	flags 
   )		
   Set up a pollset object
   
   Parameters:
   pollset	The pointer in which to return the newly created object
   size	The maximum number of descriptors that this pollset can hold
   p	The pool from which to allocate the pollset
   flags	Optional flags to modify the operation of the pollset.
   Remarks:
   If flags contains APR_POLLSET_THREADSAFE, then a pollset is created on which it is safe to make concurrent calls to apr_pollset_add(), apr_pollset_remove() and apr_pollset_poll() from separate threads. This feature is only supported on some platforms; the apr_pollset_create() call will fail with APR_ENOTIMPL on platforms where it is not supported.
   If flags contains APR_POLLSET_WAKEABLE, then a pollset is created with an additional internal pipe object used for the apr_pollset_wakeup() call. The actual size of pollset is in that case size + 1. This feature is only supported on some platforms; the apr_pollset_create() call will fail with APR_ENOTIMPL on platforms where it is not supported.
   If flags contains APR_POLLSET_NOCOPY, then the apr_pollfd_t structures passed to apr_pollset_add() are not copied and must have a lifetime at least as long as the pollset.
   Some poll methods (including APR_POLLSET_KQUEUE, APR_POLLSET_PORT, and APR_POLLSET_EPOLL) do not have a fixed limit on the size of the pollset. For these methods, the size parameter controls the maximum number of descriptors that will be returned by a single call to apr_pollset_poll().
   */
}

long TTcp::Poll(){
  apr_status_t status;
  apr_int32_t num = 0;  // num will be how many different sockes have been signalled
  // const apr_pollfd_t *return_pfd;
  
  pollCount++;
  status = apr_pollset_poll( pollsetAprId, APR_DEF_POLL_TIMEOUT, &num, &pollsetReturn_pfd );
  
  if ( num > 0 ){
    // we don't care about other status values here!
    return num; // Number of signalled descriptors (output parameter)
  }
  
  if ( APR_STATUS_IS_TIMEUP(status) ) { 	// TIMEUP = 70007
    // for non-blocking sockets this is normal case
    status = 0; // no err
  } else if ( APR_STATUS_IS_EAGAIN(status) ) { // EAGAIN	= 730035 ?
    // typical for non-blobking sockets and specially for lookahead-calls
    // 730035 ="A non-blocking socket operation could not be completed immediately."
    status = 0; // no err
  }
  
	if ( status != APR_SUCCESS ) {
    status = -abs( status ); // return error as negative number
    char errbuf[255]; // why this is needed?, why not lastErrorText = apr_strerror (apr_status);
		errTxtSocket =  String( (long)status ) + L": ";
		errTxtSocket += plg_StringToWstring( apr_strerror (status, errbuf, sizeof (errbuf)) );
	}
  return status; 
  
  /* 
   apr_status_t apr_pollset_poll	(	apr_pollset_t * 	pollset,
   apr_interval_time_t 	timeout,
   apr_int32_t * 	num,
   const apr_pollfd_t ** 	descriptors 
   )		
   Block for activity on the descriptor(s) in a pollset
   
   Parameters:
   pollset	The pollset to use
   timeout	The amount of time in microseconds to wait. This is a maximum, not a minimum. If a descriptor is signalled, the function will return before this time. If timeout is negative, the function will block until a descriptor is signalled or until apr_pollset_wakeup() has been called.
   num	Number of signalled descriptors (output parameter)
   descriptors	Array of signalled descriptors (output parameter)
   Remarks:
   APR_EINTR will be returned if the pollset has been created with APR_POLLSET_WAKEABLE, apr_pollset_wakeup() has been called while waiting for activity, and there were no signalled descriptors at the time of the wakeup call.
   Multiple signalled conditions for the same descriptor may be reported in one or more returned apr_pollfd_t structures, depending on the implementation.
   Bug:
   With versions 1.4.2 and prior on Windows, a call with no descriptors and timeout will return immediately with the wrong error code.
   */
}
// --- end of pollset code ---


// --- socket code ---
long TTcp::GetOSSocket( long socketNum )
{
	long socketNative = -2;
	apr_socket_t *apr_socket_ptr = NULL;
  // apr_os_sock_t *os_sock = NULL;
	
	TServer *server = GetServerByRef( socketNum, L"" ); // no err here yet // "-MA_TCP_GetState"
	if ( server ){
		apr_socket_ptr = server->GetAprSocket(); 
	} else {
		TSocket *socket = GetSocketByRef( socketNum, L"-MA_TCP_GetOSSocket" ); // no error here
		if( socket == NULL ) {
			socketNative = -1; // err message has been set in GetSocketByRef
		} else {
			apr_socket_ptr = socket->GetAprSocket();			
		}
	}
	
	if( apr_socket_ptr ) {
		
		apr_os_sock_t os_socket = NULL; // typedef int  apr_os_sock_t;  /**< native dir */
		
		apr_status_t stat = apr_os_sock_get( &os_socket, apr_socket_ptr );
		/*
		 apr_status_t apr_os_sock_get( apr_os_sock_t *os_sock, apr_socket_t *apr_sock )
		 Convert the socket from an apr type to an OS specific socket
		 
		 Parameters:
		 thesock 	The socket to convert.
		 sock 	The os specifc equivelant of the apr socket..
     */		 
		
		if ( stat != APR_SUCCESS ) {
			//"Address resolution failed
			//SetErrorText( stat );
			return -abs( stat );
		}
		
		socketNative = (long)os_socket; // int --> long is ok
	}
	
	return socketNative;
}

long TTcp::GetStatus( long socketNum )
{
	long socketStatus = -2;
  
	TServer *server = GetServerByRef( socketNum, L"" ); // no err here yet // "-MA_TCP_GetState"
	if ( server ){
		socketStatus = server->GetListenStatus(); 
	} else {
		TSocket *socket = GetSocketByRef( socketNum, L"-MA_TCP_GetState" ); // no error here
		if( socket == NULL ) {
			socketStatus = -1; // err message has been set in GetSocketByRef
		} else {
      
			socketStatus = socket->GetStatus();
			if( socketStatus == SOCKET_STATUS_OPEN ){
				socketStatus = socket->TestStatus();
			}
			
		}
	}
	return socketStatus;
}

long TTcp::NewOutgoingConnectionOpen( wstring address, long port, wstring options )
{
	long returnValue = kNoErr;
	TSocket*  socket = NULL;
	
  
  // PM 2012-05-12, use always new socket id, loop back if not found
  if( nextOutConnectionId >= lastOutConnectionId ){ // loop over?
    lastOutConnectionId = FIRST_OUT_CONNECTION_ID; // will force of find always after first full loop
    nextOutConnectionId = FIRST_OUT_CONNECTION_ID;
    void* found = tcpSockets->findByCustomValue( nextOutConnectionId );
    while ( found && (nextOutConnectionId < LAST_OUT_CONNECTION_ID) ) {
      nextOutConnectionId++;
      found = tcpSockets->findByCustomValue( nextOutConnectionId );
    }
  }
  
  if( nextOutConnectionId >= LAST_OUT_CONNECTION_ID ){
    return -2; // error here
  } else {
    socket = new TSocket();							// create a new client socket
  }
  
	if( socket == NULL ) {
		returnValue = -1; // could not create a new socket
	} else {
		
    socket->SetConnectionId( nextOutConnectionId ); // default always to new index
    nextOutConnectionId++;
    
		returnValue = socket->Connect( address, port, options );
		if( returnValue != APR_SUCCESS ) {
			returnValue = -abs(returnValue);
			// we will delete the socket
			SocketClose( socket );
			delete socket;
      
		} else {
			returnValue = SocketAdd( socket, SOCKET_TYPE_CLIENT_OUT );
			if( returnValue >= 0)
				returnValue = socket->GetConnectionId();
		}
	}
  
	return returnValue;
}

long TTcp::NewIncomingConnection( TServer *server, wstring options )
{
	long						socketConnectionId = -10;
	TSocket*				socket = NULL;
	apr_socket_t*		new_apr_socket;
	
  
  new_apr_socket = server->NewIncomingSocket();
	if( new_apr_socket == NULL ) {
		return 0;	// no incoming connection, return NULL
	} else {
		
		socket = new TSocket();							// create a new socket
		if( socket ) {
      
      socket->SocketSet( new_apr_socket );	
      
      apr_status_t stat = socket->SocketOptionsSet( options, 0, (apr_int32_t)socket->DefaultTimeout() ); // MUST call again, not connect call any more
      if ( stat != APR_SUCCESS ) {
        // apr_socket_status = SOCKET_STATUS_NEEDS_CLOSE;	// apr_socket_close( apr_socket );
        SocketClose( socket );
        delete socket;
        return -100;
      }
      
      if( nextInConnectionId >= lastInConnectionId ){ // loop over?
        lastInConnectionId = FIRST_IN_CONNECTION_ID; // will force of find always after first full loop
        nextInConnectionId = FIRST_IN_CONNECTION_ID;
        void* found = tcpSockets->findByCustomValue( nextInConnectionId );
        while ( found && (nextInConnectionId < LAST_IN_CONNECTION_ID) ) {
          nextInConnectionId++;
          found = tcpSockets->findByCustomValue( nextInConnectionId );
        }
      }
      
      if( nextInConnectionId >= LAST_IN_CONNECTION_ID ){
        return -2; // error here
      }
      
      socket->SetConnectionId( nextInConnectionId ); // default always to new index
      nextInConnectionId++;
      socketConnectionId = SocketAdd( socket, SOCKET_TYPE_SERVER_ANSWERER );
      
		}
	}
	if( socket ) {
		socketConnectionId = socket->GetConnectionId();
	}
	return socketConnectionId;
}

/*
 socket_type_t TTcp::CurrentSocketType()
 {
 return currentSocketType;
 }
 */

long TTcp::SocketAdd( TSocket *socket, socket_type_t type )
{
	long index = socket->GetConnectionId();
	//if( index == -1 ){
	//	 index = -1; // err here!!!
	//}
  
	tcpSockets->add( socket, type, socket->GetConnectionId() );
	return index;
}


long TTcp::SocketClose( TSocket *socket )
{
	long index = tcpSockets->findIndex( socket );
	if( index >= 0 ) {
		tcpSockets->deleteAndSetFree( socket ); // = delete socket; // socket->Close();
	} else {
		index = -2; // err here!!!
	}
	currentSocket = NULL;
  currentSocketNum = 0;
	// currentSocketType = SOCKET_TYPE_UNKNOWN;
	return index;
}

TServer* TTcp::ServerAdd( wstring address, long portNum, wstring options )
{
	currentServer = NULL; // just to clean things up
  TServer* server;
  
  // PM 2012-05-12, use always new socket id, loop back if not found
  if( nextServerConnectionId >= lastServerConnectionId ){ // loop over?
    lastServerConnectionId = FIRST_SERVER_CONNECTION_ID; // will force of find always after first full loop
    nextServerConnectionId = FIRST_SERVER_CONNECTION_ID;
    void* found = tcpSockets->findByCustomValue( nextServerConnectionId );
    while ( found && (nextServerConnectionId < LAST_SERVER_CONNECTION_ID) ) {
      nextServerConnectionId++;
      found = tcpSockets->findByCustomValue( nextServerConnectionId );
    }
  }
  
  if( nextServerConnectionId >= LAST_SERVER_CONNECTION_ID ){
    return NULL; // error here
  } else{
    server = new TServer( address, portNum, options ); // SocketServer(portNum, 5); // port 80, 5 connections
  }
  
	if( server ) {
		long socketStatus = server->GetListenStatus();
		if( socketStatus != SOCKET_STATUS_LISTEN ) { 
			delete server;
			server = NULL;
		} else {
			TSocket* socket = server->GetListenSocket();
			
      socket->SetConnectionId( nextServerConnectionId ); // default always to new index
      nextServerConnectionId++;
      
			tcpServers->add( server, SOCKET_TYPE_SERVER_LISTENER, socket->GetConnectionId() );
		}
    
	}
	return server;
}

long TTcp::ServerDelete( TServer *server )
{
  
	/*
   long count = server->ConnectionCount();
   for( long idx = 0; idx < count; idx++ )
   { 
   TServerSocket *serverSocket = server->GetServerSocketAtIndex( idx );
   if( serverSocket )
   SocketClose( (TSocket *)serverSocket );
   }
   */
  
	long index = tcpServers->findIndex( server );
	if( index >= 0 )
		tcpServers->deleteAndSetFree( server ); // = delete server; // also closes connection
	else
		index = -2; // err here!!!
  
	// server->CloseConnection(); // this would close only accepted socket, continue listening
	currentServer = NULL;
	return index;
}

TSocket* TTcp::GetSocketByRef( long socketNum, wstring callingMethod )
{
	TSocket *socketFound;
	
	if ( currentSocketNum == socketNum ){
		return currentSocket; // quick optimization for usual case when instance is already right
	}
  
	// long idx = tcpSockets->findIndexByCustomValue( socketNum );
	// socketFound = tcpSockets->getNthElement( idx - 1 ); // this is 1 -based index, not 0 -based!!
	socketFound = tcpSockets->findByCustomValue( socketNum );
	if( socketFound ) {
		currentSocket = socketFound;
    currentSocketNum = socketNum;
		// currentSocketType = (socket_type_t) tcpSockets->getCustomType( socketFound );
		return socketFound; // return, instance is right now
	}
	
	/*
	 if( callingMethod.length() > 0 ) {
   if( callingMethod.substr(0,1) != L"-" ) { // we don't always want to return error
   plg_ErrMessage( L"-Invalid socket reference: " + String(socketNum), callingMethod );
   }
   }
	 */
	// errTxt = errTxt+ ksCR + L"Invalid socket reference: " + String(serverNum) + L" (GetSocketByRef)";
  
	return socketFound; // NULL
}

TServer* TTcp::GetServerByRef( long serverNum, wstring callingMethod )
{
	TServer *serverFound = NULL;
  
	if ( (long)currentServer == serverNum ){
		return currentServer; // quick optimization for usual case when instance is already right
	}
  
	// long idx = tcpServers->findIndexByCustomValue( serverNum );
	// serverFound = tcpServers->getNthElement( idx - 1 ); // this is 1 -based index, not 0 -based!!
	serverFound = tcpServers->findByCustomValue( serverNum );
	if( serverFound ) {
		currentServer = serverFound;
		return serverFound; // return, instance is right now
	}
  
	/*
   if( callingMethod.length() > 0 ) {
   if( callingMethod.substr(0,1) != L"-" ) { // we don't always want to return error
   plg_ErrMessage( L"-Invalid server reference: " + String(serverNum), callingMethod );
   }
   }
	 */
	// errTxt = errTxt+ ksCR + L"Invalid server socket reference: " + String(serverNum) + L" (GetServerByRef)";
	
	return serverFound; // NULL
}
