/* 
 * 
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <ctype.h>
#include <errno.h>
#include <pthread.h>
#include <sched.h>
#include <unistd.h>

#include <linux/perf_event.h>
#include <sys/syscall.h>

//==========================
//#define TURN_ON_DEBUG

//==========================
#define NUM_CORES 4

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

//SELECT how the measurement is done
//only one must be enabled
#define MEASURE_TSC
//#define MEASURE_PERF


#if !defined(unix) && !defined(__unix__)
#ifdef __MACH__
#define unix		1
#define __unix__	1
#endif	/* __MACH__ */
#endif	/* unix */

/* find the appropriate way to define explicitly sized types */
/* for C99 or GNU libc (also mach's libc) we can use stdint.h */
#if (__STDC_VERSION__ >= 199900) || defined(__GLIBC__) || defined(__MACH__)
#include <stdint.h>
#elif defined(unix) || defined(__unix__)	/* some UNIX systems have them in sys/types.h */
#include <sys/types.h>
#elif defined(__WIN32__) || defined(WIN32)	/* the nameless one */
typedef unsigned __int8 uint8_t;
typedef unsigned __int32 uint32_t;
#endif	/* sized type detection */


//==================
#ifdef TURN_ON_DEBUG
 #define DEBUG__printf(msg) printf(msg)
 #define DEBUG__printf1(msg, arg1) printf(msg, arg1)
 #define DEBUG__printf2(msg, arg1, arg2) printf(msg, arg1, arg2)
#else
 #define DEBUG__printf(msg)
 #define DEBUG__printf1(msg, arg1)
 #define DEBUG__printf2(msg, arg1, arg2)
#endif
//===== RDTSC wrapper ===== //Does work for x86_64 compile

#define saveTimeStampCountInto(low, high) \
   asm volatile("RDTSC;                   \
                 movl %%eax, %0;          \
                 movl %%edx, %1;"         \
   /* outputs */ : "=m" (low), "=m" (high)\
   /* inputs  */ :                        \
   /* clobber */ : "%eax", "%edx"         \
                );

#define saveLowTimeStampCountInto(low)    \
   asm volatile("RDTSC;                   \
                 movl %%eax, %0;"         \
   /* outputs */ : "=m" (low)             \
   /* inputs  */ :                        \
   /* clobber */ : "%eax", "%edx"         \
                );

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

union timeStamp
 {
    uint32_t lowHigh[2]; //lowHigh[0] is low, lowHigh[1] is high
    uint64_t total;
 };

struct perfData
 {
    uint64_t cycles;
    uint64_t instructions;
 };

//MEASURE_TSC should be mutually exclusive with MEASURE_PERF
#ifdef MEASURE_TSC
 typedef union timeStamp MeasStruct;
#else
 #ifdef MEASURE_PERF
 typedef struct perfData MeasStruct;
 #endif
#endif

 //fast way to collect time intervals, by putting into hist right away
#define makeAMeasHist( idx, name, numBins, startVal, binWidth ) \
   makeHighestDynArrayIndexBeAtLeast( _VMSMasterEnv->measHistsInfo, idx ); \
   _VMSMasterEnv->measHists[idx] =  \
                       makeFixedBinHist( numBins, startVal, binWidth, name );

//read and save current perf-counter readings for cycles and instrs
#ifdef MEASURE_PERF
 #define takeAMeas(core, perfDataStruct) do{     \
   int cycles_fd = cycles_counter_fd[core];             \
   int nread;                                           \
                                                        \
   nread = read(cycles_fd,&(perfDataStruct.cycles),sizeof(perfDataStruct.cycles));    \
   if(nread<0){                                         \
       perror("Error reading cycles counter");          \
       cycles = 0;                                      \
   }                                                    \
 } while (0) //macro magic for scoping
#else
 #define takeAMeas(core, timeStampStruct) do{     \
   saveTimeStampCountInto(timeStampStruct.lowHigh[0], timeStampStruct.lowHigh[1]);\
 } while (0) //macro magic for scoping
#endif

 
typedef struct
 {
   int coreID;
   int numTuplesToCreate;
   int producerID;
   
 }
ProducerParams;

typedef struct
 {
   int coreID;
   int numTuplesToCreate;
   int numProducers;
   
 }
ConsumerParams;
 
//=========== Global Vars =============
pthread_mutex_t tupleIterLock;
pthread_cond_t  tupleIterCond;
int tupleIter;

pthread_mutex_t producerAccessMutex;
pthread_mutex_t productionReadyLock;
pthread_cond_t  productionReadyCond;
int currProductionNum;
int producerMessage;

pthread_mutex_t consumerReceivedAckLock;
pthread_cond_t  consumerReceivedAckCond;
int currConsumerReceivedACKNum;

//=======
void* 
producer_birthFn( void* _params );
void* 
consumer_birthFn( void* _params );


