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

#include "SameModel.h"

#include <coral/core/Element.h>
#include <coral/core/ElementDefinition.h>
#include <cstdio>

#include <coral/lang/XMI/XMI.h>

#include <algorithm>

namespace coral {

	struct unordered_sorter {
		bool operator()(const Atom* const & a1,
				const Atom* const & a2) {
			if (a1 == a2) return false;
			const AbstractElementDefinition* t1 = a1->__type__();
			const AbstractElementDefinition* t2 = a2->__type__();
			if (t1 != t2) {
				return t1 < t2;
			}
			if (t1->isDatatypeDefinition()) {
				// Evil...
				return a1->__str__() < a2->__str__();
			}
			const Element* e1 = static_cast<const Element*>(a1);
			const Element* e2 = static_cast<const Element*>(a2);
			return e1->__XMIid__() < e2->__XMIid__();
		}
	};

	static inline bool isSameDatatype(const ElementPrimitive* a1,
					  const ElementPrimitive* a2) {
		return *a1 == *a2;
	}

	bool isSameElement(const Atom* a1, const Atom* a2) {
		if (!a1) return false;
		if (!a2) return false;
		if (a1->__type__() != a2->__type__()) {
			dlog << DEBUGLEVEL_DEBUG << "Different types for "
				  << a1->__str__()
				  << " and "
				  << a2->__str__()
				  << std::endl;
			return false;
		}
		AbstractElementDefinition* type = a1->__type__();
		if (type->isDatatypeDefinition()) {
			return isSameDatatype(
				static_cast<const ElementPrimitive*>(a1),
				static_cast<const ElementPrimitive*>(a2));
		}

		const Element* e1 = static_cast<const Element*>(a1);
		const Element* e2 = static_cast<const Element*>(a2);

		/*
		 * xmi:id and xmi:idref aren't saved with
		 * the XMI file, so we can safely ignore it here.
		 */
		if (e1->__type__() == XMI::Extension) {
			return true;
		}
		if (e1->__XMIid__() != e2->__XMIid__()) {
			dlog << DEBUGLEVEL_DEBUG << "Different XMI ids: "
				  << e1->__XMIid__() << " vs. "
				  << e2->__XMIid__()
				  << std::endl;
			return false;
		}
		return true;
	}

	bool __isSameModel__(const Atom* a1, const Atom* a2, bool this_is_oppo) {
		if (a1 == a2) return true;
		if (!isSameElement(a1, a2)) return false;

		if (a1->__type__()->isDatatypeDefinition()) return true;

		const Element* e1 = static_cast<const Element*>(a1);
		const Element* e2 = static_cast<const Element*>(a2);

		for (Element::slots_type::const_iterator i = e1->begin();
		     i != e1->end();
		     ++i) {
			const Slot& s1 = *(*i).second;
			const Property* p1 = s1.getProperty();

			/*
			 * On the second run, skip the features that were
			 * in a2 (the old a1)
			 */
			if (this_is_oppo) {
				if (e2->__hasSlotByName__(p1->getName())) {
					continue;
				}
			}

			/*
			 * Skip upwards going or anonymous slots
			 */
			const Property* poppo = p1->getOppositeProperty();
			if (poppo && (poppo->isComposition() || poppo->isAnonymous()))
				continue;

			bool (*func)(const Atom* a1, const Atom* a2);
			if (p1->isComposition()) {
				func = isSameModel;
			} else {
				func = isSameElement;
			}

			/* Notice the importance of using
			 * __getSlotRO__ due to single-valued primitive
			 * attributes with default values...
			 */
			const Slot& s2 = e2->__getSlotRO__(p1->getName());
			int size1 = s1.size();
			int size2 = s2.size();
			if ((size1 == 0) && (size2 == 0))
				continue;
			if (size1 != size2) {
				dlog << DEBUGLEVEL_DEBUG
				     << "Different sizes ("
				     << size1 << " and " << size2
				     << ") for property " << p1->getName() << std::endl;
				return false;
			}

			/*
			 * Check ordered slot
			 */
			if (p1->isOrdered()) {
				Iterator m1(s1);
				Iterator m2(s2);
				const ref_counted* _n1;
				while ((_n1 = m1.next()) != NULL) {
					const Atom* n1 =
						dynamic_cast<const Atom*>(_n1);
					const ref_counted* _n2 = m2.next();
					const Atom* n2 =
						dynamic_cast<const Atom*>(_n2);
					coral_assert(n2);
					if (!func(n1, n2)) {
						dlog << DEBUGLEVEL_DEBUG
						     << "Difference in ordered slot "
						     << p1->getName()
						     << " for element with UUID "
						     << e1->__XMIid__() << "!" << std::endl;
						dlog << "Difference between "
						     << n1->__str__() << " and "
						     << n2->__str__() << "!" << std::endl;
						return false;
					}
				}
				coral_assert(m2.next() == NULL);
			} else {
				/*
				 * The atoms don't arrive in order...
				 */
				typedef std::vector<const Atom*> atoms;
				atoms v1;
				atoms v2;
				v1.reserve(s1.size());
				v2.reserve(s2.size());
				// No need to count refs here
				{
					Iterator i1(s1);
					const ref_counted* _n1;
					while ((_n1 = i1.next()) != NULL) {
						const Atom* n1 =
							dynamic_cast<const Atom*>(_n1);
						v1.push_back(n1);
					}
				}
				{
					Iterator i2(s2);
					const ref_counted* _n2;
					while ((_n2 = i2.next()) != NULL) {
						const Atom* n2 =
							dynamic_cast<const Atom*>(_n2);
						v2.push_back(n2);
					}
				}

				std::sort(v1.begin(), v1.end(),
					  unordered_sorter());

				std::sort(v2.begin(),
					  v2.end(),
					  unordered_sorter());

				atoms::const_iterator i2 = v2.begin();
				for (atoms::const_iterator i1 = v1.begin();
				     i1 != v1.end();
				     ++i1, ++i2) {
					if (!func(*i1, *i2)) {
						dlog << DEBUGLEVEL_DEBUG
						     << "Difference in unordered slot "
						     << p1->getName()
						     << " for element with UUID "
						     << e1->__XMIid__() << "!" << std::endl;
						dlog << "Difference between "
						     << (*i1)->__str__() << " and "
						     << (*i2)->__str__() << "!" << std::endl;
						return false;
					}
				}
			}
		}
		return true;
	}
	bool isSameModel(const Atom* a1, const Atom* a2) {
		if (!__isSameModel__(a1, a2, false)) return false;
		return __isSameModel__(a2, a1, true);
	}
}

