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

#include "Difference.h"
#include <coral/core/ElementIterator.h>

#include <coral/core/xmlns_conversion_table.h>
#include <coral/core/diff/MyersDiff.h>

#include <ext/functional>

#include <algorithm>

NAMESPACE_BEGIN(coral);
NAMESPACE_BEGIN(DIFF);


NAMESPACE_BEGIN();

static Language* Ldiff;
static Language* Lxmidi20;

model_elements_t getMap(const Element* root) {
	model_elements_t t;
	Atom* a;
	AtomIterator a1(const_cast<Element*>(root));
	while ((a = a1.next()) != NULL) {
		coral::Element* e = dynamic_cast<coral::Element*>(a);
		if (!e) continue;
		t[e->__XMIid__().c_str()] = e;
		e->ref();
	}
	return t;
}


typedef std::map<const coral::Element*, const coral::Element*> mapping_t;

typedef std::set<const coral::Element*> missing_t;


void fix_missing_map(const model_elements_t& m1,
		     const model_elements_t& m2,
		     mapping_t& mapping,
		     missing_t& missing) {
	for (model_elements_t::const_iterator i = m1.begin();
	     i != m1.end();
	     ++i) {
		model_elements_t::const_iterator j = m2.find((*i).first);
		if (j == m2.end()) {
			missing.insert((*i).second);
			continue;
		}
		mapping[(*i).second] = (*j).second;
	}
}

/**
 *
 * Calculates a mapping from elements in roots1 to elements in roots2
 * The current algorithm uses XMI UUID for equality
 */
void calc_mapping(const Collection* roots1,
		  const Collection* roots2,
		  mapping_t& map_1_2,
		  mapping_t& map_2_1,
		  missing_t& miss_from_1,
		  missing_t& miss_from_2
	) {
	model_elements_t m1;
	model_elements_t m2;

	getUUID2ElementMap(roots1, m1);
	getUUID2ElementMap(roots2, m2);
	
	fix_missing_map(m1, m2, map_1_2, miss_from_2);
	fix_missing_map(m2, m1, map_2_1, miss_from_1);

	coral_assert(map_1_2.size() == map_2_1.size());
}

void pri(mapping_t& m) {
	std::cout << "Mapping " << std::endl;
	for (mapping_t::const_iterator i = m.begin();
	     i != m.end();
	     ++i) {
		std::cout << (*i).first << " -> " << (*i).second << std::endl;
	}
	std::cout << std::endl;
}

/**
 * Creates DIFF elements into @difference based on what is in @missing. Uses
 * CreateElement if @is_add is set, otherwise DeleteElement.
 */
void addOrDelElements(Element* difference,
		      missing_t& missing,
		      bool is_add,
		      int params) {
	static autounref<AbstractElementDefinition> ECreateElement = Ldiff->getElement("CreateElement");
	static autounref<AbstractElementDefinition> EDeleteElement = Ldiff->getElement("DeleteElement");

	Slot& s = difference->__getSlotByName__("modelChanges");
	for (missing_t::const_iterator i = missing.begin();
	     i != missing.end();
	     ++i) {
		const Element* e = *i;
		
		/*
		 * BUG ignores metaclasses which are a subclass of
		 * a XMIDI 2.0 metaclass.
		 */
		bool b(e->__type__()->__language__() == Lxmidi20);
		if (params & MODELDIFFERENCE_IGNORE_DIAGRAM_INFORMATION
		    && b) {
			continue;
		}
		if (params & MODELDIFFERENCE_ONLY_DIAGRAM_INFORMATION
		    && !b) {
			continue;
		}

		coral::autounref<coral::Element> child;
		if (is_add) {
			child = dynamic_cast<Element*>(ECreateElement->newAtom());
		} else {
			child = dynamic_cast<Element*>(EDeleteElement->newAtom());
		}
		coral_assert(child.get());
		s.insert(child.get());

		/*
		 * What UUID does this element have?
		 */
		Slot& s_element = child.get()->__getSlotByName__("element");
		coral::ElementString* es = dynamic_cast<coral::ElementString*>(s_element.__getitem__(0));
		coral_assert(es);
		es->__setString__(coral::unicodestring(e->__XMIid__(), false));

		/*
		 * Get type of new/deleted element
		 */
		coral::autounref<coral::Element> smd_e;
		smd_e = e->__type__()->getCorrespondingElement();
		if (!smd_e.get()) {
			std::string s;
			s += "Could not get corresponding element for ";
			s += e->__type__()->getName();
			s += "!";
			throw coral_error(s);
		}
		Slot& s_type = child.get()->__getSlotByName__("type");
		s_type.insert(smd_e.get());
	}
}

