/* 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"
#include "arerrno.h"
#include "arextern.h"
#include "arstruct.h"
#include "arfree.h"

enum {
    DEBUG_0,
    DEBUG_unescapeInput,
    DEBUG_N
};
static ARBoolean driverDebug[DEBUG_N];

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

void
DebugDriver(void)
{
   int dbx = GetInt("Debug item number: ", 0);
   if (dbx < 1 || dbx > DEBUG_N-1)
      DriverPrintError("Debug item number %d is out of range [1,%d)\n",
            dbx, DEBUG_N);
   else
   {
      char b[32];
      int val = driverDebug[dbx]^1;
      sprintf(b, "Value (%d): ", val);
      val = GetInt(b, val);
      if (val&~1)
         DriverPrintError("Value %d is out of range [0,1]\n", val);
      else
      {
         driverDebug[dbx] = val;
         DriverPrintWarning("driverDebug[%d]=%d\n", dbx, driverDebug[dbx]);
      }
   }
}

/*
 * Replaces contents of buf with unexpanded version: each sequence %AB
 * (% followed by two hex digits) becomes a single byte value 0xAB.
 */
static void
unescapeInput(char *buf)
{
   char *dd;
   const char *ss;
   static const signed char xd[256] = {
      -1,-1,-1,-1, -1,-1,-1,-1,  -1,-1,-1,-1, -1,-1,-1,-1,
      -1,-1,-1,-1, -1,-1,-1,-1,  -1,-1,-1,-1, -1,-1,-1,-1,
      -1,-1,-1,-1, -1,-1,-1,-1,  -1,-1,-1,-1, -1,-1,-1,-1,
      /* 0:9 */
       0, 1, 2, 3,  4, 5, 6, 7,   8, 9,-1,-1, -1,-1,-1,-1,

      /* A:F */
      -1,10,11,12, 13,14,15,16,  -1,-1,-1,-1, -1,-1,-1,-1,
      -1,-1,-1,-1, -1,-1,-1,-1,  -1,-1,-1,-1, -1,-1,-1,-1,
      /* a:f */
      -1,10,11,12, 13,14,15,16,  -1,-1,-1,-1, -1,-1,-1,-1,
      -1,-1,-1,-1, -1,-1,-1,-1,  -1,-1,-1,-1, -1,-1,-1,-1,

      -1,-1,-1,-1, -1,-1,-1,-1,  -1,-1,-1,-1, -1,-1,-1,-1,
      -1,-1,-1,-1, -1,-1,-1,-1,  -1,-1,-1,-1, -1,-1,-1,-1,
      -1,-1,-1,-1, -1,-1,-1,-1,  -1,-1,-1,-1, -1,-1,-1,-1,
      -1,-1,-1,-1, -1,-1,-1,-1,  -1,-1,-1,-1, -1,-1,-1,-1,

      -1,-1,-1,-1, -1,-1,-1,-1,  -1,-1,-1,-1, -1,-1,-1,-1,
      -1,-1,-1,-1, -1,-1,-1,-1,  -1,-1,-1,-1, -1,-1,-1,-1,
      -1,-1,-1,-1, -1,-1,-1,-1,  -1,-1,-1,-1, -1,-1,-1,-1,
      -1,-1,-1,-1, -1,-1,-1,-1,  -1,-1,-1,-1, -1,-1,-1,-1,
   };

   for (ss = dd = buf; *ss; ++dd, ++ss)
   {
      if (*ss=='%')
      {
         int c = xd[0xff&ss[1]] << 4 | xd[0xff&ss[2]];
         if (c&~0xff)
         {
            DriverPrintWarning("Invalid input sequence: %.3s\n", ss);
            break;
         }
         *dd = (char) c;
         ss += 2;
      }
      else
         *dd = *ss;
   }
   *dd = '\0';
}

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

