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

#include <typeinfo>
#include <libxml/xmlreader.h>
#include <string>
#include <coral/core/Collection.h>
#include <map>
#include <list>
#include <vector>
#include <coral/lib/autovector.h>
#include <coral/core/ElementDefinition.h>
#include <coral/core/Element.h>
#include <coral/lib/unicodestring.h>

#include <functional>

#include <coral/core/exception.h>

/*
 * There is no need to include this file. It contains routines common
 * for XMI readers.
 * Note that Project also needs this.
 */

NAMESPACE_BEGIN(coral);
NAMESPACE_BEGIN(io);


/*
 * The ref classes contain id strings for the various
 * kinds of references that can occur in an XMI document
 */
struct _ref : public ref_counted, private noncopy {
	std::string id;
	_ref(const char* a)
		: id(a) { ; }
	virtual ~_ref() throw () {
		;
	}
	virtual int number() const throw () = 0;
	bool operator==(const _ref& s1) const throw () {
		return number() == s1.number()
			&& id == s1.id;
	}
};

struct _uuidref : public _ref {
	_uuidref(const char* a)
		: _ref(a) { ; }
	virtual ~_uuidref() throw () { ; }
	virtual int number() const throw () { return 1; }
};

struct _idref : public _ref {
	_idref(const char* a)
		: _ref(a) { ; }
	virtual ~_idref() throw () { ; }
	virtual int number() const throw () { return 2; }
};

struct _labelref : public _ref {
	_labelref(const char* a)
		: _ref(a) { ; }
	virtual ~_labelref() throw () { ; }
	virtual int number() const throw () { return 3; }
};

/*
 * This is presented as an XPath and is very special
 * Basically it is a "#//name/name/name...", e.g.
 * #//Classifier" or #//UMLClass/attribute.
 * It can't be handled using the same ref system as
 * the others.
 */
struct xpathref : public _ref {
	xpathref(const char* a)
		: _ref(a) { ; }
	virtual ~xpathref() throw () { ; }
	virtual int number() const throw () { return 4; }
};


std::ostream& operator<<(std::ostream& out, const _ref& r);

/*
 * Comparison of id strings.
 */
struct lt_std_ref {
	bool operator()(const _ref* s1,
			const _ref* s2) const throw () {
		int n1 = s1->number();
		int n2 = s2->number();
		if (n1 == n2)
			return s1->id < s2->id;
		return n1 < n2;
	}
};

/*
 * Comparison of strings
 */
struct lt_std_str {
	bool operator()(const std::string& s1,
			const std::string& s2) const throw () {
		return s1 < s2;
	}
};

struct xmistate;

typedef auto2map<Project*,
		 xmistate*,
		 std::less<Project*> > all_states_t;

/*
 * Every load is represented using exactly one object.
 * It describes all necessary Projects, xmistates etc
 * that are used while loading.
 */
struct loadstate_per_invocation {
	all_states_t all_states;
	/*
	 * The following projects have already had
	 * their fixLoadedRef() called.
	 */
	std::set<Project*> fixed_ref_info;
	
};

struct xmistate {
	coral::Project* storage;
	typedef std::pair<coral::Project*,
			  const _ref*> remote_id_t;
	typedef std::map<coral::Slot*,
			 std::vector<remote_id_t> > slots_type;
	typedef autounrefset<const _ref*,
			     lt_std_ref> all_ids_type;
	typedef std::vector<coral::Element*> parent_elems_type;
	typedef std::vector<coral::Slot*> parent_slots_type;
	autounrefvector<coral::ref_counted*> unref_these;
	parent_elems_type parent_elems;
	parent_slots_type parent_slots;
	all_ids_type all_ids;
	slots_type slots;
	coral::Package* metamodel;
	int rate_limited_ignore_property;
	int rate_limited_ignore_unknown_elems;
	bool start;
	bool need_postprocess;
	xmistate();
	xmlTextReaderPtr reader;
	virtual ~xmistate() throw ();
	/*
	 * wasCompressed implies that Project must change
	 * name
	 */
	bool was_compressed;
	int postProcess(loadstate_per_invocation& loadstate);
	/*
	 * Adds a new ID if necessary.
	 * Returns the same id, or an old copy, so use the
	 * returned reference, not the object you passed in.
	 */
	const _ref& addID(const _ref* id,
				    coral::Element*);
	int addAtom(coral::Element* e,
		    const coral::Property* p,
		    const std::string& val_utf8);
	int fixDatatype(coral::Element* new_element,
			const coral::AbstractElementDefinition* target,
			const Property*,
			const std::string& a_value);
	void ignore_property(const char* a_name,
			     const std::string& type) throw ();
	void ignore_unknown_element(const char* a_name,
				    const coral::Package*) throw ();

	/*
	 * Handling of unknown elements
	 */
	int handleUnknownElement(const char* element_name);
	/*
	 * Load routine
	 */
	virtual int loadIt();
  protected:
	virtual void handleBadID() = 0;
};

coral::Element* findParent(coral::Collection* roots);

/*
 * This is thrown when we want to go back to the
 * XML processing.
 */
class handled : public std::exception {
	const char* what() const throw () {
		return "<coral-internal> handled exception";
	}
};

/*
 * This is also thrown, but skip the next
 * call to the XML reader...
 */
class handled_no_read : public std::exception{
	const char* what() const throw () {
		return "<coral-internal> handled exception, don't read next XML stream token";
	}
};

/*
 * Retrieves a double value from a string.
 * Throws std::runtime_error if a serious error
 * occured, otherwise just complains via dlog.
 */
double retrieve_double(const std::string& s);

/*
 * Returns just one (simple heuristically determined) root.
 * Use io_load_document instead.
 */
coral::Element* io_load_file(const char *filename,
			     coral::Language* language);
/*
 * Ths is the loader that chooses which
 * subclass of xmistate to use.
 */
coral::autounref<coral::Project>
io_load_document(const char *filename_or_buffer,
		 coral::Language* language = NULL,
		 bool is_buffer = false);

NAMESPACE_END();
NAMESPACE_END();

#endif
