/* File:  util.c */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <curses.h>
#endif /* _WIN32 */

#include "ar.h"
#include "get.h"
#include "globals.h"
#include "main.h"
#include "util.h"
#include "print.h"


void BeginAPICall();
void EndAPICall();

#ifndef OUTPUT_MODE
void ProcessResultFile()
{
   /* perform any generic result operations here */
}

ARBoolean ProcessCommentLine(commentLine)
char *commentLine;  /* IN/OUT; comment line */
{
   /* perform any generic comment processing */

   return (TRUE);
}
#else

void ProcessResultFile();
ARBoolean ProcessCommentLine();
#endif

int ARTermination (ARControlStruct *, ARStatusList *);

/*****************************************************************************/
/*                                                                           */
/*                                GetInputLine                               */
/*                                                                           */
/*****************************************************************************/

void GetInputLine()

{
   int                 bufLen; /* length of the buffer */
   ThreadControlBlock *threadControlBlockPtr; /* control block pointer */
   ARStatusList        status = {0, NULL};

   threadControlBlockPtr = GetThreadControlBlockPtr();

   if (fgets(threadControlBlockPtr->buffer, 1024,
             threadControlBlockPtr->inFile[threadControlBlockPtr->currentInputDepth]) == NULL)
   {
      if (threadControlBlockPtr->inFile[threadControlBlockPtr->currentInputDepth] == stdin)
      {                          /* eof; print error and exit */
         DriverPrintError("\n*** EOF on input file; exiting\n");
	 BeginAPICall();
	 ARTermination(GetControlStructPtr(), &status);
	 EndAPICall(&status);
         exit(1);
      }
      else
      {                          /* nested file; return to previous */
         (void) fclose(threadControlBlockPtr->inFile[threadControlBlockPtr->currentInputDepth]);
         threadControlBlockPtr->inFile[threadControlBlockPtr->currentInputDepth] = NULL;
         if (threadControlBlockPtr->currentInputDepth == 0)
         {                       /* eof with no prev; print error and exit */
            DriverPrintError("\n*** EOF on input file; exiting\n");
	    BeginAPICall();
	    ARTermination(GetControlStructPtr(), &status);
	    EndAPICall(&status);
            exit(1);
         }
         --threadControlBlockPtr->currentInputDepth;
         GetInputLine();
         return;
      }
   }
                                 /* record command if recording active */
   if (threadControlBlockPtr->recordFile != NULL)
      if ((strncmp(threadControlBlockPtr->buffer, commands[COMMAND_STOP_RECORD],
                   strlen(commands[COMMAND_STOP_RECORD])) != 0) ||
          (threadControlBlockPtr->buffer[
            strlen(commands[COMMAND_STOP_RECORD])] != '\n'))
         (void) fprintf(threadControlBlockPtr->recordFile, "%s",
                        threadControlBlockPtr->buffer);
                                 /* remove any trailing whitespace */
   bufLen = strlen(threadControlBlockPtr->buffer) - 1;
   while ((bufLen >= 0) && ((threadControlBlockPtr->buffer[bufLen] == ' ') ||
           (threadControlBlockPtr->buffer[bufLen] == '\t') ||
           (threadControlBlockPtr->buffer[bufLen] == '\n')))
      bufLen--;
   threadControlBlockPtr->buffer[bufLen + 1] = '\0';
                                 /* process if a comment */
   if (threadControlBlockPtr->buffer[0] == '#')
   {
                                 /* if output redirected, echo comment */
                                 /* in the output file                 */
      if (ProcessCommentLine(threadControlBlockPtr->buffer) &&
          threadControlBlockPtr->outFile != stdout)
         DriverPrintResult("%s\n", threadControlBlockPtr->buffer);
   }
}


/*****************************************************************************/
/*                                                                           */
/*                               OpenInputFile                               */
/*                                                                           */
/*****************************************************************************/

