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

#include <coral/coral.h>

#include <coral/lib/autovector.h>
#include <coral/lib/List.h>

#include <coral/core/ElementDefinition.h>
#include <coral/core/PrimitiveProperties.h>
#include <coral/core/DCE_UUID.h>

#include <coral/lib/multithread.h>
#include <coral/lib/noncopy.h>
#include <coral/lib/struct_helpers.h>

#include <coral/core/Collection.h>

#include <coral/core/python_compat.h>

#include <stdexcept>
#include <iostream>

#include <list>
#include <string>
#include <memory>

#include <coral/lib/unicodestring.h>
#include <bits/stl_iterator.h>
#include <coral/core/PyOperation.h>
#include <coral/core/garbage_collector.h>

#include <coral/core/exception.h>

namespace coral {

	class EXPORTFWD_coral ElementPrimitive;
	class EXPORTFWD_coral Collection;
	class EXPORTFWD_coral Element;
	class EXPORTFWD_coral AtomIterator;
	class EXPORTFWD_coral Project;
	class EXPORTFWD_coral OrderedSet;

#ifndef SWIG
	typedef List all_modelelements_type;

#ifdef COMPILING_coral
	/* only the core of Coral needs to have these */

	/* Protects all_modelelements. */
	EXTERN_MUTEX(mutex_modelelements);

	extern all_modelelements_type all_modelelements;
#endif
	std::size_t EXPORT_coral all_modelelements_size();
#endif

	class EXPORTFWD_coral Slot;
	class EXPORT_coral Atom : public ref_counted, public noncopy {
	protected:
		AbstractElementDefinition* m_type;
		explicit Atom(AbstractElementDefinition* type);
	public:
		virtual ~Atom() throw ();
		coral::AbstractElementDefinition* __type__() const throw () {
			return m_type;
		}
		/*
		 * Checks if @this owns @obj.
		 * Succeeds even though there is no path back
		 * from @obj to @this. (cf. __isConnectedTo__)
		 */
		virtual bool __isPart__(const Atom* obj,
				    bool recurse = true) const throw () {
			return this == obj;
		}
		virtual coral::unicodestring __str__() const CORAL_VIRTUAL;

		
		coral::autounref<coral::Set> __asSet__()
			throw (std::exception);
		bool oclIsKindOf(const coral::AbstractElementDefinition* e) const throw () {
			return this->__type__()->isSubclass(e);
		}

		// ALL BELOW MOVED FROM ELEMENTPRIMITIVE

	     /* Returns the integer representation. Valid for enumerations
		and integers. */
	     virtual int __int__() const throw ();
	     long __long__() const throw () { return __int__(); }
	     /* Returns the floating-point approximation. */
	     virtual double __float__() const throw ();
	     virtual int __getEValue__() const throw ();
	     virtual coral::unicodestring __getEValueByName__();
	     virtual void __setEValue__(int newValue);
	     virtual void __setEValueByName__(const char* newValue);
	     virtual PyObject* __getPyValue__() const throw ();
	     virtual void __setPyValue__(PyObject*) throw ();
	     virtual int __getIValue__() const throw ();
	     virtual void __setIValue__(int newValue);
	     virtual double __getDValue__() const throw ();
	     virtual void __setDValue__(double newValue) throw ();
	     virtual void __setString__ (const coral::unicodestring& newValue);
	     virtual const coral::unicodestring& __getString__() const throw ();
	};

#ifndef SWIG
	class slots_lessthancmp {
	public:
		bool operator()(const Property* const& x, const Property* const & y) const throw () {
			return x < y;
		}
	};
#endif

     class EXPORT_coral Element : public Atom, private List {
#ifdef COMPILING_coral
		friend void x__garbage_collect();
#endif
     public:
	     typedef auto2map<const Property*,
			      Slot*,
			      slots_lessthancmp > slots_type;
     protected:
	     mutable DCE_UUID m_uuid;
     private:
	     Slot& createSlot(Property*) throw (coral::bad_slot,
						std::exception);
	     slots_type slots;
     public:
	  explicit Element(coral::ElementDefinition* type, bool generate_element_attributes = true);
	  virtual ~Element() throw ();

