/* Agent Tcl
   Bob Gray
   4 January 1998

   genAgentIO.cc

   This file implements the AgentIO class which handles the background 
   connection.
*/

#include "platPorting.h"		
#include "platDelete.h"		// DELETE_OBJECT and DELETE_ARRAY_OBJECT
#include "platCharact.h"	// PlatformCharacteristics
#include "platExclusion.h"	// Guard and MonitorLock
#include "genAgent.h"		// AGENT
#include "genAgentConn.h"	// AgentConnection
#include "genAgentIO.h"		// AgentIO
#include "genLocation.h"	// LOCATION
#include "genManager.h"		// RestrictMgr
#include "genMasks.h"		// AgentMask
#include "genRestrict.h"	// RESTRICT

#ifdef FIX_LATER

/*
 * This code will break badly if multiple receiver threads are calling into it
 * at the same time (e.g., m_queuedItem is a BOOLEAN flag, so if we have two
 * receiving threads and two incoming items, one receiving thread might think
 * that there are no new items.  Note that we are not yet sure if we are
 * actually going to allow an agent to have multiple receiving threads,
 * so this is not much of an issue yet.
 */

#endif

/* AgentIO::cpuAlarm
 
   Purpose: This procedure is called if there is a potential violation of the
	    cpu-time limit.

    Output: The procedure wakes up anyone who is waiting on the monitor.
*/

void AgentIO::cpuAlarm (void) 
{
    MonitorLock &monitor = m_agent -> getMonitorLock();
    Guard guard (monitor);
 
    if (m_waiters) {
	monitor.wakeup ();
    }

    m_potentialLimitViolation = e_TRUE;
}

/* AgentIO::wallAlarm
 
   Purpose: This procedure is called if there is a potential violation of the
	    wall-time limit.

    Output: The procedure wakes up anyone who is waiting on the monitor.
*/

void AgentIO::wallAlarm (void) 
{
    MonitorLock &monitor = m_agent -> getMonitorLock();
    Guard guard (monitor);

    if (m_waiters) {
	monitor.wakeup ();
    }

    m_potentialLimitViolation = e_TRUE;
}

/* AgentIO::connectionAlarm

   Purpose: This procedure is called when there is an incoming item on the
	    background connection.

     Input: connection = the background connection
		         (Connection &)

	    status     = OR'ed combination of READ_READY, WRITE_READY and EXCEPT_READY
		         (Connection::ConnectionStatus)

    Output: The procedure reads the incoming item and takes the appropriate
	    action.
*/

void AgentIO::connectionAlarm (Connection &, Connection::ConnectionStatus status)
{
    int code;
    MESSAGE *item;

	// NOTE -- we don't need to acquire the "monitor" since it was 
	// acquired by the lower-level routines that made this callback

	// NOTE -- We know that this routine is only called when the
	// Tcl interpreter is in a safe state since the SigioHandler
	// class calls up to the Tcl interpreter level.

    assert (m_connection != NULL);

        // nothing to do if it is not a read event

    if (status != Connection::e_READ_READY) {
	return;
    }

	// get the AgentMasks instance

    AgentMasks *masks = m_agent -> get_masks();
    assert (masks != NULL);

	// receive the incoming item

    if ((item = m_connection -> unsecureReceiveAny (messagesFromServer)) == NULL) {

	code = AGENT_LOST_BACKGROUND;

    } else if ((masks -> handleIncoming_noLock (item)) != MASK_OK) {

	code = AGENT_LOST_BACKGROUND;
	DELETE_OBJECT(item);

    } else {

	code = AGENT_OK;
	DELETE_OBJECT(item);
    }

	// wake up any waiters if we have a connection error or an 
	// incoming item

    if ((code != AGENT_OK) || (m_queuedItem)) {
	if (m_waiters) {
	    MonitorLock &monitor = m_agent -> getMonitorLock();
	    monitor.wakeup ();
	}
    }

	// turn off the background handler and invoke the broken connection
	// callback on error

    if (code != AGENT_OK) {

	turnBackgroundOff ();

#ifdef FIX_LATER
	    /* must invoke a "broken connection" callback */
#endif
    }
}

