/*
 * Copyright 2010  OpenSourceResearchInstitute
 *
 * Licensed under BSD
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <inttypes.h>
#include <sys/time.h>

#include "PR.h"


#define thdAttrs NULL


/* MEANING OF   WL  PI  SS  int
 * These indicate which places the function is safe to use.  They stand for:
 * WL: Wrapper Library
 * PI: Plugin 
 * SS: Startup and Shutdown
 * int: internal to the PR implementation
 */


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

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

/*Setup has two phases:
 * 1) Semantic layer first calls init_PR, which creates masterEnv, and puts
 *    the master Slv into the work-queue, ready for first "call"
 * 2) Semantic layer then does its own init, which creates the seed virt
 *    slave inside the semantic layer, ready to assign it when
 *    asked by the first run of the animationMaster.
 *
 *This part is bit weird because PR really wants to be "always there", and
 * have applications attach and detach..  for now, this PR is part of
 * the app, so the PR system starts up as part of running the app.
 *
 *The semantic layer is isolated from the PR internals by making the
 * semantic layer do setup to a state that it's ready with its
 * initial Slvs, ready to assign them to slots when the animationMaster
 * asks.  Without this pattern, the semantic layer's setup would
 * have to modify slots directly to assign the initial virt-procrs, and put
 * them into the readyToAnimateQ itself, breaking the isolation completely.
 *
 * 
 *The semantic layer creates the initial Slv(s), and adds its
 * own environment to masterEnv, and fills in the pointers to
 * the requestHandler and slaveAssigner plug-in functions
 */

//Check the comments above -- likely out of sync

/*This allocates PR data structures, populates the top environments.  After
 * this call, processes can be started.
 */
void
PR__start()
 {
   PR_SS__create_topEnv();
   
  #ifdef DEBUG__TURN_ON_SEQUENTIAL_MODE
   printf( "\n\n Running in SEQUENTIAL mode -- NUM_CORES: %d \n\n", NUM_CORES );
      //Only difference between version with an OS thread pinned to each core and
      // the sequential version of PR is PR__init_Seq, this, and coreCtlr_Seq.

      //Don't do anything here -- using main thread for all PR activity, so
      // do PR activity when main thread calls "wait for process to end"
  #else
   DEBUG__printf1(dbgInfra,"Offset of lock in masterEnv: %d ", (int32)offsetof(TopEnv,masterLock) );
   PR_SS__create_the_coreCtlr_OS_threads();

  #endif
 }


/*For now, this is ONLY called from the main thread -- seems this can be relaxed
 * at some point, but want to reduce complexity to get the first version working
 * so making this restriction for now..
 * 
 *It creates a seed slave, from the top-level fn and initial data passed into
 * this fn.
 *The only langlet in the created process is the default PRServ.  The rest
 * must be started up via calls  made by the seed VP's top-level fn (passed in
 * to this call).
 *That places the information about which langlets are used within the process
 * into the seed Fn of that process, where all the langlet start() calls are
 * made.
 * 
 *A process is represented by a structure that holds all the process-specific
 * information:
 *-] The hash-array containing the language environs of any langlets started
 *   inside the process.
 *-] Counter of num live slaves and num live tasks in the process
 * 
 */
