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



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

#include "PR.h"

//=========================  Local Declarations  ========================
inline PRProcess * 
pickAProcess( AnimSlot *slot );
inline bool32      
assignWork( PRProcess *process, AnimSlot *slot );

inline void 
PRHandle__CreateTask( PRReqst *req, SlaveVP *slave );
inline void 
PRHandle__EndTask(    PRReqst *req, SlaveVP *slave );
inline void 
PRHandle__CreateSlave(PRReqst *req, SlaveVP *slave );
inline void        
PRHandle__EndSlave(   PRReqst *req, SlaveVP *slave );

inline void
PRHandle__LangShutdown( PRReqst *req, SlaveVP *requestingSlv );

inline void
handleMakeProbe( PRServiceReq *langReq, PRLangEnv *protoLangEnv );
inline void
handleThrowException( PRServiceReq *langReq, PRLangEnv *protoLangEnv );

void
debug_print_req(AnimSlot *slot, PRReqst *req);

//===========================================================================

/*Note: there used to be a coreController that was another animation 
 * layer below both the masterVP and the slaveVPs.. in that case, the
 * masterVP was a virtual processor whose processor-state was the same
 * as a slaveVP's processor sate, both implemented as a SlaveVP struct.
 * Have removed that, and
 * changed the masterVP implementation.  Instead of being a special  version 
 * of a proto-runtime virtual processor, using the slaveVP stuct, the 
 * Master "virtual processor" is now implemented as a pthread pinned to
 * a physical core.
 */

/*This is the behavior of the Master.  The physical processor switches
 * between animating the master, and animating a slave.  When a slave
 * suspends, the PR "suspend" primitive switches the physical core over
 * to animating the masterVP, which is implemented as a pinned pthread.
 * This function is the behavior of that masterVP.
 *This function's job is to manage processing
 * requests and to trigger assignment of new work to the physical core,
 * and to manage sharing the core among processes.
 */
inline
bool32
masterFunction( AnimSlot  *slot )
 {    //Scan the animation slots
   int32           magicNumber;
   SlaveVP        *slave;
   PRLangEnv      *langEnv;
   PRReqst        *req;
   PRProcess      *process;
   bool32          didAssignWork;

      //Check if newly-done slave in slot, which will need request handled
      //NOTE: left over from when had a coreController & MasterVP managed
      // several slots
   if( slot->workIsDone )
    { slot->workIsDone = FALSE;
      slot->needsWorkAssigned = TRUE;
      
         //An Idle VP has no request to handle, so skip to assign..
      if( slot->slaveAssignedToSlot->typeOfVP != IdleVP && 
          slot->slaveAssignedToSlot->typeOfVP != ShutdownVP)
       {
               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;
         debug_print_req(slot, req);

            //If the requesting slave is a slot slave, and request is not
            // task-end, then turn it into a free task slave & continue
         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:      PRHandle__ServiceReq( 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 LangShutdown: PRHandle__LangShutdown( req, slave ); break;
            case Language: //normal lang request
             { magicNumber = req->langMagicNumber;
               langEnv = PR_PI__give_lang_env_for_slave( slave, magicNumber );
               (*req->handler)( req->langReq, slave, langEnv );
             }
          }
      
           MEAS__endReqHdlr;          
           HOLISTIC__Record_AppResponder_end;
       }//if not idleVP
      else if( slot->slaveAssignedToSlot->typeOfVP == ShutdownVP )
       {    //ShutdownVP used, essentially, as a flag, to cause terminate here
         PR_int__release_master_lock();
         terminateCoreCtlr( slot->slaveAssignedToSlot );
       }
    } //if have request to be handled

      //NOTE: IF statement is leftover from when master managed many slots
   didAssignWork = FALSE;
   if( slot->needsWorkAssigned ) //can probably remove IF, now that only one slot
    {
            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
      if( process != NULL )
       { didAssignWork =
           assignWork( process, slot );
       }
            HOLISTIC__Record_Assigner_end;

      if( !didAssignWork ) //if no work assigned, be sure idle slave is in slot
       { slot->slaveAssignedToSlot = _PRTopEnv->idleSlv[slot->coreSlotIsOn][0];
       }
//      fixme; //make into a loop that tries more processes if fails to assign
    }//if slot needs slave assigned
   
   return didAssignWork;
 }

