/* OpenDoors Online Software Programming Toolkit
 * (C) Copyright 1991 - 1999 by Brian Pirie.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *
 *        File: ODInQue.h
 *
 * Description: OpenDoors input queue management. This input queue is where
 *              all input events (e.g. keystrokes) from both local and remote
 *              systems are combined into a single stream.
 *
 *   Revisions: Date          Ver   Who  Change
 *              ---------------------------------------------------------------
 *              Nov 16, 1995  6.00  BP   Created.
 *              Nov 17, 1995  6.00  BP   Added multithreading support.
 *              Jan 04, 1996  6.00  BP   tODInQueueEvent -> tODInputEvent.
 *              Jan 30, 1996  6.00  BP   Replaced od_yield() with od_sleep().
 *              Jan 30, 1996  6.00  BP   Add semaphore timeout.
 *              Jan 30, 1996  6.00  BP   Add ODInQueueGetNextEvent() timeout.
 *              Feb 19, 1996  6.00  BP   Changed version number to 6.00.
 *              Mar 03, 1996  6.10  BP   Begin version 6.10.
 *              Aug 10, 2003  6.23  SH   *nix support
 */

#define BUILDING_OPENDOORS

#include <stdlib.h>
#include <string.h>

#include "OpenDoor.h"
#include "ODGen.h"
#include "ODInQue.h"
#include "ODPlat.h"
#include "ODKrnl.h"


/* Input queue handle structure. */
typedef struct
{
   tODInputEvent *paEvents;
   INT nQueueEntries;
   INT nInIndex;
   INT nOutIndex;
   time_t nLastActivityTime;
#ifdef OD_MULTITHREADED
   tODSemaphoreHandle hItemCountSemaphore;
   tODSemaphoreHandle hAddEventSemaphore;
#endif /* OD_MULTITHREADED */
} tInputQueueInfo;


/* ----------------------------------------------------------------------------
 * ODInQueueAlloc()
 *
 * Allocates a new input queue.
 *
 * Parameters: phInQueue         - Pointer to location where a handle to the
 *                                 newly allocated input queue should be
 *                                 stored.
 *
 *             nInitialQueueSize - The minimum number of events that the
 *                                 input queue should be able to hold.
 *
 *     Return: kODRCSuccess on success, or an error code on failure.
 */
tODResult ODInQueueAlloc(tODInQueueHandle *phInQueue, INT nInitialQueueSize)
{
   tInputQueueInfo *pInputQueueInfo = NULL;
   tODInputEvent *pInputQueue = NULL;
   tODResult Result = kODRCNoMemory;

   ASSERT(phInQueue != NULL);

   if(phInQueue == NULL) return(kODRCInvalidCall);

   /* Attempt to allocate a serial port information structure. */
   pInputQueueInfo = malloc(sizeof(tInputQueueInfo));

   /* If memory allocation failed, return with failure. */
   if(pInputQueueInfo == NULL) goto CleanUp;

   /* Initialize semaphore handles to NULL. */
#ifdef OD_MULTITHREADED
   pInputQueueInfo->hItemCountSemaphore = NULL;
   pInputQueueInfo->hAddEventSemaphore = NULL;
#endif /* OD_MULTITHREADED */
   
   /* Attempt to allocate space for the queue itself. */
   pInputQueue = calloc(nInitialQueueSize, sizeof(tODInputEvent));
   if(pInputQueue == NULL) goto CleanUp;

   /* Create semaphores if this is a multithreaded platform. */
#ifdef OD_MULTITHREADED
   if(ODSemaphoreAlloc(&pInputQueueInfo->hItemCountSemaphore, 0,
      nInitialQueueSize) != kODRCSuccess)
   {
      goto CleanUp;
   }

   if(ODSemaphoreAlloc(&pInputQueueInfo->hAddEventSemaphore, 1, 1)
      != kODRCSuccess)
   {
      goto CleanUp;
   }
#endif /* OD_MULTITHREADED */

   /* Initialize input queue information structure. */
   pInputQueueInfo->paEvents = pInputQueue;
   pInputQueueInfo->nQueueEntries = nInitialQueueSize;
   pInputQueueInfo->nInIndex = 0;
   pInputQueueInfo->nOutIndex = 0;

   /* Convert intut queue information structure pointer to a handle. */
   *phInQueue = ODPTR2HANDLE(pInputQueueInfo, tInputQueueInfo);

   /* Reset the time of the last activity. */
   ODInQueueResetLastActivity(*phInQueue);

   Result = kODRCSuccess;

CleanUp:
   if(Result != kODRCSuccess)
   {
#ifdef OD_MULTITHREADED
      if(pInputQueueInfo != NULL
         && pInputQueueInfo->hItemCountSemaphore != NULL)
      {
         ODSemaphoreFree(pInputQueueInfo->hItemCountSemaphore);
      }

      if(pInputQueueInfo != NULL
         && pInputQueueInfo->hAddEventSemaphore != NULL)
      {
         ODSemaphoreFree(pInputQueueInfo->hAddEventSemaphore);
      }
#endif /* OD_MULTITHREADED */

      if(pInputQueue != NULL) free(pInputQueue);
      if(pInputQueueInfo != NULL) free(pInputQueueInfo);
      *phInQueue = ODPTR2HANDLE(NULL, tInputQueueInfo);
   }

   /* Return with the appropriate result code. */
   return(Result);
}