void GetInputLine()

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

   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 */
         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'))
         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);
   }
   if (driverDebug[DEBUG_unescapeInput])
      unescapeInput(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 of '%s'; no change to input file\n",
                            filename);
      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 of '%s'; no change to output file\n",
                               filename);
         else
         {                       /* if there is a current output file; close it */
            threadControlBlockPtr = GetThreadControlBlockPtr();
            if (threadControlBlockPtr->outFile != stdout)
               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 */
      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 */
      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)
         fclose(threadControlBlockPtr->inFile[i]);
   }

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

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

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

   ProcessResultFile();

   if (threadControlBlockPtr->resultFile != NULL)
      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);
}


/*****************************************************************************/
/*                                                                           */
/*                          SaveFirstSchemaListName                          */
/*                                                                           */
/*****************************************************************************/

void SaveFirstSchemaListName(schemaList)
ARNameList *schemaList;     /* IN; contains schema name to save */
{
   ThreadControlBlock  *threadControlBlockPtr;

   /* get the control block pointer for the current thread */
   threadControlBlockPtr = GetThreadControlBlockPtr();

   /* clear out any existing schema names */
   threadControlBlockPtr->firstSchemaListName[0] = '\0';

   /* set the first schema name to the appropriate values */
   if (schemaList->numItems > 0)
      strcpy(threadControlBlockPtr->firstSchemaListName,
             schemaList->nameList[0]);
}


/*****************************************************************************/
/*                                                                           */
/*                             SaveCreateEntryId                             */
/*                                                                           */
/*****************************************************************************/

void SaveCreateEntryId(entryId)
AREntryIdType   entryId;     /* IN; entry id to save */
{
   ThreadControlBlock  *threadControlBlockPtr;

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

   threadControlBlockPtr = GetThreadControlBlockPtr();

   /* set the entry id to the supplied value */

   strcpy(threadControlBlockPtr->createEntryId, entryId);
}


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

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

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

   threadControlBlockPtr = GetThreadControlBlockPtr();

   /* clear out any existing entry ids */

   for (i = 0; i < MAX_JOIN_ENTRY_IDS; i++)
   {
      threadControlBlockPtr->firstGLEListId[i][0] = '\0';
      threadControlBlockPtr->secondGLEListId[i][0] = '\0';
      threadControlBlockPtr->lastGLEListId[i][0] = '\0';
   }

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

   if (entryListList->numItems > 0)
   {
      tempPtr = entryListList->entryList;

      for (i = 0; i < MAX_JOIN_ENTRY_IDS, i < tempPtr->entryId.numItems; i++)
      {
         strcpy(threadControlBlockPtr->firstGLEListId[i],
                tempPtr->entryId.entryIdList[i]);
      }
      
      tempPtr = (entryListList->numItems > 1)
                ? &entryListList->entryList[1]
                : entryListList->entryList;

      for (i = 0; i < MAX_JOIN_ENTRY_IDS, i < tempPtr->entryId.numItems; i++)
      {
         strcpy(threadControlBlockPtr->secondGLEListId[i],
                tempPtr->entryId.entryIdList[i]);
      }

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

      for (i = 0; i < MAX_JOIN_ENTRY_IDS, i < tempPtr->entryId.numItems; i++)
      {
         strcpy(threadControlBlockPtr->lastGLEListId[i],
                tempPtr->entryId.entryIdList[i]);
      }
   }
}


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

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

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

   threadControlBlockPtr = GetThreadControlBlockPtr();

   /* clear out any existing entry ids */

   for (i = 0; i < MAX_JOIN_ENTRY_IDS; i++)
   {
      threadControlBlockPtr->firstGLEWFListId[i][0] = '\0';
      threadControlBlockPtr->secondGLEWFListId[i][0] = '\0';
      threadControlBlockPtr->lastGLEWFListId[i][0] = '\0';
   }

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

   if (entryListFieldValueList->numItems > 0)
   {
      tempPtr = entryListFieldValueList->entryList;

      for (i = 0; i < MAX_JOIN_ENTRY_IDS, i < tempPtr->entryId.numItems; i++)
      {
         strcpy(threadControlBlockPtr->firstGLEWFListId[i],
                tempPtr->entryId.entryIdList[i]);
      }
      
      tempPtr = (entryListFieldValueList->numItems > 1)
                ? &entryListFieldValueList->entryList[1]
                : entryListFieldValueList->entryList;

      for (i = 0; i < MAX_JOIN_ENTRY_IDS, i < tempPtr->entryId.numItems; i++)
      {
         strcpy(threadControlBlockPtr->secondGLEWFListId[i],
                tempPtr->entryId.entryIdList[i]);
      }

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

      for (i = 0; i < MAX_JOIN_ENTRY_IDS, i < tempPtr->entryId.numItems; i++)
      {
         strcpy(threadControlBlockPtr->lastGLEWFListId[i],
                tempPtr->entryId.entryIdList[i]);
      }
   }
}