void OpenInputFile(inputFilename)
char     *inputFilename;
{
   char               *filename; /* pointer to filename for input file */
   FILE               *tempFile; /* temporary pointer to new input file */
   ThreadControlBlock *threadControlBlockPtr; /* control block pointer */

                                 /* get the filename of the new input file */
   if (inputFilename)
      filename = inputFilename;
   else
      filename = GetChar("Filename of input file (): ", "");
   if (strlen(filename) == 0)
      DriverPrintWarning(" **** No filename specified so no change to input file\n");
   else
   {
      threadControlBlockPtr = GetThreadControlBlockPtr();

      if (threadControlBlockPtr->currentInputDepth == MAX_NESTED_INPUT_DEPTH - 1)
      {                          /* print error and return */
         DriverPrintError("\n*** Maximum nested input depth exceeded\n");
         return;
      }
                                 /* open the new file for reading */
      tempFile = fopen(filename, "r");
      if (tempFile == NULL)
         DriverPrintWarning(
                     " **** File error during open; no change to input file\n");
      else
      {                          /* record input file pointer */   
         threadControlBlockPtr->inFile[++threadControlBlockPtr->currentInputDepth] = tempFile;
      }
   }
}


/*****************************************************************************/
/*                                                                           */
/*                               OpenOutputFile                              */
/*                                                                           */
/*****************************************************************************/

void OpenOutputFile()

{
   char                filename[AR_MAX_FULL_FILENAME + 1];
   char               *inputValue; /* pointer to filename for output file */
   FILE               *tempFile;   /* temporary pointer to new output file */
   ThreadControlBlock *threadControlBlockPtr; /* control block pointer */

                                 /* get the filename of the new output file */
   inputValue = GetChar("Filename of output file (): ", "");
   if (strlen(inputValue) == 0)
      DriverPrintWarning(" **** No filename specified so no change to output file\n");
   else
   {                             /* open the new file for writing */
      if (!(gQuietMode & SUPPRESS_RESULTS))
      {
         sprintf(filename, "%s%s%s", gResultDir,
                 (gResultDir[0] != '\0') ? "\\" : "", inputValue);

         tempFile = fopen(filename, "w");
         if (tempFile == NULL)
            DriverPrintWarning(
                    " **** File error during open; no change to output file\n");
         else
         {                       /* if there is a current output file;close it*/
            threadControlBlockPtr = GetThreadControlBlockPtr();
            if (threadControlBlockPtr->outFile != stdout)
               (void) fclose(threadControlBlockPtr->outFile);
            if (threadControlBlockPtr->outFileName)
               free(threadControlBlockPtr->outFileName);
            threadControlBlockPtr->outFile = tempFile;
            threadControlBlockPtr->outFileName = malloc(strlen(filename) + 1);
            if (threadControlBlockPtr->outFileName)
               strcpy(threadControlBlockPtr->outFileName, filename);
         }
      }
   }
}


/*****************************************************************************/
/*                                                                           */
/*                               CloseOutputFile                             */
/*                                                                           */
/*****************************************************************************/

void CloseOutputFile()

{
   ThreadControlBlock *threadControlBlockPtr; /* control block pointer */

   threadControlBlockPtr = GetThreadControlBlockPtr();

   if (threadControlBlockPtr->outFile == stdout)
      DriverPrintWarning(" **** Output to stdout; cannot close stdout file\n");
   else
   {                             /* close output file */
      (void) fclose(threadControlBlockPtr->outFile);
      threadControlBlockPtr->outFile = stdout;
      if (threadControlBlockPtr->outFileName)
      {
         free(threadControlBlockPtr->outFileName);
         threadControlBlockPtr->outFileName = NULL;
      }
   }
}


/*****************************************************************************/
/*                                                                           */
/*                               StartRecording                              */
/*                                                                           */
/*****************************************************************************/

void StartRecording()

{
   char               *filename; /* pointer to filename for record file */
   ThreadControlBlock *threadControlBlockPtr; /* control block pointer */

   threadControlBlockPtr = GetThreadControlBlockPtr();

   if (threadControlBlockPtr->recordFile != NULL)
      DriverPrintWarning(
                " **** Recording already active; stop previous to start new\n");
   else
   {                             /* get a filename and open for recording */
      filename = GetChar("Filename of record file (): ", "");
      if (strlen(filename) == 0)
         DriverPrintError(" **** No filename specified so no recording started\n");
      else
      {                          /* open the new file for write */
         threadControlBlockPtr->recordFile = fopen(filename, "w");
         if (threadControlBlockPtr->recordFile == NULL)
            DriverPrintError(
                        " **** File error during open; no recording started\n");
      }
   }
}


/*****************************************************************************/
/*                                                                           */
/*                                StopRecording                              */
/*                                                                           */
/*****************************************************************************/

void StopRecording()