/* AgentIO::sleep

   Purpose: Sleep for a given number of seconds 

     Input: delay = sleep period
		    (struct timeval)

    Output: The procedure returns one of the following codes.

	    AGENT_TIMEOUT         = a permit violation has occurred
	    AGENT_LOST_BACKGROUND = background connection has been lost 
*/

int AgentIO::sleep (struct timeval delay)
{
    int code;

	/* guard the access (lock is automatically released) */

    MonitorLock &monitor = m_agent -> getMonitorLock();
    Guard guard (monitor);

	/* get RESTRICT and LOCATION */

    RESTRICT *restrict = m_agent -> get_restrict ();
    LOCATION *location = m_agent -> get_location ();
    assert (restrict != NULL);
    assert (location != NULL);

	/* set the timeout */

    Permit permit;
    permit.setWallLimit (delay);
    RestrictMgr restrictMgr (*restrict, permit, this);

	/* sleep until permit violation */

    while (1) {

	    /* if we are *not* multi-threaded, SIGIO interrupts are */
	    /* not handled automatically.                           */
	   
#ifndef MULTIPLE_THREADS
	if (SigioHandler::hasSigioFired()) {
	    SigioHandler::checkAndCallTraps ();
	}
#endif

	    /* check for lost background connection */

	if (m_connection == NULL) {
	    if (location -> isRegistered()) {
		code = AGENT_LOST_BACKGROUND;
		break;
	    }
	}

	    /* check for permit violation */

	if (m_potentialLimitViolation) {

	    m_potentialLimitViolation = e_FALSE;

	    if (restrict -> checkPermits ()) {
		code = AGENT_TIMEOUT;
		break;
	    }
	}

	    /* sleep */

	m_waiters += 1;
	monitor.wait();
	m_waiters -= 1;
    }

	/* done */

    return (code);
}

/* AgentIO::receiveIncomingItem

   Purpose: Wait for an incoming message, event or meeting request

     Input: which    = MESSAGE_ITEM, MEETING_ITEM or EVENT_ITEM
		       (int)

	    delay    = maximum time to wait for the incoming item
		       (struct timeval)

    Output: The procedure returns one of the following codes.

	    AGENT_OK              = success
            AGENT_TIMEOUT         = permit violation occurred
	    AGENT_LOST_BACKGROUND = background handler has failed

	    On success

	    item = pointer to a dyanmically allocated INCOMING object
		   (class INCOMING *&)
*/

int AgentIO::receiveIncomingItem (int which, INCOMING *&item, struct timeval delay)
{
    int code;
    BOOLEAN first = e_TRUE;

	/* assertions on the parameters */

    assert ((which == MESSAGE_ITEM) || (which == EVENT_ITEM) || (which == MEETING_ITEM));

	/* assume that there is no incoming item */

    item = NULL;

	/* guard access (lock is automatically released) */

    MonitorLock &monitor = m_agent -> getMonitorLock();
    Guard guard (monitor);

	/* get the RESTRICT and AgentMasks instances */

    RESTRICT   *restrict = m_agent -> get_restrict();
    AgentMasks *masks    = m_agent -> get_masks();
    assert (restrict != NULL);
    assert (masks != NULL);

	/* add the timeout handler */

    Permit permit;
    permit.setWallLimit (delay);
    RestrictMgr restrictMgr (*restrict, permit, (TimerTrap *) this);

	/* loop since we may be blocking */

    while (1) {

	    /* if we are *not* multi-threaded, SIGIO interrupts are */
	    /* not handled automatically                            */

#ifndef MULTIPLE_THREADS
	if (SigioHandler::hasSigioFired()) {
	    SigioHandler::checkAndCallTraps ();
	}
#endif
	
	    /* see if background connection has broken */

	if (m_connection == NULL) {
	    code = AGENT_LOST_BACKGROUND;
	    break;
	}

	    /* check every item in the queue */

	if (first || m_queuedItem) {
	    first        = e_FALSE;
	    m_queuedItem = e_FALSE;
	    item         = masks -> getIncoming_noLock (which);
	}

	    /* done if we have an item */

	if (item != NULL) {
	    code = MASK_OK;
	    break;
	}

	    /* check for permit violation */

	if (m_potentialLimitViolation) {

	    m_potentialLimitViolation = e_FALSE;

	    if (restrict -> checkPermits()) {
		code = MASK_TIMEOUT;
		break;
	    }
	}

	    /* wait for timeout or arrival */

	m_waiters += 1;
	monitor.wait();
	m_waiters -= 1;
    }

	/* cleanup */ 

    return (code);
}

