/*BEGIN_LEGAL
Intel Open Source License
Copyright (c) 2017, Intel Corporation. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.  Redistributions
in binary form must reproduce the above copyright notice, this list of
conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.  Neither the name of
the Intel Corporation nor the names of its contributors may be used to
endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL OR
ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
END_LEGAL */
/// @file spsc.c
///single producer single consumer busy waiting
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define SIZE 10000000
//#define DEBUG
typedef unsigned long long ticks;
typedef struct {
	ticks start;
	int jobNum;
	int padding;
} packet;

//Method declarations
static __inline__ ticks getticks(void);
ticks getHz();

//Variable declarations
int bufferSize, numOfPacketsToSend, packetsPerSecond;
volatile int count = 0;
double frequency, avg_lat = 0, avg_lat_out = 0, lowest_lat, highest_lat, lat_threshold;
double g_latency[SIZE];
volatile unsigned int produceCount = 1;
int paddin[16];
unsigned long long stdev = 0;
unsigned long long stdev_out = 0;
int outlier = 0;
unsigned long long ticksPerPacket;
static packet *buffer;
typedef struct
{
	int num;
	int my_num;
	long pad0; long pad1; long pad2; long pad3; long pad4; long pad5; long pad6; long pad7;
} thread_data;
volatile unsigned int consumeCount = 1;
unsigned long long startt = 0;
unsigned long long endt = 0;
int padding[16];

void *consumerFunc(void *td) {
	ticks start, end;
	double latency_l = 0;
	thread_data* t = (thread_data*)td;
	int threadID = t->my_num;
	int job = 0, it;
	unsigned int l_consumeCount = 0;
	lat_threshold = 1. * frequency / 1000000;
	while (1) {
		if (!(consumeCount % (bufferSize + 1)))
			l_consumeCount = 1;
		else
			l_consumeCount++;
		while (produceCount - consumeCount == 0)
			;//_mm_pause(); // buffer is empty
		start = buffer[l_consumeCount - 1].start;
		job = buffer[l_consumeCount - 1].jobNum;
		end = getticks();
		it = consumeCount;
		consumeCount++;
		latency_l = (double)(end - start);
		g_latency[it] = latency_l;
		if (consumeCount == numOfPacketsToSend) {
			endt = getticks();
			break;
		}
		if (job == 0)
			printf("Read error on job \n");
	}
	pthread_exit(NULL);
}

void *producerFunc(void *td)
{
	ticks firstSentMsg = 0;
	unsigned int l_produceCount = 0;
	usleep(100000); //wait for consumer threads to be created
	startt = getticks();
	while (produceCount < numOfPacketsToSend)
	{
		if ((getticks() - firstSentMsg) > ticksPerPacket) {
			while (produceCount - consumeCount == bufferSize)
				; //don't let producer overwrite the buffer
			if (!(produceCount % (bufferSize + 1)))
				l_produceCount = 1;
			else
				l_produceCount++;
			buffer[l_produceCount - 1].jobNum = produceCount;
			firstSentMsg = getticks();
			buffer[l_produceCount - 1].start = firstSentMsg;
			produceCount++;
		}
	}
}

int main(int argc, char* argv[]) {
	int i;
	if (argc < 4) {
		printf("Incorrect number of arguments, correct syntax:\n./box numofPacketsToSend bufferSize numofPacketsPerSecond\n"); return 1;
	}
	numOfPacketsToSend = atoi(argv[1]);
	bufferSize = atoi(argv[2]);
	buffer = (packet*)malloc((bufferSize + 1) * sizeof(packet));
	packetsPerSecond = atoi(argv[3]);

	avg_lat = 0;
	lowest_lat = 100000000;
	highest_lat = 0;
	pthread_t pths[1];
	pthread_attr_t attr;
	/* Initialize and set thread detached attribute */
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
	void *status;

	//Calculate the frequency of the CPU
	ticks freq = getHz();
	frequency = (double)freq;
	ticksPerPacket = (unsigned long long)(frequency / (double)packetsPerSecond);

	thread_data td[100] __attribute__((aligned(64)));
	td[0].num = 1;
	td[0].my_num = i;
	//create consumer threads
	pthread_create(&pths[1], &attr, consumerFunc, &td[1]);
	// Create the producer thread
	pthread_create(&pths[0], &attr, producerFunc, &td[0]);
	int ret = pthread_join(pths[0], &status);
	// let's give consumers some time to finish before calculating latencies
	usleep(1000000);
	for (i = 1; i<numOfPacketsToSend; i++)
	{
		if (g_latency[i] < lat_threshold)
			avg_lat += g_latency[i];
		else
			outlier++;
		avg_lat_out += g_latency[i];
		if (g_latency[i] < lowest_lat)
			lowest_lat = g_latency[i];
		if (g_latency[i]>highest_lat)
			highest_lat = g_latency[i];
	}
	int* outliers = (int*)malloc(sizeof(int)*outlier);
	int count = 0;
	for (i = 1; i<numOfPacketsToSend; i++)
	{
		if (g_latency[i] > lat_threshold) {
			outliers[count] = i + 1;
			count++;
		}
		else
			stdev_out += (g_latency[i] - avg_lat)*(g_latency[i] - avg_lat_out);

		stdev += (g_latency[i] - avg_lat)*(g_latency[i] - avg_lat);
	}
	stdev_out = sqrt(stdev_out / (numOfPacketsToSend - outlier));
	stdev = sqrt(stdev / numOfPacketsToSend);
	printf("Avg lat = %f, Avg lat w/o outliers = %f, lowest lat = %f, highest lat = %f, outliers = %d\n", avg_lat_out / frequency * 1000000 / (double)(numOfPacketsToSend), avg_lat / frequency * 1000000 / (double)(numOfPacketsToSend - outlier), lowest_lat / frequency * 1000000, highest_lat / frequency * 1000000, outlier);
	printf("Stdev = %f, stdev w/o outliers = %f\n\n", stdev / frequency, stdev_out / frequency);
	pthread_exit(NULL);
}
static __inline__ ticks getticks(void) {
	unsigned int hi, lo;
	asm volatile("rdtsc" : "=a" (lo), "=d" (hi));
	return (((ticks)hi << 32) | lo);
}
/* Function to get speed of the cpu in Hz */
ticks getHz() {
	ticks tick1, tick2, frequency;
	tick1 = getticks();
	sleep(1);
	tick2 = getticks();
	frequency = (((tick2 - tick1)));
	return frequency;
}