{
   ThreadControlBlock *threadControlBlockPtr; /* control block pointer */

   threadControlBlockPtr = GetThreadControlBlockPtr();

   if (threadControlBlockPtr->recordFile == NULL)
      DriverPrintWarning(" **** Recording is not active\n");
   else
   {                             /* close record file */
      (void) fclose(threadControlBlockPtr->recordFile);
      threadControlBlockPtr->recordFile = NULL;
   }
}


/*****************************************************************************/
/*                                                                           */
/*                                  BeginLoop                                */
/*                                                                           */
/*****************************************************************************/

void BeginLoop()

{
   long                numIterations;
   ThreadControlBlock *threadControlBlockPtr; /* control block pointer */

   DriverPrintHeader("BEGIN LOOP");

   threadControlBlockPtr = GetThreadControlBlockPtr();

   if (threadControlBlockPtr->inFile[threadControlBlockPtr->currentInputDepth] == stdin)
   {                             /* print error and return */
      DriverPrintError("\n*** Command must be issued from input file\n");
      return;
   }

   numIterations = GetLong("Number of iterations (1): ", 1);

   if (threadControlBlockPtr->currentLoopDepth == MAX_NESTED_LOOP_DEPTH - 1)
   {                             /* print error and return */
      DriverPrintError("\n*** Maximum nested loop depth exceeded\n");
      threadControlBlockPtr->numFailedBeginLoop++;
      return;
   }

   threadControlBlockPtr->currentLoopDepth++;

   threadControlBlockPtr->numIterations[threadControlBlockPtr->currentLoopDepth] =
      (numIterations <= 0) ? 1 : (unsigned int) numIterations;

   threadControlBlockPtr->loopBeginFilePos[threadControlBlockPtr->currentLoopDepth] =
      ftell(threadControlBlockPtr->inFile[threadControlBlockPtr->currentInputDepth]);  
}


/*****************************************************************************/
/*                                                                           */
/*                                   EndLoop                                 */
/*                                                                           */
/*****************************************************************************/

void EndLoop()

{
   ThreadControlBlock *threadControlBlockPtr; /* control block pointer */

   DriverPrintHeader("END LOOP");

   threadControlBlockPtr = GetThreadControlBlockPtr();

   if (threadControlBlockPtr->inFile[threadControlBlockPtr->currentInputDepth] == stdin)
   {                             /* print error and return */
      DriverPrintError("\n*** Command must be issued from input file\n");
      return;
   }

   if (threadControlBlockPtr->currentLoopDepth < 0)
   {                             /* print warning and return */
      DriverPrintWarning("\n*** Command ignored because no corresponding begin loop issued\n");
      return;
   }

   if (threadControlBlockPtr->numFailedBeginLoop > 0)
   {                             /* print warning and return */
      DriverPrintWarning("\n*** Command ignored because previous begin loop failed\n");
      threadControlBlockPtr->numFailedBeginLoop--;
      return;
   }

   if (--threadControlBlockPtr->numIterations[threadControlBlockPtr->currentLoopDepth] > 0)
      fseek(threadControlBlockPtr->inFile[threadControlBlockPtr->currentInputDepth],
            threadControlBlockPtr->loopBeginFilePos[threadControlBlockPtr->currentLoopDepth], SEEK_SET);
   else
      threadControlBlockPtr->currentLoopDepth--;
}


/*****************************************************************************/
/*                                                                           */
/*                                DriverSleep                                */
/*                                                                           */
/*****************************************************************************/

void DriverSleep(
unsigned int  seconds
)

