#include "LoggerFactory.hpp"
#include "Feedlet.hpp"
#include "TimerEvent.hpp"
#include "ValueEvent.hpp"
#include "PortLibrary.hpp"
#include 
#include 

#ifndef M_PI
#define M_PI 3.1415926
#endif

/* --------------------- INSTRUMENTATION START ------------------------- */
ILogger* logger;
IFeedlet* signalFeedlet;
IFeedlet* activityFeedlet;
IFeedlet* midiFeedlet;
TimerEvent* activityTimer;
ValueEvent* signalET;
ValueEvent* loudnessET;
PortLibrary *portLibrary;
EventType *midiEventET;

void instrumentationInit(char* fileNameOrPortNumber, int durationSeconds) {

	portLibrary = new PortLibrary();
	
	// Create a logger
	int portNumber = atoi(fileNameOrPortNumber);
	const int numEventTypeSpaces = 1;
	EventTypeSpaceVersion* eventTypeSpaces[numEventTypeSpaces];
	eventTypeSpaces[0] = EventTypeSpaceVersion::newInstance(portLibrary, "com.ibm.tuningfork.music", 1);
	if (portNumber > 0) {
		logger = LoggerFactory::makeServerLogger(portLibrary, portNumber, eventTypeSpaces, numEventTypeSpaces);
	} else {
		if (strcmp(fileNameOrPortNumber, "none") == 0) {
			logger = LoggerFactory::makeNullLogger(portLibrary);
		} else {
			logger = LoggerFactory::makeFileLogger(portLibrary, fileNameOrPortNumber, eventTypeSpaces, numEventTypeSpaces);
		}
		if (logger == NULL) {
			fprintf(stderr, "Could not open file %s for writing.\n", fileNameOrPortNumber);
			exit(-1);
		}
	}

	// Add some properties
	char valueBuffer[20];
	sprintf(valueBuffer, "%d sec", durationSeconds);
	logger->addProperty("Program Run Length", valueBuffer);
	
	// Add some event types - two for each type of threads
	activityTimer = logger->makeTimerEvent("Activity");
	signalET = logger->makeValueEvent("Signal Value");
	loudnessET = logger->makeValueEvent("Loudness");
	EventAttribute* eventAttrs[4];
	eventAttrs[0] = EventAttribute::newInstance(portLibrary, "channel", "channel", logger->getInt(portLibrary));
	eventAttrs[1] = EventAttribute::newInstance(portLibrary, "status", "status", logger->getInt(portLibrary));
	eventAttrs[2] = EventAttribute::newInstance(portLibrary, "data1", "data1", logger->getInt(portLibrary));
	eventAttrs[3] = EventAttribute::newInstance(portLibrary, "data2", "data2", logger->getInt(portLibrary));
	midiEventET = EventType::newInstance(portLibrary, logger, "MIDI Events", "MIDI Events", 4, eventAttrs);
	logger->addEventType(midiEventET);
				
	midiFeedlet = logger->makeFeedlet();
	midiFeedlet->setName("midi");
	signalFeedlet = logger->makeFeedlet();
	signalFeedlet->setName("signal feedlet");
	activityFeedlet = logger->makeFeedlet();
	activityFeedlet->setName("activity");
}


void setSignal(double amplitude, double loudness) {
	signalET->addValue(amplitude);   // ---------------- INSTRUMENTATION -------------------
	loudnessET->addValue(loudness);  // ---------------- INSTRUMENTATION -------------------

	// Do some work related to setting signal - we simulate by sleeping briefly
	portLibrary->sleep(1);
}

void runOneInterval(int timeMs) {
	activityTimer->start();  // ---------------------- INSTRUMENTATION -------------------
	portLibrary->sleep(timeMs);		
	activityTimer->stop();   // --------------------- INSTRUMENTATION -------------------
}