void helpOrderedDiff(std::vector<unicodestring>& v1,
		     std::vector<unicodestring>& v2,
		     Collection* coll) {
	static unicodestring empty("", true);
	typedef coral::DIFF::answer<coral::unicodestring> ans_t;
	typedef std::deque<ans_t > deq_ans_t;
	const deq_ans_t deq(MyersDiff(v1.begin(), v1.end(), v2.begin(), v2.end(), empty));
				
	static autounref<AbstractElementDefinition> xERemoveOrdered
		= Ldiff->getElement("RemoveOrdered");
	static autounref<AbstractElementDefinition> xEInsertOrdered
		= Ldiff->getElement("InsertOrdered");
	static ElementDefinition* ERemoveOrdered = dynamic_cast<ElementDefinition*>(xERemoveOrdered.get());
	static ElementDefinition* EInsertOrdered = dynamic_cast<ElementDefinition*>(xEInsertOrdered.get());

	for (deq_ans_t::const_iterator i = deq.begin();
	     i != deq.end();
	     ++i) {
		const ans_t& ans = *i;
		ElementDefinition* ed = ERemoveOrdered;
		if (ans.cmd == ans_t::INSERT) {
			ed = EInsertOrdered;
		}
		coral_assert(ed);
		Element* _e = (*ed)();
		autounref<coral::Element> e(_e);
		coral_assert(_e);
		Slot& s_idx = e->__getSlotByName__("index");
		Slot& s_target = e->__getSlotByName__("target");
		ElementPrimitive* idx1 = dynamic_cast<ElementPrimitive*>(s_idx.__getitem__(0));
		ElementPrimitive* str1 = dynamic_cast<ElementPrimitive*>(s_target.__getitem__(0));
		idx1->__setIValue__(ans.idx);
		str1->__setString__(ans.t);
		coll->insert(_e);
	}
}

void helphelpUnorderedDiff(const std::set<unicodestring>& v1,
			   const std::set<unicodestring>& v2,
			   Sequence* coll,
			   ElementDefinition* ed_to_use) {
	typedef std::set<coral::unicodestring> res_t;
	res_t res;
	std::insert_iterator<std::set<coral::unicodestring> >
		ins(res, res.begin());
	std::set_difference(v1.begin(), v1.end(),
			    v2.begin(), v2.end(),
			    ins);
	for (res_t::const_iterator i = res.begin();
	     i != res.end();
	     ++i) {
		Element* _e = (*ed_to_use)();
		autounref<coral::Element> e(_e);
		Slot& s_target = e->__getSlotByName__("target");
		ElementPrimitive* str1 = dynamic_cast<ElementPrimitive*>(s_target.__getitem__(0));
		str1->__setString__(*i);
		coll->insert(_e);
	}
}

void helpUnorderedDiff(std::set<unicodestring>& v1,
		       std::set<unicodestring>& v2,
		       Sequence* coll) {
	static autounref<AbstractElementDefinition> xERemove
		= Ldiff->getElement("Remove");
	static coral::ElementDefinition* ERemove
		= dynamic_cast<ElementDefinition*>(xERemove.get());
	coral_assert(ERemove);

	static autounref<AbstractElementDefinition> xEInsert
		= Ldiff->getElement("Insert");
	static coral::ElementDefinition* EInsert
		= dynamic_cast<ElementDefinition*>(xEInsert.get());
	coral_assert(EInsert);
	
	helphelpUnorderedDiff(v1, v2, coll, ERemove);
	helphelpUnorderedDiff(v2, v1, coll, EInsert);
}

/**
 * Compares slots of Property @p in @e1 and @e2.
 * Result is inserted into @coll.
 */
