/*
 * Copyright 2012  OpenSourceResearchInstitute.org
 * 
 * Licensed under BSD
 */



#include <stdio.h>
#include <stddef.h>

#include "PR.h"
#include "VSs_impl/VSs.h"

/*
void        PRHandle_CreateTask_SL(SlaveVP *slave);

void        PRHandle_CreateSlave_SL(SlaveVP *slave);
void        PRHandle_Dissipate_SL(SlaveVP *slave);
void        PR_int__handle_PRServiceReq_SL(SlaveVP *slave);
*/
inline void PRHandle_CreateTask( PRReqst *req, SlaveVP *slave );
inline void PRHandle_EndTask(    PRReqst *req, SlaveVP *slave );
inline void PRHandle_CreateSlave(PRReqst *req, SlaveVP *slave );
void        PRHandle_EndSlave(  PRReqst *req, SlaveVP *slave );


//inline void  masterFunction_SingleLang( PRLangEnv *protoLangEnv, AnimSlot *slot );
inline PRProcess * pickAProcess( AnimSlot *slot );
inline bool32 assignWork( PRProcess *process, AnimSlot *slot );

/*The animationMaster embodies most of the animator of the language.  The
 * animator is what emodies the behavior of language constructs. 
 * As such, it is the animationMaster, in combination with the plugin
 * functions, that make the language constructs do their behavior.   
 * 
 *Within the code, this is the top-level-function of the masterVPs, and
 * runs when the coreController has no more slave VPs.  It's job is to
 * refill the animation slots with slaves that have work.
 *
 *There are multiple versions of the master, each tuned to a specific 
 * combination of modes.  This keeps the master simple, with reduced overhead,
 * when the application is not using the extra complexity.
 * 
 *As of Sept 2012, the versions available will be:
 * 1) Single langauge, which only exposes slaves (such as SSR or Vthread)
 * 2) Single language, which only exposes tasks  (such as pure dataflow)
 * 3) Single language, which exposes both (like Cilk, StarSs, and OpenMP)
 * 4) Multi-language, which always assumes both tasks and slaves
 * 5) Multi-language and multi-process, which also assumes both tasks and slaves
 *
 * 
 *
 */

//This version of the master selects one of three loops, depending upon
// whether stand-alone single language (just slaves), or standalone with
// tasks, or multi-lang (implies multi-process)
void animationMaster( void *_environment, SlaveVP *masterVP )
 { 
   TopEnv         *masterEnv = (TopEnv *)_environment;
   int32           slotIdx;
   AnimSlot       *currSlot;
      //Used while scanning and filling animation slots
   AnimSlot      **animSlots;
   
      //Local copies, for performance
   int32           thisCoresIdx;
   
   //======================== Initializations ========================
   thisCoresIdx     = masterVP->coreAnimatedBy;
   animSlots        = masterEnv->allAnimSlots[thisCoresIdx];
      
      HOLISTIC__Insert_Master_Global_Vars;
   
   //======================== animationMaster ========================
   //Have three different modes, and the master behavior is different for
   // each, so jump to the loop that corresponds to the mode.
   //
   while(1)
    {       MEAS__Capture_Pre_Master_Point
      for( slotIdx = 0; slotIdx < NUM_ANIM_SLOTS; slotIdx++)
       {
         currSlot = animSlots[ slotIdx ];

         masterFunction( currSlot );
       }
            MEAS__Capture_Post_Master_Point;
      masterSwitchToCoreCtlr( masterVP ); //returns when ctlr switches back to master
      flushRegisters();
    }
 }