/*****************************************************************************/
/*                                                                           */
/*                                GetJDValue                                 */
/*                                                                           */
/*****************************************************************************/

int GetJDValue(
char  *dateStr,     /* IN: date string in ISO-8601 format [-]YYYY-MM-DD */
int   *jd           /* OUT: julian date number of input date */
)
{
   ARDateStruct   date;
   char          *dateStrPtr;
   int            errorCode;
   ARStatusList   status;

   errorCode = AR_ERROR_NONE;
   dateStrPtr = dateStr;

   /*
    * Perform basic date format check.
    */
   /* Skip negative sign if present. */
   if (*dateStrPtr == '-') dateStrPtr++;

   /* Check year. */
   if (!isdigit(*dateStrPtr++)) return AR_ERROR_INVALID_DATE_FORMAT;
   if (!isdigit(*dateStrPtr++)) return AR_ERROR_INVALID_DATE_FORMAT;
   if (!isdigit(*dateStrPtr++)) return AR_ERROR_INVALID_DATE_FORMAT;
   if (!isdigit(*dateStrPtr++)) return AR_ERROR_INVALID_DATE_FORMAT;
   if (*dateStrPtr++ != '-')    return AR_ERROR_INVALID_DATE_FORMAT;

   /* Check month. */ 
   if (!isdigit(*dateStrPtr++)) return AR_ERROR_INVALID_DATE_FORMAT;
   if (!isdigit(*dateStrPtr++)) return AR_ERROR_INVALID_DATE_FORMAT;
   if (*dateStrPtr++ != '-')    return AR_ERROR_INVALID_DATE_FORMAT;
   
   /* Check day. */
   if (!isdigit(*dateStrPtr++)) return AR_ERROR_INVALID_DATE_FORMAT;
   if (!isdigit(*dateStrPtr++)) return AR_ERROR_INVALID_DATE_FORMAT;


   date.year  = atoi(dateStr);
   date.month = atoi(dateStr + 5);
   date.day   = atoi(dateStr + 8);

   ARDateToJulianDate(NULL, &date, jd, &status);

   if (status.numItems > 0 &&
       status.statusList[0].messageType > AR_RETURN_WARNING)
   {
      errorCode = status.statusList[0].messageNum;
      FreeARStatusList(&status, FALSE);
   }

   return errorCode;
}


/*****************************************************************************/
/*                                                                           */
/*                               GetDateString                               */
/*                                                                           */
/*****************************************************************************/

int GetDateString(
int    jd,          /* IN: julian date number to convert */
char  *dateStr      /* OUT: date string in ISO-8601 format [-]YYYY-MM-DD */
)

{
   ARDateStruct   date;
   int            errorCode;
   ARStatusList   status;

   errorCode = AR_ERROR_NONE;

   ARJulianDateToDate(NULL, jd, &date, &status);

   sprintf(dateStr, "%.4d-%.2d-%.2d", date.year, date.month, date.day);

   if (status.numItems > 0 &&
       status.statusList[0].messageType > AR_RETURN_WARNING)
   {
      errorCode = status.statusList[0].messageNum;
      FreeARStatusList(&status, FALSE);
   }

   return errorCode;
}