void compareTwoSlots(const Element* e1,
		     const Element* e2,
		     const Property* p,
		     autounref<coral::Sequence>& coll) {
	coral_assert(p);
	coral_assert(!p->isBag());

	const Slot* s1 = NULL;
	const Slot* s2 = NULL;
	if (e1) {
		try {
			s1 = &e1->__getSlotRO__(p->getName());
		} catch (bad_slot& exc) {
			;
		}
	}
	if (e2) {
		try {
			s2 = &e2->__getSlotRO__(p->getName());
		} catch (bad_slot& exc) {
			;
		}
	}


	if (s1 && s2) {
		unsigned int s = s1->size();
		if (s == 0 && s == s2->size()) return;
	}

	if (p->getTarget()->isDatatypeDefinition()) {
		if (p->isMultSingle()) {
			const ElementPrimitive* a1 = NULL;
			const ElementPrimitive* a2 = NULL;
			if (s1) a1 = dynamic_cast<const ElementPrimitive*>(s1->__getitem__(0));
			if (s2) a2 = dynamic_cast<const ElementPrimitive*>(s2->__getitem__(0));
			if (a1 && a2 && *a1 == *a2) return;
			if (!a1 && !a2) return;
			autounref<const ElementPrimitive> aep;
			if (!a1) {
				ElementPrimitive* aa = a2->clone();
				a1 = aa;
				aep = a1;
				aa->reset();
			} else if (!a2) {
				ElementPrimitive* aa = a1->clone();
				a2 = aa;
				aep = a2;
				aa->reset();
			}
			static autounref<AbstractElementDefinition> EChangeOneValue
				= Ldiff->getElement("ChangeOneValue");
			autounref<coral::Element>
				sc(dynamic_cast<coral::Element*>(EChangeOneValue->newAtom()));
			Slot& oldValue = sc->__getSlotByName__("oldValue");
			Slot& newValue = sc->__getSlotByName__("newValue");
			ElementPrimitive* str1 = dynamic_cast<ElementPrimitive*>(oldValue.__getitem__(0));
			ElementPrimitive* str2 = dynamic_cast<ElementPrimitive*>(newValue.__getitem__(0));
			coral_assert(str1 && str2);
			if (a1) str1->__setString__(a1->__unicode__());
			if (a2) str2->__setString__(a2->__unicode__());
			if (*str1 == *str2) return;
			coll->insert(sc.get());
		} else if (p->isOrdered()) {
			std::vector<coral::unicodestring> v1;
			std::vector<coral::unicodestring> v2;
			if (s1) {
				for (Iterator i(s1); const ref_counted* r = i.next(); ) {
					const ElementPrimitive* ep =
						dynamic_cast<const ElementPrimitive*>(r);
					coral_assert(ep);
					v1.push_back(ep->__unicode__());
				}
			}

			if (s2) {
				for (Iterator i(s2); const ref_counted* r = i.next(); ) {
					const ElementPrimitive* ep =
						dynamic_cast<const ElementPrimitive*>(r);
					coral_assert(ep);
					v2.push_back(ep->__unicode__());
				}
			}
			helpOrderedDiff(v1,
					v2,
					coll.get());
		} else {
			std::set<coral::unicodestring> v1;
			std::set<coral::unicodestring> v2;
			if (s1) {
				for (Iterator i(s1); const ref_counted* r = i.next(); ) {
					const ElementPrimitive* ep =
						dynamic_cast<const ElementPrimitive*>(r);
					coral_assert(ep);
					v1.insert(ep->__unicode__());
				}
			}

			if (s2) {
				for (Iterator i(s2); const ref_counted* r = i.next(); ) {
					const ElementPrimitive* ep =
						dynamic_cast<const ElementPrimitive*>(r);
					coral_assert(ep);
					v2.insert(ep->__unicode__());
				}
			}
			helpUnorderedDiff(v1, v2, coll.get());
		}
	} else if (p->isMultSingle()) {
		const Element* a1 = NULL;
		const Element* a2 = NULL;
		if (s1) a1 = dynamic_cast<const Element*>(s1->__getitem__(0));
		if (s2) a2 = dynamic_cast<const Element*>(s2->__getitem__(0));
		if (a1 == a2 || a1 && a2 && a1->__XMIid__() == a2->__XMIid__())
			return;
		static autounref<AbstractElementDefinition> EChangeOneValue
			= Ldiff->getElement("ChangeOneValue");
		autounref<coral::Element>
			sc(dynamic_cast<coral::Element*>(EChangeOneValue->newAtom()));
		Slot& oldValue = sc->__getSlotByName__("oldValue");
		Slot& newValue = sc->__getSlotByName__("newValue");
		ElementPrimitive* str1
			= dynamic_cast<ElementPrimitive*>(oldValue.__getitem__(0));
		ElementPrimitive* str2
			= dynamic_cast<ElementPrimitive*>(newValue.__getitem__(0));
		coral_assert(str1 && str2);
		if (a1) str1->__setString__(unicodestring(a1->__XMIid__(), false));
		if (a2) str2->__setString__(unicodestring(a2->__XMIid__(), false));
		coll->insert(sc.get());

	} else if (p->isOrdered()) {
		std::vector<coral::unicodestring> v1;
		std::vector<coral::unicodestring> v2;
		if (s1) {
			for (Iterator i(s1);
			     const ref_counted* r = i.next(); ) {
				const Element* ep =
					dynamic_cast<const Element*>(r);
				coral_assert(ep);
				v1.push_back(unicodestring(ep->__XMIid__(), false));
			}
		}
		if (s2) {
			for (Iterator i(s2);
			     const ref_counted* r = i.next(); ) {
				const Element* ep =
					dynamic_cast<const Element*>(r);
				coral_assert(ep);
				v2.push_back(unicodestring(ep->__XMIid__(), false));
			}
		}
		helpOrderedDiff(v1,
				v2,
				coll.get());
	} else {
		std::set<coral::unicodestring> v1;
		std::set<coral::unicodestring> v2;
		if (s1) {
			for (Iterator i(s1);
			     const ref_counted* r = i.next(); ) {
				const Element* ep =
					dynamic_cast<const Element*>(r);
				coral_assert(ep);
				v1.insert(unicodestring(ep->__XMIid__(), false));
			}
		}
		if (s2) {
			for (Iterator i(s2);
			     const ref_counted* r = i.next(); ) {
				const Element* ep =
					dynamic_cast<const Element*>(r);
				coral_assert(ep);
				v2.insert(unicodestring(ep->__XMIid__(), false));
			}
		}
		helpUnorderedDiff(v1, v2, coll.get());

	}
}