/* ----------------------------------------------------------------------------
 * ODInQueueFree()
 *
 * Destroys an input queue that was previously created by ODInQueueAlloc().
 *
 * Parameters: hInQueue - Handle to the input queue to destroy.
 *
 *     Return: void
 */
void ODInQueueFree(tODInQueueHandle hInQueue)
{
   tInputQueueInfo *pInputQueueInfo = ODHANDLE2PTR(hInQueue, tInputQueueInfo);

   ASSERT(pInputQueueInfo != NULL);

   /* Deallocate semaphores, if appropriate. */
#ifdef OD_MULTITHREADED
   ASSERT(pInputQueueInfo->hItemCountSemaphore != NULL);
   ODSemaphoreFree(pInputQueueInfo->hItemCountSemaphore);
#endif /* OD_MULTITHREADED */

   /* Deallocate the input queue itself. */
   ASSERT(pInputQueueInfo->paEvents != NULL);
   free(pInputQueueInfo->paEvents);

   /* Deallocate port information structure. */
   free(pInputQueueInfo);
}


/* ----------------------------------------------------------------------------
 * ODInQueueWaiting()
 *
 * Determines whether or not an event is currently waiting in the input queue.
 *
 * Parameters: hInQueue - Handle to the input queue to check.
 *
 *     Return: TRUE if there is one or more waiting events, or FALSE if the
 *             queue is empty.
 */
BOOL ODInQueueWaiting(tODInQueueHandle hInQueue)
{
   tInputQueueInfo *pInputQueueInfo = ODHANDLE2PTR(hInQueue, tInputQueueInfo);
   BOOL bEventWaiting;

   ASSERT(pInputQueueInfo != NULL);

   /* There is data waiting in the queue if the in index is not equal to */
   /* the out index.                                                     */
   bEventWaiting = (pInputQueueInfo->nInIndex != pInputQueueInfo->nOutIndex);

   return(bEventWaiting);
}


/* ----------------------------------------------------------------------------
 * ODInQueueAddEvent()
 *
 * Adds a new event to the input queue.
 *
 * Parameters: hInQueue  - Handle to the input queue to add an event to.
 *
 *             pEvent    - Pointer to the event structure to obtain the
 *                         event information from.
 *
 *     Return: kODRCSuccess on success, or an error code on failure.
 */
