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

#include "ModelCopy.h"

#include <coral/core/Element.h>
#include <coral/core/ElementDefinition.h>
#include <coral/core/Collection.h>
#include <coral/lib/assert.h>

#include <coral/lib/autovector.h>
#include <coral/lib/ref_counted.h>
#include <coral/lib/helper_uuid.h>

NAMESPACE_BEGIN(coral);
typedef autounrefhash_map<const coral::Atom*,
			  coral::Atom*,
			  coral::ptr_hash<coral::Atom>
> copy1_type;

/* BUG this could just use DFSIterator()? */
Atom* __copy1(const Atom* a,
	      copy1_type& C,
	      int params) {
	copy1_type::const_iterator i = C.find(a);
	if (i != C.end()) return (*i).second;

	AbstractElementDefinition* type = a->__type__();
	coral_assert(type);
	Atom* newatom;
	/*
	 * Clone primitives, but special case PyObjects, don't deepcopy them 
	 */
	if (type->isDatatypeDefinition()
	    && ((!(params & MODELCOPY_DONT_SKIP_PYOBJECTS)) ? !dynamic_cast<const ElementPyObject*>(a) : true)) {
		const ElementPrimitive* e =
			static_cast<const ElementPrimitive*>(a);
		newatom = e->clone();
	} else {
		newatom = type->newAtom();
	}
	coral_assert(newatom != a);
	autounref<ref_counted> r(newatom);
	C[a] = newatom; // C has newatom's ref
	a->ref(); // will be eaten by C's destructor
	r.release();
	if (type->isDatatypeDefinition())
		return newatom;

	const Element* e = static_cast<const Element*>(a);
	Element* newelem = static_cast<Element*>(newatom);
	if (params & MODELCOPY_DONT_CREATE_NEW_UUIDS) {
		newelem->__setXMIid__(e->__XMIid__());
	}
		
	for (Element::slots_type::const_iterator ff = e->begin();
	     ff != e->end();
	     ++ff) {
		const Slot* s = (*ff).second;
		Property* p = s->getProperty();
		if (!p->isComposition())
			continue;
			
		Iterator m(s);
		ref_counted* _child;
		while ((_child = m.next()) != NULL) {
			Atom* child = dynamic_cast<Atom*>(_child);
			coral_assert(child);
			__copy1(child, C, params);
		}
	}

	return newatom;
}

Atom* __copy2(Atom* a,
	      copy1_type& C,
	      Set* has_new,
	      Set* phase3,
	      Set* roots,
	      int params) {
	if (has_new->includes(a))
		return (*C.find(a)).second;

	has_new->add(a);
		
	Atom* newatom = C[a];
	coral_assert(newatom);
	const AbstractElementDefinition* type = newatom->__type__();
	if (type->isDatatypeDefinition()) return newatom;

	Element* newelem = static_cast<Element*>(newatom);
	Element* oldelem = static_cast<Element*>(a);
		
	bool dont_attach = false;
	if (params & MODELCOPY_DONT_ATTACH_TO_PARENTS && roots->includes(oldelem)) {
		dont_attach = true;
	}

	for (Element::slots_type::const_iterator ff = oldelem->begin();
	     ff != oldelem->end();
	     ++ff) {
		const Slot* slot = (*ff).second;
		const Property* prop = slot->getProperty();

		bool oppo_isAtMostOne;
		const Property* opposite = prop->getOppositeProperty();
		/*
		 * Skip if instructed not to attach to parents
		 */
		if (dont_attach && opposite && opposite->isComposition())
			continue;

		if (opposite && !opposite->isAnonymous()) {
			oppo_isAtMostOne = opposite->isAtMostOne();
		} else {
			oppo_isAtMostOne = false;
		}

		bool skip_if_no_new =
			(oppo_isAtMostOne)
			|| (params & MODELCOPY_DONT_KEEP_ASSOCIATIONS_OUTSIDE_SUBTREE);
		/*
		 * Shallow-copy simple associations, but remember
		 * that they can point to newly copied, or old elements
		 */
		if (prop->isAssociation()) {
			Iterator m(slot);
			ref_counted* _child;
			while ((_child = m.next()) != NULL) {
				Atom* child
					= dynamic_cast<Atom*>(_child);
				coral_assert(child);
				Atom* newchild = NULL;
				Atom* use = child;
				copy1_type::const_iterator i = C.find(child);
				if (i != C.end()) {
					newchild = (*i).second;
					use = newchild;
				}
				coral_assert(use);
				/*
				 * Skip, if target can't keep multiple elements
				 * and it's not copied
				 */
				if (skip_if_no_new && !newchild) {
					continue;
				}

				/*
				 * Don't update the other end
				 */
#if 0
				dlog << DEBUGLEVEL_DEBUG << "Connecting " << newelem->__str__() << ":"
				     << prop->getName() << " to " << use->__str__()
				     << std::endl;
#endif
				Slot& newslot = newelem->__getSlotByName__(prop->getName());
				coral_assert(newelem != use);
				/*
				 * BUG pass 3
				 *
				 *
				 * If @use is outside our deep copy tree, it means that
				 * we'll never visit it, and set the bidi link
				 */
				bool uni = true;
				if (!newchild) {
					uni = false;
				}
				newslot.insert(use, -1, -1, uni);
			}
		} else if (prop->isComposition()) {
			/*
			 * Attributes and compositions are deep-copied
			 */
			Iterator m(slot);
			ref_counted* _child;
			while ((_child = m.next()) != NULL) {
				Atom* child
					= dynamic_cast<Atom*>(_child);
				coral_assert(child);
				Atom* newchild;
				newchild = __copy2(child,
						   C,
						   has_new,
						   phase3,
						   roots,
						   params);

				/*
				 * Don't update the other end
				 */
				autounref<ref_counted> _t(newchild);
				Slot& newslot = newelem->__getSlotByName__(prop->getName());
				coral_assert(newelem != newchild);
				newslot.insert(newchild, -1, -1, true);
				_t.release();

				/*
				 * BUG pass 3
				 */
			}
		} else {
			coral_assert(0);
		}
	}
	return newatom;
}

