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

static const unsigned N = 100_000;

enum state_t { WAITED, SIGNAL, BARGE };

monitor global_data_t {
	thread_desc * last_thread;
	thread_desc * last_signaller;
};

void ?{} ( global_data_t * this ) {
	this->last_thread = NULL;
	this->last_signaller = NULL;
}

void ^?{} ( global_data_t * this ) {}

global_data_t globalA, globalB;

condition cond;

volatile bool done;

//------------------------------------------------------------------------------
void wait_op( global_data_t * mutex a, global_data_t * mutex b, unsigned i ) {
	wait( &cond, (uintptr_t)this_thread() );

	yield( ((unsigned)rand48()) % 10 );

	if(a->last_thread != a->last_signaller || b->last_thread != b->last_signaller ) {
		sout | "ERROR Barging detected, expected" | a->last_signaller | b->last_signaller | "got" | a->last_thread | b->last_thread | endl;
		abort();
	}

	a->last_thread = b->last_thread = this_thread();

	yield( ((unsigned)rand48()) % 10 );
}

thread Waiter {};
void main( Waiter* this ) {
	for( int i = 0; i < N; i++ ) {
		wait_op( &globalA, &globalB, i );
	}
}

//------------------------------------------------------------------------------
void signal_op( global_data_t * mutex a, global_data_t * mutex b ) {
	yield( ((unsigned)rand48()) % 10 );

	a->last_thread = b->last_thread = a->last_signaller = b->last_signaller = this_thread();

	if( !is_empty( &cond ) ) {

		thread_desc * next = front( &cond );

		if( ! signal_block( &cond ) ) {
			sout | "ERROR expected to be able to signal" | endl;
			abort();
		}

		yield( ((unsigned)rand48()) % 10 );

		if(a->last_thread != next || b->last_thread != next) {
			sout | "ERROR Barging detected, expected" | next | "got" | a->last_thread | b->last_thread | endl;
			abort();
		}
	}

}

thread Signaller {};
void main( Signaller* this ) {
	while( !done ) {
		signal_op( &globalA, &globalB );
	}
}

//------------------------------------------------------------------------------
void barge_op( global_data_t * mutex a ) {
	a->last_thread = this_thread();
}

thread Barger {};
void main( Barger* this ) {
	for( unsigned i = 0; !done; i++ ) {
		//Choose some monitor to barge into with some irregular pattern
		bool choose_a = (i % 13) > (i % 17);
		barge_op( choose_a ? &globalA : &globalB );
	}
}

//------------------------------------------------------------------------------

int main(int argc, char* argv[]) {
	rand48seed(0);
	done = false;
	processor p;
	{
		Signaller s[4];
		Barger b[13];
		sout | "Starting waiters" | endl;
		{
			Waiter w[3];
		}
		sout | "Waiters done" | endl;
		done = true;
	}
}