/**
 * Calculates all slot changes between @e1 and @2 and places
 * them in @difference.
 */
void calcElementChange(Element* difference,
		       const Element* e1,
		       const Element* e2,
		       int params) {
	coral_assert(e1 || e2);

	const ElementDefinition* ed1 = e1 ? e1->__type__() : e2->__type__();
	const ElementDefinition* ed2 = e2 ? e2->__type__() : e1->__type__();

	const Element* e_valid = e1 ? e1 : e2;
	coral_assert(e_valid);

	/*
	 * BUG ignores metaclasses which are a subclass of
	 * a XMIDI 2.0 metaclass.
	 */
	bool b(e_valid->__type__()->__language__() == Lxmidi20);
	if (params & MODELDIFFERENCE_IGNORE_DIAGRAM_INFORMATION
	    && b) {
		return;
	}
	if (params & MODELDIFFERENCE_ONLY_DIAGRAM_INFORMATION
	    && !b) {
		return;
	}

	typedef std::set<const Property*> keys_t;
	keys_t keys;
	std::insert_iterator<std::set<const Property*> >
		ins(keys, keys.begin());

	if (ed1) {
		std::transform(ed1->getProperties()->begin(),
			       ed1->getProperties()->end(),
			       ins,
			       __gnu_cxx::select2nd<coral::ElementDefinition::properties_type::value_type>());
	}

	if (ed2) {
		std::transform(ed2->getProperties()->begin(),
			       ed2->getProperties()->end(),
			       ins,
			       __gnu_cxx::select2nd<coral::ElementDefinition::properties_type::value_type>());
	}

	static autounref<AbstractElementDefinition> EElementChange
		= Ldiff->getElement("ElementChange");
	static autounref<AbstractElementDefinition> EPropertyChange
		= Ldiff->getElement("PropertyChange");

	autounref<coral::Element> ec(dynamic_cast<Element*>(EElementChange->newAtom()));
	coral_assert(ec.get());

	Slot& s_propertyChanges = ec->__getSlotByName__("propertyChanges");

	autounref<coral::Sequence> coll(NULL);
	for (keys_t::const_iterator i = keys.begin();
	     i != keys.end();
	     ++i) {
		const Property* p = *i;
		if (p->isUnserializable()) continue;
		if (p->isAnonymous()) continue;
		if (!coll.get() || coll->size() > 0) {
			coll = new coral::Sequence();
		}
		compareTwoSlots(e1, e2, p, coll);
		if (coll->size() == 0) continue;

		autounref<coral::Element> ep(dynamic_cast<Element*>(EPropertyChange->newAtom()));
		Slot& s_ops = ep->__getSlotByName__("operations");
		Slot& s_name = ep->__getSlotByName__("propertyName");
		ElementPrimitive* str_p_name
			= dynamic_cast<ElementPrimitive*>(s_name.__getitem__(0));
		str_p_name->__setString__(unicodestring(p->getName(), true));
		
		for (Iterator i(coll.get()); ref_counted* r = i.next(); ) {
			Atom* a = dynamic_cast<coral::Atom*>(r);
			coral_assert(a);
			s_ops.insert(a);
		}

		s_propertyChanges.insert(ep.get());
	}

	if (s_propertyChanges.size() > 0) {
		Slot& s_ec = difference->__getSlotByName__("elementChanges");
		s_ec.insert(ec.get());

		Slot& s_element = ec->__getSlotByName__("element");
		dynamic_cast<ElementPrimitive*>(s_element.__getitem__(0))->__setString__(coral::unicodestring(e_valid->__XMIid__(), false));

	}
}