coral::autounref<Sequence>
ModelCopy(const Collection* collection,
	  int params,
	  modelcopy_map_t* orig2copy,
	  modelcopy_map_t* copy2orig) {
	autounref<Sequence> result(new Sequence());

	copy1_type old2new;
	autounref<Set> has_new(new Set());

	autounref<Set> roots(new Set());
	roots->unionWith(collection);
		
	{
		Iterator m(collection);
		ref_counted* _a;
		while ((_a = m.next()) != NULL) {
			Atom* a = dynamic_cast<Atom*>(_a);
			coral_assert(a);
			Atom* newatom =
				__copy1(a, old2new, params);
			autounref<ref_counted> _t3(newatom);
			result->add(newatom);
			_t3.release();
			// Attach to old project?
			if (params & MODELCOPY_DONT_ATTACH_TO_PARENTS)
				continue;
			Element* e = dynamic_cast<Element*>(a);
			if (!e) continue;
			Project* old = e->__getProject__();
			if (!old) continue;
			Element* enew = dynamic_cast<Element*>(newatom);
			coral_assert(enew);
			enew->__setProject__(old);
		}
	}

	{
		Iterator m(collection);
		ref_counted* _a;
		while ((_a = m.next()) != NULL) {
			Atom* a = dynamic_cast<Atom*>(_a);
			coral_assert(a);
			(void) __copy2(a,
				       old2new,
				       has_new.get(),
				       NULL,
				       roots.get(),
				       params);
		}
	}

	/*
	 * Bug pass 3 missing
	 */


	/*
	 * Fill in the orig2copy and copy2orig
	 */
	if (orig2copy || copy2orig) {
		for (copy1_type::const_iterator i = old2new.begin();
		     i != old2new.end();
		     ++i) {
			const Atom* a = i->first;
			const Element* e1 = dynamic_cast<const Element*>(a);
			if (!e1) continue;
			const Element* e2 = dynamic_cast<const Element*>(i->second);
			coral_assert(e2);
			if (orig2copy) {
				(*orig2copy)[e1] = e2;
			}
			if (copy2orig) {
				(*copy2orig)[e2] = e1;
			}
		}
	}



	return result;
}

autounref<coral::Atom> ModelCopy_One(const coral::Atom* a,
			   int params,
			   modelcopy_map_t* orig2copy,
			   modelcopy_map_t* copy2orig) {
	coral::Set* fake = new coral::Set();
	autounref<ref_counted> _f(fake);
	fake->add(a);
	autounref<Sequence>
		m = ModelCopy(fake,
			      params,
			      orig2copy,
			      copy2orig);
	ref_counted* _result = m->__getitem__(0);
	Atom* result = dynamic_cast<Atom*>(_result);
	coral_assert(result);
	result->ref();
	return autounref<Atom>(result);
}
NAMESPACE_END();