	  // Wrap m_uuid instead of superclassing DCE_UUID. This makes python
	  // faster...
	  const coral::uuid_t& __XMIid__() const { return m_uuid.__XMIid__(); }
	  void __resetXMIid__() throw () { m_uuid.__resetXMIid__(); }
#ifndef SWIG
	  void swapXMIid(std::string& newID) throw () { m_uuid.swapXMIid(newID); }
#endif
	  /*
	   * Amazing stuff. Will switch types of a Element.
	   * Caller is responsible for any dataloss, though.
	   * It will keep the slots which are common, and of the same
	   * type. The rest will be thrown away, and new slots
	   * will be created (lazily) with default, empty values.
	   * Returns true if switch was successful.
	   */
	  bool __switchType__(coral::ElementDefinition* type);

	  bool isWellFormed() /* const */;
	  bool isWellFormedRecursive() /* const */;

	  /*
	   * Returns element which is this element's parent,
	   * i.e. the parent has a composition relation to this
	   * There can be only one, due to our assumption
	   * that composition forms a tree.
	   * Won't traverse up ElementPrimitives, since they don't
	   * have a pointer upwards. :(
	   * Returns NULL if there is no parent.
	   */
	  Element* __parent__() const throw ();
	  /*
	   * Returns the slot in which the parent element is,
	   * or NULL if no parent.
	   */
	  Slot* __parentSlot__() const throw ();

	  /*
	   * Returns top-most __parent__, or itself.
	   */
	  Element* __getRoot__() const throw (coral::coral_error);

	  coral::autounref<coral::Set> __getAllParts__();
	  
#ifndef SWIG
	  AtomIterator DFSIterator();
#endif

	  /*
	   * Returns Project if Element is root
	   */
	  coral::Project* __getProject__() const throw ();
	  /*
	   * Sets the Project of an element.
	   */
	  void __setProject__(coral::Project*) throw ();
	  /*
	   * Returns Project of topmost element
	   */
	  coral::Project* __getRootsProject__() const throw (coral::coral_error);

	  void __setXMIid__(const coral::uuid_t&);
	  /* Returns feature iff it exists */
	  Slot* __hasSlotByName__(const char* fname) const throw ();
	  /* Returns feature if it exists, otherwise a read-only empty
	     slot. Always succeeds with valid @fname! */
	  const Slot& __getSlotRO__(const char* fname) const
		  throw (coral::bad_slot);
	  /* Creates feature if possible and it doesn't exist, and
	     returns it. */
	  Slot& __getSlotByName__(const char* fname)
		  throw (coral::bad_slot, std::exception);
	  /*
	   * For const elements.
	   */
#ifndef SWIG
	  const Slot& __getSlotByName__(const char* fname) const
		  throw (coral::bad_slot, std::exception) {
		  return __getSlotRO__(fname);
	  }
#endif
	     Slot& __getSlotByOppositeName__(const coral::ElementDefinition* e,
					     const char* fname)
		     throw (coral::bad_slot, std::exception);
	  /* Faster python, and we need it for proper TM stuff
	     as well. Argh! */
	  void __getSlotByNameInsert__(const char* fname, PyObject*)
		  throw (std::invalid_argument, std::exception);
	  void __getSlotByNameInsertElement__(const char* fname, Element*)
		  throw (std::invalid_argument, std::exception);
	  /* Returns non-null if the given feature is defined, and is
	     a SlotOneElement */
	  Atom* __getSlotOneElement__(const char* fname) const throw ();

	  coral::ElementDefinition* __type__() const throw () {
	       return static_cast<coral::ElementDefinition*>(this->Atom::__type__());
	  }
	  bool __isPart__(const Atom*, bool recurse = true) const throw ();
#ifndef SWIG
	  bool __isPart__(const Element* obj, bool recurse = true) const throw ();
#endif
	  /*
	   * Returns true if @this owns @obj, or @obj owns @this.
	   */
	  bool __isConnectedTo__(const Element* obj) const throw ();
#ifndef SWIG

	  DELEGATE_FUNC(slots_type::iterator, begin,, slots);
	  DELEGATE_FUNC(slots_type::iterator, end,, slots);
	  DELEGATE_TYPE(slots_type, iterator, iterator);

	  DELEGATE_FUNC(slots_type::const_iterator, begin, const, slots);
	  DELEGATE_FUNC(slots_type::const_iterator, end, const, slots);
#endif


	  /** Resets all slots to their default.
	      Note that this might imply you need to ref() to keep
	      the element alive. */
	  void __resetAttrs__();
#ifndef SWIG
	  void __detachUsFromEverybodyElse__(const std::set<Element*>&
					     all_elems_participating);
	  virtual coral::unicodestring __str__() const;
#endif

