#include <string>
using namespace std;
#include <uActor.h>
#include <chrono>
using namespace chrono;
#include <iostream>

struct IntMsg : public uActor::SenderMsg { int val; };
struct CharMsg : public uActor::SenderMsg { char val; };

size_t Messages = 100'000, Processors = 4, QScale = 256, Times = 10;
time_point<steady_clock> starttime;

_Actor Server {
	Allocation receive( uActor::Message & msg ) {
		Case( IntMsg, msg ) { msg_d->val = 7; *msg_d->sender() | msg; }
		else Case( CharMsg, msg ) { msg_d->val = 'x'; *msg_d->sender() | msg; }
		else Case( StopMsg, msg ) { return Finished; }
		return Nodelete;								// reuse actor
	}
};

Server * servers;

_Actor Client {
	IntMsg * intmsg;
	CharMsg * charmsg;
	size_t results = 0, times = 0;

	Allocation reset() {
		times += 1;
		if ( times == Times ) {
			for ( unsigned int i = 0; i < Messages; i += 1 ) {
				servers[i] | uActor::stopMsg;
			} // for
			return Finished;
		}
		results = 0;
		*this | uActor::startMsg;						// restart experiment
		return Nodelete;
	}

	Allocation receive( uActor::Message & msg ) {		// receive callback messages
		Case( IntMsg, msg ) results += 1;
		else Case( CharMsg, msg ) results += 1;
		else Case( StartMsg, msg ) {
			for ( size_t i = 0; i < Messages; i += 1 ) { // send out work
				servers[i] | intmsg[i];					// tell send
				servers[i] | charmsg[i];
			}
		}
		if ( results == 2 * Messages ) return reset();	// all messages handled ?
		return Nodelete;								// reuse actor
	}
  public:
	Client() {
		intmsg = new IntMsg[Messages];
		charmsg = new CharMsg[Messages];
	}
	~Client() {
		delete [] charmsg;
		delete [] intmsg;
	}
};

int main( int argc, char * argv[] ) {
	switch ( argc ) {
	  case 5:
		if ( strcmp( argv[4], "d" ) != 0 ) {			// default ?
			QScale = stoi( argv[4] );
			if ( QScale < 1 ) goto Usage;
		} // if
      case 4:
		if ( strcmp( argv[3], "d" ) != 0 ) {			// default ?
			Times = stoi( argv[3] );
			if ( Times < 1 ) goto Usage;
		} // if
	  case 3:
		if ( strcmp( argv[2], "d" ) != 0 ) {			// default ?
			Processors = stoi( argv[2] );
			if ( Processors < 1 ) goto Usage;
		} // if
	  case 2:
		if ( strcmp( argv[1], "d" ) != 0 ) {			// default ?
			Messages = stoi( argv[1] );
			if ( Messages < 1 ) goto Usage;
		} // if
	  case 1:											// use defaults
		break;
	  default:
	  Usage:
		cerr << "Usage: " << argv[0]
			 << ") ] [ messages (> 0) | 'd' (default " << Messages
			 << ") ] [ processors (> 0) | 'd' (default " << Processors
             << ") ] [ Times (> 0) | 'd' (default " << Times
			 << ") ] [ qscale (> 0) | 'd' (default " << QScale
			 << ") ]" << endl;
		exit( EXIT_FAILURE );
	} // switch

	uExecutor executor( Processors, Processors, Processors == 1 ? 1 : Processors * QScale, true, -1 );
	time_point<steady_clock> starttime = steady_clock::now();
    uActor::start( &executor );							// start actor system
	servers = new Server[Messages];
	Client client;
	client | uActor::startMsg;							// start actors
	uActor::stop();										// wait for all actors to terminate
	cout << (steady_clock::now() - starttime).count() / 1'000'000'000.0 << endl;
	delete [] servers;
}
