// 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 <coral/lib/debug.h>
#include <coral/core/Project.h>

#include <ext/functional>

#include <algorithm>

NAMESPACE_BEGIN(coral);
NAMESPACE_BEGIN(DIFF);

NAMESPACE_BEGIN();

static Language* Ldiff;
static coral::ElementDefinition* EChangeOneValue;
static coral::ElementDefinition* ERemoveOrdered;
static coral::ElementDefinition* EInsertOrdered;
static coral::ElementDefinition* ERemove;
static coral::ElementDefinition* EInsert;


void do_slot_change_ed(Element* target,
		       Slot& targetslot,
		       const Element* op,
		       const Property* p) {
	if (p->isMultSingle()) {
		coral_assert(op->oclIsKindOf(EChangeOneValue));
		const Slot& oldValue =
			op->__getSlotByName__("oldValue");
		const Slot& newValue =
			op->__getSlotByName__("newValue");
		ElementPrimitive* v1 = dynamic_cast<ElementPrimitive*>(oldValue.__getitem__(0));
		ElementPrimitive* v2 = dynamic_cast<ElementPrimitive*>(newValue.__getitem__(0));
		coral_assert(v1 && v2);
		ElementPrimitive* current = dynamic_cast<ElementPrimitive*>(targetslot.__getitem__(0));
		if (*current == *v1) {
			current->swapWithAtom(v2);
		} else {
			throw std::exception();
		}
	} else if (p->isOrdered()) {
		coral_assert(op->oclIsKindOf(EInsertOrdered)
			     || op->oclIsKindOf(ERemoveOrdered));
	} else {
		coral_assert(op->oclIsKindOf(EInsert)
			     || op->oclIsKindOf(ERemove));
	}
}
void do_slot_change(Element* target,
		    const Element* propertyChange,
		    model_elements_t& u2e) {
	const Slot& s_ops = propertyChange->__getSlotByName__("operations");
	const Slot& s_name = propertyChange->__getSlotByName__("propertyName");
	coral_assert(target);

	const ElementPrimitive* str_p_name
		= dynamic_cast<const ElementPrimitive*>(s_name.__getitem__(0));
	coral_assert(str_p_name);
	const unicodestring& u(str_p_name->__getString__());
	const std::string& s(u.toUTF8());
	const char* propertyname = s.c_str();

	Slot& targetslot = target->__getSlotByName__(propertyname);
	const Property* p = targetslot.getProperty();

	/*
	 * Patch the targetslot with the operations
	 */
	for (Iterator i(s_ops); const ref_counted* r = i.next(); ) {
		const Element* op = throwing_cast<const Element*>(r);
		if (p->getTarget()->isDatatypeDefinition()) {
			do_slot_change_ed(target, targetslot, op, p);
		}
	}
}

void do_element_changes(model_elements_t& u2e,
			Collection* roots,
			const Slot& elementChanges) {

	for (Iterator i(elementChanges); const ref_counted* r = i.next(); ) {
		const Element* e = throwing_cast<const Element*>(r);
		const Slot& uuid = e->__getSlotByName__("element");
		const ElementString* uuid_es =
			dynamic_cast<const ElementString*>(uuid.__getitem__(0));
		const unicodestring& u(uuid_es->__str__());
		const std::string& uuid_s(u.toUTF8());
		model_elements_t::const_iterator change
			= u2e.find(uuid_s.c_str());
		if (change == u2e.end()) {
			throw std::exception();
		}

		Element* echange = const_cast<Element*>(change->second);

		const Slot& s_propertyChanges
			= e->__getSlotByName__("propertyChanges");
		for (Iterator j(s_propertyChanges);
		     const ref_counted* pc = j.next(); ) {
			do_slot_change(echange,
				       dynamic_cast<const Element*>(pc),
				       u2e);
		}
	}
}

void ModelPatch_internal(Collection* roots,
			 Project* prj,
			 Element* difference,
			 int params = 0) {

	if (prj) {
		coral_assert(!roots);
		roots = prj->contents();
	}

	coral_assert(roots);
	coral_assert(difference);
	coral_assert(params == 0);

	static coral::autounref<coral::Package> pdiff(
		coral::Package::__getLanguageByXMLNS__("http://www.abo.fi/DIFF/1.0"));
	if (!Ldiff) {
		EChangeOneValue
			= throwing_cast<coral::ElementDefinition*>(
				Ldiff->getElement("ChangeOneValue").get());
		ERemoveOrdered
			= throwing_cast<coral::ElementDefinition*>(
				Ldiff->getElement("RemoveOrdered").get());
		EInsertOrdered
			= throwing_cast<coral::ElementDefinition*>(
				Ldiff->getElement("InsertOrdered").get());
		ERemove
			= throwing_cast<coral::ElementDefinition*>(
				Ldiff->getElement("Remove").get());
		EInsert
			= throwing_cast<coral::ElementDefinition*>(
				Ldiff->getElement("Insert").get());
		Ldiff = throwing_cast<coral::Language*>(pdiff.get());
	}

	static autounref<AbstractElementDefinition> ECreateElement
		= Ldiff->getElement("CreateElement");
	static autounref<AbstractElementDefinition> EDeleteElement
		= Ldiff->getElement("DeleteElement");

	Slot& s = difference->__getSlotByName__("modelChanges");

	/*
	 * Create new elements
	 */
	for (Iterator i(s); const ref_counted* r = i.next(); ) {
		const Element* e = throwing_cast<const Element*>(r);
		coral_assert(e);
		if (!e->oclIsKindOf(ECreateElement.get()))
			continue;
		const Slot& s_type = e->__getSlotByName__("type");
		const Element* smd_m_type =
			throwing_cast<const Element*>(s_type.__getitem__(0));
		coral::autounref<AbstractElementDefinition>
			smd_mm_type = 
			AbstractElementDefinition::getAbstractElementDefinitionFromSMD(smd_m_type);
			
		coral::autounref<Element> created;
		created =
			dynamic_cast<Element*>(smd_mm_type->newAtom());
		// BUG...
		if (prj)
			created->__setProject__(prj);
		else
			roots->append(created.get());
	}

	model_elements_t u2e;
	getUUID2ElementMap(roots, u2e);


	Slot& se = difference->__getSlotByName__("elementChanges");
	do_element_changes(u2e, roots, se);

	/*
	 * Delete old elements
	 */
	for (Iterator i(s); ref_counted* r = i.next(); ) {
		const Element* e = throwing_cast<Element*>(r);
		if (!e->oclIsKindOf(EDeleteElement.get()))
			continue;
		const Slot& s_type = e->__getSlotByName__("type");
		const Element* smd_m_type =
			throwing_cast<const Element*>(s_type.__getitem__(0));
		coral_assert(smd_m_type);
		autounref<AbstractElementDefinition>
			smd_mm_type =
			AbstractElementDefinition::getAbstractElementDefinitionFromSMD(smd_m_type);
			
		// not done
		coral_assert(0);

		Element* todelete = NULL;
		if (prj)
			todelete->__setProject__(NULL);
		else
			roots->drop(todelete);
	}
}

NAMESPACE_END();

void ModelPatch(Project* prj,
		Element* difference,
		int params = 0) throw (std::exception) {
	ModelPatch_internal(NULL, prj, difference, params);
}

void ModelPatch_Collection(Collection* roots,
			   Element* difference,
			   int params = 0) throw (std::exception) {
	ModelPatch_internal(roots, NULL, difference, params);
}

NAMESPACE_END();
NAMESPACE_END();
