//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Wed Jan 13 08:31:19 PST 1999
// Last Modified: Mon Feb  9 21:26:32 PST 2015 Updated for C++11.
// Filename:      tools/vlv.cpp
// URL:           https://github.com/craigsapp/midifile/blob/master/tools/vlv.cpp
// Syntax:        C++11
// vim:           ts=3
//
// Description:   Converts Variable Length Values into integers as
//                well as converts integers into VLVs.
//

#include "Options.h"

#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <string>

using namespace std;
using namespace smf;

#define DECODE 0
#define ENCODE 1


void     checkOptions        (Options& opts);
void     displayVLV          (int number, ostream& out);
void     example             (void);
void     printBinary         (int number, ostream& out);
void     usage               (const string& command);

// User interface variables:
Options  options;              // for command-line processing
int      Direction   = DECODE; // decoding=0, encoding=1
int      InputStyle  = 16;     // number base of the input (2, 10, or 16)
int      OutputStyle = 16;     // number base of the output (2, 10, or 16)
vector<int> Input;             // storage of input numbers


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

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

	if (Direction == DECODE) { // decoding
		unsigned long output = 0;
		for (int i=0; i<(int)Input.size(); i++) {
			output = output << 7;
			output |= Input[i] & 0x7f;
		}

		switch (OutputStyle) {
			case 2:
				printBinary(output, cout);
				cout << endl;
				break;
			case 16:
				if (output < 16) {
					cout << '0';
				}
				cout << hex << output << endl;
				break;
			default:
				cout << dec << output << endl;
		}

	} else {                   // encoding
		displayVLV(Input[0], cout);
		cout << endl;
	}

	return 0;
}


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

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

void checkOptions(Options& opts) {
	opts.define("b|2|bin|binary=b",   "input is binary");
	opts.define("d|10|dec|decimal=b", "input is decimal");
	opts.define("B=b",                "print binary output");
	opts.define("D=b",                "print decimal output");
	opts.define("author=b",           "author of the program");
	opts.define("version=b",          "version of the program");
	opts.define("example=b",          "example usage of the program");
	opts.define("h|help=b",           "list of options for the program");
	opts.process();

	if (opts.getBoolean("author")) {
		cout << "Written by Craig Stuart Sapp, "
		     << "craig@ccrma.stanford.edu, January 1999" << endl;
		exit(0);
	} else if (opts.getBoolean("version")) {
		cout << "vlv, version 2.0 (9 Feb 2015)\n"
		     << "compiled: " << __DATE__ << endl;
		exit(0);
	} else if (opts.getBoolean("help")) {
		usage(opts.getCommand());
		exit(0);
	} else if (opts.getBoolean("example")) {
		example();
		exit(0);
	}

	// determine input style (the default is hexadecimal)
	if (opts.getBoolean("binary")) {
		InputStyle = 2;
	} else if (opts.getBoolean("decimal")) {
		InputStyle = 10;
	} else {
		InputStyle = 16;
	}

	// determine output style (the default is hexadecimal)
	if (opts.getBoolean("B")) {
		OutputStyle = 2;
	} else if (opts.getBoolean("D")) {
		OutputStyle = 10;
	} else {
		OutputStyle = 16;
	}

	if (opts.getArgCount() == 0) {
		usage(opts.getCommand());
		exit(1);
	} else if (opts.getArgCount() == 1) {     // encoding into VLV values
		Direction = ENCODE;
	} else {                                  // decoding from VLV values
		Direction = DECODE;
	}

	// process the input numbers:
	int inCount = opts.getArgCount();
	if (inCount > 4) {
		cout << "Error: VLV values cannot have more than four bytes" << endl;
		exit(1);
	}

	Input.reserve(opts.getArgCount());
	Input.resize(0);

	for (int i=1; i<=opts.getArgCount(); i++) {
		Input.push_back(strtol(opts.getArg(i).c_str(), NULL, InputStyle));

		if (Direction == DECODE && i != inCount) {
			if (Input.back() < 0x80 || Input.back() > 255) {
				cout << "Invalid VLV byte: " << opts.getArg(i) << endl;
				exit(1);
			}
		} else if (Direction == DECODE && i == inCount) {
			if (Input.back() < 0 || Input.back() >0x7f) {
				cout << "Invalid VLV byte: " << opts.getArg(i) << endl;
				exit(1);
			}
		}

	}
}


//////////////////////////////
//
// displayVLV -- prints the number in VLV form
//

void displayVLV(int number, ostream& out) {
	unsigned long value = (unsigned long)number;

	if (value >= (1 << 28)) {
		cout << "Error: number too large to handle" << endl;
		exit(1);
	}

	unsigned long byte[4];
	byte[0] = (value >> 21) & 0x7f;
	byte[1] = (value >> 14) & 0x7f;
	byte[2] = (value >>  7) & 0x7f;
	byte[3] = (value >>  0) & 0x7f;

	int flag = 0;
	for (int i=0; i<3; i++) {
		if (byte[i] != 0) {
			flag = 1;
		}
		if (flag) {
			byte[i] |= 0x80;
		}
	}

	for (int i=0; i<4; i++) {
		if (byte[i] >= 0x80 || i == 3) {
			switch (OutputStyle) {
				case 2:   printBinary(byte[i], out);   break;
				case 16:
					if (byte[i] < 0x0f) {
						out << '0';
					}
					out << hex << byte[i];
					break;
				default:  out << dec << byte[i];
			}
			if (i != 3) {
				cout << ' ';
			}
		}
	}
}



//////////////////////////////
//
// example -- shows various command-line option calls to program.
//

void example(void) {
	cout <<
	"\n"
	"# convert a hex number to VLV bytes:\n"
	"vlv 3fff\n"
	"\n"
	"# convert a hex number to VLV bytes in decimal notation:\n"
	"vlv -D 3fff\n"
	"\n"
	"# convert a decimal number to VLV bytes in decimal notation:\n"
	"vlv -d -D 54632\n"
	"\n"
	"# convert hex VLV bytes into hex number:\n"
	"vlv 81 80 F4 00\n"
	"\n"
	"# convert hex VLV bytes into dec number:\n"
	"vlv -D 81 80 F4 00\n"
	"\n"
	<< endl;
}



//////////////////////////////
//
// printBinary -- prints the number in binary form with no
//    leading zeros.
//

void printBinary(int number, ostream& out) {
	unsigned long value = (unsigned long) number;
	int flag = 0;

	for (int i=0; i<32; i++) {
		if (value & (1 << (31-i))) {
			flag = 1;
		}

		if (flag) {
			if (value & (1 << (31-i))) {
				out << '1';
			} else {
				out << '0';
			}
		}
	}

	if (flag == 0) {
		out << '0';
	}
}



//////////////////////////////
//
// usage -- how to run this program from the command-line.
//

void usage(const string& command) {
	cout <<
	"\n"
	"Convert/Create Variable Length Values.\n"
	"\n"
	"Usage: " << command << " [-d|-b] [-D|-B] number(s)\n"
	"\n"
	"Options:\n"
	"   -d = input numbers are given in decimal notation (hex is default)\n"
	"   -b = input numbers are given in binary notation (hex is default)\n"
	"   -D = output numbers are given in decimal notation (hex is default)\n"
	"   -B = output numbers are given in binary notation (hex is default)\n"
	"   --options = list all options, default values, and aliases\n"
	"\n"
	<< endl;
}