/*
 * Copyright 2010  OpenSourceStewardshipFoundation
 *
 * Licensed under BSD
 */

#include <stdio.h>
#include <malloc.h>
#include <sys/time.h>

#include "VMS.h"
#include "Queue_impl/BlockingQueue.h"
#include "Histogram/Histogram.h"


//================================ STATS ====================================

inline TSCount getTSCount()
 { unsigned int low, high;
   TSCount  out;

   saveTimeStampCountInto( low, high );
   out = high;
   out = (out << 32) + low;
   return out;
 }



//====================  Probes =================
#ifdef STATS__USE_TSC_PROBES

int32
VMS__create_histogram_probe( int32 numBins, float32 startValue,
                             float32 binWidth, char *nameStr )
 { IntervalProbe *newProbe;
   int32 idx;
   FloatHist *hist;

   idx = VMS__create_single_interval_probe( nameStr );
   newProbe =  _VMSMasterEnv->intervalProbes[ idx ];

   hist =  makeFloatHistogram( numBins, startValue, binWidth );
   newProbe->hist = hist;
   return idx;
 }

void
VMS_impl__record_interval_start_in_probe( int32 probeID )
 { IntervalProbe *probe;

   probe = _VMSMasterEnv->intervalProbes[ probeID ];
   probe->startStamp = getTSCount();
 }

void
VMS_impl__record_interval_end_in_probe( int32 probeID )
 { IntervalProbe *probe;
   TSCount endStamp;

   endStamp = getTSCount();

   probe = _VMSMasterEnv->intervalProbes[ probeID ];
   probe->endStamp = endStamp;

   if( probe->hist != NULL )
    { TSCount interval = probe->endStamp - probe->startStamp;
         //if the interval is sane, then add to histogram
      if( interval < probe->hist->endOfRange * 10 )
         addToFloatHist( interval, probe->hist );
    }
 }

void
VMS_impl__print_stats_of_probe( int32 probeID )
 { IntervalProbe *probe;

   probe = _VMSMasterEnv->intervalProbes[ probeID ];

   if( probe->hist == NULL )
    {
      printf("probe: %s, interval: %.6lf\n", probe->nameStr,probe->interval);
    }

   else
    {
      printf( "probe: %s\n", probe->nameStr );
      printFloatHist( probe->hist );
    }
 }
#else

/*
 * In practice, probe operations are called from the app, from inside slaves
 *  -- so have to be sure each probe is single-VP owned, and be sure that
 *  any place common structures are modified it's done inside the master.
 * So -- the only place common structures are modified is during creation.
 *  after that, all mods are to individual instances.
 *
 * Thniking perhaps should change the semantics to be that probes are
 *  attached to the virtual processor -- and then everything is guaranteed
 *  to be isolated -- except then can't take any intervals that span VPs,
 *  and would have to transfer the probes to Master env when VP dissipates..
 *  gets messy..
 *
 * For now, just making so that probe creation causes a suspend, so that
 *  the dynamic array in the master env is only modified from the master
 * 
 */
IntervalProbe *
create_generic_probe( char *nameStr, VirtProcr *animPr )
{
   VMSSemReq reqData;

   reqData.reqType  = createProbe;
   reqData.nameStr  = nameStr;

   VMS__send_VMSSem_request( &reqData, animPr );

   return animPr->dataRetFromReq;
 }

/*Use this version from outside VMS -- it uses external malloc, and modifies
 * dynamic array, so can't be animated in a slave VP
 */
IntervalProbe *
ext__create_generic_probe( char *nameStr )
 { IntervalProbe *newProbe;
   int32          nameLen;

   newProbe          = malloc( sizeof(IntervalProbe) );
   nameLen = strlen( nameStr );
   newProbe->nameStr = malloc( nameLen );
   memcpy( newProbe->nameStr, nameStr, nameLen );
   newProbe->hist    = NULL;
   newProbe->schedChoiceWasRecorded = FALSE;
   newProbe->probeID =
             addToDynArray( newProbe, _VMSMasterEnv->dynIntervalProbesInfo );

   return newProbe;
 }


/*Only call from inside master or main startup/shutdown thread
 */
void
VMS_impl__free_probe( IntervalProbe *probe )
 { if( probe->hist != NULL )   freeDblHist( probe->hist );
   if( probe->nameStr != NULL) VMS__free( probe->nameStr );
   VMS__free( probe );
 }


int32
VMS_impl__record_time_point_into_new_probe( char *nameStr, VirtProcr *animPr)
 { IntervalProbe *newProbe;
   struct timeval *startStamp;
   float64 startSecs;

   newProbe           = create_generic_probe( nameStr, animPr );
   newProbe->endSecs  = 0;

   gettimeofday( &(newProbe->startStamp), NULL);

      //turn into a double
   startStamp = &(newProbe->startStamp);
   startSecs = startStamp->tv_sec + ( startStamp->tv_usec / 1000000.0 );
   newProbe->startSecs = startSecs;

   return newProbe->probeID;
 }

int32
VMS_ext_impl__record_time_point_into_new_probe( char *nameStr )
 { IntervalProbe *newProbe;
   struct timeval *startStamp;
   float64 startSecs;

   newProbe           = ext__create_generic_probe( nameStr );
   newProbe->endSecs  = 0;

   gettimeofday( &(newProbe->startStamp), NULL);

      //turn into a double
   startStamp = &(newProbe->startStamp);
   startSecs = startStamp->tv_sec + ( startStamp->tv_usec / 1000000.0 );
   newProbe->startSecs = startSecs;

   return newProbe->probeID;
 }