PRProcess *
PR__create_process( BirthFnPtr seed_Fn, void *seedData )
 { SlaveVP    *seedSlv;
   PRProcess  *process;
   PRLangEnv **langEnvs, **langEnvsList;
      
      //This runs outside of the master lock, so use PR_WL form of malloc
   process = PR_WL__malloc( sizeof(PRProcess) );
   _PRTopEnv->processes[_PRTopEnv->numProcesses] = process;
   _PRTopEnv->numProcesses += 1;
   
   langEnvs     = 
      (PRLangEnv **)PR_int__make_collection_of_size( NUM_IN_COLLECTION );
   langEnvsList = PR_WL__malloc( NUM_IN_COLLECTION * sizeof(PRCollElem *) );
   process->langEnvs     = langEnvs;
   process->protoLangEnvsList = langEnvsList;
   process->numLangEnvs  = 0;
   process->hasWaitingToEnd = FALSE;

      //A Process starts with one slave, the seed slave
   seedSlv = PR_int__create_slaveVP( seed_Fn, seedData, process );
   seedSlv->typeOfVP           = SeedSlv;
   seedSlv->processSlaveIsIn   = process;
   process->numLiveGenericSlvs = 1; //count the  seed 
   process->numLiveTasks       = 0;
   
   PRServLangEnv * 
   servicesLangEnv = 
     PRServ__start(seedSlv);
   
      //resume seedVP into PR's built-in services language's lang env
   process->numEnvsWithWork = 0; //Seed is in PRServ lang env.. resume incrs this
   PR_PI__make_slave_ready( seedSlv, servicesLangEnv );
   

      //The first process created has to unblock the core controllers.
      // This is the "magic" that starts the activity of PR going.
   #ifdef DEBUG__TURN_ON_SEQUENTIAL_MODE
      //Do nothing here..  in sequential mode, are using the main thread, so
      // don't use it to do anything yet..  do the PR activity when main thread
      // calls "wait for process to end"
   #else
   if( _PRTopEnv->numProcesses == 1 )
    {
         //tell the core controller threads that a process is ready to be animated
         //get lock, to lock out any threads still starting up -- they'll see
         // that firstProcessReady is true before entering while loop, and so never
         // wait on the condition
      pthread_mutex_lock(     &suspendLock );
      _PRTopEnv->firstProcessReady = 1;
      pthread_mutex_unlock(   &suspendLock );
      pthread_cond_broadcast( &suspendCond );
    }
   #endif
   pthread_mutex_init( &(process->doneLock), NULL );
   pthread_cond_init(  &(process->doneCond), NULL );
   process->executionIsComplete = FALSE;
   
   return process;
 }

void
PR__end_seedVP( SlaveVP *seedSlv )
 {
   PR_WL__send_end_slave_req( NULL, (RequestHandler)&PRServ__handleDissipateSeed, seedSlv, 
                              PRServ_MAGIC_NUMBER );
 }

void
PR__end_process_from_inside( SlaveVP *seedSlv )
 {   
   PR_WL__send_lang_request( NULL, (RequestHandler)&PRServ__handle_end_process_from_inside,
                             seedSlv, PRServ_MAGIC_NUMBER );
 }



/*When all work in the process has completed, then return from this call.
 * The seedVP of the process may still exist, but it has no work, nor do any
 * other VPs..
 *The process must be shutdown via a separate call.  That shutdown frees the
 * process struct and bookkeeping structs.
 *First checks whether the process is done, if yes, calls the clean-up fn then
 * returns the result extracted from the PRProcess struct.
 *If process not done yet, then performs a wait (in a loop to be sure the
 * wakeup is not spurious, which can happen).  PR registers the wait, and upon
 * the process ending (last SlaveVP owned by it dissipates), then PR signals
 * this to wakeup.  This then calls the cleanup fn and returns the result.
 */
void *
PR__give_results_from_process_when_ready( PRProcess *process )
 { void *result;
      //First get the "ACK" lock, then do normal wait for signal, then release
      // ACK lock, to let end-process know it can free the process struct
   pthread_mutex_lock( &(process->doneAckLock) );
   
   pthread_mutex_lock( &(process->doneLock) );
   while( process->executionIsComplete != TRUE )
    {
      pthread_cond_wait( &(process->doneCond),
                         &(process->doneLock) );
    }
   pthread_mutex_unlock( &(process->doneLock) );
   result = process->resultToReturn;
   
      //now send "ACK" signal to process_end Fn, that it may proceed
   pthread_mutex_unlock( &(process->doneAckLock) ); 
      
   return result;
      //TODO: BUG? -- can process be created and end, before this acquires the
      // first lock?  Can see some rare code that creates a bunch, before getting
      // to waiting..  leave for now..  pain to fix..
 }