tODResult ODInQueueAddEvent(tODInQueueHandle hInQueue,
   tODInputEvent *pEvent)
{
   tInputQueueInfo *pInputQueueInfo = ODHANDLE2PTR(hInQueue, tInputQueueInfo);
   INT nNextInPos;

   ASSERT(pInputQueueInfo != NULL);
   ASSERT(pEvent != NULL);
   if(pInputQueueInfo == NULL || pEvent == NULL) return(kODRCInvalidCall);

   /* Serialize access to add event function. */
#ifdef OD_MULTITHREADED
   ODSemaphoreDown(pInputQueueInfo->hAddEventSemaphore, OD_NO_TIMEOUT);
#endif /* OD_MULTITHREADED */

   /* Reset the time of the last activity. */
   ODInQueueResetLastActivity(hInQueue);

   /* Determine what the next in index would be after this addition to the */
   /* queue.                                                               */
   nNextInPos = (pInputQueueInfo->nInIndex + 1)
      % pInputQueueInfo->nQueueEntries;

   /* If the queue is full, then return an out of space error. */
   if(nNextInPos == pInputQueueInfo->nOutIndex)
   {
      /* Allow further access to input queue. */
#ifdef OD_MULTITHREADED
      ODSemaphoreUp(pInputQueueInfo->hAddEventSemaphore, 1);
#endif /* OD_MULTITHREADED */

      return(kODRCNoMemory);
   }

   /* Otherwise, add the new event to the input queue. */
   memcpy(&pInputQueueInfo->paEvents[pInputQueueInfo->nInIndex], pEvent,
      sizeof(tODInputEvent));

   /* Update queue in index. */
   pInputQueueInfo->nInIndex = nNextInPos;

   /* Increment queue items count semaphore. */
#ifdef OD_MULTITHREADED
   ODSemaphoreUp(pInputQueueInfo->hItemCountSemaphore, 1);
#endif /* OD_MULTITHREADED */

   /* Allow further access to add event function. */
#ifdef OD_MULTITHREADED
   ODSemaphoreUp(pInputQueueInfo->hAddEventSemaphore, 1);
#endif /* OD_MULTITHREADED */

   return(kODRCSuccess);
}


/* ----------------------------------------------------------------------------
 * ODInQueueGetNextEvent()
 *
 * Obtains the next event from the input queue. If no events are currently
 * waiting in the input queue, this function blocks until an item is added
 * to the queue, or the maximum wait time is reached.
 *
 * Parameters: hInQueue - Handle to the input queue to obtain the next event
 *                        from.
 *
 *             pEvent   - Pointer to structure to store input event information
 *                        in.
 *
 *             Timeout  - Maximum time, in milliseconds, to wait for next input
 *                        event. A value of OD_NO_TIMEOUT causes this function
 *                        to only return when an input event is obtained.
 *
 *     Return: kODRCSuccess on succes, or kODRCTimeout if the maximum wait time
 *             is exceeded.
 */