int32
VMS_impl__create_single_interval_probe( char *nameStr, VirtProcr *animPr )
 { IntervalProbe *newProbe;

   newProbe = create_generic_probe( nameStr, animPr );
   
   return newProbe->probeID;
 }

int32
VMS_impl__create_histogram_probe( int32   numBins, float64    startValue,
               float64 binWidth, char   *nameStr, VirtProcr *animPr )
 { IntervalProbe *newProbe;
   DblHist *hist;

   newProbe = create_generic_probe( nameStr, animPr );
   
   hist =  makeDblHistogram( numBins, startValue, binWidth );
   newProbe->hist = hist;
   return newProbe->probeID;
 }

void
VMS_impl__index_probe_by_its_name( int32 probeID, VirtProcr *animPr )
 { IntervalProbe *probe;

   //TODO: fix this To be in Master -- race condition
   probe = _VMSMasterEnv->intervalProbes[ probeID ];

   addValueIntoTable(probe->nameStr, probe, _VMSMasterEnv->probeNameHashTbl);
 }

IntervalProbe *
VMS_impl__get_probe_by_name( char *probeName, VirtProcr *animPr )
 {
   //TODO: fix this To be in Master -- race condition
   return getValueFromTable( probeName, _VMSMasterEnv->probeNameHashTbl );
 }


/*Everything is local to the animating procr, so no need for request, do
 * work locally, in the anim Pr
 */
void
VMS_impl__record_sched_choice_into_probe( int32 probeID, VirtProcr *animatingPr )
 { IntervalProbe *probe;
 
   probe = _VMSMasterEnv->intervalProbes[ probeID ];
   probe->schedChoiceWasRecorded = TRUE;
   probe->coreNum = animatingPr->coreAnimatedBy;
   probe->procrID = animatingPr->procrID;
   probe->procrCreateSecs = animatingPr->createPtInSecs;
 }

/*Everything is local to the animating procr, so no need for request, do
 * work locally, in the anim Pr
 */
void
VMS_impl__record_interval_start_in_probe( int32 probeID )
 { IntervalProbe *probe;

         DEBUG( dbgProbes, "record start of interval\n" )
   probe = _VMSMasterEnv->intervalProbes[ probeID ];
   gettimeofday( &(probe->startStamp), NULL );
 }


/*Everything is local to the animating procr, so no need for request, do
 * work locally, in the anim Pr
 */
void
VMS_impl__record_interval_end_in_probe( int32 probeID )
 { IntervalProbe *probe;
   struct timeval *endStamp, *startStamp;
   float64 startSecs, endSecs;

         DEBUG( dbgProbes, "record end of interval\n" )
      //possible seg-fault if array resized by diff core right after this
      // one gets probe..?  Something like that?  Might be safe.. don't care
   probe = _VMSMasterEnv->intervalProbes[ probeID ];
   gettimeofday( &(probe->endStamp), NULL);

      //now turn into an interval held in a double
   startStamp = &(probe->startStamp);
   endStamp   = &(probe->endStamp);

   startSecs = startStamp->tv_sec + ( startStamp->tv_usec / 1000000.0 );
   endSecs   = endStamp->tv_sec   + ( endStamp->tv_usec / 1000000.0 );

   probe->interval  = endSecs - startSecs;
   probe->startSecs = startSecs;
   probe->endSecs   = endSecs;

   if( probe->hist != NULL )
    {
         //if the interval is sane, then add to histogram
      if( probe->interval < probe->hist->endOfRange * 10 )
         addToDblHist( probe->interval, probe->hist );
    }
 }

void
print_probe_helper( IntervalProbe *probe )
 {
   printf( "\nprobe: %s, ",  probe->nameStr );
   
   
   if( probe->schedChoiceWasRecorded )
    { printf( "coreNum: %d, procrID: %d, procrCreated: %0.6f | ",
              probe->coreNum, probe->procrID, probe->procrCreateSecs );
    }

   if( probe->endSecs == 0 ) //just a single point in time
    {
      printf( " time point: %.6f\n",
              probe->startSecs - _VMSMasterEnv->createPtInSecs );
    }
   else if( probe->hist == NULL ) //just an interval
    {
      printf( " startSecs: %.6f interval: %.6f\n", 
         (probe->startSecs - _VMSMasterEnv->createPtInSecs), probe->interval);
    }
   else  //a full histogram of intervals
    {
      printDblHist( probe->hist );
    }
 }

//TODO: change so pass around pointer to probe instead of its array-index..
// will eliminate chance for timing of resize to cause problems with the
// lookup -- even though don't think it actually can cause problems..
// there's no need to pass index around -- have hash table for names, and
// only need it once, then have ptr to probe..  the thing about enum the
// index and use that as name is clunky in practice -- just hash.
void
VMS_impl__print_stats_of_probe( int32 probeID )
 { IntervalProbe *probe;

   probe = _VMSMasterEnv->intervalProbes[ probeID ];

   print_probe_helper( probe );
 }


inline void doNothing(){};

void
generic_print_probe( void *_probe )
 { 
   //IntervalProbe *probe = (IntervalProbe *)_probe;
   
   //TODO segfault in printf
   //print_probe_helper( probe );
 }

void
VMS_impl__print_stats_of_all_probes()
 {
   forAllInDynArrayDo( _VMSMasterEnv->dynIntervalProbesInfo,
                       &generic_print_probe );
   fflush( stdout );
 }
#endif