/* AgentIO::simpleSelect_noLock 

   Purpose: Wait for a meeting status change (including an incoming message
	    or file on the meeting)

     Input: meeting = the meeting
		      (MEETING &)

	    delay   = timeout
		      (struct timeval)

    Output: The procedure returns one of the following codes.

	    AGENT_OK              = success
	    AGENT_LOST_BACKGROUND = lost the background connection
	    AGENT_TIMEOUT	  = timeout has occurred

	    On success

	    meetingStatus = meeting's new status 
			    (UINT_8 *)

      Note: The procedure assumes that the lock has already been acquired.
*/ 

int AgentIO::simpleSelect_noLock (MEETING &meeting, UINT_8 &meetingStatus, struct timeval delay)
{
    int code;

	/* get the RESTRICT instance */

    RESTRICT *restrict = m_agent -> get_restrict();
    assert (restrict != NULL); 

	/* add the timeout handler */

    Permit permit;
    permit.setWallLimit (delay);
    RestrictMgr restrictMgr (*restrict, permit, (TimerTrap *) this);

	/* loop since we may be blocking */

    while (1) {

	    /* if we are *not* multi-threaded, SIGIO interrupts */
	    /* are not handled automatically                    */

#ifdef FIX_LATER
	    /* 
	     * On Linux machines the SIGIO is delivered too soon when the  
	     * socket is waiting for a connection (i.e., if the meeting is in
	     * the MEET_ACCEPTING state).  Specifically "select" reports 
	     * that the socket is NOT ready for a few moments after the SIGIO 
	     * is delivered).  Thus we must poll the socket if the meeting is 
	     * in the MEET_ACCEPTING state.
	     *
	     * Things to fix:
	     *
	     * 1. Find a better solution
	     * 2. Move the solution into a lower level
             * 3. Set compilation flags so that the solution is only used on
	     *    Linux machines
	     */
#endif

	BOOLEAN stupidPolling = e_FALSE;

#ifndef MULTIPLE_THREADS
 
	if (meeting.status == MEET_ACCEPTING) {
	    stupidPolling = e_TRUE;
	}

	if (SigioHandler::hasSigioFired() || stupidPolling) {
	    SigioHandler::checkAndCallTraps();
	}

#endif

	    /* see if the background connection has broken */

	if (m_connection == NULL) {
	    code = AGENT_LOST_BACKGROUND;
	    break;
	}

	    /* see if the meeting has changed status */

	if (meeting.status != meeting.lastStatus) {
	    meeting.lastStatus = meeting.status;
	    meetingStatus = meeting.status;
	    code = MEET_OK;
	    break;
	}

	if (meeting.fileReady) {
	    meetingStatus = MEET_FILE_READY;
	    code = MEET_OK;
	    break;
	}

	if (meeting.messageReady) {
	    meetingStatus = MEET_MESSAGE_READY;
	    code = MEET_OK;
	    break;
	}
	
	    /* check for permit violation */

	if (m_potentialLimitViolation) {

	    m_potentialLimitViolation = e_FALSE;

	    if (restrict -> checkPermits ()) {
		code = AGENT_TIMEOUT;
		break;
            }
	}

	    /* wait for something to happen -- note that adding the permit */
	    /* could cause m_potentialLimitViolation to become true        */
	    /* immediately                                                 */

	if (stupidPolling) {
	    struct timeval shortDelay = {0, 1000000};
	    Permit shortPermit;
	    shortPermit.setWallLimit (shortDelay);
	    restrict -> addPermit (shortPermit, this);
	}

	if (!m_potentialLimitViolation) {
	    MonitorLock &monitor = m_agent -> getMonitorLock();
	    m_waiters += 1;
	    monitor.wait();
	    m_waiters -= 1;
	}

	if (stupidPolling) {
	    restrict -> removePermit();
	}
    }

	/* done */

    return (code);
}