/*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 )
 { int32           coreNum;

   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 **protoLangEnvsList, *protoLangEnv;
   protoLangEnvsList = process->protoLangEnvsList;
   numEnvs = process->numLangEnvs;
   for( envIdx = 0; envIdx < numEnvs; envIdx++ ) //keep langEnvs in hash & array
    { protoLangEnv = protoLangEnvsList[envIdx];
      if( protoLangEnv->numReadyWork > 0 )
       { bool32 
         didAssignWork =
          (*protoLangEnv->workAssigner)( PR_int__give_lang_env(protoLangEnv), slot ); //assigner calls PR to put slave/task into slot

         if(didAssignWork)
          { protoLangEnv->numReadyWork -= 1;
            if( protoLangEnv->numReadyWork == 0 )
             { process->numEnvsWithWork -= 1;
             }
            goto ReturnAfterAssigningWork; //quit for-loop, 'cause found work
          }
         else
            goto NoWork; //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][0];  //only one slot now, so [0]
    
         //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;
    }
 }


//=================================
//===
//=
/*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     *protoMetaTask;
   PRProcess      *process;
   PRLangEnv      *protoLangEnv;
   void           *task;
                
   process = slave->processSlaveIsIn;
   
   protoLangEnv = PR_int__give_proto_lang_env_for_slave( 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 that PR can then put stuff into the prolog
   //typedef void * (*CreateHandler)( void *, SlaveVP *, void * ); //req, slv, langEnv
   //
   task = 
      (*req->createHdlr)(req->langReq, slave, PR_int__give_lang_env(protoLangEnv) );
   protoMetaTask = PR_int__give_prolog_of_lang_meta_task( task );
   protoMetaTask->ID         = req->ID; //may be NULL
   protoMetaTask->topLevelFn = req->topLevelFn;
   protoMetaTask->initData   = req->initData;
   protoMetaTask->processTaskIsIn = process;
           
   process->numLiveTasks += 1;
   protoLangEnv->numLiveWork += 1;  //used in wait statements -- small added overhead
   
   return;
 }

/*When a task ends, have two scenarios: 1) task ran to completion, or 2) task
 * has been 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;
   PRLangEnv  *protoLangEnv;
   PRProcess  *process;
   void       *langMetaTask;
   
   process = requestingSlv->processSlaveIsIn;
   langEnv = PR_int__give_lang_env_of_req( req, requestingSlv ); //magic num in req
   protoLangEnv = PR_int__give_proto_lang_env( langEnv );
   
   langMetaTask = PR_int__give_lang_meta_task_from_slave( requestingSlv, req->langMagicNumber);
   
   //Do the langlet's request handler
   //Want to keep PR structs hidden from plugin, so extract langReq..
   //This is supposed to free any langlet-malloc'd mem, including meta task 
   (*req->handler)( req->langReq, requestingSlv, langEnv );

   protoLangEnv->numLiveWork -= 1;  //used in wait statements -- small added overhead
   if( protoLangEnv->numLiveWork == 0 &&
       numInPrivQ( protoLangEnv->waitingForWorkToEndQ ) > 0 )
    { SlaveVP *
      waitingSlave = readPrivQ( protoLangEnv->waitingForWorkToEndQ );
         //can't resume into langlet that just ended its last work!
         // and don't have env that the waiter was created in, so resume
         // into PRServ env..
      void *
      resumeEnv = PR_PI__give_lang_env_from_process( process, PRServ_MAGIC_NUMBER );
      while( waitingSlave != NULL )
       {    //resume a slave that was waiting for work in this env to finish
         PR_PI__make_slave_ready( waitingSlave, resumeEnv );
            //get next waiting slave, repeat..
         waitingSlave = readPrivQ( protoLangEnv->waitingForWorkToEndQ );
       }
    }
   

   //Now that the langlet's done with it, recycle the slave if it's a freeTaskSlv
   if( requestingSlv->typeOfVP == FreeTaskSlv )
      PR_int__recycle_slaveVP( requestingSlv ); //Doesn't decr num live slaves
   
   process->numLiveTasks -= 1;
   //NOTE: end-task is unrelated to work available (just in case wondering)
  
      //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__end_process_normally( process );
    }
 }



/*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( slave, req->langMagicNumber );
      
   //create handler, or a future request handler will call PR_PI__make_slave_ready
   // which will in turn handle updating which langlets and which processes have
   // work available.
   //NOTE: 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; //not same as work ready!
   protoLangEnv->numLiveWork += 1;  //used in wait statements -- small added overhead
 }

/*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( slave, slave->request->langMagicNumber );
   
   if(req->handler != NULL)
      (*req->handler)( req->langReq, slave, PR_int__give_lang_env(protoLangEnv) );
   
   protoLangEnv->numLiveWork -= 1;  //used in wait statements -- small added overhead
   if( protoLangEnv->numLiveWork == 0 &&
       numInPrivQ( protoLangEnv->waitingForWorkToEndQ ) > 0 )
    { SlaveVP *
      waitingSlave = readPrivQ( protoLangEnv->waitingForWorkToEndQ );
         //can't resume into langlet that just ended its last work!
         // and don't have env that the waiter was created in, so resume
         // into PRServ env..
      void *
      resumeEnv = PR_PI__give_lang_env_from_process( process, PRServ_MAGIC_NUMBER );
      while( waitingSlave != NULL )
       {    //resume a slave that was waiting for work in this env to finish
         PR_PI__make_slave_ready( waitingSlave, resumeEnv );
            //get next waiting slave, repeat..
         waitingSlave = readPrivQ( protoLangEnv->waitingForWorkToEndQ );
       }
    }
   
   process->numLiveGenericSlvs -= 1; 
   PR_int__recycle_slaveVP( slave );
   //NOTE: dissipate is unrelated to work available (just in case wondering)

      //check End Of Process Condition
   if( process->numLiveTasks == 0 &&
       process->numLiveGenericSlvs == 0 )
      PR_SS__end_process_normally( process );
 }

//=======================
//===
//=
/*Langlet shutdown triggers this, which calls the registered shutdown 
 * handler for the langlet, and removes the lang's env from the process
 */
