// mrDiff.cpp

/*

Copyright (c) 2005-2009 by Matthias Hertel, http://www.mathertel.de/
Copyright (c) 2011 by Mason Giles

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of
   conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list
   of conditions and the following disclaimer in the documentation and/or other materials
   provided with the distribution.
3. Neither the name of the copyright owners nor the names of its contributors may be used
   to endorse or promote products derived from this software without specific prior
   written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

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

// Constants

enum {
	MRDIFF_INVALID,
	MRDIFF_REFERENCE,
	MRDIFF_LITERAL
};

struct point {
	int x, y;
};

struct diffable {
	string data;
	vector<bool> modified;
	diffable(string str) : data(str) {
		modified.resize( data.length() );
	}
	int length() { return data.length(); }
};

point SMS(
	diffable& DataA, int LowerA, int UpperA, diffable& DataB, int LowerB, int UpperB,
	vector<int>& DownVector, vector<int>& UpVector
) {
	point ret;
	int MAX = DataA.length() + DataB.length() + 1;

	int DownK = LowerA - LowerB; // the k-line to start the forward search
	int UpK   = UpperA - UpperB; // the k-line to start the reverse search

	int Delta = (UpperA - LowerA) - (UpperB - LowerB);
	bool oddDelta = (Delta & 1) != 0;

	// The vectors in the publication accepts negative indexes. the vectors implemented
	//  here are 0-based and are access using a specific offset: UpOffset UpVector
	//  and DownOffset for DownVektor
	int DownOffset = MAX - DownK, UpOffset = MAX - UpK;

	int MaxD = ((UpperA - LowerA + UpperB - LowerB) / 2) + 1;

	// init vectors
	DownVector[DownOffset + DownK + 1] = LowerA;
	UpVector[UpOffset + UpK - 1] = UpperA;

	for (int D = 0; D <= MaxD; D++) {

		// Extend the forward path.
		for (int k = DownK - D; k <= DownK + D; k += 2) {

			// find the only or better starting point
			int x, y;
			if (k == DownK - D) {
				x = DownVector[DownOffset + k + 1]; // down
			} else {
				x = DownVector[DownOffset + k - 1] + 1; // a step to the right
				if ((k < DownK + D) && (DownVector[DownOffset + k + 1] >= x))
					x = DownVector[DownOffset + k + 1]; // down
			}
			y = x - k;

			// find the end of the furthest reaching forward D-path in diagonal k.
			while ((x < UpperA) && (y < UpperB) && (DataA.data[x] == DataB.data[y])) {
				x++; y++;
			}
			DownVector[DownOffset + k] = x;

			// overlap ?
			if (oddDelta && (UpK - D < k) && (k < UpK + D)) {
				if (UpVector[UpOffset + k] <= DownVector[DownOffset + k]) {
					ret.x = DownVector[DownOffset + k];
					ret.y = DownVector[DownOffset + k] - k;
					return (ret);
				}
			}

		}

		// Extend the reverse path.
		for (int k = UpK - D; k <= UpK + D; k += 2) {
			
			// find the only or better starting point
			int x, y;
			if (k == UpK + D) {
				x = UpVector[UpOffset + k - 1]; // up
			} else {
				x = UpVector[UpOffset + k + 1] - 1; // left
				if ((k > UpK - D) && (UpVector[UpOffset + k - 1] < x))
				x = UpVector[UpOffset + k - 1]; // up
			} // if
			y = x - k;

			while (
				(x > LowerA) &&
				(y > LowerB) &&
				(DataA.data[x - 1] == DataB.data[y - 1])
			) {
				x--; y--; // diagonal
			}
			UpVector[UpOffset + k] = x;

			// overlap ?
			if (!oddDelta && (DownK - D <= k) && (k <= DownK + D)) {
				if (UpVector[UpOffset + k] <= DownVector[DownOffset + k]) {
					ret.x = DownVector[DownOffset + k];
					ret.y = DownVector[DownOffset + k] - k;
					return (ret);
				}
			}

		}
	}

	// The algorithm should never come here.
	MRDIFF_perror("WARNING: SMS Algorithm in invalid state");
	return point();
}

void LCS(
	diffable& DataA, int LowerA, int UpperA, diffable& DataB, int LowerB, int UpperB,
	vector<int>& DownVector, vector<int>& UpVector
) {
	
	// Fast walkthrough equal lines at the start
	while (
		LowerA < UpperA &&
		LowerB < UpperB && DataA.data[LowerA] == DataB.data[LowerB]
	) {
		LowerA++; LowerB++;
	}

	// Fast walkthrough equal lines at the end
	while (
		LowerA < UpperA &&
		LowerB < UpperB && DataA.data[UpperA - 1] == DataB.data[UpperB - 1]
	) {
		--UpperA; --UpperB;
	}

	if (LowerA == UpperA) {
		
		// mark as inserted lines.
		while (LowerB < UpperB) DataB.modified[LowerB++] = true;

	} else if (LowerB == UpperB) {

		// mark as deleted lines.
		while (LowerA < UpperA) DataA.modified[LowerA++] = true;

	} else {

		// Find the middle snakea and length of an optimal path for A and B
		point smsrd = SMS(
			DataA, LowerA, UpperA, DataB, LowerB, UpperB, DownVector, UpVector
		);
		
		// The path is from LowerX to (x,y) and (x,y) to UpperX
		LCS(DataA, LowerA, smsrd.x, DataB, LowerB, smsrd.y, DownVector, UpVector);
		LCS(DataA, smsrd.x, UpperA, DataB, smsrd.y, UpperB, DownVector, UpVector);
	}
}

vector<diff_t> CreateDiffs(diffable DataA, diffable DataB) {
	vector<diff_t> a; // a = new ArrayList();
	diff_t aItem;
	//Item[] result;

	int StartA, StartB, LineA = 0, LineB = 0, pos = 0;

	while (LineA < DataA.length() || LineB < DataB.length()) {
		if (
			(LineA < DataA.length()) && (!DataA.modified[LineA])
			&& (LineB < DataB.length()) && (!DataB.modified[LineB])
		) {
			// equal lines
			LineA++;
			LineB++;

		} else {
			// maybe deleted and/or inserted lines
			StartA = LineA;
			StartB = LineB;

			while (
				LineA < DataA.length() &&
				(LineB >= DataB.length() || DataA.modified[LineA])
			) LineA++;

			while (
				LineB < DataB.length() &&
				(LineA >= DataA.length() || DataB.modified[LineB])
			) LineB++;

			if ((StartA < LineA) || (StartB < LineB)) {
				// store a new difference-item
								
				if (pos < StartA) {
					// Insert a reference to get us up to date.
					diff_t rItem = {MRDIFF_REFERENCE, "", StartA - pos};
					a.push_back(rItem);

					pos += rItem.src_len; // = StartA
				}

				diff_t aItem = {
					MRDIFF_LITERAL,
					DataB.data.substr(StartB, LineB-StartB),
					LineA-StartA
				};
				pos += aItem.src_len;

				a.push_back(aItem);
			}
		}
	}

	if (pos < DataA.length()) { // Insert remaining data
		diff_t rItem = {MRDIFF_REFERENCE, "", DataA.length() - pos};
		a.push_back(rItem);
	}

	return a;
}

#ifndef MRDIFF_USE_NAIVE_PATH
vector<diff_t> mrdiff_alg(string sl, string sr) {

	
	diffable DataA(sl), DataB(sr);

	int MAX = DataA.length() + DataB.length() + 1;
	
	// vector for the (0,0) to (x,y) search
	vector<int> DownVector; DownVector.resize(2 * MAX + 2);
	// vector for the (u,v) to (N,M) search
	vector<int> UpVector; UpVector.resize(2 * MAX + 2);

	LCS(DataA, 0, DataA.length(), DataB, 0, DataB.length(), DownVector, UpVector);
	return CreateDiffs(DataA, DataB);
}

#else

// 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;
}
#endif

// 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};
	
#ifdef MRDIFF_ENABLE_PREDIFF_OPTIMISATIONS
	// 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);
	}
#endif

	// 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
				MRDIFF_perror("WARNING: Unknown object type");
			} 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) {
						// >: 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 l/l 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