tODResult ODInQueueGetNextEvent(tODInQueueHandle hInQueue,
   tODInputEvent *pEvent, tODMilliSec Timeout)
{
   tInputQueueInfo *pInputQueueInfo = ODHANDLE2PTR(hInQueue, tInputQueueInfo);

   ASSERT(pInputQueueInfo != NULL);
   ASSERT(pEvent != NULL);

#ifdef OD_MULTITHREADED

   /* In multithreaded implementations, we wait for there to be an item in  */
   /* the queue by decrementing the queue size semaphore. This will cause   */
   /* this thread to be blocked until an event is added to the queue, if it */
   /* is currently empty.                                                   */

   if(ODSemaphoreDown(pInputQueueInfo->hItemCountSemaphore, Timeout)==kODRCTimeout)
      return(kODRCTimeout);

#else /* !OD_MULTITHREADED */

   /* In non-multithreaded implementations, we check queue in and out     */
   /* indicies to determine whether there are any events waiting in the   */
   /* queue. If the queue is empty we loop, calling od_kernel() to check  */
   /* for new events and od_yeild() to give more time to other processors */
   /* if there is nothing for us to do, until an event is added to the    */
   /* queue.                                                              */
   if(pInputQueueInfo->nInIndex == pInputQueueInfo->nOutIndex)
   {
      tODTimer Timer;

      /* If a timeout has been specified, then start timer to keep track */
      /* of how long we have been waiting.                               */
      if(Timeout != 0 && Timeout != OD_NO_TIMEOUT)
      {
         ODTimerStart(&Timer, Timeout);
      }

      /* As soon as we see that there is nothing in the queue, we do an */
      /* od_kernel() call to check for new input.                       */
      CALL_KERNEL_IF_NEEDED();

      /* As long as we don't have new input, we loop, yielding to other */
      /* processes, and then giving od_kernel() a chance to run.        */
      while(pInputQueueInfo->nInIndex == pInputQueueInfo->nOutIndex)
      {
         /* If a timeout has been specified, then ensure that the maximum */
         /* wait time has not elapsed.                                    */
         if(Timeout != 0 && Timeout != OD_NO_TIMEOUT
            && ODTimerElapsed(&Timer))
         {
            return(kODRCTimeout);
         }

         /* Yield the processor to other tasks. */
         od_sleep(0);

         /* Call od_kernel(). */
         CALL_KERNEL_IF_NEEDED();
      }
   }

#endif /* !OD_MULTITHREADED */

   /* Copy next input event from the queue into the caller's structure. */
   memcpy(pEvent, &pInputQueueInfo->paEvents[pInputQueueInfo->nOutIndex],
      sizeof(tODInputEvent));

   /* Move out pointer to the next queue item, wrapping back to the start */
   /* of the queue if needed.                                             */
   pInputQueueInfo->nOutIndex
      = (pInputQueueInfo->nOutIndex + 1) % pInputQueueInfo->nQueueEntries;

   /* Now, return with success. */
   return(kODRCSuccess);
}


/* ----------------------------------------------------------------------------
 * ODInQueueEmpty()
 *
 * Removes all events from the input queue.
 *
 * Parameters: hInQueue - Handle to the input queue to be emptied.
 *
 *     Return: void
 */
void ODInQueueEmpty(tODInQueueHandle hInQueue)
{
   tODInputEvent InputEvent;

   ASSERT(hInQueue != NULL);

   /* Remove all items from the queue. */
   while(ODInQueueWaiting(hInQueue))
   {
      ODInQueueGetNextEvent(hInQueue, &InputEvent, OD_NO_TIMEOUT);
   }
}


/* ----------------------------------------------------------------------------
 * ODInQueueGetLastActivity()
 *
 * Returns the time of the last input activity. This is the latest of the time
 * that the queue was created, the time of the last call to
 * ODInQueueAddEvent() on this input queue, and the time of the last call to
 * ODInQueueResetLastActivity() on this input queue.
 *
 * Parameters: hInQueue - Handle to the input queue.
 *
 *     Return: void
 */
time_t ODInQueueGetLastActivity(tODInQueueHandle hInQueue)
{
   tInputQueueInfo *pInputQueueInfo = ODHANDLE2PTR(hInQueue, tInputQueueInfo);

   ASSERT(pInputQueueInfo != NULL);

   /* Returns the last activity time. */
   return(pInputQueueInfo->nLastActivityTime);
}


/* ----------------------------------------------------------------------------
 * ODInQueueResetLastActivity()
 *
 * Resets the time of the last input activity to the current time.
 *
 * Parameters: hInQueue - Handle to the input queue.
 *
 *     Return: void
 */
void ODInQueueResetLastActivity(tODInQueueHandle hInQueue)
{
   tInputQueueInfo *pInputQueueInfo = ODHANDLE2PTR(hInQueue, tInputQueueInfo);

   ASSERT(pInputQueueInfo != NULL);

   /* Resets the last activity time to the current time. */
   pInputQueueInfo->nLastActivityTime = time(NULL);
}