inline
bool32
masterFunction( AnimSlot  *slot )
 {    //Scan the animation slots
   int32           magicNumber;
   SlaveVP        *slave;
   PRLangEnv      *langEnv;
   PRReqst        *req;
   PRProcess      *process;
   bool32          foundWork;

      //Check if newly-done slave in slot, which will need request handled
   if( slot->workIsDone )
    { slot->workIsDone = FALSE;
      slot->needsWorkAssigned = TRUE;

            HOLISTIC__Record_AppResponder_start; //TODO: update to check which process for each slot
            MEAS__startReqHdlr;


         //process the request made by the slave (held inside slave struc)
      slave = slot->slaveAssignedToSlot;
      req = slave->request;

         //If the requesting slave is a slot slave, and request is not
         // task-end, then turn it into a free task slave. 
      if( slave->typeOfVP == SlotTaskSlv && req->reqType != TaskEnd )
         PR_int__replace_with_new_slot_slv( slave );

      //Handle task create and end first -- they're special cases..
      switch( req->reqType )
       { case TaskEnd: 
          { //do PR handler, which calls lang's hdlr and does recycle of
            // free task slave if needed -- PR handler checks for free task Slv
            PRHandle_EndTask( req, slave );                          break;
          }
         case TaskCreate:
          { //Do PR's create-task handler, which calls the lang's hdlr
            // PR handler checks for free task Slv
            PRHandle_CreateTask( req, slave );                       break;
          }
         case SlvCreate:    PRHandle_CreateSlave( req, slave );      break;
         case SlvDissipate: PRHandle_EndSlave( req, slave );         break;
         case Service:      PR_int__handle_PRServiceReq( slave );    break; //resumes into Service lang env
         case Hardware: //for future expansion
         case IO:       //for future expansion
         case OSCall:   //for future expansion
            PR_int__throw_exception("Not implemented", slave, NULL); break;
         case Language: //normal lang request
          { magicNumber = req->langMagicNumber;
            langEnv = PR_PI__give_lang_env_for( slave, magicNumber );
            (*req->handler)( req->langReq, slave, langEnv );
          }
       }

           MEAS__endReqHdlr;          
           HOLISTIC__Record_AppResponder_end;
    } //if have request to be handled

   if( slot->needsWorkAssigned )
    {
            HOLISTIC__Record_Assigner_start;

         //Pick a process to get this slot
      process = pickAProcess( slot );

         //Scan lang environs, looking for langEnv with ready work.
         // call the Assigner for that lang Env, to get a slave for the slot
      foundWork =
      assignWork( process, slot );

            HOLISTIC__Record_Assigner_end;
    }//if slot needs slave assigned
   
   return foundWork;
 }

/*When several processes exist, use some pattern for picking one to give
 * the animation slot to.
 *First, it has to be a process that has work available.
 *For now, just do a round-robin
 */
inline
PRProcess *
pickAProcess( AnimSlot *slot )
 { int32 idx;
   PRProcess *process;
 
   for( idx = _PRTopEnv->currProcessIdx; idx < _PRTopEnv->numProcesses; idx++)
    {
      process = _PRTopEnv->processes[ idx ];
      if( process->numEnvsWithWork != 0 )
       { _PRTopEnv->currProcessIdx = idx;
         return process;
       }
    }
   for( idx = 0; idx < _PRTopEnv->currProcessIdx; idx++)
    {
      process = _PRTopEnv->processes[ idx ];
      if( process->numEnvsWithWork != 0 )
       { _PRTopEnv->currProcessIdx = idx;
         return process;
       }
    }
      //none found
   return NULL;
 }

/*This does:
 * 1) searches the language environments for one with work ready
 *    if finds one, asks its assigner to return work
 * 2) checks what kind of work: new task, resuming task, resuming slave
 *    if new task, gets the slot slave and assigns task to it and returns slave
 *    else, gets the slave attached to the metaTask and returns that.
 * 3) if no work found, then prune former task slaves waiting to be recycled.
 *    If no work and no slaves to prune, check for shutdown conditions.
 * 
 * language env keeps its own work in its own structures, and has its own
 *  assigner.  It chooses 
 * However, include a switch that switches-in an override assigner, which
 *  sees all the work in all the language env's.  This is most likely  
 *  generated by static tools and included in the executable.  That means it
 *  has to be called via a registered pointer from here.  The idea is that
 *  the static tools know which languages are grouped together.. and the
 *  override enables them to generate a custom assigner that uses info from
 *  all the languages in a unified way..  Don't really expect this to happen,
 *  but am making it possible.
 */
