// fgfilelist.cc

#include "fgfilelist.h"

#ifndef _FGFILEGRAB_H
#include "fgfilegrab.h"
#endif

#ifndef __SGI_STL_VECTOR_H
#include <vector>
#endif

#ifndef _FGSTRING_H
#define "fgstring.h"
#endif

#ifndef _FGLOGGER_H
#include "fglogger.h"
#endif

#ifndef _FGEXC_H
#include "fgexc.h"
#endif

#ifndef _FGGLOB_H
#include "fgglob.h"
#endif

#include <stdio.h>
#include <string.h>
#include <assert.h>

// Threading. Very not cool.
#include <pthread.h>

// Class scope variables
int FGFileList::msThreadsActive = 0;

struct FGFileList::Internal_FGFileList {
  // The individual rules one by one
  std::vector<FGFileGrab> mFiles;

  // Threading synchronization object
  // Children flag this when they exit to alert
  // master process to spawn more
  static pthread_cond_t msChildExitCondition;
  // Mutex to go with the above
  static pthread_mutex_t msChildExitMutex;
  // Mutex to ensure no children run before the parent
  // has acknowledged the broadcast
  static pthread_mutex_t msBroadcastSerializer;
};

pthread_cond_t FGFileList::Internal_FGFileList::msChildExitCondition;
pthread_mutex_t FGFileList::Internal_FGFileList::msChildExitMutex;
pthread_mutex_t FGFileList::Internal_FGFileList::msBroadcastSerializer;

FGFileList::FGFileList()
{
  // Allocate the implementation
  mpInternals = new Internal_FGFileList;

  // Set up the threading objects
  pthread_cond_init(&Internal_FGFileList::msChildExitCondition, NULL);
  pthread_mutex_init(&Internal_FGFileList::msChildExitMutex, NULL);
  pthread_mutex_init(&Internal_FGFileList::msBroadcastSerializer, NULL);
}

FGFileList::~FGFileList()
{
  if (mpInternals) {
    // Free allocated resources of each FGFileGrab object
    std::vector<FGFileGrab>::iterator iFiles;

    for (iFiles = mpInternals->mFiles.begin();
         iFiles != mpInternals->mFiles.end(); iFiles++) {
      iFiles->FreeResources();
    }

    pthread_mutex_destroy(&Internal_FGFileList::msChildExitMutex);
    pthread_mutex_destroy(&Internal_FGFileList::msBroadcastSerializer);
    pthread_cond_destroy(&Internal_FGFileList::msChildExitCondition);

    delete mpInternals;
  }

  // Bug if we've still got threads hanging around
  assert(msThreadsActive == 0);
}

FGString
FGFileList::GetLineString(void) const
{
  FGString ret("at line ");
  char intBuf[10];
  sprintf(intBuf, "%d", mLine);
  ret += intBuf;

  return ret;
}

void
FGFileList::LoadConfig(const FGString& configFile)
{
  FILE* pFile = fopen(configFile, "r");
  if (pFile == NULL) {
    FGString details("`");
    details += configFile;
    details += '\'';
    throw FGException(FGException::kConfigFileNotFound, details);
  }

  // Catch then rethrow exceptions so we can clean up and close file
  try {
    // Temp. strings as we build up a file entry
    FGString ruleName;
    FGString host;
    FGString remDir;
    FGString localDir;
    FGString file;
    // This indicates how many bits we have to go before we have a
    // complete file entry
    int componentsToFind = 5;

    mLine = 0;
    char buf[1024];
    while (fgets(buf, sizeof(buf), pFile) != NULL) {
      mLine++;

      // Nail carrige returns into end of lines
      char* pCR = strchr(buf, '\n');
      if (pCR != NULL) {
        *pCR = '\0';
      }

      // Skip whitespace
      char* pPos = buf;
      while (*pPos == ' ' || *pPos == '\t') {
        pPos++;
      }

      // 09-Dec-99 - Also ignore whitespace at end of line!
      char* pStart = pPos;
      while (*pStart != '\0')
      {
        pStart++;
      }
      while (pStart > pPos)
      {
        pStart--;
        if (*pStart == ' ' || *pStart == '\t')
        {
          *pStart = '\0';
        }
        else
        {
          break;
        }
      }

      // Ignore comments or blank lines
      if (*pPos == '#' || *pPos == '\0') {
        continue;
      }

      // First ":" is separator between token and value
      char* pSep = strchr(pPos, ':');
      if (pSep == NULL) {
        // Must have a :
        FGString details = GetLineString();
        throw FGException(FGException::kConfigMissingColon, details);
      }
      *pSep = '\0';
      pSep++;
      while (*pSep == ' ' || *pSep == '\t') {
        pSep++;
      }
      if (!strcmp(pPos, "RemoteDir")) {
        if (!remDir.IsEmpty()) {
          FGString details("RemoteDir, ");
          details += GetLineString();
          throw FGException(FGException::kDuplicateToken, details);
        }
        remDir = pSep;
        componentsToFind--;
      } else if (!strcmp(pPos, "LocalDir")) {
        if (!localDir.IsEmpty()) {
          FGString details("LocalDir, ");
          details += GetLineString();
          throw FGException(FGException::kDuplicateToken, details);
        }
        localDir = pSep;
        componentsToFind--;
      } else if (!strcmp(pPos, "Name")) {
        if (!ruleName.IsEmpty()) {
          FGString details("Name, ");
          details += GetLineString();
          throw FGException(FGException::kDuplicateToken, details);
        }
        ruleName = pSep;
        componentsToFind--;
      } else if (!strcmp(pPos, "Host")) {
        if (!host.IsEmpty()) {
          FGString details("Host, ");
          details += GetLineString();
          throw FGException(FGException::kDuplicateToken, details);
        }
        host = pSep;
        componentsToFind--;
      } else if (!strcmp(pPos, "File")) {
        if (!file.IsEmpty()) {
          FGString details("File, ");
          details += GetLineString();
          throw FGException(FGException::kDuplicateToken, details);
        }
        file = pSep;
        componentsToFind--;
      } else {
        // Unknown tag
        FGString details(pPos);
        details += ", ";
        details += GetLineString();
      }

      if (!componentsToFind) {
        // Oooh ready to make a FileGrab
        FGFileGrab theGrab(ruleName, host, remDir, localDir, file);
        mpInternals->mFiles.push_back(theGrab);
        componentsToFind = 5;
        ruleName.MakeEmpty();
        host.MakeEmpty();
        file.MakeEmpty();
        localDir.MakeEmpty();
        remDir.MakeEmpty();
      }

    } // end: while(more lines)

    // Ended halfway through a rule?
    if (componentsToFind != 5) {
      throw FGException(FGException::kUnexpectedEndOfConfig);
    }
  } // end try() block
  catch (FGException&) {
    fclose(pFile);
    throw;
  }

  fclose(pFile);
}

