// mrDiff

// Configuration --------------------------------------------------------------

// Compile with MRDIFF_TESTS_ONLY to validate the library.
// Compile without this flag to build the command-line client.
//#define MRDIFF_TESTS_ONLY

// Headers --------------------------------------------------------------------

#include <cstdio>
#include "mrDiff.h"
using namespace std;

// Test framework -------------------------------------------------------------

#ifdef MRDIFF_TESTS_ONLY

static int total_tests = 0;

int basictest(const char* orig, const char* updated) {
	total_tests++;
	
	patch_t testDiff = mrdiff(orig, updated);
	string recovered = mrpatch(orig, testDiff);

	printf("Test #%d\nOriginal:  '%s'\nChanged:   '%s'\nRecovered: '%s'\n",
		total_tests, orig, updated, recovered.c_str()
	);

	puts((recovered == updated) ? "Test passed.\n" : "Test FAILED!\n");
	return (recovered == updated) ? 1 : 0;
}

int mergetest(const char* orig, const char* mod1, const char* mod2, const char* expect) {
	total_tests++;

	patch_t diffA = mrdiff(orig, mod1), diffB = mrdiff(orig, mod2);
	patch_t mergeDiff = mrmerge(diffA, diffB);
	string merged = mrpatch(orig, mergeDiff);

	printf(
		"Test #%d\nOriginal:  '%s'\nChanged:   '%s', '%s'\n"
		"Expected:  '%s'\nRecovered: '%s'\n",
		total_tests, orig, mod1, mod2, expect, merged.c_str()
	);

	puts((merged == expect) ? "Test passed.\n" : "Test FAILED!\n");
	return (merged == expect) ? 1 : 0;
}

int serialisetest(const char* orig, const char* updated) {
	total_tests++;

	patch_t diff = mrdiff(orig, updated);
	patch_t repack = mrunserialise(mrserialise(diff));
	string recovered = mrpatch(orig, repack);

	printf(
		"Test #%d\nOriginal:  '%s'\nChanged:   '%s'\nRecovered: '%s'\n",
		total_tests, orig, updated, recovered.c_str()
	);

	puts((recovered == updated) ? "Test passed.\n" : "Test FAILED!\n");
	return (recovered == updated) ? 1 : 0;
}

void stabteststart() {
	total_tests++;
	printf("Test #%d\nCustom parameters\n", total_tests);
}

int stabtestpassed() {
	puts("Test passed.\n");
	return 1;
}

// Entry point ----------------------------------------------------------------

int main(int argc, char* argv[]) {
	int passed = 0;

	// Basic tests
	// See if a diff can be computed and reapplied to give the correct result

	puts("## Beginning basic tests...\n");

	passed += basictest("This is a sample string", "This is not a test string");
	passed += basictest("This is a sample string", "This is a string");
	passed += basictest("Foo", "Foo");
	passed += basictest("", "Foo");
	passed += basictest("Bar", "");
	passed += basictest("", "");

	// Merge tests
	// See if two diffs against a common base can be merged to give the expected result

	puts("## Beginning merge tests...\n");

	passed += mergetest(
		"The cat sat on the mat",
		"The dog sat on the mat", "The cat sat on the dirty mat",
		"The dog sat on the dirty mat"
	);
	passed += mergetest(
		"The cat sat on the mat",
		"The cat on the mat", "The cat sat on the mat",
		"The cat on the mat"
	);
	passed += mergetest(
		"The cat sat on the mat",
		"The cat sat on the mat", "The cat on the mat",
		"The cat on the mat"
	);

	// Test literal-literal collisions.
	// In general, literal-literal collisions are unspecified. Often times the left
	//  entry will take precendence, but diffs are char-grained and will often intersect
	//  in more places than one might expect.
	// Gibberish is to be expected when two people are editing the same area anyway.

	passed += mergetest(
		"Foo",
		"Bar", "Baz",
		"Bar"
	);
	passed += mergetest(
		"Occupy Wall Street",
		"Occupy the area in town", "Occupy the playground",
		"Occupy the aroundown" // Doesn't matter what it is, it's gibberish.
	);
	passed += mergetest(
		"Occupy Low Street",
		"Occupy Middle Street", "Occupy High Street",
		"Occupy Middle Street"
	);
	passed += mergetest(
		"This is a sentence",
		"This isn't a sentence", "This ISN'T a sentence, allright?",
		"This ISN'Tn't a sentence, allright?" // Interesting how that worked out..
	);
	passed += mergetest(
		"abcdefghi",
		"abc123ghi", "abcdef456",
		"abc123456"
	);

	// Test serialisation

	puts("## Beginning serialisation tests...\n");

	passed += serialisetest("The big cat", "The small cat");

	// Test binary safety

	puts("## Beginning binary safety tests...\n");

	passed += basictest("\x00\x00\x01\x00", "\x01\x00\x01\x00");
	
	stabteststart();
	mrdiff("Example", "\0");
	passed += stabtestpassed();

	// Stability tests

	// Basic: a test is considered passed if the program doesn't crash.
	// Advanced: let's try to recover semi-gracefully from invalid input.

	puts("## Beginning stability tests...\n");

	// Test mrunserialise's ability to handle invalid strings

	stabteststart();
	mrpatch("Example", mrunserialise(""));
	passed += stabtestpassed();

	stabteststart();
	mrpatch("Example", mrunserialise("a:::::"));
	passed += stabtestpassed();

	stabteststart();
	mrpatch("Example", mrunserialise("b::::"));
	passed += stabtestpassed();

	stabteststart();
	mrpatch("Example", mrunserialise("\0"));
	passed += stabtestpassed();
	
	stabteststart(); 
	mrpatch("Example", mrunserialise("b:0"));
	passed += stabtestpassed();

	stabteststart();
	mrpatch("Example", mrunserialise("b:b:"));
	passed += stabtestpassed();
	
	// Test patching against data that can't exist

	stabteststart();
	mrpatch("A", mrunserialise("b:100:b:100:b:100:"));
	passed += stabtestpassed();
		
	// Completed testing.

	printf("Passed %d/%d tests.\n", passed, total_tests);

	return (passed == total_tests) ? 0 : 1;

}