	  PythonSequenceIteratorSecond<slots_type::iterator, coral::Slot*>* __all_slots__() {
	       return new PythonSequenceIteratorSecond<slots_type::iterator, coral::Slot*>(this, this->slots.begin(), this->slots.end());
	  }
	  PythonSequenceIteratorDirect<coral::ElementDefinition::ordered_properties_type::const_iterator, coral::Property*>* __properties__() {
	       return this->__type__()->__properties__();
	  }
       
     };





     class EXPORT_coral Slot : public noncopy {
	private:
	     void insert_py(PyObject* e, int index = -1, int other_index = -1,
			    bool unidirectional = false) throw (std::exception);
     protected:
	  Property* mmproperty;
	  /* This is NULL for RO-slots */
	  Element* element;
	  CORAL_HIDDEN bool TRANSACTION() throw ();
     public:
	  coral::Element* getElement() const throw () { return this->element; }
	  Slot(coral::Element* e, coral::Property* f);
	  virtual ~Slot() throw ();
	  virtual std::auto_ptr<Slot> clone() = 0;
	  virtual coral::Collection* getCollection() const throw ();
	  coral::Property* getProperty() const throw () {
	       return this->mmproperty;
	  }

	  /*
	   * Swaps slot contents with another slot.
	   * ONLY used by the TransactionManager
	   * Note that this can't be put around
	   * #ifdef COMPILING_coral because it's virtual, I think.
	   */
#ifndef SWIG
	  CORAL_HIDDEN virtual void swapSlotContents(Slot*) throw () = 0;
#endif

	     /**
	      * Returns an iterator object suitable for Python.
	      * Don't call directly.
	      */
	  coral::Iterator* __iter__();
	  /** Returns a new string object giving a string representation
	      of the current value. */
	  /** Returns element at given index. */
	  virtual Atom* __getitem__(int index) const throw (std::out_of_range);
	  virtual coral::unicodestring __str__() const = 0;
#ifndef SWIG
	  Atom* operator[](int index) const throw (std::out_of_range) {
		  return this->__getitem__(index);
	  }
#endif
	  virtual void __setitem__(int index, Atom*) throw (std::out_of_range, std::exception);
	  /* Returns amount of elements. */
	  virtual std::size_t __len__() const throw ();
	  /* OCL compat. */
	  std::size_t size() const throw () { return this->__len__(); }
	  /* Resets the feature. */
	  virtual void resetSlot() = 0;

	  /*
	    Convenience, so user doesn't have to cast to correct type...
	    Note that they are all virtual functions, which default
	    to coral_assert(0). Override in subclasses _only_ where necessary.
	  */

	  /** Adds an element e at a specified index.
	      If index is 0 or positive, e will be inserted at
	      that location. If index is negative, it will be inserted
	      counting from the back. Default is append.
	  */
	  virtual void insert(Atom* e, int index = -1, int other_index = -1, bool unidirectional = false) throw (std::exception);

	  /** Removes the first occurence of e, returning its (old) index.
	      NOTE! e might be unavailable after this! */
	  virtual int drop(Atom* e, bool unidirectional = false) throw (std::exception);
	  int remove(Atom* e, bool unidirectional = false) throw (std::exception) { return this->drop(e, unidirectional); }
	  /** Removes the element at specifed index.
	      If index is 0 or positive, element will be removed from
	      that location. If index is negative, element will be removed
	      counting from the back.
	      See also remove_keepref().
	  */
	  void drop_index(int index, bool unidirectional = false) {
	       this->remove_keepref(index, unidirectional)->unref();
	  }


	  /** Add, for unordered connections. */
	  void add(Atom* e, bool unidirectional = false) throw (std::exception) {
	       //coral_assert(this->getProperty()->isOrdered());
	       coral_assert(!this->getProperty()->isAtMostOne());
	       this->insert(e, -1, -1, unidirectional);
	  }
	  /** Add, for ordered connections. */
	  void append(Atom* e, bool unidirectional = false) throw (std::exception) {
	       //coral_assert(!this->getProperty()->isOrdered());
	       coral_assert(!this->getProperty()->isAtMostOne());
	       this->insert(e, -1, -1, unidirectional);
	  }

	  // CRL-451 compatibility with plain Python objects
	  void add(PyObject* e, bool unidirectional = false)
	       throw (std::exception) {
	       coral_assert(!this->getProperty()->isAtMostOne());
	       this->insert_py(e, -1, -1, unidirectional);
	  }
	  void append(PyObject* e, bool unidirectional = false)
	       throw (std::exception) {
	       coral_assert(!this->getProperty()->isAtMostOne());
	       this->insert_py(e, -1, -1, unidirectional);
	  }
#ifndef SWIG
	  void helper_insert(ElementPrimitive* old,
			     PyObject* e, int index = -1, int other_index = -1,
			     bool unidirectional = false)
	       throw (std::exception);

