//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Thu Dec  2 12:45:43 PST 1999
// Last Modified: Fri Feb 20 20:34:39 PST 2015
// Filename:      tools/midimixup.cpp
// URL:           https://github.com/craigsapp/midifile/blob/master/tools/midimixup.cpp
// Syntax:        C++11
// vim:           ts=3
//
// Description:   Reads a standard MIDI file, move the pitches around
//                into a random order.
//

#include "MidiFile.h"
#include "Options.h"

#include <algorithm>
#include <iostream>
#include <iterator>
#include <random>

using namespace std;
using namespace smf;


void checkOptions         (Options& opts);
void example              (void);
void usage                (const char* command);
void randomizeNotes       (vector<MidiEvent*>& notes);
void reverseNotes         (vector<MidiEvent*>& notes);
void swapNotes            (vector<MidiEvent*>& notes, int index1, int index2);

class pairing {
	public:
		int index;
		double value;
};

// User interface variables:
int     reverseQ = 0;         // used with -r option: reverse notes


///////////////////////////////////////////////////////////////////////////

int main(int argc, char** argv) {
	Options options(argc, argv);
	checkOptions(options);

	MidiFile midifile;
	if (options.getArgCount() == 2) {
		midifile.read(options.getArg(1));
	} else if (options.getArgCount() == 1) {
		midifile.read(options.getArg(1));
	} else {
		cerr << "Need one optional MIDI file (or standard input), ";
		cerr << "and one output" << endl;
		exit(1);
	}

	midifile.linkNotePairs();
	vector<MidiEvent*> notes;
	notes.reserve(123456);
	int track, event;
	for (track=0; track<midifile.getNumTracks(); track++) {
		for (event=0; event<midifile.getNumEvents(track); event++) {
			if (midifile[track][event].isNoteOn()) {
				if (midifile[track][event].isLinked()) {
					notes.push_back(&midifile[track][event]);
				}
			}
		}
	}

	if (reverseQ) {
		reverseNotes(notes);
	} else {
		randomizeNotes(notes);
	}

	if (options.getArgCount() == 2) {
		midifile.write(options.getArg(2));
	} else {
		midifile.write(options.getArg(1));
	}

	return 0;
}

///////////////////////////////////////////////////////////////////////////


//////////////////////////////
//
// reverseNotes -- Reverse the order of the notes in the file.
//

void reverseNotes(vector<MidiEvent*>& notes) {
	int count = (int)notes.size();
	for (int i=0; i<count/2; i++) {
		swapNotes(notes, i, count-1-i);
	}
}



//////////////////////////////
//
// randomizeNotes -- adjust an absolute MIDI time.  If the time
//    becomes negative, then the time will be set to zero.
//

void randomizeNotes(vector<MidiEvent*>& notes) {
	random_device rd;
	mt19937 md(rd());
	uniform_real_distribution<double> dist(0, 100);
	int count = (int)notes.size();
	vector<pairing> neworder;
	neworder.resize(notes.size());
	int i;
	for (i=0; i<(int)neworder.size(); i++) {
		neworder[i].index = i;
		neworder[i].value = dist(md);
	}
	sort(neworder.begin(), neworder.end(),
		[](const pairing& a, const pairing& b) {
			return a.value > b.value;
		});

	for (i=0; i<count; i++) {
		swapNotes(notes, i, neworder[i].index);
	}
}



//////////////////////////////
//
// swapNotes -- move a note from one location to another.
//

void swapNotes(vector<MidiEvent*>& notes, int index1, int index2) {
	MidiEvent* noteon1  = notes[index1];
	MidiEvent* noteon2  = notes[index2];
	MidiEvent* noteoff1 = notes[index1]->getLinkedEvent();
	MidiEvent* noteoff2 = notes[index2]->getLinkedEvent();
	if (noteon1  == NULL) { return; }
	if (noteon2  == NULL) { return; }
	if (noteoff1 == NULL) { return; }
	if (noteoff2 == NULL) { return; }
	int pitch1 = noteon1->getKeyNumber();
	int pitch2 = noteon2->getKeyNumber();
	if (pitch1 == pitch2) { return; }
	if (pitch1 < 0) { return; }
	if (pitch2 < 0) { return; }

	noteon1->setKeyNumber(pitch2);
	noteoff1->setKeyNumber(pitch2);
	noteon2->setKeyNumber(pitch1);
	noteoff2->setKeyNumber(pitch1);
}



//////////////////////////////
//
// checkOptions -- handle command-line options.
//

void checkOptions(Options& opts) {
	opts.define("r|reverse=b", "Reverse the order of notes");

	opts.define("author=b",    "Author of the program");
	opts.define("version=b",   "Print version of the program");
	opts.define("example=b",   "Display example use of the program");
	opts.define("help=b",      "Dispay help for the program");
	opts.process();

	if (opts.getBoolean("author")) {
		cout << "Written by Craig Stuart Sapp, "
			  << "craig@ccrma.stanford.edu, 2 December 1999" << endl;
		exit(0);
	}
	if (opts.getBoolean("version")) {
		cout << "midimixup version 2.0" << endl;
		cout << "compiled: " << __DATE__ << endl;
	}
	if (opts.getBoolean("help")) {
		usage(opts.getCommand().c_str());
		exit(0);
	}
	if (opts.getBoolean("example")) {
		example();
		exit(0);
	}

	reverseQ = opts.getBoolean("reverse");
}



//////////////////////////////
//
// example -- gives example calls to the midiplay program.
//

void example(void) {
	cout <<
	"                                                                         \n"
	<< endl;
}



//////////////////////////////
//
// usage -- how to run the textmidi program on the command line.
//

void usage(const char* command) {
	cout <<
	"                                                                         \n"
	<< endl;
}