inline 
bool32
assignWork( PRProcess *process, AnimSlot *slot )
 { SlaveVP        *returnSlv;
   int32           coreNum, slotNum;
   PRMetaTask     *assignedMetaTask;

   coreNum = slot->coreSlotIsOn;
   
   if( process->overrideAssigner != NULL )
    { if( process->numEnvsWithWork != 0 )
       {  (*process->overrideAssigner)( process, slot ); //calls PR fn that inserts work into slot
         goto ReturnAfterAssigningWork; //quit for-loop, cause found work
       }
      else
         goto NoWork;
    }
   
      //If here, then no override assigner, so search language envs for work
   int32 envIdx, numEnvs; PRLangEnv **langEnvsList, *langEnv;
   langEnvsList = process->langEnvsList;
   numEnvs = process->numLangEnvs;
   for( envIdx = 0; envIdx < numEnvs; envIdx++ ) //keep langEnvs in hash & array
    { langEnv = langEnvsList[envIdx];
      if( langEnv->hasWork )
       { (*langEnv->workAssigner)( langEnv, slot ); //assigner calls PR to put slave/task into slot
         goto ReturnAfterAssigningWork; //quit for-loop, cause found work
         //NOTE: bad search alg -- should start where left off, then wrap around
       }
    }
   //If reach here, then have searched all langEnv's & none have work..
   
 NoWork:     //No work, if end up here..
    { 
   #ifdef HOLISTIC__TURN_ON_OBSERVE_UCC
      returnSlv = process->idleSlv[coreNum][slotNum]; 
    
         //things that would normally happen in resume(), but idle VPs
         // never go there
      returnSlv->numTimesAssignedToASlot++; //gives each idle unit a unique ID
      Unit newU;
      newU.vp = returnSlv->slaveNum;
      newU.task = returnSlv->numTimesAssignedToASlot;
      addToListOfArrays(Unit,newU,process->unitList);

      if (returnSlv->numTimesAssignedToASlot > 1) //make a dependency from prev idle unit
       { Dependency newD;             // to this one
         newD.from_vp = returnSlv->slaveNum;
         newD.from_task = returnSlv->numTimesAssignedToASlot - 1;
         newD.to_vp = returnSlv->slaveNum;
         newD.to_task = returnSlv->numTimesAssignedToASlot;
         addToListOfArrays(Dependency, newD ,process->ctlDependenciesList);  
       }
   #endif
            HOLISTIC__Record_Assigner_end;
      return FALSE;
    }
 
 ReturnAfterAssigningWork:  //All paths goto here.. to provide single point for holistic..
    {
            HOLISTIC__Record_Assigner_end;
      return TRUE;
    }
 }



/*This is first thing called when creating a slave..  it hands off to the 
 * langlet's creator, then adds updates of its own..
 * 
 *There's a question of things like lang data, meta tasks, and such..
 *In creator, only PR related things happen, and things for the langlet whose
 * creator construct was used.
 * 
 *Other langlets still get a chance to create langData -- but by registering a
 * "createLangData" handler in the langEnv.  When a construct  of the langlet
 * calls "PR__give_lang_data()", if there is no langData for that langlet,
 * the PR will call the creator in the langlet's langEnv, place whatever it
 * makes as the langData in that slave for that langlet, and return that langData
 *
 *So, as far as counting things, a langlet is only allowed to count creation
 * of slaves it creates itself..  may have to change this later.. add a way for
 * langlet to register a trigger Fn called each time a slave gets created.. 
 * need more experience with what langlets will do at create time..  think Cilk
 * has interesting create behavior..  not sure how that will differ in light
 * of true tasks and langlet approach.  Look at it after all done and start
 * modifying the langs to be langlets..
 * 
 *PR itself needs to create the slave, then update numLiveSlaves in process,
 * copy processID from requestor to newly created
 */
inline
void
PRHandle_CreateSlave( PRReqst *req, SlaveVP *slave )
 { SlaveVP   *newSlv;
   PRProcess *process;
   PRLangEnv *protoLangEnv;
 
   process = slave->processSlaveIsIn;
   protoLangEnv = PR_int__give_proto_lang_env_for_slave__ML( slave, req->langMagicNumber );
   
//   newSlv  = PR_int__create_slave( req->topLevelFn, req->initData );
   
   //create slv has diff prototype than standard reqst hdlr
   newSlv = 
      (*req->createHdlr)(req->langReq, slave, PR_int__give_lang_env(protoLangEnv)); 
   
   newSlv->typeOfVP = GenericSlv;
   newSlv->processSlaveIsIn = process;
   newSlv->ID = req->ID;
   process->numLiveGenericSlvs += 1;
 }

/*The dissipate handler has to, update the number of slaves of the type, within
 * the process, and call the langlet handler linked into the request,
 * and after that returns, then call the PR function that frees the slave state
 * (or recycles the slave).
 * 
 *The PR function that frees the slave state has to also free all of the
 * langData in the slave..  or else reset all of the langDatas.. by, say, marking
 * them, then in PR__give_langData( magicNum ) call the langlet registered
 * "resetLangData" Fn.
 */