	  /** Remove, when you want to use the returned element.
	      You must manually do unref() on the return value. */
	  virtual Atom* remove_keepref(int index, bool unidirectional = false);
#endif
     };


#ifndef SWIG
/* A zillion different Slots... */


     class EXPORT_coral SlotManyElements: public Slot {
     protected:
	  friend class Element;
	  friend void unmark(__gnu_cxx::hash_set<Atom*, ptr_hash<Atom> >& element_blacks, Element* white);

	  Collection* collection;
	  void insert(Atom* e, int index, int other_index, bool unidirectional = false) throw (std::exception);
	  int drop(Atom* e, bool unidirectional = false) throw ();
	  Atom* __getitem__(int index) const throw (std::out_of_range);
	  void __setitem__(int index, Atom*) throw (std::out_of_range, std::exception);
	  Atom* __remove_continued(Atom* child, bool unidirectional) throw ();
	  Atom* remove_keepref(int index, bool unidirectional = false) throw ();

     public:
	  SlotManyElements(Element* e, Property* f, coral::Collection* m);
	  Collection* getCollection() const throw ();
	  virtual ~SlotManyElements() throw ();

	  CORAL_HIDDEN virtual void swapSlotContents(Slot*) throw ();

	  std::size_t __len__() const throw ();
	  virtual coral::unicodestring __str__() const;
	  void resetSlot();
	  std::auto_ptr<coral::Slot> clone();
     };

     class EXPORT_coral SlotOneElement: public Slot {
     private:
	  Atom* value;
     public:
	  SlotOneElement(Element* e, Property* f);
	  virtual ~SlotOneElement() throw ();
	  std::size_t __len__() const throw ();
	  virtual coral::unicodestring __str__() const;
	  virtual void resetSlot();

	  CORAL_HIDDEN virtual void swapSlotContents(Slot*) throw ();

	  Atom* __getitem__(int index) const throw (std::out_of_range);

	  void insert(Atom* e, int index = -1, int other_index = -1, bool unidirectional = false) throw (std::exception);
	  int drop(Atom* e, bool unidirectional = false) throw ();
	  Atom* remove_keepref(int index, bool unidirectional = false);
	  std::auto_ptr<coral::Slot> clone();
     };


#endif

     /*
      * Elements of primitive type
      */

    class EXPORT_coral ElementPrimitive : public Atom {
	protected:
	     CORAL_HIDDEN void TRANSACTION() throw ();
	     ElementPrimitive(DatatypeDefinition*);
     public:
	     virtual ~ElementPrimitive() throw ();
	     coral::DatatypeDefinition* __type__() const throw () {
		     return static_cast<coral::DatatypeDefinition*>(this->Atom::__type__());
	     }
	     /** Makes a new Atom with the same values. */
	     virtual coral::ElementPrimitive* clone() const = 0;
	     /*
	      * Resets a primitive to its default value
	      */
	     virtual void reset() = 0;
	     /** Swaps values with atom, which must be of
	      * the exact same subtype as this.
	      */
	     virtual void swapWithAtom(ElementPrimitive*) throw () = 0;
	     virtual bool operator==(const ElementPrimitive&) const throw () = 0;
	     bool operator!=(const ElementPrimitive& o) const throw () {
	       return !(*this == o);
	     }
	     bool operator==(const Element&) const throw () { return false; }
	     bool operator!=(const Element&) const throw () { return true; }

	     bool operator==(const PyObject*) const throw ();
	     bool operator!=(const PyObject* p) const throw () {
		  return !(*this == p);
	     }
	     virtual bool operator<(const ElementPrimitive&) const throw () = 0;
	     virtual bool operator<=(const ElementPrimitive& o) const throw () = 0;
	     bool operator>(const ElementPrimitive& o) const throw () {
		  return o <= *this;
	     }
	     bool operator>=(const ElementPrimitive& o) const throw () {
		  return o < *this;
	     }
	     virtual std::size_t __len__() const throw () = 0;
#ifndef SWIG
	     virtual coral::unicodestring __str__() const = 0;
#endif
	     coral::unicodestring __unicode__() const { return __str__(); }
     };

#ifndef SWIG

     class EXPORT_coral ElementPyObject: public ElementPrimitive {
     private:
	     PyObject* obj;
     public:
	     ElementPyObject(DatatypeDefinition*);
	     virtual ~ElementPyObject() throw ();

