// mrDiff.cpp

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

// Constants

enum {
	MRDIFF_INVALID,
	MRDIFF_REFERENCE,
	MRDIFF_LITERAL
};

// Main diff algorithm
vector<diff_t> mrdiff_alg(string sl, string sr) {

	vector<diff_t> ret;
	int maxlen = 0, nmax = 0, omax = 0;
	int *matrix;
	try {
		matrix = new int[sl.length() * sr.length()]; // might be very large..
	} catch (std::bad_alloc ex) {
		MRDIFF_perror("FATAL: Out of memory.");
		return ret;
	}

	memset(matrix, 0, sl.length() * sr.length() * sizeof(int));
	int matrix_width = sr.length();

	for (int i = 0, e = sl.length(); i != e; i++) {
		int  oindex = i;
		char ovalue = sl[i];

		// Find intersections of the character
		vector<int> nkeys;
		for (int j=0, k=sr.length(); j!=k; j++) if (sr[j]==ovalue) nkeys.push_back(j);
		
		for (int j = 0, k = nkeys.size(); j != k; j++) {
			int nindex = nkeys[j];
			
			int check_idx = ((oindex-1)*matrix_width + (nindex-1));
			matrix[oindex*matrix_width + nindex] = 
				check_idx >= 0 && matrix[check_idx] ?
				matrix[(oindex-1)*matrix_width + (nindex-1)] + 1  :
				1
			;

			if ( matrix[oindex*matrix_width + nindex] > maxlen ) {
				maxlen = matrix[oindex*matrix_width + nindex];
				omax   = oindex + 1 - maxlen;
				nmax   = nindex + 1 - maxlen;
			}
		}
	}

	if (!maxlen) { // Literal
		diff_t replacement = {MRDIFF_LITERAL, sr, sl.length()};
		ret.push_back( replacement );
	} else { // Recurse
		vector<diff_t> on_left =
			mrdiff_alg( sl.substr(0, omax), sr.substr(0, nmax) );
		ret.insert( ret.end(), on_left.begin(), on_left.end() );

		diff_t on_centre = { MRDIFF_REFERENCE, "", maxlen };
		ret.push_back( on_centre );
		
		vector<diff_t> on_right =
			mrdiff_alg( sl.substr(omax + maxlen), sr.substr(nmax + maxlen) );
		ret.insert( ret.end(), on_right.begin(), on_right.end() );
	}

	// Tidy up
	delete[] matrix;
	return ret;
}

// Helper: Optimise away neighbouring objects of the same type
vector<diff_t> mroptimise(vector<diff_t>& d) {
	vector<diff_t> ret;
	int last_type = MRDIFF_INVALID;
	for (int i = 0, e = d.size(); i < e; i++) {
		if (d[i].type == MRDIFF_LITERAL && ! d[i].src_len && ! d[i].literal.length()) {
			continue;
		}

		if (d[i].type == last_type) { // Coalesce
			switch( last_type ) {
				case MRDIFF_LITERAL: {
					ret.back().literal.append(d[i].literal);
					ret.back().src_len += d[i].src_len;
				} break;
				case MRDIFF_REFERENCE: {
					ret.back().src_len += d[i].src_len;
				} break;
				default: { // Unknown or invalid type... push it through as-is
					ret.push_back(d[i]);
				} break;
			}
		} else { // Add 
			ret.push_back(d[i]);
		}
		last_type = d[i].type;
	}
	return ret;
}

// Wrapper around mrdiff_alg with some simple optimisations.
vector<diff_t> mrdiff(string sl, string sr) {
	vector<diff_t> ret;
	diff_t common_suffix = {0};

	// Trivial case
	if (sl.length() == sr.length() && sl == sr) {
		diff_t trivial = {MRDIFF_REFERENCE, "", sl.length()};
		ret.push_back( trivial );
		return ret;
	}

	// Extract common prefix
	int p = 0, m = (sl.length() < sr.length()) ? sl.length() : sr.length();
	while(p < m && sl[p] == sr[p]) p++;

	if (p) { // Found a common prefix
		diff_t common_prefix = {MRDIFF_REFERENCE, "", p};
		ret.push_back(common_prefix);
		sr = sr.substr(p); sl = sl.substr(p);
	}

	// Extract common suffix
	int sl_m = sl.length(), sr_m = sr.length(); p = 0; m = (sl_m < sr_m) ? sl_m : sr_m;
	while (p < m && sl[sl_m - p - 1] == sr[sr_m - p - 1]) p++;

	if (p) { // Found a common suffix
		common_suffix.type = MRDIFF_REFERENCE;
		common_suffix.src_len = p;
		sr = sr.substr(0, sr_m-p); sl = sl.substr(0, sl_m-p);
	}

	// Apply real diff algorithm to interior, and optimise away shared types
	vector<diff_t> real_diff = mroptimise(mrdiff_alg(sl, sr));

	// Tidy up
	ret.insert( ret.end(), real_diff.begin(), real_diff.end() );
	if (common_suffix.src_len) ret.push_back(common_suffix);

	return ret;
}

