///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// Copyright (C) 2006 by Intel Corporation and Carnegie Mellon University    //
// Contacts: casey.j.helfrich @ intel.com                                    //
//           bdr @ cs.cmu.edu                                                //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

#include <iostream>
#include <errno.h>
#include <string.h>

#include "Catom.hxx"
#include "CatomSim.hxx"
#include "SmartYield.hxx"
#include "CatomWorld.hxx"
#include "TestMessaging.hxx"
#include "Util.hxx"

using namespace std;

// The pointer to the main CatomWorld data structure
extern CatomWorld *worldPtr;

// flag signifying whether loop is done
// set by DPRSim main loop
extern bool isComplete;
extern bool NC_AllThreads;
extern pthread_mutex_t NC_lock;

// global yield
// used by catom threads and master thread for synchronization
extern pt_yield yield;

//////////////////////////////////////////////////////////////////////////////
// catomFunction
//
// This is the wrapper function that launches all of the CodeModules
// and provides for launch of messaging.

void *catomFunction(void *vargp) {
  
  Catom* hostCatom = (Catom *)vargp;
  CatomSim* catomSim = worldPtr->catomHash[hostCatom->getID()];
  Feature* featureMap = hostCatom->getFeatureMap();
  
  Assert(thisCatom() == hostCatom, "catom ptr not ok");
  Assert(thisCatomSim() == catomSim, "sim ptr not ok");

  // debugMsg("Before test: %d == %d\n", catomSim->okIndex, okToRunIndex);
  // if (catomSim->okIndex != okToRunIndex) return NULL;

  if DPR_CATOMCODE_DEBUG 
    cout << "Entered CatomFunction() for Catom " 
				 << hostCatom->getID() << endl;
  
  ///////////////////////////////////////////////////
  // Step 1: Notify hardware resources of new tick //
  ///////////////////////////////////////////////////
  
  // network adapters
  for(featureID i = 1; i <= NUM_FEATURES; i++) {
    
    if DPR_CATOMCODE_DEBUG 
      cout << "Reached Step 1 for Feature " << i << endl;
    
    featureMap[i].getNetworkAdapter()->newTick();
  }

  // mailbox manager
  hostCatom->mailboxManager.newTick();

  // speculation manager
  hostCatom->speculationManager.newTick();
  
  /////////////////////////////////////////////////
  // Step 2: Allow software to run new tick code //
  /////////////////////////////////////////////////
  
  Assert(thisCatom() == hostCatom, "step 2 failed");

  if DPR_CATOMCODE_DEBUG 
    cout << "Reached Step 2..." << endl;
  
  vector<CodeModule*>::iterator moduleIterator = catomSim->codeModules.begin();
  while(moduleIterator != catomSim->codeModules.end()) {
    (*moduleIterator)->newTick();
    moduleIterator++;
  }
  
  //////////////////////////////////////////////
  // Step 3: Handle message dispatch for tick //
  //////////////////////////////////////////////
  
  Assert(thisCatom() == hostCatom, "step 3 failed");

  if DPR_CATOMCODE_DEBUG 
    cout << "Reached Step 3..." << endl;
  
  while(hostCatom->mailboxManager.handleMessage())
    ;
  
  /////////////////////////////////////////////////
  // Step 4: Allow software to run end tick code //
  /////////////////////////////////////////////////
  
  Assert(thisCatom() == hostCatom, "step 4 failed");

  if DPR_CATOMCODE_DEBUG 
    cout << "Reached Step 4..." << endl;
  
  moduleIterator = catomSim->codeModules.begin();
  while(moduleIterator != catomSim->codeModules.end()) {
    (*moduleIterator)->endTick();
    moduleIterator++;
  }

  // int nextRun = okToRunIndex+1;
  // if (nextRun == wrapArdoundIndex) oktoRunIndex = 0; else okToRunIndex = nextRun;
  // debugMsg("After Running: %d of %d\n", okToRunIndex, wrapAroundIndex);
  
  return NULL;
}

/////////////////////////////////////////////////////////////////////////////
// catomMain()
//
// This is the wrapper function that takes care of the synchronization.  
// This function is used to created the catom thread.  When it is first 
// created, it must first yield (to wait for other catoms to start at the 
// same time).  Then, it enters the loop, and it only breaks if it has 
// been notified by the main loop that the loop has finished.  Each 
// iteration signifies one timestep; the one-timestep catomFunction is 
// called, and once the program finishes, it yields for the main thread 
// to work

#ifdef __THREADED__

void *catomMain(void *vargp) {
  Catom* hostCatom = (Catom *)vargp;
  
  if DPR_THREADING_DEBUG
    cout << "Thread #" << pthread_self() << " yielding (1st)..." << endl;
  
  // yield first to synchronize starts
  do_yield(&yield);
  if (NC_AllThreads) pthread_mutex_lock( &NC_lock );
  
  // call simulationStart()
  CatomSim* catomSim = worldPtr->catomHash[hostCatom->getID()];

  // create thread specific data ptr to catom/catomsim
  if (pthread_setspecific(_tcatom, hostCatom))
    die("Cannot set _tcatom");
  if (pthread_setspecific(_tcatomsim, worldPtr->catomHash[hostCatom->getID()]))
    die("Cannot set _tcatomsim");

  vector<CodeModule*>::iterator moduleIterator = catomSim->codeModules.begin();
  while(moduleIterator != catomSim->codeModules.end()) {
    (*moduleIterator)->simulationStart();
    moduleIterator++;
  }
  
  if (worldPtr->threadStart)
    worldPtr->threadStart();
  
  if (!isComplete) {
    if (NC_AllThreads) pthread_mutex_unlock( &NC_lock );
    // yield to syncrhonize after simulationStart() calls
    do_yield(&yield);
    // only oracles run here
    do_yield(&yield);
    if (NC_AllThreads) pthread_mutex_lock( &NC_lock );
  }
  
  // work until notified that main loop is done (timestep has reached 
  // maximum point)
  while(1) {
    
    // check if main loop done
    if(isComplete) {
      break;
    }
    
    if DPR_THREADING_DEBUG
      cout << "Thread #" << pthread_self() 
	   << " starting work..." << endl;
    
    // call custom program to work
    catomFunction(vargp);
    
    if DPR_THREADING_DEBUG
      cout << "Thread #" << pthread_self() 
	   << "work done, start yielding..." << endl;
    
    if (NC_AllThreads) pthread_mutex_unlock( &NC_lock );
    // one timestep over, yield for master
    do_yield(&yield);
    // only oracles run here
    do_yield(&yield);
    if (NC_AllThreads) pthread_mutex_lock( &NC_lock );
  }
  
  // call simulationEnd()
  moduleIterator = catomSim->codeModules.begin();
  while(moduleIterator != catomSim->codeModules.end()) {
    (*moduleIterator)->simulationEnd();
    moduleIterator++;
  }
  
  hostCatom->speculationManager.simulationEnd();
  if (NC_AllThreads) pthread_mutex_unlock( &NC_lock );
  
  return NULL;
}

#endif // __THREADED__