/*This should only be called from main.  It makes the OS thread that is animating
 * main to suspend until the process completes shutdown.
 */
void
PR__wait_for_process_to_end( PRProcess *process )
 { 
   process->hasWaitingToEnd = TRUE;
   
  #ifdef DEBUG__TURN_ON_SEQUENTIAL_MODE
      // call the one and only core ctlr (sequential version), in the main thread.
   if( process->executionIsComplete )
      return;
   else
    { coreCtlr_Seq( NULL );
      flushRegisters();  //Not sure why here, but leaving to be safe
      process->executionIsComplete = TRUE;
    }
  #else
      //This is called from main thread, so must use OS thread constructs to
      // force the main thread to block (suspend) until it is signalled that
      // the process is complete.  One issue is that the mutex and cond variable
      // are inside the process data struct, which is freed when the process
      // ends!
   pthread_mutex_lock( &(process->doneLock) );
   while( process->executionIsComplete != TRUE )
    {
      pthread_cond_wait( &(process->doneCond),
                         &(process->doneLock) );
    }
   pthread_mutex_unlock( &(process->doneLock) );
      //now send "ACK" signal to process_end Fn, that it may proceed
   pthread_mutex_unlock( &(process->doneAckLock) ); 
   
      //TODO: BUG? -- can process be created and end, before this acquires the
      // first lock?  Can see some rare code that creates a bunch, before getting
      // to waiting..  leave for now..  pain to fix..
  #endif
 }


void
PR__wait_for_all_activity_to_end()
 { 
  #ifdef DEBUG__TURN_ON_SEQUENTIAL_MODE
      //In sequential mode, can't reach this call unless all activity has
      // already completed, so just return.
   return;
  #else
   pthread_mutex_lock( &(_PRTopEnv->activityDoneLock) );
   while( !(_PRTopEnv->allActivityIsDone) )
    {
      pthread_cond_wait( &(_PRTopEnv->activityDoneCond),
                         &(_PRTopEnv->activityDoneLock) );
    }
   pthread_mutex_unlock( &(_PRTopEnv->activityDoneLock) );
  #endif
 }


/*This info is retrieved by PRServ's "give environ string" function
 *These Fn s are meant to be called from main, or possibly the seed slave. 
 */
void
PR__set_app_info( char *info )
 { int32 len;
   char *copy;
   len = strlen(info) +1;
   copy = PR_int__malloc(len);
   strcpy(copy, info);
   _PRTopEnv->metaInfo->appInfo = copy;
 }
void
PR__set_input_info( char *info )
 { int32 len;
   char *copy;
   len = strlen(info) +1;
   copy = PR_int__malloc(len);
   strcpy(copy, info);
   _PRTopEnv->metaInfo->inputInfo = copy;
 }




//==========================  SHUT DOWN  ===========================

/*This is called from the main thread, and causes PR's OS threads to stop
 * then cleans up any memory allocated by PR from the OS.
 * 
 *The main thread has a separate call it can use to wait for all work to
 * finish, so when this is called, it just shuts down, whether there's
 * unfinished work or not.
 * 
 *However, cores that are performing work won't see this shutdown until
 * they finish their current work-unit.
 */
void
PR__shutdown()
 { int32 coreIdx;
   
   PR_SS__shutdown_OS_threads();
   
      //wait for the OS threads to exit
   for( coreIdx=0; coreIdx < NUM_CORES; coreIdx++ )
    {
      pthread_join( coreCtlrThdHandles[coreIdx], NULL );
    }

      //Before getting rid of everything, print out any measurements made
   PR_SS__print_out_measurements();

      //free all memory allocated from the OS
   PR_SS__cleanup_at_end_of_shutdown();
 }