inline
void
PRHandle__LangShutdown( PRReqst *req, SlaveVP *requestingSlv )
 { void       *langEnv;
   PRLangEnv  *protoLangEnv;
   PRProcess  *process;
   
   process = requestingSlv->processSlaveIsIn;
   protoLangEnv = PR_int__give_proto_lang_env_from_process( process, req->langMagicNumber );
   langEnv = PR_int__give_lang_env( protoLangEnv );
   
      //call the langlet's registered handler
   (*protoLangEnv->shutdownHdlr)( langEnv );
   
   PR_int__remove_lang_env_from_process_and_free( langEnv ); //removes from process and frees
   
   PR_PI__resume_slave_in_PRServ( requestingSlv );
 }


/*This is for OS requests and PR infrastructure requests, which are not
 * part of the PRServ language -- this is for things that have to be in the
 * infrastructure of PR itself, such as I/O requests, which have to go through
 * pthreads inside the core controller..
 * 
 *As of Jan 2013, doesn't do much of anything..
 */
void inline
PRHandle__ServiceReq( SlaveVP *requestingSlv )
 { PRReqst      *req;
   PRServiceReq *langReq;
   PRLangEnv    *protoLangEnv;
   int32         magicNumber;
   
 
   req = requestingSlv->request;
 
   magicNumber = req->langMagicNumber;
   protoLangEnv = PR_int__give_proto_lang_env_for_slave( requestingSlv, magicNumber );

   langReq = PR_PI__take_lang_reqst_from(req);
   if( langReq == NULL ) return;
   switch( langReq->reqType )  //lang handlers are all in other file
    {
      case make_probe:      handleMakeProbe(   langReq, protoLangEnv );
         break;
      case throw_excp:  handleThrowException(  langReq, protoLangEnv );
         break;
    }
 }


/*These handlers are special -- they don't belong to a language, because they
 * deal with things internal to PR, so put them here..
 */
inline
void
handleMakeProbe( PRServiceReq *langReq, PRLangEnv *protoLangEnv )
 { IntervalProbe *newProbe;

   newProbe          = PR_int__malloc( sizeof(IntervalProbe) );
   newProbe->nameStr = PR_int__strDup( langReq->nameStr );
   newProbe->hist    = NULL;
   newProbe->schedChoiceWasRecorded = FALSE;

      //This runs in masterVP, so no race-condition worries
      //BUG: move to process
   newProbe->probeID =
            addToDynArray( newProbe, _PRTopEnv->dynIntervalProbesInfo );

   langReq->requestingSlv->dataRetFromReq = newProbe;

   (*protoLangEnv->makeSlaveReadyFn)( langReq->requestingSlv, PR_int__give_lang_env(protoLangEnv) );
 }

inline
void
handleThrowException( PRServiceReq *langReq, PRLangEnv *protoLangEnv )
 {
   PR_int__throw_exception(  langReq->msgStr, langReq->requestingSlv, langReq->exceptionData );
   
   (*protoLangEnv->makeSlaveReadyFn)( langReq->requestingSlv, PR_int__give_lang_env(protoLangEnv) );
 }

void
debug_print_req(AnimSlot *slot, PRReqst *req)
 {
   if(dbgMaster)
       { printf("top handle request: %d | reqType: ", slot->coreSlotIsOn );
         switch(req->reqType)
          { case TaskCreate: printf("TaskCreate \n"); break;
            case TaskEnd: printf("TaskEnd \n"); break;
            case   SlvCreate: printf("SlvCreate \n"); break;
            case SlvDissipate: printf("SlvDissipate \n"); break;
            case Language: printf("Language \n"); break;
            case Service: printf("Service \n"); break;
            case Hardware: printf("Hardware \n"); break;
            case IO: printf("IO \n"); break;
            case OSCall: printf("OSCall \n"); break;
            case LangShutdown: printf("LangShutdown \n"); break;
            case ProcessEnd: printf("ProcessEnd \n"); break;
            case PRShutdown: printf("PRShutdown \n"); break;
          } 
         fflush(stdin);
       }
 }