// This function spawns lots of threads to attempt to complete several
// rules simultaneously. The semantics are this: This call does not
// complete until all spawned threads complete. At any given time the
// number of threads active is limited to a maximum value

void
FGFileList::GetAllFiles(void) const
{
  assert(msThreadsActive == 0);

  // Grab the mutex for child exit notification
  int lockRet = pthread_mutex_trylock(&Internal_FGFileList::msChildExitMutex);
  assert(lockRet == 0);

  std::vector<FGFileGrab>::const_iterator iFiles;

  for (iFiles = mpInternals->mFiles.begin(); iFiles != mpInternals->mFiles.end();
       iFiles++) {

    // If we are operating with max. children, must wait for some to finish
    assert(msThreadsActive >=0 && msThreadsActive <= FGGlob::gMaxThreads);
    if (msThreadsActive == FGGlob::gMaxThreads) {
      // Wait for a single thread to complete
      pthread_cond_wait(&Internal_FGFileList::msChildExitCondition,
                        &Internal_FGFileList::msChildExitMutex);
      msThreadsActive--;

      pthread_mutex_unlock(&Internal_FGFileList::msBroadcastSerializer);
    }

    // OK so we are ready to launch a thread
    // We never sync on a thread's exit so make them detached
    pthread_attr_t threadAttr;
    pthread_attr_init(&threadAttr);
    int detachState = PTHREAD_CREATE_DETACHED;
    pthread_attr_setdetachstate(&threadAttr, detachState);

    pthread_t newThread;
    void* pArg = (void*)(&(*iFiles));
    // And they're off..
    msThreadsActive++;
    pthread_create(&newThread, &threadAttr,
                   &ThreadStartPoint, pArg);
    pthread_attr_destroy(&threadAttr);
  }

  // OK so all grabs are scheduled. We might have some still active
  // child threads (in fact that's almost certain). Hence we must
  // wait for them to finish
  while (msThreadsActive > 0) {
    pthread_cond_wait(&Internal_FGFileList::msChildExitCondition,
                      &Internal_FGFileList::msChildExitMutex);
    msThreadsActive--;

    pthread_mutex_unlock(&Internal_FGFileList::msBroadcastSerializer);
  }
}

// ALERT: When this function executes, it is in the context of the
// child thread.

void*
FGFileList::ThreadStartPoint(void* pArg)
{
  // Cast the arg, which is actually a pointer to the FGFileGrab object
  FGFileGrab* pGrab = (FGFileGrab*)pArg;

  FGString vmsg("About to run rule \"");
  vmsg += pGrab->GetRuleName();
  vmsg += '"';
  FGLogger::GetLogger().LogMsg(vmsg, FGLogger::kFGLLVerbose);

  try {
    pGrab->GrabIt();
  }
  catch (FGException& e) {
    FGString msg("Failed to complete rule \"");
    msg += pGrab->GetRuleName();
    msg += "\", reason follows";

    // To guarantee reason is line _immediately_ after "reason follows"
    FGLogger::Lock();
    FGLogger::GetLogger().LogMsg(msg, FGLogger::kFGLLWarn, true);
    FGLogger::GetLogger().LogException(e, true);
    FGLogger::Unlock();
  }

  // Prevent other threads broadcasting before parent has seen
  // the broadcast
  pthread_mutex_lock(&FGFileList::Internal_FGFileList::msBroadcastSerializer);

  // Thread about to die; signal master parent thread
  // Only signal when parent is waiting for it (parent unlocks mutex when
  // it starts to wait for children to exit)
  pthread_mutex_lock(&FGFileList::Internal_FGFileList::msChildExitMutex);

  pthread_cond_broadcast(&FGFileList::Internal_FGFileList::msChildExitCondition);
  // Parent is trying to lock the mutex now - so it is blocked until we
  // unlock the mutex
  pthread_mutex_unlock(&FGFileList::Internal_FGFileList::msChildExitMutex);

  // However! There is a race condition. When we unlock, other children
  // are competing with the parent for the processor. If another child
  // runs first it will broadcast the condition before the parent has
  // acknowledged it and a broadcast will be lost

  // Hence the broadcast serializing mutex

  return 0;
}