// Patch
string mrpatch(string orig, vector<diff_t>& d) {
	int p = 0, orig_len = orig.length();
	string ret;

	for (int i = 0, e = d.size(); i < e; i++) {
		// Error checking:
		if (p > orig_len) return ret;

		switch (d[i].type) {

			case MRDIFF_LITERAL: {
				ret.append(d[i].literal);
				p += d[i].src_len;
			} break;

			case MRDIFF_REFERENCE: {
				ret.append(orig.substr(p, d[i].src_len));
				p += d[i].src_len;
			} break;

			default: { // Invalid... ignore it
			} break;
		}
	}

	// Tidy up

	return ret;
}

// Two-way merge
vector<diff_t> mrmerge(vector<diff_t>& dl, vector<diff_t>& dr) {
	vector<diff_t> ret;
	
	// Copy the vectors into something we can clobber
	list<diff_t> ll; ll.insert(ll.begin(), dl.begin(), dl.end());
	list<diff_t> lr; lr.insert(lr.begin(), dr.begin(), dr.end());
	
	// We're always working with the first element of ll/lr.

	for(;;) {

		// Find longest common reference area from current offsets
		// If this length is greater than zero:
		//		Copy shortest common reference area	
		// Otherwise, if the length is zero:
		//		Copy literal, and advance src_len places in the other string


		// Find longest common reference area from current offsets
		if (ll.size() && lr.size()) {
			if (ll.front().type == MRDIFF_REFERENCE && lr.front().type == MRDIFF_REFERENCE) {
				
				// Copy the shortest common reference area.
				int l_len = ll.front().src_len, r_len = lr.front().src_len;
				int common_len = (l_len > r_len) ? r_len : l_len;

				diff_t common_reference_area = {MRDIFF_REFERENCE, "", common_len};
				ret.push_back( common_reference_area );

				// Apply offsets
				ll.front().src_len -= common_len;
				lr.front().src_len -= common_len;

			} else { // At least one of them is a literal.
				
				// Determine which literal to keep.
				list<diff_t> *llit = &lr, *lref = &ll;
				if (ll.front().type == MRDIFF_LITERAL) {
					llit = &ll; lref = &lr;
				}

				// Copy either literal, and advance the other string, swapping
				//  to the next diff_t if necessary

				ret.push_back( llit->front() );
			
				// We need to clear llit:src_len characters from lref, regardless of 
				// whatever type they are.
				// Iterate, and just ignore any literals by converting them to references.

				int clear_len = llit->front().src_len;
				llit->pop_front();

				while (clear_len) {
					int obj_len = lref->front().src_len;

					if (clear_len >= obj_len) {
						// >: We can't clear enough characters from this object, need more
						// =: Easy case, this is just what we need
						clear_len -= obj_len;
						lref->pop_front();
						// =: clear_len is now 0 and we're done

					} else { // (clear_len < obj_len)
						// We... only need a part of this object
						// Truncate it to a reference and shrink it by clear_len-obj_len
						lref->front().type = MRDIFF_REFERENCE; // ignore lit-lit collisions
						lref->front().src_len -= clear_len;
						clear_len = 0;
					}

				};
			}

			// Remove the first entry if they've been nulled

			if (ll.size() && ll.front().type == MRDIFF_REFERENCE && ll.front().src_len == 0) {
				ll.pop_front();
			}
			if (lr.size() && lr.front().type == MRDIFF_REFERENCE && lr.front().src_len == 0) {
				lr.pop_front();
			}
		
		}

		// Check if either list has been cleared
		// If so, keep everything remaining in the other list if there are any insertions.
		// If there were only references, then one of the diffs specifies deleting this area.
		// FIXME: deduplicate this somehow
		
		if (ll.empty()) {
			if (! lr.empty()) {
				// Check lr for insertions
				bool found_insertion = false;
				for (list<diff_t>::iterator i = lr.begin(), e = lr.end(); i != e; i++) {
					if (i->type == MRDIFF_LITERAL) {
						found_insertion = true;
						break;
					}
				}
				if (found_insertion) {
					ret.insert(ret.end(), lr.begin(), lr.end());
				}
			}
			break; // Finished
		}

		if (lr.empty()) {
			if (! ll.empty()) {
				// Check lr for insertions
				bool found_insertion = false;
				for (list<diff_t>::iterator i = ll.begin(), e = ll.end(); i != e; i++) {
					if (i->type == MRDIFF_LITERAL) {
						found_insertion = true;
						break;
					}
				}
				if (found_insertion) {
					ret.insert(ret.end(), ll.begin(), ll.end());
				}
			}
			break; // Finished
		}

	}	
		
	return ret;
}

