#ifndef H_CORAL_DIFF_MYERS
#define H_CORAL_DIFF_MYERS
// This file is part of the Coral project.
// Please read the copyright notice in the LICENSE file.

#include <vector>
#include <deque>
#include <coral/lib/assert.h>

#include <iostream>

namespace coral { namespace DIFF {

template <class Type>
struct answer {
	enum command_t { INSERT, DELETE };
	command_t cmd;
	int idx;
	Type t;
	answer(command_t _cmd, int _idx, Type _t)
		: cmd(_cmd), idx(_idx), t(_t) {
		;
	}
	friend std::ostream& operator<<(std::ostream &output,
					const answer<Type>& a) {
		output << "Diff "
		       << (a.cmd == INSERT ? "Insert" : "Delete")
		       << " idx " << a.idx << " with information '"
		       << a.t << "'";
		return output;
	}
};


template <class Type>
static std::deque<answer<Type> >
	__getpath__(const std::vector<Type>& a,
		    const std::vector<Type>& b,
		    int D, int k,
		    const std::vector<std::vector<int> >& Vs,
		    int offset) {
	typedef std::deque<answer<Type> > ret_t;
	ret_t result;
	for (std::vector<std::vector<int> >::const_reverse_iterator i
		     = Vs.rbegin();
	     i != Vs.rend();
	     ++i) {
		coral_assert(!Vs.empty());
		const std::vector<int>& V = *i;
		int x = V[k + offset];
		int y = x - k;
		if (D == 0) {
			coral_assert(k == 0);
			coral_assert(x == y);
			coral_assert(x >= 0);
			x = 0;
			y = 0;
			break;
		}
		coral_assert(i + 1 != Vs.rend());
		const std::vector<int>& oldV  = *(i + 1);
		// Is parent a horizontal edge?
		int px = oldV[k - 1 + offset];
		int py = px - (k - 1);
		if ((px >= 0) && (py >= 0) && (px-py == x - y -1)) {
			;
		} else {
			px = oldV[k + 1 + offset];
			py = px - (k + 1);
		}
		coral_assert(px >= 0);
		coral_assert(py >= 0);
		while ((x > px) && (y > py) && (a[x] == b[y])) {
			--x;
			--y;
		}
		coral_assert(x >= 0);
		coral_assert(y >= 0);
		/*
		 * Horizontal movement
		 */
		if ((px + 1 == x) && (py == y)) {
			result.push_front(answer<Type>(
				answer<Type>::DELETE,
				x - 1,
				a[x]));
			--k; // Going left
		} else {
			result.push_front(answer<Type>(
				answer<Type>::INSERT,
				y - 1,
				b[y]));
			++k; // Going up
		}
		--D;
	}

	int deletes = 0;
	int inserts = 0;
	for (typename ret_t::iterator i = result.begin();
	     i != result.end();
	     ++i) {
		if ((*i).cmd == answer<Type>::DELETE) {
			(*i).idx = (*i).idx - deletes + inserts;
			++deletes;
		} else {
			coral_assert((*i).cmd == answer<Type>::INSERT);
			++inserts;
		}
	}

	return result;
}

/**
 *
 * Myers Difference Algorithm of two sequences.
 *
 * i1first to i1last is the first sequence,
 * i2first to i2last is the second sequence
 * nullvalue is a value which can be added at the beginning
 * Must support copying the iterated values
 * Determines equality with operator==.
 */
template <class InputIterator, class Type>
std::deque<answer<Type> >
MyersDiff(InputIterator i1first,
	  InputIterator i1last,
	  InputIterator i2first,
	  InputIterator i2last,
	  Type nullvalue = 0) {
	std::vector<Type> a;
	a.push_back(nullvalue);
	a.insert(a.end(), i1first, i1last);
	std::vector<Type> b;
	b.push_back(nullvalue);
	b.insert(b.end(), i2first, i2last);
	int alen = a.size();
	int blen = b.size();
	if ((alen == 1) && (blen == 1))
		return std::deque<answer<Type> >();
	coral_assert(alen >= 1);
	coral_assert(blen >= 1);
	int maxlen = alen + blen - 2;
	std::vector<int> V;
	std::vector<std::vector<int> > result;
	const int sentinel = -999;
	V.insert(V.begin(), maxlen * 2 + 1, sentinel);
	coral_assert(V.size() == static_cast<unsigned int>(maxlen) * 2 + 1);
	// Needed for initial step;
	V[maxlen+1] = 0;
	for (int D = 0; D < maxlen+1; ++D) {
		for (int k = -D; k < D+1; k += 2) {
			int x;
			int y;
			if ((k == -D) || ((k != D)
			    && (V[k - 1 + maxlen] < V[k + 1 + maxlen]))) {
				x = V[k + 1 + maxlen];
			} else {
				x = V[k - 1 + maxlen] + 1;
			}
			y = x - k;
			while ((x < alen - 1) && (y < blen - 1)
			       && (a[x+1] == b[y + 1])) {
				++x;
				++y;
			}
			V[k + maxlen] = x;
			if ((x >= alen-1) && (y >= blen - 1)) {
				result.push_back(V);
				return __getpath__(a,b,D,k,result,maxlen);
			}
		}
		result.push_back(V);
	}
	// There must be a solution
	coral_assert(0);
}

} }

#endif