inline
void
PRHandle_EndSlave( PRReqst *req, SlaveVP *slave )
 { PRProcess *process;
   PRLangEnv *protoLangEnv;
   
   process = slave->processSlaveIsIn;
   
      //do the language's dissipate handler
   protoLangEnv = PR_int__give_proto_lang_env_for_slave__ML( slave, slave->request->langMagicNumber );
   
   if(req->handler != NULL)
      (*req->handler)( req->langReq, slave, PR_int__give_lang_env(protoLangEnv) );
   
   process->numLiveGenericSlvs -= 1;
   PR_int__recycle_slave__ML( slave );
  
      //check End Of Process Condition
   if( process->numLiveTasks == 0 &&
       process->numLiveGenericSlvs == 0 )
      PR_SS__shutdown_process__ML( process );
 }

/*Create task is a special form, that has PR behavior in addition to plugin
 * behavior.  Master calls this first, and it then calls the plugin's
 * create task handler.
 * 
 *Note: the requesting slave must be either generic slave or free task slave
 */
inline
void
PRHandle_CreateTask( PRReqst *req, SlaveVP *slave )
 { PRMetaTask     *metaTask;
   PRProcess      *process;
   PRLangEnv      *protoLangEnv;
   void           *task;
                
   process = slave->processSlaveIsIn;
   
   protoLangEnv = PR_int__give_proto_lang_env_for_slave__ML( slave, 
                                                        req->langMagicNumber );
   
   //Do the langlet's create-task handler, which keeps the task
   // inside the langlet's lang env, but returns the langMetaTask
   // so PR can put stuff into the prolog
   task = 
      (*req->createHdlr)(req->langReq, slave, PR_int__give_lang_env(protoLangEnv) );
   metaTask = PR_int__give_prolog_of_task( task );
   metaTask->ID         = req->ID; //may be NULL
   metaTask->topLevelFn = req->topLevelFn;
   metaTask->initData   = req->initData;
           
   process->numLiveTasks += 1;

   return;
 }

/*When a task ends, are two scenarios: 1) task ran to completion, or 2) task
 * suspended at some point in its code.
 *For 1, just decr count of live tasks (and check for end condition) -- the
 * master loop will decide what goes into the slot freed up by this task end,
 * so, here, don't worry about assigning a new task to the slot slave.
 *For 2, the task's slot slave has been converted to a free task slave, which
 * now has nothing more to do, so send it to the recycle Q (which includes
 * freeing all the langData and meta task structs alloc'd for it).  Then
 * decrement the live task count and check end condition.
 * 
 *PR has to update count of live tasks, and check end of process condition.
 * The "main" can invoke constructs that wait for a process to end, so when
 * end detected, have to resume what's waiting..
 *Thing is, that wait involves the main OS thread.  That means
 * PR internals have to do OS thread signaling.  Want to do that in the
 * core controller, which has the original stack of an OS thread.  So the
 * end process handling happens in the core controller.
 * 
 *So here, when detect process end, signal to the core controller, which will
 * then do the condition variable notify to the OS thread that's waiting.
 * 
 *Note: slave may be either a slot slave or a free task slave. 
 */
inline 
void
PRHandle_EndTask( PRReqst *req, SlaveVP *requestingSlv )
 { void       *langEnv;
   PRProcess  *process;
   void       *langMetaTask;
   
   langEnv = PR_int__give_lang_env_of_req__ML( req, requestingSlv ); //magic num in req
   langMetaTask = PR_int__give_lang_meta_task_from_slave__ML( requestingSlv, req->langMagicNumber);
   
   //Do the langlet's request handler
   //Want to keep PR structs hidden from plugin, so extract langReq..
   (*req->handler)( req->langReq, requestingSlv, langEnv );
   
   //Now that the langlet's done with it, recycle the slave if it's a freeTaskSlv
   if( requestingSlv->typeOfVP == FreeTaskSlv )
      PR_int__recycle_slave__ML( requestingSlv );
   
   process->numLiveTasks -= 1;
  
      //check End Of Process Condition
   if( process->numLiveTasks == 0 &&
       process->numLiveGenericSlvs == 0 )
    { //Tell the core controller to do wakeup of any waiting OS thread
      PR_SS__shutdown_process__ML( process );
    }
 }