/* AgentMeetings::select

   Purpose: Wait for a meeting status change (including an incoming message 
	    or file on the meeting) --OR-- an incoming message, event or 
	    meeting request

     Input: delay        = timeout
                           (struct timeval)

            selectParams = items, handles and fds to watch
                           (AgentSelectParameters &)

    Output: The procedure returns one of the following codes.
 
	    AGENT_OK              = success
	    AGENT_BAD_MEETING     = one or more of the meetings do not exist
	    AGENT_LOST_BACKGROUND = lost the background connection
	    AGENT_TIMEOUT	  = timeout has occurred 

	    On success, the procedure fills in selectParams to indicate
            which items, handles and fds were actually ready.
*/

int AgentIO::select 
	(struct timeval delay, AgentSelectParameters &selectParams)
{
    int rc;
    UINT_32 i;
    int readyCount;
    BOOLEAN first = e_TRUE;
    MEETING **meetList = NULL;

	// mutual exclusion

    MonitorLock &monitor = m_agent -> getMonitorLock();
    Guard guard (monitor);

	// get the AgentMeetings, AgentMasks and RESTRICT instances

    RESTRICT *restrict = m_agent -> get_restrict ();
    AgentMasks *masks = m_agent -> get_masks ();
    AgentMeetings *meetings = m_agent -> get_meetings ();
    assert (restrict != NULL);
    assert (masks != NULL);
    assert (meetings != NULL);

	// add the permit

    Permit permit;
    permit.setWallLimit (delay);
    RestrictMgr restrictMgr (*restrict, permit, this);

	// make sure that all the output flags are set to "NO CHANGE" --
	// This is not stricly necessary since the constructor and support
	// methods for AgentSelectParameters initialize the output flags 
	// correctly, but it avoids weirdness due to any usage error in the
	// client code (i.e., a client re-using an AgentSelectParameters
	// instance over and over).

    selectParams.resetOutputFlags();
    
	// find the MEETING that goes with each handle and assume that no
	// meetings are ready

    UINT_32 numHandles = selectParams.getNumHandles();

    if (numHandles != 0) {

	meetList = new MEETING * [numHandles];

	for (i = 0; i < numHandles; i++) {

	    UINT_32 handle = selectParams.getHandle(i);

	    if ((meetList[i] = meetings -> lookup_noLock (handle)) == NULL) {
		selectParams.setHandleStatus (i, MEET_DOES_NOT_EXIST);
		DELETE_ARRAY_OBJECT(meetList);
		return AGENT_BAD_MEETING;
	    }
	}
    }

	/* loop since we may be blocking */

    while (1) {

	    /* if we are *not* multi-threaded, SIGIO interrupts */
	    /* are not handled automatically                    */ 

#ifdef FIX_LATER
	    /* 
	     * On Linux machines the SIGIO is delivered too soon when the  
	     * socket is waiting for a connection (i.e., if the meeting is in
	     * the MEET_ACCEPTING state).  Specifically "select" reports 
	     * that the socket is NOT ready for a few moments after the SIGIO 
	     * is delivered).  Thus we must poll the socket if the meeting is 
	     * in the MEET_ACCEPTING state.
	     *
	     * Things to fix:
	     *
	     * 1. Find a better solution
	     * 2. Move the solution into a lower level
             * 3. Set compilation flags so that the solution is only used on
	     *    Linux machines
	     */
#endif

	BOOLEAN stupidPolling = e_FALSE;

#ifndef MULTIPLE_THREADS

	for (i = 0; i < numHandles; i++) {
	    if (meetList[i] -> status == MEET_ACCEPTING) {
		stupidPolling = e_TRUE;
		break;
	    }
	}
	
	if (SigioHandler::hasSigioFired()) {
	    SigioHandler::checkAndCallTraps ();
	}

#endif

	    /* see if the background connection has broken */

	if (m_connection == NULL) {
	    rc = AGENT_LOST_BACKGROUND;
	    break;
	}

	    /* see if there are any ready things -- INEFFICIENT! */

	readyCount = 0;

	if (first || m_queuedItem) {

	    m_queuedItem = e_FALSE;
	   
	    for (int which = 0; which < NUM_ITEMS; which++) {

		if (selectParams.areWatchingItem(which)) {
		    if (masks -> checkIncoming_noLock (which)) {
			selectParams.markItemReady(which);
			readyCount += 1;
		    }
		}
	    }
	}

	for (i = 0; i < numHandles; i++) {

	    MEETING *meeting = meetList[i];

	    if (meeting -> status != meeting -> lastStatus) {

		meeting -> lastStatus = meeting -> status;
		selectParams.setHandleStatus (i, meeting->status);
		readyCount += 1;

	    } else if (meeting -> fileReady) {

		selectParams.setHandleStatus (i, MEET_FILE_READY);
		readyCount += 1;

	    } else if (meeting -> messageReady) {

		selectParams.setHandleStatus (i, MEET_MESSAGE_READY);
		readyCount += 1;
	    }
  	}
	
	first = e_FALSE;

	    /* done if we have ready things */

	if (readyCount > 0) {
	    rc = AGENT_OK;
	    break;
	}

	    /* check for permit violation */

	if (m_potentialLimitViolation) {

	    m_potentialLimitViolation = e_FALSE;

	    if (restrict -> checkPermits ()) {
		rc = AGENT_TIMEOUT;
		break;
            }
	}

	    /* wait for something to happen -- note that adding the permit  */
	    /* could cause m_potentialLimitViolation to immediately to true */

	if (stupidPolling) {
	    struct timeval shortDelay = {0, 1000000};
	    Permit shortPermit;
	    shortPermit.setWallLimit (shortDelay);
	    restrict -> addPermit (shortPermit, this);
	}

	if (!m_potentialLimitViolation) {
	    m_waiters += 1;
	    monitor.wait();
	    m_waiters -= 1;
	}

	if (stupidPolling) {
	    restrict -> removePermit();
	}
    }

	/* done */

    DELETE_ARRAY_OBJECT(meetList);
    return (rc);
}