#else // MRDIFF_TESTS_ONLY

// Command-line client helpers ------------------------------------------------

void usage() {
	puts(
		"mrdiff " MRDIFF_VERSION "\n" \
		"Usage: mrdiff -[dpmh] {arguments...}\n" \
		"  -d file1 file2 output   Diff file2 against file1. \n" \
		"  -p file1 diff  output   Patch file1 with the file (diff).\n" \
		"  -m diff1 diff2 output   Merge diff1 and diff2 to produce a new diff.\n" \
		"  -h                      Print this message.\n" \
		"\n" \
		"The parameter (output) may be -, in which case any output is written to stdout."
	);
	exit(1);
}

void writeFile(const char* path, std::string& data) {
	if (!strcmp("-", path)) {
		// Write to stdout (including \00's)
		for(int i = 0, e = data.length(); i < e; i++) fputc(data[i], stdout);
	} else {
		// Write to real file

		FILE* f = fopen(path, "wb");
		if (! f) {
			fprintf(stderr, "WARNING: Could not open file \"%s\"\n", path);
		} else {
			fwrite(data.c_str(), 1, data.length(), f);
			fclose(f);
		}
	}
}

std::string readFile(const char* path) {
	FILE* f = fopen(path, "rb");
	if (!f) {
		fprintf(stderr, "FATAL: Could not open file \"%s\"\n", path);
		exit(1);
		return "";
	}

	fseek(f, 0, SEEK_END);
	long flen = ftell(f);
	fseek(f, 0, SEEK_SET);
	
	char *buff = new char[flen];
	memset(buff, 0, flen * sizeof(char));

	fread(buff, 1, flen, f);
	fclose(f);

	std::string ret;
	ret.assign(buff, flen);
	delete[] buff;

	return ret;
}

// Command-line entry point ---------------------------------------------------

int main(int argc, char* argv[]) {

	if (argc < 2) usage();
	string mode = argv[1];

	if (mode == "-h") { usage();
	} else if (mode == "-d") {
		if (argc != 5) { puts("Insufficient arguments."); usage(); }

		// Diff: file1 file2 output
		
		writeFile(argv[4], mrserialise(mrdiff(
			readFile(argv[2]),
			readFile(argv[3])
		)));

	} else if (mode == "-p") {
		if (argc != 5) { puts("Insufficient arguments."); usage(); }

		// Patch: file diff output
		
		writeFile(argv[4], mrpatch( 
			readFile(argv[2]),
			mrunserialise(readFile(argv[3]))
		));

	} else if (mode == "-m") {
		if (argc != 5) { puts("Insufficient arguments."); usage(); }

		// Merge: diff1 diff2 output
		
		writeFile(argv[4], mrserialise(mrmerge(
			mrunserialise(readFile(argv[2])),
			mrunserialise(readFile(argv[3]))
		)));

	} else {
		puts("Unknown mode.");
		usage();
	}

	return 1;
}

#endif