#include <fstream>
#include <monitor>
#include <stdlib>
#include <thread>

#include "bench.h"

coroutine GreatSuspender {};

void ?{}( GreatSuspender * this ) {
	prime(this);
}

void main( GreatSuspender * this )
{
	while( true ) {
		suspend();
	}
}

void resumer( GreatSuspender * this, const unsigned int NoOfTimes ) {
	for ( volatile unsigned int i = 0; i < NoOfTimes; i += 1 ) {
		resume( this );
	}
}

//-----------------------------------------------------------------------------
// coroutine context switch
long long int measure_coroutine() {
	const unsigned int NoOfTimes = N;
	long long int StartTime, EndTime;

	GreatSuspender s;

	StartTime = Time();
	resumer( &s, NoOfTimes );
	EndTime = Time();

	return ( EndTime - StartTime ) / NoOfTimes;
}

//-----------------------------------------------------------------------------
// thread context switch
long long int measure_thread() {
	const unsigned int NoOfTimes = N;
	long long int StartTime, EndTime;

	StartTime = Time();
	for ( volatile unsigned int i = 0; i < NoOfTimes; i += 1 ) {
		yield();
	}
	EndTime = Time();

	return ( EndTime - StartTime ) / NoOfTimes;
}

//-----------------------------------------------------------------------------
// single monitor entry
monitor mon_t {};
void dummy( mon_t * mutex m ) {}

long long int measure_1_monitor_entry() {
	const unsigned int NoOfTimes = N;
	long long int StartTime, EndTime;
	mon_t mon;

	StartTime = Time();
	for ( volatile unsigned int i = 0; i < NoOfTimes; i += 1 ) {
		dummy( &mon );
	}
	EndTime = Time();

	return ( EndTime - StartTime ) / NoOfTimes;
}

//-----------------------------------------------------------------------------
// multi monitor entry
void dummy( mon_t * mutex m1,  mon_t * mutex m2 ) {}

long long int measure_2_monitor_entry() {
	const unsigned int NoOfTimes = N;
	long long int StartTime, EndTime;
	mon_t mon1, mon2;

	StartTime = Time();
	for ( volatile unsigned int i = 0; i < NoOfTimes; i += 1 ) {
		dummy( &mon1, &mon2 );
	}
	EndTime = Time();

	return ( EndTime - StartTime ) / NoOfTimes;
}

//-----------------------------------------------------------------------------
// single internal sched entry
mon_t mon1;

condition cond1a;
condition cond1b;

thread thrd1a { long long int * out; };
thread thrd1b {};

void ?{}( thrd1a * this, long long int * out ) {
	this->out = out;
}

void side1A( mon_t * mutex a, long long int * out ) {
	long long int StartTime, EndTime;

	StartTime = Time();
	for( int i = 0;; i++ ) {
		signal(&cond1a);
		if( i > N ) break;
		wait(&cond1b);
	}
	EndTime = Time();

	*out = ( EndTime - StartTime ) / N;
}

void side1B( mon_t * mutex a ) {
	for( int i = 0;; i++ ) {
		signal(&cond1b);
		if( i > N ) break;
		wait(&cond1a);
	}
}

void main( thrd1a * this ) { side1A( &mon1, this->out ); }
void main( thrd1b * this ) { side1B( &mon1 ); }

long long int measure_1_sched_int() {
	long long int t;
	{
		thrd1a a = { &t };
		thrd1b b;
	}
	return t;
}

//-----------------------------------------------------------------------------
// multi internal sched entry
mon_t mon2;

condition cond2a;
condition cond2b;

thread thrd2a { long long int * out; };
thread thrd2b {};

void ?{}( thrd2a * this, long long int * out ) {
	this->out = out;
}

void side2A( mon_t * mutex a, mon_t * mutex b, long long int * out ) {
	long long int StartTime, EndTime;

	StartTime = Time();
	for( int i = 0;; i++ ) {
		signal(&cond2a);
		if( i > N ) break;
		wait(&cond2b);
	}
	EndTime = Time();

	*out = ( EndTime - StartTime ) / N;
}

void side2B( mon_t * mutex a, mon_t * mutex b ) {
	for( int i = 0;; i++ ) {
		signal(&cond2b);
		if( i > N ) break;
		wait(&cond2a);
	}
}

void main( thrd2a * this ) { side2A( &mon1, &mon2, this->out ); }
void main( thrd2b * this ) { side2B( &mon1, &mon2 ); }

long long int measure_2_sched_int() {
	long long int t;
	{
		thrd2a a = { &t };
		thrd2b b;
	}
	return t;
}

//-----------------------------------------------------------------------------
// main loop
int main()
{
	sout | time(NULL) | ',';
	sout | measure_coroutine() | ',';
	sout | measure_thread() | ',';
	sout | measure_1_monitor_entry() | ',';
	sout | measure_2_monitor_entry() | ',';
	sout | measure_1_sched_int() | ',';
	sout | measure_2_sched_int() | endl;
}