	     virtual void reset();
	     virtual PyObject* __getPyValue__() const throw ();
	     virtual void __setPyValue__(PyObject* newObj) throw ();
	     
	     virtual coral::unicodestring __str__() const;
	     virtual std::size_t __len__() const throw ();
	     virtual ElementPyObject* clone() const;
	     virtual void swapWithAtom(ElementPrimitive*) throw ();
	     virtual bool operator==(const ElementPrimitive&) const throw ();
	     virtual bool operator<(const ElementPrimitive&) const throw ();
	     virtual bool operator<=(const ElementPrimitive&) const throw ();
     };

     class EXPORT_coral ElementEnumeration: public ElementPrimitive {
     private:
	     int enumValue;
     public:
	     coral::EnumerationDefinition* __type__() const throw () {
		     return static_cast<coral::EnumerationDefinition*>(this->Atom::__type__());
	     }

	     ElementEnumeration(EnumerationDefinition* e);
	     virtual ~ElementEnumeration() throw ();

	     virtual void reset();
	     virtual int __getEValue__() const throw () {
		     return this->enumValue;
	     }
	     virtual coral::unicodestring __getEValueByName__();
	     virtual void __setEValue__(int newValue);
	     virtual void __setEValueByName__(const char* newValue);
	     
	     virtual coral::unicodestring __str__() const;
	     virtual int __int__() const throw ();
	     virtual ElementEnumeration* clone() const;
	     virtual void swapWithAtom(ElementPrimitive*) throw ();
	     virtual bool operator==(const ElementPrimitive&) const throw ();
	     virtual bool operator<(const ElementPrimitive&) const throw ();
	     virtual bool operator<=(const ElementPrimitive&) const throw ();
	     virtual std::size_t __len__() const throw ();
     };

     /*
      * BUG We should use this much much more...
      * We probably want to go to long longs anyway
      */
     typedef int coral_integer;
     typedef double coral_double;

     class EXPORT_coral ElementInteger: public ElementPrimitive {
     private:
	  coral_integer ivalue;
     public:
	  ElementInteger(DatatypeDefinition*);
	  virtual ~ElementInteger() throw ();

	  virtual void reset();
	  virtual int __int__() const throw ();
	  virtual double __double__() const throw ();

	  virtual int __getIValue__() const throw () {
	       return this->ivalue;
	  }
	  virtual void __setIValue__(int newValue) throw ();
	  virtual std::size_t __len__() const throw ();

	  virtual coral::unicodestring __str__() const;
	  virtual ElementInteger* clone() const;
	  virtual void swapWithAtom(ElementPrimitive*) throw ();
	  virtual bool operator==(const ElementPrimitive&) const throw ();
	  virtual bool operator<(const ElementPrimitive&) const throw ();
	  virtual bool operator<=(const ElementPrimitive&) const throw ();
     };

     class EXPORT_coral ElementDouble: public ElementPrimitive {
     private:
	  double dvalue;
     public:
	  ElementDouble(DatatypeDefinition*);
	  virtual ~ElementDouble() throw ();

	  virtual void reset();
	  virtual double __float__() const throw ();
	  virtual int __int__() const throw ();
	  virtual double __getDValue__() const throw () {
	       return this->dvalue;
	  }
	  virtual void __setDValue__(double newValue) throw ();
	  virtual std::size_t __len__() const throw ();

	  virtual coral::unicodestring __str__() const;
	  virtual ElementDouble* clone() const;
	  virtual void swapWithAtom(ElementPrimitive*) throw ();
	  virtual bool operator==(const ElementPrimitive&) const throw ();
	  virtual bool operator<(const ElementPrimitive&) const throw ();
	  virtual bool operator<=(const ElementPrimitive&) const throw ();
     };

     class EXPORT_coral ElementString: public ElementPrimitive {
     private:
	  unicodestring svalue;
     public:
	  ElementString(DatatypeDefinition*);
	  virtual ~ElementString() throw ();

	  virtual void reset();
	  virtual void __setString__(const unicodestring& newValue);
	  virtual const coral::unicodestring& __getString__() const throw ();

	  virtual unicodestring __str__() const;
	  virtual std::size_t __len__() const throw ();
	  virtual ElementString* clone() const;
	  virtual void swapWithAtom(ElementPrimitive*) throw ();
	  virtual bool operator==(const ElementPrimitive&) const throw ();
	  virtual bool operator<(const ElementPrimitive&) const throw ();
	  virtual bool operator<=(const ElementPrimitive&) const throw ();
     };

#endif

}

#endif