int mrpos(int pos, vector<diff_t>& patch) {
	return 0;
}

// Serialise helper functions

std::string itos(int i) {
	char buff[16] = {0};
	sprintf(buff, "%d", i);
	return buff;
}

int getSerialisedToken(std::string& str, int *ptr) {
	int pos = str.find(':', *ptr);
	if (pos != str.npos) {
		std::string token = str.substr(*ptr, pos - *ptr);
		*ptr = pos;
		return atoi(token.c_str());
	} else {
		// Invalid stream!
		return -1;
	}
}

// Serialise main functions

std::string mrserialise (vector<diff_t>& d) {
	std::string ret;
	for (vector<diff_t>::iterator i = d.begin(), e = d.end(); i != e; i++) {
		if (i->type == MRDIFF_LITERAL) {

			ret += "a:" + itos(i->src_len) + ":" + itos(i->literal.length()) + ":";
			ret += i->literal;
			ret += ":";
			
		} else if (i->type == MRDIFF_REFERENCE) {

			ret += "b:" + itos(i->src_len) + ":";
		}
	}
	return ret;
}

#define MRDIFF_UNSERIALISE_CHECKP { \
	if (p >= e) { \
		MRDIFF_perror("mrunserialise: Ran past end of string"); \
		ret.clear(); \
		return ret; \
	} \
} \

vector<diff_t> mrunserialise (std::string d) {
	vector<diff_t> ret;
	int p = 0, e = d.length();

	while (p < e) {
		if (d[p] == 'a' && d[p+1] == ':') {
			p += 2;
			MRDIFF_UNSERIALISE_CHECKP;

			// Extract length tokens
			int src_len = getSerialisedToken(d, &p); p++;
			MRDIFF_UNSERIALISE_CHECKP;
			int lit_len = getSerialisedToken(d, &p); p++;
			MRDIFF_UNSERIALISE_CHECKP;

			// Extract string literal
			std::string literal = d.substr(p, lit_len);
			p += lit_len;
			MRDIFF_UNSERIALISE_CHECKP;

			if (d[p] == ':') {
				p++;
				diff_t lit_entry = {MRDIFF_LITERAL, literal, src_len};
				ret.push_back(lit_entry);
			} else {
				// Something's gone wrong: Invalid ending token
				MRDIFF_perror("mrunserialise: Invalid ending token");
				ret.clear();
				return ret;
			}

		} else if (d[p] == 'b' && d[p+1] == ':') {
			p += 2;
			MRDIFF_UNSERIALISE_CHECKP;

			// Extract length token
			int src_len = getSerialisedToken(d, &p);
			MRDIFF_UNSERIALISE_CHECKP;

			if (d[p] == ':') {
				p++;
				diff_t ref_entry = {MRDIFF_REFERENCE, "", src_len};
				ret.push_back(ref_entry);
			} else {
				// Something's gone wrong: Invalid ending token
				MRDIFF_perror("mrunserialise: Invalid ending token");
				ret.clear();
				return ret;
			}

		} else {
			// Something's gone wrong: Invalid diff format
			MRDIFF_perror("mrunserialise: Invalid diff format");
			ret.clear();
			return ret;
		}		
	}

	return ret;
}

#undef MRDIFF_UNSERIALISE_CHECKP

