//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Tue Oct 16 07:34:30 PDT 2012
// Last Modified: Mon Feb  9 20:40:50 PST 2015 Updated for C++11.
// Filename:      tools/midicat.cpp
// URL:           https://github.com/craigsapp/midifile/blob/master/tools/midicat.cpp
// Syntax:        C++11
// vim:           ts=3
//
// Description:   Concatenate multiple MIDI files into single type-0 MIDI file.
//

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

#include <iostream>
#include <vector>

using namespace std;
using namespace smf;


void      checkOptions      (Options& opts, int argc, char** argv);
void      example           (void);
void      usage             (const char* command);
void      appendMidi        (MidiFile& outfile, const char* filename,
                             double seconds, int initQ);

// user interface variables
Options options;
double seconds         = 2.0;  // used with -p option
int    binaryQ         = 1;    // used with -a option


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

int main(int argc, char* argv[]) {
	checkOptions(options, argc, argv);
	MidiFile outfile;
	outfile.joinTracks();
	outfile.deltaTicks();

	int i;
	int initQ = 0;
	for (i=1; i<=options.getArgCount(); i++) {
		appendMidi(outfile, options.getArg(i).c_str(), seconds, initQ++);
	}

	// insert an end-of track Meta Event
	int tpq = outfile.getTicksPerQuarterNote();
	MidiEvent mfevent;
	mfevent.tick = tpq;
	mfevent.track = 0;
	mfevent.resize(3);
	mfevent[0] = 0xff;
	mfevent[1] = 0x2f;
	mfevent[2] = 0;
	outfile.addEvent(mfevent);

	if (binaryQ) {
		outfile.write(cout);
	} else {
		cout << outfile;
	}

	return 0;
}


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


//////////////////////////////
//
// appendMidi --
//

void appendMidi(MidiFile& outfile, const char* filename,
		double seconds, int initQ) {
	MidiFile infile(filename);
	infile.joinTracks();
	infile.deltaTicks();
	int i;
	int tpq;
	int count;

	MidiEvent anevent;
	if (initQ == 0) {
		outfile.joinTracks();
		count = infile.getEventCount(0);
		// don't include end-of-track meta event
		count--;
		tpq = infile.getTicksPerQuarterNote();
		outfile.setTicksPerQuarterNote(tpq);
		for (i=0; i<count; i++) {
			anevent = infile.getEvent(0,i);
			// storing as a type-0 file, so remove track information
			anevent.track = 0;
			outfile.addEvent(anevent);
		}
		return;
	}

	// presuming constant tpq for different files.
	tpq = outfile.getTicksPerQuarterNote();

	if (seconds > 0.0) {
		// insert a tempo marking of 120, and then a pause related to how
		// long in second to wait until next MIDI file contents.
		// micro-seconds per quarter note is last three bytes of meta message
		// If quarter note is 120 bpm, that is 0.5 seconds or 500000 usec.
		// In hex 500000 is 07 a1 20
		// Tempo meta event:  ff 51 03 07 a1 20
		vector<uchar> bpm120;
		bpm120.resize(3);
		bpm120[0] = 0x07;
		bpm120[1] = 0xa1;
		bpm120[2] = 0x20;

		outfile.addMetaEvent(0, 0, 0x51, bpm120);
		infile.getEvent(0,0).tick = int(seconds * 2 * tpq + 0.5);
	}

	count = infile.getEventCount(0);
	// don't include end-of-track meta event
	count--;
	for (i=0; i<count; i++) {
		anevent = infile.getEvent(0,i);
		anevent.track = 0;
		outfile.addEvent(anevent);
	}
}



//////////////////////////////
//
// checkOptions --
//

void checkOptions(Options& opts, int argc, char* argv[]) {
	opts.define("p|pause=d:2.0",  "Pause given number of secs after each file");
	opts.define("a|ascii=b",  "Display MIDI output as ASCII text");

	opts.define("author=b",  "author of program");
	opts.define("version=b", "compilation info");
	opts.define("example=b", "example usages");
	opts.define("h|help=b",  "short description");
	opts.process(argc, argv);

	// handle basic options:
	if (opts.getBoolean("author")) {
		cout << "Written by Craig Stuart Sapp, "
		     << "craig@ccrma.stanford.edu, 16 October 2012" << endl;
		exit(0);
	} else if (opts.getBoolean("version")) {
		cout << argv[0] << ", version: October 2012" << endl;
		cout << "compiled: " << __DATE__ << endl;
		exit(0);
	} else if (opts.getBoolean("help")) {
		usage(opts.getCommand().c_str());
		exit(0);
	} else if (opts.getBoolean("example")) {
		example();
		exit(0);
	}

	if (opts.getArgCount() <= 1) {
		usage(opts.getCommand().c_str());
		exit(1);
	}

	seconds     =  opts.getDouble("pause");
	binaryQ     = !opts.getBoolean("ascii");
}



//////////////////////////////
//
// example --
//

void example(void) {
	// add examples here
}



//////////////////////////////
//
// usage --
//

void usage(const char* command) {
	// add usages here
}