void calcAllElementChanges(Element* difference,
			   mapping_t& map_1_2,
			   mapping_t& map_2_1,
			   missing_t& miss_from_1,
			   missing_t& miss_from_2,
			   int params) {

	for (mapping_t::const_iterator i = map_1_2.begin();
	     i != map_1_2.end();
	     ++i) {
		calcElementChange(difference, i->first, i->second, params);
	}

	for (missing_t::const_iterator i = miss_from_1.begin();
	     i != miss_from_1.end();
	     ++i) {
		calcElementChange(difference, NULL, *i, params);
	}

	for (missing_t::const_iterator i = miss_from_2.begin();
	     i != miss_from_2.end();
	     ++i) {
		calcElementChange(difference, *i, NULL, params);
	}
}

NAMESPACE_END();

void getUUID2ElementMap(const Collection* roots,
			model_elements_t& mret) {
	for (Iterator i(roots); const ref_counted* r = i.next(); ) {
		model_elements_t m(getMap(dynamic_cast<const coral::Element*>(r)));
		std::copy(m.begin(), m.end(), inserter(mret, mret.begin()));
		m.clear();
	}
}

coral::autounref<Element> ModelDifference(const Collection* roots1,
					  const Collection* roots2,
					  int params) throw (std::exception) {
	static coral::autounref<coral::Package> pdiff(
		coral::Package::__getLanguageByXMLNS__("http://www.abo.fi/DIFF/1.0"));
	if (!Ldiff) Ldiff = dynamic_cast<coral::Language*>(pdiff.get());
	coral_assert(Ldiff);

	static coral::autounref<coral::Package> pxmidi20(
		coral::Package::__getLanguageByXMLNS__("org.omg.xmi.namespace.XMIDI"));
	if (!Lxmidi20) Lxmidi20 = dynamic_cast<coral::Language*>(pxmidi20.get());
	coral_assert(Lxmidi20);


	static autounref<AbstractElementDefinition> EDifference = Ldiff->getElement("Difference");
	coral::autounref<Element> root(dynamic_cast<Element*>(EDifference->newAtom()));

	mapping_t map_1_2;
	mapping_t map_2_1;
	missing_t missing_from_1;
	missing_t missing_from_2;

	calc_mapping(roots1, roots2, map_1_2, map_2_1, missing_from_1, missing_from_2);

	addOrDelElements(root.get(), missing_from_1, true, params);
	addOrDelElements(root.get(), missing_from_2, false, params);

	//pri(map_2_1);
	//pri(map_1_2);

	calcAllElementChanges(root.get(),
			      map_1_2, map_2_1,
			      missing_from_1, missing_from_2,
			      params);

	return root;
}


coral::autounref<Element> ModelDifference_One(const Element* root1,
					      const Element* root2,
					      int params) throw (std::exception) {
	Set s1;
	Set s2;
	s1.insert(root1);
	s2.insert(root2);
	return ModelDifference(&s1, &s2, params);
}

NAMESPACE_END();
NAMESPACE_END();