{
#ifdef _WIN32
   Sleep((DWORD) (seconds * 1000));
#else
   sleep(seconds);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                              DriverSleepMsec                              */
/*                                                                           */
/*****************************************************************************/

void DriverSleepMsec(
unsigned int  milliseconds
)

{
#ifdef _WIN32
   Sleep((DWORD) milliseconds);
#else
   napms((int) milliseconds);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                      DriverInitializeCriticalSection                      */
/*                                                                           */
/*****************************************************************************/

void DriverInitializeCriticalSection(
OS_CRITICAL_SECTION_PTR  criticalSectionPtr
)

{
#ifdef _WIN32
   InitializeCriticalSection(criticalSectionPtr);
#else
   pthread_mutex_init(criticalSectionPtr, NULL);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                         DriverEnterCriticalSection                        */
/*                                                                           */
/*****************************************************************************/

void DriverEnterCriticalSection(
OS_CRITICAL_SECTION_PTR  criticalSectionPtr
)

{
#ifdef _WIN32
   EnterCriticalSection(criticalSectionPtr);
#else
   pthread_mutex_lock(criticalSectionPtr);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                         DriverLeaveCriticalSection                        */
/*                                                                           */
/*****************************************************************************/

void DriverLeaveCriticalSection(
OS_CRITICAL_SECTION_PTR  criticalSectionPtr
)

{
#ifdef _WIN32
   LeaveCriticalSection(criticalSectionPtr);
#else
   pthread_mutex_unlock(criticalSectionPtr);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                        DriverDeleteCriticalSection                        */
/*                                                                           */
/*****************************************************************************/

void DriverDeleteCriticalSection(
OS_CRITICAL_SECTION_PTR  criticalSectionPtr
)

{
#ifdef _WIN32
   DeleteCriticalSection(criticalSectionPtr);
#else
   pthread_mutex_destroy(criticalSectionPtr);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                               DriverTLSAlloc                              */
/*                                                                           */
/*****************************************************************************/

void DriverTLSAlloc(
OS_TLS_OBJECT_PTR  tlsObjectPtr
)

{
#ifdef _WIN32
   *tlsObjectPtr = TlsAlloc();
#else
   pthread_key_create(tlsObjectPtr, NULL);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                             DriverTLSGetValue                             */
/*                                                                           */
/*****************************************************************************/

void * DriverTLSGetValue(
OS_TLS_OBJECT  tlsObject
)

{
#ifdef _WIN32
   return(TlsGetValue(tlsObject));
#else
   return(pthread_getspecific(tlsObject));
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                             DriverTLSSetValue                             */
/*                                                                           */
/*****************************************************************************/

void DriverTLSSetValue(
OS_TLS_OBJECT  tlsObject,
void          *tlsValue
)

{
#ifdef _WIN32
   TlsSetValue(tlsObject, tlsValue);
#else
   pthread_setspecific(tlsObject, tlsValue);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                               DriverTLSFree                               */
/*                                                                           */
/*****************************************************************************/

void DriverTLSFree(
OS_TLS_OBJECT  tlsObject
)

{
#ifdef _WIN32
   TlsFree(tlsObject);
#else
   pthread_key_delete(tlsObject);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                              DriverCreateEvent                            */
/*                                                                           */
/*****************************************************************************/

void DriverCreateEvent(
OS_EVENT_OBJECT  *eventObject,
unsigned int      releaseType
)

{
   eventObject->releaseType = releaseType;

#ifdef _WIN32
   if ((eventObject->eventHandle = CreateEvent(NULL,
                                               (releaseType == RELEASE_ALL)
                                               ? TRUE : FALSE, FALSE,
                                               NULL)) == NULL)
   {
      DriverPrintError(" **** unable to create an event\n");
   }
#else
   pthread_mutex_init(&eventObject->eventMutex, NULL);
   pthread_cond_init(&eventObject->eventCondVar, NULL); 
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                              DriverLockEvent                              */
/*                                                                           */
/*****************************************************************************/

void DriverLockEvent(
OS_EVENT_OBJECT  *eventObject
)

{
#ifndef _WIN32
   pthread_mutex_lock(&eventObject->eventMutex);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                               DriverSetEvent                              */
/*                                                                           */
/*****************************************************************************/

void DriverSetEvent(
OS_EVENT_OBJECT  *eventObject
)

{
#ifdef _WIN32
   if (eventObject->releaseType == RELEASE_ALL)
      PulseEvent(eventObject->eventHandle);
   else
      SetEvent(eventObject->eventHandle);
#else
   pthread_mutex_lock(&eventObject->eventMutex);
   if (eventObject->releaseType == RELEASE_ALL)
      pthread_cond_broadcast(&eventObject->eventCondVar);
   else
      pthread_cond_signal(&eventObject->eventCondVar);
   pthread_mutex_unlock(&eventObject->eventMutex); 
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                             DriverWaitForEvent                            */
/*                                                                           */
/*****************************************************************************/

void DriverWaitForEvent(
OS_EVENT_OBJECT  *eventObject
)

{
#ifdef _WIN32
   WaitForSingleObject(eventObject->eventHandle, INFINITE);
#else
   pthread_cond_wait(&eventObject->eventCondVar,
                     &eventObject->eventMutex);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                             DriverUnlockEvent                             */
/*                                                                           */
/*****************************************************************************/

void DriverUnlockEvent(
OS_EVENT_OBJECT  *eventObject
)

{
#ifndef _WIN32
   pthread_mutex_unlock(&eventObject->eventMutex);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                              DriverDeleteEvent                            */
/*                                                                           */
/*****************************************************************************/

void DriverDeleteEvent(
OS_EVENT_OBJECT  *eventObject
)

{
#ifdef _WIN32
   CloseHandle(eventObject->eventHandle);
#else
   pthread_mutex_destroy(&eventObject->eventMutex);
   pthread_cond_destroy(&eventObject->eventCondVar);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                            ReleaseWaitingThreads                          */
/*                                                                           */
/*****************************************************************************/

void ReleaseWaitingThreads()

{
   ThreadControlBlock *threadControlBlockPtr; /* control block pointer */

   DriverPrintHeader("RELEASE WAITING THREADS");

   threadControlBlockPtr = GetThreadControlBlockPtr();

#ifdef _WIN32
   /* pause for a second to allow for all launched threads to be */
   /* waiting for the release event                              */
   
   DriverSleep(1);
#endif /* _WIN32 */

   /* release all threads waiting on this particular event */

   DriverSetEvent(&threadControlBlockPtr->releaseWaitEvent);
}


/*****************************************************************************/
/*                                                                           */
/*                           MillisecondSleepTimer                           */
/*                                                                           */
/*****************************************************************************/

void MillisecondSleepTimer()

{
   ARStatusList  dummyStatus;
   unsigned int  sleepMilliseconds; /* number of milliseconds to sleep */

   DriverPrintHeader("MILLISECOND SLEEP TIMER");

   dummyStatus.numItems = 0;

   sleepMilliseconds = (unsigned int) GetLong("Number of milliseconds (0): ", 0);

   DriverPrintResult("\n   Sleeping for %u millisecond(s) . . .\n", sleepMilliseconds);
   
   BeginAPICall();
   DriverSleepMsec(sleepMilliseconds);
   EndAPICall(&dummyStatus);
}


/*****************************************************************************/
/*                                                                           */
/*                                SleepTimer                                 */
/*                                                                           */
/*****************************************************************************/

void SleepTimer()

{
   ARStatusList  dummyStatus;
   unsigned int  sleepSeconds; /* number of seconds to sleep */

   DriverPrintHeader("SLEEP TIMER");

   dummyStatus.numItems = 0;

   sleepSeconds = (unsigned int) GetLong("Number of seconds (0): ", 0);

   DriverPrintResult("\n   Sleeping for %u second(s) . . .\n", sleepSeconds);
   
   BeginAPICall();
   DriverSleep(sleepSeconds);
   EndAPICall(&dummyStatus);
}


/*****************************************************************************/
/*                                                                           */
/*                                RandomSleep                                */
/*                                                                           */
/*****************************************************************************/

void RandomSleep(
unsigned int  lowerBound,
unsigned int  upperBound
)

{
   ARStatusList  dummyStatus;  /* dummy status array */
   unsigned int  randomValue;  /* generated random number 0 to RAND_MAX */
   unsigned int  sleepSeconds; /* number of seconds to sleep */

   dummyStatus.numItems = 0;

   /* synchronize the operation so that only one thread can be asking for */
   /* a random number at any one time                                     */

   DriverEnterCriticalSection(&gRandomNumberCriticalSection);

   DriverLockEvent(&gRandomNumberReplyEvent);

   DriverSetEvent(&gRandomNumberRequestEvent);

   DriverWaitForEvent(&gRandomNumberReplyEvent);

   randomValue = gRandomNumberValue;

   DriverUnlockEvent(&gRandomNumberReplyEvent);

   DriverLeaveCriticalSection(&gRandomNumberCriticalSection);

   sleepSeconds = lowerBound + (randomValue * (upperBound - lowerBound) +
                                ((RAND_MAX + 1) / 2)) / (RAND_MAX + 1);

   DriverPrintResult("\n   Sleeping for %u second(s) . . .\n", sleepSeconds);

   BeginAPICall();
   DriverSleep(sleepSeconds);
   EndAPICall(&dummyStatus);
}


/*****************************************************************************/
/*                                                                           */
/*                             RandomSleepTimer                              */
/*                                                                           */
/*****************************************************************************/

void RandomSleepTimer()

{
   unsigned int  lowerBound;   /* lower bound of acceptable range */
   unsigned int  upperBound;   /* upper bound of acceptable range */

   DriverPrintHeader("RANDOM SLEEP TIMER");

   lowerBound = (unsigned long) GetLong("Lower Bound (0): ", 0);
   upperBound = (unsigned long) GetLong("Upper Bound (60): ", 60);

   if (upperBound < lowerBound)
   {
      DriverPrintError(" **** upper bound is less than lower bound\n");
      return;
   }

   RandomSleep(lowerBound, upperBound);
}


/*****************************************************************************/
/*                                                                           */
/*                         CreateThreadControlBlock                          */
/*                                                                           */
/*****************************************************************************/

void * CreateThreadControlBlock()

{
   ThreadControlBlock *threadControlBlockPtr;

   if ((threadControlBlockPtr =
         (ThreadControlBlock *) malloc(sizeof(ThreadControlBlock))) == NULL)
   {
      DriverPrintError(" **** malloc error creating thread control block\n");
   }
   else
   {
      /* initialize the thread control block noting that all items starting */
      /* with some form of zero will not be initialized individually        */

      memset((char *) threadControlBlockPtr, 0, sizeof(ThreadControlBlock));

      threadControlBlockPtr->currentLoopDepth = -1;

      /* save the control block pointer in the appropriate location and */
      /* create the necessary synchronization objects                   */

      DriverTLSSetValue(gTLSKey, (void *) threadControlBlockPtr);

      DriverCreateEvent(&threadControlBlockPtr->launchWaitEvent, RELEASE_ONE);
      DriverCreateEvent(&threadControlBlockPtr->releaseWaitEvent, RELEASE_ALL);
   }

   return ((void *) threadControlBlockPtr);
}


/*****************************************************************************/
/*                                                                           */
/*                         GetThreadControlBlockPtr                          */
/*                                                                           */
/*****************************************************************************/

void * GetThreadControlBlockPtr()

{
   /* get the control block pointer from the appropriate location */

   return (DriverTLSGetValue(gTLSKey));
}


/*****************************************************************************/
/*                                                                           */
/*                         DestroyThreadControlBlock                         */
/*                                                                           */
/*****************************************************************************/

void DestroyThreadControlBlock()

{
   unsigned int         i;
   unsigned int         numHandlesProcessed;
   ThreadControlBlock  *threadControlBlockPtr;

   /* get the control block pointer for the current thread */

   threadControlBlockPtr = GetThreadControlBlockPtr();

   /* close any files that need closing */

   for (i = 1; i <= threadControlBlockPtr->currentInputDepth; i++)
   {
      if (threadControlBlockPtr->inFile[i] != NULL)
         (void) fclose(threadControlBlockPtr->inFile[i]);
   }

   if (threadControlBlockPtr->inFile[0] != stdin)
      (void) fclose(threadControlBlockPtr->inFile[0]);

   if (threadControlBlockPtr->outFile != stdout)
      (void) fclose(threadControlBlockPtr->outFile);

   if (threadControlBlockPtr->recordFile != NULL)
      (void) fclose(threadControlBlockPtr->recordFile);

   ProcessResultFile();

   if (threadControlBlockPtr->resultFile != NULL)
      (void) fclose(threadControlBlockPtr->resultFile);

   /* free any memory that needs freeing */

   if (threadControlBlockPtr->outFileName != NULL)
      free(threadControlBlockPtr->outFileName);

   /* wait until all threads launched by this thread have completed */

   if (threadControlBlockPtr->numHandles > 0)
   {
#ifdef _WIN32
      /* since we can only wait on a limited number of handles we will */
      /* potentially have to wait multiple times                       */

      numHandlesProcessed = 0;

      while (numHandlesProcessed < threadControlBlockPtr->numHandles)
      {
         WaitForMultipleObjects(min(MAXIMUM_WAIT_OBJECTS,
                                    threadControlBlockPtr->numHandles -
                                     numHandlesProcessed),
                                &threadControlBlockPtr->
                                 threadHandles[numHandlesProcessed],
                                TRUE, INFINITE);

         numHandlesProcessed += MAXIMUM_WAIT_OBJECTS;
      }
#else
      for (i = 0; i < threadControlBlockPtr->numHandles; i++)
         pthread_join(threadControlBlockPtr->threadHandles[i], NULL);
#endif /* _WIN32 */
   }

   /* close the thread handles now that we know the threads have terminated */

   for (i = 0; i < threadControlBlockPtr->numHandles; i++)
   {
#ifdef _WIN32
      CloseHandle(threadControlBlockPtr->threadHandles[i]);
#else
      pthread_detach(threadControlBlockPtr->threadHandles[i]);
#endif /* _WIN32 */
   }

   /* clean up the event objects */

   DriverDeleteEvent(&threadControlBlockPtr->launchWaitEvent);
   DriverDeleteEvent(&threadControlBlockPtr->releaseWaitEvent);

   if (threadControlBlockPtr->threadHandles)
      free(threadControlBlockPtr->threadHandles);

   free(threadControlBlockPtr);
}


/*****************************************************************************/
/*                                                                           */
/*                            GetControlStructPtr                            */
/*                                                                           */
/*****************************************************************************/

ARControlStruct * GetControlStructPtr()

{
   return(&((ThreadControlBlock *) GetThreadControlBlockPtr())->control);
}


/*****************************************************************************/
/*                                                                           */
/*                           SaveEntryListEntryIds                           */
/*                                                                           */
/*****************************************************************************/

void SaveEntryListEntryIds(entryListList)
AREntryListList *entryListList;     /* IN; entry ids to save */
{
   AREntryListStruct  *tempPtr;
   ThreadControlBlock *threadControlBlockPtr;

   /* get the control block pointer for the current thread */

   threadControlBlockPtr = GetThreadControlBlockPtr();

   /* clear out any existing entry ids */

   threadControlBlockPtr->firstListId[0] = '\0';
   threadControlBlockPtr->secondListId[0] = '\0';
   threadControlBlockPtr->lastListId[0] = '\0';

   /* set the entry ids to the appropriate values */

   if (entryListList->numItems > 0)
   {
      tempPtr = entryListList->entryList;
   
      if (tempPtr->entryId.numItems > 0)
         strcpy(threadControlBlockPtr->firstListId, tempPtr->entryId.entryIdList[0]);
      
      tempPtr = (entryListList->numItems > 1)
                ? &entryListList->entryList[1] : entryListList->entryList;

      if (tempPtr->entryId.numItems > 0)
         strcpy(threadControlBlockPtr->secondListId, tempPtr->entryId.entryIdList[0]);

      tempPtr = &entryListList->entryList[entryListList->numItems - 1];

      if (tempPtr->entryId.numItems > 0)
         strcpy(threadControlBlockPtr->lastListId, tempPtr->entryId.entryIdList[0]);
   }
}


/*****************************************************************************/
/*                                                                           */
/*                      SaveEntryListFieldValueEntryIds                      */
/*                                                                           */
/*****************************************************************************/

void SaveEntryListFieldValueEntryIds(entryListFieldValueList)
AREntryListFieldValueList *entryListFieldValueList; /* IN; entry ids to save */
{
   AREntryListFieldValueStruct *tempPtr;
   ThreadControlBlock          *threadControlBlockPtr;

   /* get the control block pointer for the current thread */

   threadControlBlockPtr = GetThreadControlBlockPtr();

   /* clear out any existing entry ids */

   threadControlBlockPtr->firstListId[0] = '\0';
   threadControlBlockPtr->secondListId[0] = '\0';
   threadControlBlockPtr->lastListId[0] = '\0';

   /* set the entry ids to the appropriate values */

   if (entryListFieldValueList->numItems > 0)
   {
      tempPtr = entryListFieldValueList->entryList;
   
      if (tempPtr->entryId.numItems > 0)
         strcpy(threadControlBlockPtr->firstListId, tempPtr->entryId.entryIdList[0]);
      
      tempPtr = (entryListFieldValueList->numItems > 1)
                ? &entryListFieldValueList->entryList[1]
                : entryListFieldValueList->entryList;

      if (tempPtr->entryId.numItems > 0)
         strcpy(threadControlBlockPtr->secondListId, tempPtr->entryId.entryIdList[0]);

      tempPtr = &entryListFieldValueList->entryList[entryListFieldValueList->numItems - 1];

      if (tempPtr->entryId.numItems > 0)
         strcpy(threadControlBlockPtr->lastListId, tempPtr->entryId.entryIdList[0]);
   }
}