/* AgentIO::turnBackgroundOn

   Purpose: Turn on the background connection 

     Input: connection = background connection to the server
			 (class AgentConnection *) 
 
      Note: The procedure assumes ownership of the connection which should
	    be dynamically allocated.  
*/

void AgentIO::turnBackgroundOn (AgentConnection *connection)
{
	/* assertions on the parameters */

    assert (connection != NULL);

	/* guard the access (lock is automatically released) */

    MonitorLock &monitor = m_agent -> getMonitorLock();
    Guard guard (monitor);

	/* turn on asynchonous I/O for the connection */

    m_connection = connection;
    m_connection -> makeBackground (this, MonitorGroup(monitor));
}

/* AgentIO::turnBackgroundOff

   Purpose: Turn off the background handler.
*/

void AgentIO::turnBackgroundOff (void)
{
	/* guard the access (lock is automatically released) */

    Guard guard (m_agent -> getMonitorLock());

	/* destroy the connection if it is still active */

    if (m_connection != NULL) {
	DELETE_OBJECT(m_connection);
    }
}

/* AgentIO::AgentIO and AgentIO::AgentIO
*/

AgentIO::AgentIO (AGENT *agent):
    m_agent (agent),
    m_connection (NULL),
    m_potentialLimitViolation (e_FALSE),
    m_waiters (0),
    m_queuedItem (e_FALSE)
{
    // empty
}

AgentIO::~AgentIO ()
{
    turnBackgroundOff();
}