int runMidi(void* durSec) {
	
	int durationSec = (int) durSec;
	midiFeedlet->bindToCurrentThread();   // --------------------- INSTRUMENTATION -------------------

	// Now make some MIDI events
	int channel = 0;
	int status = 0x90; // on
	int data1 = 0x30; // pitch
	int data2 = 64; // velocity

	U_64 start = portLibrary->nanoTimeCoarse();
	while (true) {
		double timeSec = (portLibrary->nanoTimeCoarse() - start) / 1e9;

		I_32 ivals[4];
		ivals[0] = channel;
		ivals[1] = status;
		ivals[2] = data1;
		ivals[3] = data2;
		midiFeedlet->addEvent(midiEventET, 4, ivals, 0, NULL, 0, NULL, 0, NULL);
		if (status == 0x90) {
			status = 0x80;
		} else {
			status = 0x90;
			if (++data1 > 0x60) {
				data1 = 0x30;
			}
		}
		if (timeSec > durationSec) {
			break;
		}

		portLibrary->sleep(100);

	}
}

int runSignal(void *durSec) {
	
	int durationSec = (int) durSec;
	signalFeedlet->bindToCurrentThread();   // --------------------- INSTRUMENTATION -------------------

	// Now make some events
	double pitchFrequency = 5;
	double loudnessFrequency = 0.5;
	U_64 start = portLibrary->nanoTimeCoarse();
	while (true) {
		double timeSec = (portLibrary->nanoTimeCoarse() - start) / 1e9;
		double pitchPosition = timeSec * pitchFrequency * 2 * M_PI;
		double loudnessPosition = timeSec * loudnessFrequency * 2 * M_PI;
		double loudness = 1 + 0.5 * sin(loudnessPosition); 
		double amplitude = sin(pitchPosition) * loudness;
		
		setSignal(amplitude, loudness);
		if (timeSec > durationSec) {
			break;
		}
	}
	return 0;
}


int runIntervals(void* durSec) {
	
	int durationSec = (int) durSec;

	activityFeedlet->bindToCurrentThread();    // --------------------- INSTRUMENTATION -------------------
	
	double onMaxLenMs = 200; // activity's length is from a uniform distribution from 0 to onMaxLen
	double offMaxLenMs = 500; // time until next activity is from a uniform distribution from 0 to onMaxLen
	U_64 start = portLibrary->nanoTimeCoarse();
	while (true) {
		double r1 = rand() / ((double) RAND_MAX);
		double r2 = rand() / ((double) RAND_MAX);
		int onLenMs = (int) (onMaxLenMs * r1);
		int offLenMs = (int) (offMaxLenMs * r2);

		runOneInterval(onLenMs);
		portLibrary->sleep(offLenMs);

		double timeSec = (portLibrary->nanoTimeCoarse() - start) / 1e9;
		if (timeSec > durationSec) {
			break;
		}
	}
	return 0;
}

	
	
int main(int argc, char **args) {

	/* ------- INSTRUMENTATION MODIFICATION START --------- */
	if (argc != 3) {
		fprintf(stderr, "Usage: Client  ");  
		return -1;
	}
	char* duration = args[1];
	int durationSeconds = atoi(duration);
	if (durationSeconds == 0) {
		durationSeconds = 60; // Default is 60 seconds
		fprintf(stderr, "Could not parse duration argument.  Using %d seconds.", durationSeconds);			
	}
	char* fileNameOrPortNumber = args[2];      // INSRUMENTATION
	instrumentationInit(fileNameOrPortNumber, durationSeconds);  // INSRUMENTATION
	/* ------- INSTRUMENTATION MODIFICATION END --------- */


	// Run 2 threads that make events for some time
	fprintf(stderr, "Running 2 threads for %d seconds.\n", durationSeconds);
	portLibrary->runOnNewThread(runSignal, (void*) durationSeconds);
	portLibrary->runOnNewThread(runIntervals, (void*) durationSeconds);
	portLibrary->runOnNewThread(runMidi, (void*) durationSeconds);

	// This is only an approximation - not synchronized with event-generating threads
	portLibrary->sleep(durationSeconds * 1000);
	
	logger->flush(); /* INSTRUMENTATION */

	// Flush buffered data
	fprintf(stderr, "Done.");
}

}