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

#include <libxml/xmlreader.h>
#include <libxml/tree.h>

#include <iostream>

#include "xmi_reader_2.h"
#include <coral/lib/regexp.h>

#include <coral/core/ElementDefinition.h>
#include <coral/core/Element.h>
#include <coral/core/Collection.h>
#include <coral/lib/autovector.h>
#include <coral/core/CorePrimitives.h>
#include <coral/lang/XMI/XMI.h>
#include <coral/lib/assert.h>
#include <coral/core/Project.h>

#include "xmi_reader_common.h"

/*
SOME WEIRD/INTERESTING THINGS IN ECLIPSE I/O FORMAT
- Does not understand xmi:documentation at all.
- Sometimes seems to use xmlns:xmi="http://www.omg.org/XMI" for XMI.
- Uses xsi:type instead of xmi:type.
- References http://www.eclipse.org/emf/2002/Ecore as a "magic" file with
  predefined datatypes.
- Outputs list of references inside an XMI attribute using xmi:id separated by
  whitespace. I don't know what occurs if a reference is in another file.
- Uses chain of names separated by '/'. But if it has to, it seems to
  revert to xmi:id, even though element is in another file
- But at least it does have " <eOpposite href="model0.xmi#//foo"/> "
- But at least it does have " <eOpposite href="model1.xmi#e12"/> " (xmi:id)
- Targets elements with xmi:id from another file, does not use xmi:uuid.
  But at least it doesn't choke on xmi:uuid
*/

NAMESPACE_BEGIN(coral);
NAMESPACE_BEGIN(io);

namespace {
	const char* XMI20NS = "http://schema.omg.org/spec/XMI/2.0";
	const char* ECLIPSE_XMI20NS = "http://www.omg.org/XMI";
	const char* ECLIPSE_ECORE10NS = "http://www.eclipse.org/emf/2002/Ecore";
	const char* XMI12NS = "http://schema.omg.org/spec/XMI/1.2";
	const char* XSI_NS = "http://www.w3.org/2001/XMLSchema-instance";
}

_state2::_state2() : xmistate() {
	reading_elem.push_back(true);
}

_state2::~_state2() throw () {
	;
}

void _state2::handleBadID() {
	coral_assert(!parent_slots.empty());
	parent_slots.pop_back();
	coral_assert(!reading_elem.empty());
	reading_elem.pop_back();
	coral_assert(!parent_elems.empty());
	parent_elems.pop_back();
	handleUnknownElement("<bogus>");
}

bool _state2::isXMIPrefix(const char* s) {
	if (!s) return false;
	const xmlChar* prefix = reinterpret_cast<const xmlChar*>(s);
	wXmlTextReaderLookupNamespace w_ns(reader, prefix);
	const char* ns = reinterpret_cast<const char*>(w_ns.get());
	return ns && (!strcmp(ns, XMI20NS) || !strcmp(ns, ECLIPSE_XMI20NS));
}

bool _state2::isXSIPrefix(const char* s) {
	if (!s) return false;
	const xmlChar* prefix = reinterpret_cast<const xmlChar*>(s);
	wXmlTextReaderLookupNamespace w_ns(reader, prefix);
	const char* ns = reinterpret_cast<const char*>(w_ns.get());
	return ns && !strcmp(ns, XSI_NS);
}

NAMESPACE_BEGIN();

std::string getRelativePath(std::string relative_filename) {
	std::string::size_type pos = relative_filename.find_last_of('/');
	if (pos != std::string::npos) {
		relative_filename.erase(pos);
	} else {
		relative_filename = "";
	}
	return relative_filename;
}

autounref<Package> getLanguageByPrefix(_state2& state,
					const std::string& prefix) {
	if (state.isXMIPrefix(prefix.c_str())) {
		autounref<Package> x(XMI::language);
		x->ref();
		return x;
	    }
	const char* xmlns;
	wXmlTextReaderLookupNamespace w_xmlns
		(state.reader,
		 reinterpret_cast<const xmlChar*>(prefix.c_str()));
	xmlns = reinterpret_cast<const char*>(w_xmlns.get());

	if (!xmlns) {
		dlog << DEBUGLEVEL_ERROR
		     << "ERROR Invalid namespace \""
		     << prefix << "\"!" << std::endl;
		throw load_error();
	}
	autounref<Package> L(Package::__getLanguageByXMLNS__(xmlns));
	if (!L.get()) {
		dlog << DEBUGLEVEL_ERROR
		     << "ERROR Couldn't find language with namespace \""
		     << xmlns << "\"!" << std::endl;
		throw load_error();
	}
	return L;
}

void __fixIdref_counted(_state2& state,
	       const _ref* a_value) {
	coral_assert(!state.parent_slots.empty());
	coral::Slot* pf = state.parent_slots.back();
	coral_assert(pf);
	const _ref& id = state.addID(a_value, NULL);
	state.slots[pf].push_back(xmistate::remote_id_t(state.storage, &id));
}

void
add_an_id(_state2& state,
	  Slot* slot, 
	  const _ref& id,
	  const std::string& filename) {
	autounref<Project> _labelstorage;
	Project* labelstorage = NULL;
	if (filename.empty()) {
		labelstorage = state.storage;
		labelstorage->ref();
		_labelstorage = labelstorage;
	} else {
		std::string relative_path =
			getRelativePath(state.storage->getFilename());
		bool did_create;
		_labelstorage =
			Project::getProjectByFilename(filename,
						      relative_path,
						      &did_create);
		labelstorage = _labelstorage.get();
	}
	coral_assert(labelstorage);
	state.unref_these.push_back(labelstorage);
	_labelstorage.release();

	state.slots[slot].push_back(xmistate::remote_id_t(labelstorage, &id));
}

void parse_href_w_slot(_state2& state,
		       Slot* slot,
		       const char* a_value) {

	const char* hash_mark = strchr(a_value, '#');
	if (!hash_mark) goto end;
	{
		++hash_mark;
		std::string filename(&a_value[0], hash_mark - 1);
		/*
		 * Eclipse hack
		 */
		if (filename == "http://www.eclipse.org/emf/2002/Ecore") {
			filename = "coral/lang/ecore10/def/ecore_model.xmi";
		}
		if (*hash_mark == '/' && *(hash_mark+1) == '/') {
			/* Eclipse-style identifier */
			const _ref& id = state.addID(new xpathref(hash_mark-1), NULL);
			add_an_id(state, slot, id, filename);
			return;
		}

		if (!*hash_mark) goto end;

		const int n_matches = 4;
		regmatch_t match[n_matches];
		/* update these indices according to the RE. 0 is for the
		   whole string. */
		const int i_attr_prefix = 1;
		const int i_attr_name = 2;
		const int i_value = 3;
	
		static RE r("xpointer\\(\\(//\\*\\[@(.*):(.*)='(.*)'\\]\\)\\[1\\]\\)",
			    REG_EXTENDED|REG_NEWLINE);
		int err = r.executeRE(hash_mark, n_matches, match, 0);
		if (err) {
			/* Eclipse-style identifier, it's an xmi:id (!) */
			const _ref& id = state.addID(new _idref(hash_mark), NULL);
			add_an_id(state, slot, id, filename);
			return;
		}
		if (match[i_attr_prefix].rm_so == -1
		    || match[i_attr_name].rm_so == -1
		    || match[i_value].rm_so == -1) {
			dlog << DEBUGLEVEL_ERROR
			     << "ERROR Couldn't match the href argument: "
			     << a_value 
			     << "." << std::endl;
			throw load_error();
		}

		std::string attr_prefix(&hash_mark[match[i_attr_prefix].rm_so],
					match[i_attr_prefix].rm_eo - match[i_attr_prefix].rm_so);
		std::string attr_name(&hash_mark[match[i_attr_name].rm_so],
				      match[i_attr_name].rm_eo - match[i_attr_name].rm_so);
		std::string value(&hash_mark[match[i_value].rm_so],
				  match[i_value].rm_eo - match[i_value].rm_so);

		if (state.isXMIPrefix(attr_prefix.c_str())) {
			if (!strcmp(attr_name.c_str(), "uuid")) {
				const _ref& id
					= state.addID(new _uuidref(value.c_str()),
						      NULL);
				add_an_id(state, slot, id, filename);
				return;
			} else if (!strcmp(attr_name.c_str(), "label")) {
				const _ref& id = state.addID(new _labelref(value.c_str()), NULL);
				add_an_id(state, slot, id, filename);
				return;
			}
		}
	}
end:
	dlog << DEBUGLEVEL_ERROR
	     << "ERROR Coral can't handle the following href: "
	     << std::endl
	     << a_value 
	     << "." << std::endl;
	throw load_error();
}

void parse_href(_state2& state,
	       const char* a_value) {
	coral_assert(!state.parent_slots.empty());
	coral::Slot* pf = state.parent_slots.back();
	coral_assert(pf);
	parse_href_w_slot(state, pf, a_value);
}
	
/*
 * Helper for checking the various references in
 * an XMI element .
 */
void fixIdref_counted(_state2& state) {
	while (xmlTextReaderMoveToNextAttribute(state.reader)) {
		wXmlTextReaderPrefix wa_prefix(state.reader);
		const xmlChar* _a_prefix = wa_prefix.get();
		wXmlTextReaderLocalName wa_name(state.reader);
		const xmlChar* _a_name = wa_name.get();
		wXmlTextReaderValue wa_value(state.reader);
		const xmlChar* _a_value = wa_value.get();
		const char* a_prefix = reinterpret_cast<const char*>(_a_prefix);
		const char* a_name = reinterpret_cast<const char*>(_a_name);
		const char* a_value = reinterpret_cast<const char*>(_a_value);
		if (!a_value) continue;

		/*
		 * Reserved word "xml" in any case
		 * NOTE! We ignore _anything_ starting with
		 * the letters x, m, and l. This is according
		 * to the XML specification.
		 */
		if (a_prefix) {
			// Note this also skips "xmlns" declarations
			if (!strncasecmp(a_prefix, "xml", 3)) {
				continue;
			}
		}
		if (!strcmp(a_name, "href")) {
			parse_href(state, a_value);
			return;
		}
		if (state.isXMIPrefix(a_prefix)) {
			if (!strcmp(a_name, "idref")) {
				__fixIdref_counted(state, new _idref(a_value));
				return;
			} else if (!strcmp(a_name, "uuidref")) {
				__fixIdref_counted(state, new _uuidref(a_value));
				return;
			} else if (!strcmp(a_name, "label")) {
				__fixIdref_counted(state, new _labelref(a_value));
				return;
			} else if (!strcmp(a_name, "type")) {
				/* Ignored here. */
				continue;
			}
		}
		/*
		 * Eclipse XSI compatibility
		 */
		if (state.isXSIPrefix(a_prefix)) {
			if (!strcmp(a_name, "type")) {
				/* Ignored here. */
				continue;
			}
		}

		state.ignore_property(a_name, "(unknown)");
	}
	dlog << DEBUGLEVEL_ERROR
	     << "ERROR Couldn't get reference to element!" << std::endl;
	throw load_error();
}

void
addXPath(_state2& state, Slot* slot, const char* value) {
	coral_assert(value);
	coral_assert(state.storage);
	// File-local #// thingy
	if (!strncmp("#//", value, 3)) {
		state.slots[slot].push_back(
			std::make_pair(state.storage,
				       &state.addID(new xpathref(value),
						    NULL)));
		return;
	}
	
	const char* space = strchr(value, ' ');
	if (!space) {
		state.slots[slot].push_back(
			std::make_pair(state.storage,
				       &state.addID(new _idref(value), NULL)));
		return;
	}
	// Like "ecore:EClass noNs.ecore#//ProviderLocation"
	while (*++space == ' ') { ; }
	parse_href_w_slot(state, slot, space);
}

/*
 * Fix "object attributes", i.e. attributes which reference
 * some other element instead of being of primitive type.
 */
void fixObjAttr(_state2& state,
		coral::Element* new_element,
		const char* a_name,
		const char* a_value) {
	
	Slot* slot;
	try {
		coral::Slot& sslot = new_element->__getSlotByName__(a_name);
		slot = &sslot;
	} catch (bad_slot& exc) {
		dlog << DEBUGLEVEL_ERROR
		     << "ERROR No " << a_name << " for "
		     << new_element->__str__() << std::endl;
		throw load_error();
	}

	if (slot->getProperty()->isAtMostOne()) {
		addXPath(state, slot, a_value);
	} else {
		std::stringstream values(a_value);
		std::string s;
		while (values >> s) {
			addXPath(state,
				 slot, 
				 s.c_str());
		}
	}

}

void dropOld(_state2& state, coral::Atom* new_element) {
	if (!state.parent_slots.empty()) {
		coral::Slot* pf = state.parent_slots.back();
		pf->drop(new_element);
	} else {
		state.unref_these.pop_back();
		new_element->unref();
	}
}

/*
 * Support reading values such as
 * <XMI.field>value</XMI.field>
 * Some models use it, and it's useful
 * in writing multi-valued slots of primitive
 * type.
 */
void fixXMIField(_state2& state) {

	if (state.parent_slots.empty()) {
		throw load_error();
	}
	coral::Slot* s = state.parent_slots.back();
	coral_assert(s);
	coral::Property* p = s->getProperty();
	coral_assert(p);
	coral::AbstractElementDefinition* target = p->getTarget();
	coral_assert(target);
	if (!target->isDatatypeDefinition()) {
		dlog << DEBUGLEVEL_ERROR
		     << "ERROR While processing XMI:field, not a primitive type."
		     << std::endl;
		throw load_error();
	}
	coral::Atom* atom = target->newAtom();
	coral::autounref<coral::Atom> _p(atom);

	/*
	 * Read the actual XMI.field value
	 */
	int ret = xmlTextReaderRead(state.reader);
	if (ret != 1) {
		dlog << DEBUGLEVEL_ERROR
		     << "ERROR Reading <XMI:field> value."
		     << std::endl;
		throw load_error();
	}

	w wa_value;
	const char* a_value;
	bool xmi_field_ending = true;
	if (xmlTextReaderIsEmptyElement(state.reader)) {
		dlog << DEBUGLEVEL_INFO
		     << "INFO XMI:field is empty " << std::endl;
		a_value = "";
		xmi_field_ending = false;
	} else {
		wXmlTextReaderValue d(state.reader);
		wa_value = d; // slicing ok
		a_value = (const char*) wa_value.get();
		if (!a_value) {
			a_value = "";
		}
	}

	//dlog << DEBUGLEVEL_DEBUG << "Read " << val << std::endl;

	if (xmi_field_ending) {
		// Skip </XMI:field>
		//BUG state.reading_slots = true;
		ret = xmlTextReaderRead(state.reader);
		if (ret != 1) {
			dlog << DEBUGLEVEL_ERROR
			     << "ERROR Reading ending element </XMI:field>."
			     << std::endl;
			throw load_error();
		}
	}
#if 0
	wXmlTextReaderValue x(state.reader)
	if (strcmp((const char*) w.get(), "XMI:field")) {
		dlog << DEBUGLEVEL_ERROR
		     << "ERROR Ending element </XMI:field> does not exist."
		     << std::endl;
		throw load_error();
	}
#endif

	if (a_value[0] == 0) {
		s->append(atom);
		return;
	}
	/*
	 * Could have a helper function here, we have some code duplication
	 */
	if (dynamic_cast<coral::ElementPyObject*>(atom)) {
		/*
		 * We don't support this.
		 */
		coral_assert(0);
	} else if (coral::ElementEnumeration* a = dynamic_cast<coral::ElementEnumeration*>(atom)) {
		a->__setEValueByName__(a_value);
	} else if (coral::ElementString* a = dynamic_cast<coral::ElementString*>(atom)) {
		a->__setString__(unicodestring(a_value, true)); // UTF-8
	} else if (coral::ElementInteger* a = dynamic_cast<coral::ElementInteger*>(atom)) {
	     // This can be slow...
	     int i;
	     std::istringstream thestream(a_value);
	     thestream.imbue(coral::clocale);
	     thestream >> i;
	     if (!thestream.eof()) {
		  if (thestream.fail()) {
		       dlog << DEBUGLEVEL_ERROR
			    << "ERROR Integer \"" << a_value << "\" outside limits!?" << std::endl;
		       throw load_error();
		  }
		  dlog << DEBUGLEVEL_WARNING
		       << "WARNING Extra information after integer in \"" << a_value << "\" (ignored, continuing)" << std::endl;
	     }
	     coral_assert(!thestream.fail());
	     a->__setIValue__(static_cast<int>(i));
	} else if (coral::ElementDouble* a = dynamic_cast<coral::ElementDouble*>(atom)) {
		coral_double d;
		d = retrieve_double(a_value);
		a->__setDValue__(d);
	} else {
		dlog << DEBUGLEVEL_ERROR
		     << "ERROR Can't load primitive value "
		     << a_value << "." << std::endl;
		throw load_error();
	}

	s->append(atom);

}
/*
 * Creation of a new element. This is further complicated
 * by the need to recognize special "XMI." elements,
 * and to sometimes simply drop the new element if we suddenly
 * find a xmi.idref. (no, it shouldn't happen in proper XMI
 * files...)
 * Also we take care of root elements, attributes,
 * all references.
 */
void createElem(_state2& state,
		coral::Package* package,
		const std::string& element_name,
		bool isAssociation,
		bool isEmpty) {
	
	coral::ElementDefinition* ed = NULL;
	/*
	 * Hack to recognize the xmi:extension and xmi:field elements
	 */
	coral_assert(package);
	if (package == XMI::language) {
		if (!strcmp("field", element_name.c_str())) {
			fixXMIField(state);
			return;
		}
		try {
			autounref<AbstractElementDefinition> _ed
				= XMI::language->getElement(element_name.c_str());
			ed = dynamic_cast<coral::ElementDefinition*>(_ed.get());
		} catch (...) {
			;
		}

	} else {
		try {
			autounref<AbstractElementDefinition> _ed
				 = package->getElement(element_name.c_str());
			ed = dynamic_cast<coral::ElementDefinition*>(_ed.get());
		} catch (...) {
			;
		}
	}
	if (!ed) {
		state.ignore_unknown_element(element_name.c_str(), package);
		int err = state.handleUnknownElement(element_name.c_str());

		coral_assert(!state.reading_elem.empty());
		state.reading_elem.pop_back();
		coral_assert(!state.parent_slots.empty());
		state.parent_slots.pop_back();

		if (err) throw load_error();
		return;
	}
	/*
	 * Fix xmi:idref
	 */
	if (isAssociation) {
		fixIdref_counted(state);
		return;
	}
	/*
	 * Create a new element, populate it with attributes.
	 */
	coral::Element* new_element = (*ed)(false);
	{
		coral::autounref<coral::Element> _t(new_element);
		if (!state.parent_slots.empty()) {
			coral::Slot* pf = state.parent_slots.back();
			/*
			 * bidirectional, since there shouldn't be an object
			 * attribute back to the parent element in a
			 * composition relation.
			 * But even if there is, it won't matter: check
			 * SlotOneElement::insert()
			 */
			pf->insert(new_element, -1, -1, false);
		} else {
			/*
			 * This unreffing needs to be done at some later time.
			 * Clearly, 99% of elements will run in the above code,
			 * only root elements will be here.
			 */
			state.unref_these.push_back(new_element);
			new_element->ref();
			/*
			 * Every new_element is a root candidate, not only
			 * elements of the root_ed type. This is done
			 * so that model fragments can be transported.
			 */
			new_element->__setProject__(state.storage);
		}
	}

	//dlog << DEBUGLEVEL_DEBUG << "Pushed E " << new_element->__type__()->getName() << std::endl;
	if (!isEmpty) {
		state.parent_elems.push_back(new_element);
	}

	while (xmlTextReaderMoveToNextAttribute(state.reader)) {
		wXmlTextReaderPrefix wa_prefix(state.reader);
		const char* a_prefix
			= reinterpret_cast<const char*>(wa_prefix.get());
		if (a_prefix && !strcmp(a_prefix, "xmlns"))
			continue;
		wXmlTextReaderValue wa_value(state.reader);
		const char* _a_value =
			(const char*) wa_value.get();
		if (!_a_value) continue; // also for xmlns?

		wXmlTextReaderLocalName wa_name(state.reader);
		const char* a_name
			= reinterpret_cast<const char*>(wa_name.get());

		/*
		 * Reserved word "xml" in any case
		 * NOTE! We ignore _anything_ starting with
		 * the letters x, m, and l. This is according
		 * to the XML specification.
		 */
		if (a_prefix && !strncasecmp(a_prefix, "xml", 3)) {
			if (a_prefix[3] == 0 && !strcmp(a_name, "id")) {
				// Accept xml:id as xmi:id
				state.addID(new _idref(_a_value), new_element);
			}
			continue;
		}

		if (!strcmp(a_name, "href")) {
			parse_href(state, _a_value);
			return;
		}
		if (!state.isXMIPrefix(a_prefix)) {
			/*
			 * Eclipse XSI compatibility
			 */
			if (state.isXSIPrefix(a_prefix)) {
				if (!strcmp(a_name, "type")) {
					/* Ignored here, BUG should
					 * double-check */
					continue;
				}
			}
			/*
			 * Other attributes
			 */
			coral::Property* p = ed->getPropertyByName(a_name);
			if (!p) {
				state.ignore_property(a_name, ed->getName());
				continue;
			}
			const coral::AbstractElementDefinition* target
				= p->getTarget();
			coral_assert(target);
			if (target->isDatatypeDefinition()) {
				int err = state.fixDatatype(new_element,
							    target,
							    p,
							    _a_value);
				if (err) throw load_error();
			} else {
				fixObjAttr(state, new_element,
					   a_name, _a_value);
			}
			continue;
		}

		/*
		 * The xmi: namespace attributes
		 */
		if (!strcmp(a_name, "id")) {
			state.addID(new _idref(_a_value), new_element);
			continue;
		} else if (!strcmp(a_name, "uuid")) {
			new_element->__setXMIid__(std::string(_a_value));
			/*
			 * We save the UUIDs as well, if somebody has
			 * an xmi:uuidref.
			 * (xmi:idref uses xmi:id)
			 */
			state.addID(new _uuidref(_a_value),
				    new_element);
			continue;
		} else if (!strcmp(a_name, "label")) {
			/* Gather this for future XLinks... */
			state.addID(new _labelref(_a_value), new_element);
			continue;
		} else if (!strcmp(a_name, "uuidref")) {
			/* Allow in any XMI version... What harm can it do? */
			dropOld(state, new_element);
			__fixIdref_counted(state, new _uuidref(_a_value));
			return;
		} else if (!strcmp(a_name, "idref")) {
			/*
			 * xmi:idrefs shouldn't occur here,
			 * but they do anyway. Yawn.
			 * We'll just drop the current element
			 * we created in vain.
			 */
			dropOld(state, new_element);
			__fixIdref_counted(state, new _idref(_a_value));
			return;
		} else if (!strcmp(a_name, "type")) {
			const char* curtype
				= new_element->__type__()->getName().c_str();
			/*
			 * NOTE/BUG. When mixing metamodels, we can have
			 * a xmi:type
			 * that points to another metamodel's elements.
			 * I'm not sure what this should look like in XMI 2
			 * So we'll just expect a "language:elementname"
			 *
			 * We only check the element name because
			 * well, this whole thing here is just
			 * a safety-guard.
			 */
			const char* p = strchr(_a_value, ':');
			if (p) {
				_a_value = p+1;
			}
			if (strcmp(curtype, _a_value)) {
				dlog << DEBUGLEVEL_ERROR
				     << "ERROR Coral created the wrong type "
				     << curtype << " for some reason, "
				     << "should be " << _a_value << "!"
				     << std::endl;
				throw load_error();
			}
			continue;
		} else if (!strcmp(a_name, "version")) {
			continue;
		} else {
			dlog << DEBUGLEVEL_ERROR
			     << "ERROR Unknown attribute \""
			     << a_name << "\" in xmi namespace "
			     << "(ignoring)."
			     << std::endl;
			continue;
		}
	}
}

void popSlot(_state2& state,
	    const char* prefix,
	    const char* name);


void readSlot(_state2& state,
	     const char* prefix) {
	
	Slot* s = state.parent_slots.back();
	Property* p = s->getProperty();
	AbstractElementDefinition* ed = p->getTarget();

	Package* package = ed->__package__();
	coral::autounref<Package> rp(package);
	package->ref();

	if (ed->isDatatypeDefinition()) {
		fixXMIField(state);
		// The pop_back() is required in e.g. <viewport>0.0</viewport>

		// BUG not exception-safe.
		state.parent_slots.pop_back();
		return;
	}

	static const xmlChar* xmi_ns =
		reinterpret_cast<const xmlChar*>
		(XMI20NS);
	static const xmlChar* xsi_ns = 
		reinterpret_cast<const xmlChar*>
		(XSI_NS);

	bool had_attrib_xmi20 = false;
	bool had_attrib_xsi = false;
	had_attrib_xmi20 = xmlTextReaderMoveToAttributeNs(
		state.reader,
		reinterpret_cast<const xmlChar*>("type"),
		xmi_ns);
	if (!had_attrib_xmi20) {
		had_attrib_xsi = xmlTextReaderMoveToAttributeNs(
			state.reader,
			reinterpret_cast<const xmlChar*>("type"),
			xsi_ns);
	}
	bool had_attrib = had_attrib_xmi20 | had_attrib_xsi;

	const char* elemname = NULL;
	w welemname;
	if (had_attrib) {
		wXmlTextReaderValue d(state.reader);
		welemname = d; // slicing ok
		elemname =
			(const char*) welemname.get();
		coral_assert(elemname);
		/*
		 * NOTE/BUG. When mixing metamodels, we can have a xmi:type
		 * that points to another metamodel's elements.
		 * I'm not sure what this should look like in XMI 2
		 * So we'll just expect a "language:elementname"
		 * In Eclipse, we have xsi:type
		 */
		const char* colon = strchr(elemname, ':');
		if (colon) {
			std::string sprefix(elemname, colon-elemname);
			autounref<Package> L(getLanguageByPrefix(state,
								 sprefix));
			package = L.get();
			elemname = colon+1;
		}
	}
	if (!elemname || elemname[0] == '\0') {
		elemname = ed->getName().c_str();
	}
	if (had_attrib) {
		int e = xmlTextReaderMoveToElement(state.reader);
		if (e != 1) {
			dlog << DEBUGLEVEL_ERROR
			     << "ERROR Couldn't move back to attribute 0"
			     << std::endl;
			throw load_error();
		}
	}

	bool isempty = false;
	if (xmlTextReaderIsEmptyElement(state.reader)) {
		isempty = true;
	}

	state.reading_elem.push_back(false);
	createElem(state, package, elemname,
		   s->getProperty()->isAssociation(),
		   isempty);
	if (isempty) {
		state.reading_elem.pop_back();
		popSlot(state, NULL, state.parent_slots.back()->getProperty()->getName());
	}
}

void fixSlot(_state2& state,
	    const char* propertyname) {
	if (state.parent_elems.empty()) {
		dlog << DEBUGLEVEL_ERROR
		     << "ERROR While loading model!" << std::endl
		     << " Trying to fix a slot " << propertyname
		     << " but there is no parent element." << std::endl;
		throw load_error();
	}

	coral::Element* parent = state.parent_elems.back();
	const char* feature = propertyname;
	try {
		coral::Slot& s = parent->__getSlotByName__(feature);
		state.parent_slots.push_back(&s);
		return;
	} catch (bad_slot& exc) {
		; // fall-thru
	}
	dlog << DEBUGLEVEL_WARNING
	     << "WARNING"
#if LIBXML_VERSION > 20507
	     << " Line "
	     << xmlGetLineNo(xmlTextReaderCurrentNode(state.reader))
#endif
	     << " Couldn't find property \""
	     << propertyname << "\" in" << std::endl
	     << "        " << parent->__str__() << std::endl
	     << "        with UUID "
	     << parent->__XMIid__()
	     << " (ignoring)."
	     << std::endl;
	int err = state.handleUnknownElement(propertyname);
	if (err) throw load_error();
	throw handled();
}

void fixOutsideXMIextension(_state2& state,
			   const char* prefix,
			   const char* name) {
	//dlog << DEBUGLEVEL_DEBUG << "Are we outside xmi:Extension" << std::endl;
	if (!state.isXMIPrefix(prefix) || strcasecmp(name, "Extension"))
		return;
	//dlog << DEBUGLEVEL_DEBUG << "Outside xmi:Extension" << std::endl;

	try {
		fixSlot(state, "xmi_extension");
	} catch (...) {
		dlog << DEBUGLEVEL_ERROR
		     << "ERROR This means you'll lose all XMI extensions"
		     << " if you save your model now."
		     << std::endl;
		throw;
	}
	state.reading_elem.push_back(true);
}

void fixInsideXMIextension(_state2& state) {
	if (state.parent_elems.empty()) {
		return;
	}
	coral::Element* parent = state.parent_elems.back();
	if (parent->__type__() != XMI::Extension)
		return;

	if (!state.parent_slots.empty()) {
		/*
		 * Don't add the XMI.xmi_contents
		 * slot twice.
		 * I don't know why it comes
		 * to the inside routine twice, but it does... :(
		 */
		coral::Slot* s;
		s = state.parent_slots.back();
		if (s->getElement() == parent) {
			return;
		}
	}
	fixSlot(state, "xmi_contents");

	//dlog << DEBUGLEVEL_DEBUG << "inside XMI" << std::endl;
	/*
	 * BUG At this point we should have a different routine.
	 * For our extensions, it should create the appropriate
	 * model elements (we only use model elements).
	 * For other's extensions, it should create XMI/XML elements
	 * which contain attributes and children, and keep xmi:id:s
	 * and xmi:idrefs straight. This is because we really can't
	 * tell what it will contain. libxml:s routines could perhaps
	 * be used for this?
	 * Good fun.
	 * 
	 */
}

/*
 * Resolve what xmi:, xlink: and <ourmetamodel>:
 * are really called.
 */
void resolveNS(_state2& state,
	      const char* ns) {
	if (!state.metamodel && !ns) throw handled();
	if (!ns) return;
	// The eclipse check due to a bug in eclipse (I think)
	if (!strcmp(ns, XMI20NS) || !strcmp(ns, ECLIPSE_XMI20NS)) {
		if (state.metamodel)
			return;
		throw handled();
	}

	if (state.metamodel) return;
	autounref<Package> L(Package::__getLanguageByXMLNS__(ns));
	state.metamodel = L.release();
	if (!state.metamodel) {
		dlog << DEBUGLEVEL_ERROR
		     << "ERROR Don't know the language with namespace "
		     << ns << std::endl;
		throw load_error();
	}
	return;
}

int popElem(_state2& state,
	    const char* prefix,
	    const char* name) {
	coral_assert(!state.parent_elems.empty());
	Element* e = state.parent_elems.back();
	const std::string& elemname = e->__type__()->getName();
	//dlog << DEBUGLEVEL_DEBUG << "Popped E " << elemname << std::endl;
	if (strcmp(elemname.c_str(), name)) {
		dlog << DEBUGLEVEL_ERROR
		     << "ERROR We're out of sync in the XMI stream, "
		     << "expected end of element \"" << elemname << "\" "
		     << "but got \"" << name << "\" instead!" << std::endl;
		return 1;
	}
	state.parent_elems.pop_back();
	return 0;
}

void popSlot(_state2& state,
	     const char* prefix,
	     const char* name) {
	coral_assert(!state.parent_slots.empty());
	Slot* s = state.parent_slots.back();
	const std::string& propname = s->getProperty()->getName();
	//dlog << DEBUGLEVEL_DEBUG << "Popped S " << propname << std::endl;
	if (strcmp(propname.c_str(), name)) {
		dlog
			<< "ERROR We're out of sync in the XMI stream, "
			<< "expected end of slot \"" << propname << "\" "
			<< "but got \"" << name << "\" instead!" << std::endl;
		throw load_error();
	}
	state.parent_slots.pop_back();
	return;
}

int processNode(_state2& state) {
	switch (xmlTextReaderNodeType(state.reader)) {
	/*
	 * Start XML element
	 */
	case XML_ELEMENT_NODE: {
		wXmlTextReaderName wfullname(state.reader);
		const char* fullname =
			reinterpret_cast<const char*>(wfullname.get());
		if (!fullname) break;

		wXmlTextReaderLocalName w_name(state.reader);
		const char* name = 
			reinterpret_cast<const char*>(w_name.get());

		wXmlTextReaderPrefix w_prefix(state.reader);
		const char* prefix =
			reinterpret_cast<const char*>(w_prefix.get());

		wXmlTextReaderLookupNamespace w_ns(state.reader, w_prefix.get());
		const char* ns =
			reinterpret_cast<const char*>(w_ns.get());
#if 0
		dlog << DEBUGLEVEL_DEBUG
		     << "ns-name combo: " << (ns ? ns : "" )
		     << ":" << (name ? name : "" )<< std::endl;
#endif
		resolveNS(state, ns);
		/*
		 * Hack! XMI.extension comes in and destroys
		 * everything for us...
		 * This is when we just are getting inside
		 * the XMI.extension.
		 */
		fixInsideXMIextension(state);
		/*
		 * Hack! And the case of entering XMI.extension
		 * from the outside
		 */
		fixOutsideXMIextension(state, prefix, name);
		/*
		 * Crude hack, but it works for now.
		 * Forwards XMI 2.0 to XMI 1.2.
		 */
		if (ns && !strcmp(ns, XMI20NS)) {
			ns = XMI12NS;
		}
				
		/*
		 * Assume an Element or a Slot starts here
		 */
		coral_assert(!state.reading_elem.empty());
		if (!state.reading_elem.back()) {
			//dlog << DEBUGLEVEL_DEBUG << "Fixing " << name << "." << std::endl;
			fixSlot(state, name);
			readSlot(state, prefix);
			return 0;
		} else {
			bool isempty = xmlTextReaderIsEmptyElement(state.reader);
			autounref<Package> L(Package::__getLanguageByXMLNS__(ns));
			createElem(state,
				   L.get(),
				   name,
				   false,
				   isempty);
			if (isempty) {
				//dlog << DEBUGLEVEL_DEBUG << "Popped E " << std::endl;
				//state.parent_elems.pop_back();
				break;
			}
			if (state.isXMIPrefix(prefix)) {
				//state.reading_elem.push_back(true);
			} else {
				state.reading_elem.push_back(false);
			}
		}
		break;
	}
	/*
	 * End XML Element
	 */
	case XML_ELEMENT_DECL: {
		if (!state.metamodel) break;

		wXmlTextReaderPrefix w_prefix(state.reader);
		const char* prefix
			= reinterpret_cast<const char*>(w_prefix.get());
		wXmlTextReaderLocalName w_name(state.reader);
		const char* name = 
			reinterpret_cast<const char*>(w_name.get());
		/*
		 * Hack! XMI.extension comes in and destroys
		 * everything for us...
		 */
		if (state.isXMIPrefix(prefix)) {
			if (!strcasecmp(name, "Extension")) {
				// XMI.extension
				int err = popElem(state, NULL, "Extension");
				coral_assert(!state.reading_elem.empty());
				state.reading_elem.pop_back();

				coral_assert(!state.parent_slots.empty());
				const char* propname = state.parent_slots.back()->getProperty()->getName();
				if (strcmp(propname, "xmi_contents") == 0) {
					state.parent_slots.pop_back();
					coral_assert(!state.parent_slots.empty());
				}

				coral_assert(!state.parent_slots.empty());
				assert(strcmp(state.parent_slots.back()->getProperty()->getName(), "xmi_extension") == 0);
				state.parent_slots.pop_back();

				coral_assert(!state.reading_elem.empty());

				return err;
			} else {
				/* E.g. the xmi:XMI element goes here. */
				break;
			}
		}

		coral_assert(!state.reading_elem.empty());
		bool read_elem = state.reading_elem.back();
		state.reading_elem.pop_back();
		if (!read_elem) {
			int err;
			coral_assert(!state.parent_elems.empty());
			err = popElem(state, NULL, state.parent_elems.back()->__type__()->getName().c_str());
			if (!state.reading_elem.empty()
			    && state.reading_elem.back() == true) {
				break;
			}
			if (err) return err;
			popSlot(state, NULL, name);
		} else {
			int err;
			err = popElem(state, NULL, name);
			if (err) return err;
		}
	}
	case XML_TEXT_NODE: {
		/*
		 * This is for the following cases:
		 * <UML:ModelElement.name>MyName</UML:ModelElement.name>
		 * i.e. primitive attributes not specified as attributes
		 * but as text nodes inside an XML element.
		 */
		wXmlTextReaderValue wvalue(state.reader);
		const char* value =
			reinterpret_cast<const char*>(wvalue.get());
		if (!state.parent_slots.empty()
		    && !state.parent_elems.empty()) {
			coral::Slot* s = state.parent_slots.back();
			coral::Property* p = s->getProperty();
			coral::AbstractElementDefinition* target
				= p->getTarget();
			if (target->isDatatypeDefinition()) {
				state.fixDatatype(state.parent_elems.back(),
						  target,
						  p,
						  value);
			}
		} else {
			/*
			dlog << DEBUGLEVEL_DEBUG << "Skipping text \"" << value
				  << "\"" << std::endl;
			*/
			;

		}
	}
	case XML_COMMENT_NODE: return 0;
	default: {
		wXmlTextReaderName w_name(state.reader);
		wXmlTextReaderValue w_value(state.reader);
		dlog << DEBUGLEVEL_ERROR
		     << "ERROR Unknown type " << xmlTextReaderNodeType(state.reader)
		     << " with name " << w_name.get()
		     << std::endl
		     << "      and value "
		     << w_value.get()
		     << std::endl
		     << "      (ignoring, continuing loading)"
		     << std::endl;
		break;
	}
	case XML_DTD_NODE: return 0;
	}
	return 0;
}

NAMESPACE_END();

int _state2::loadIt() {
	coral_assert(reader);

	int ret = 0;

	storage->setXMIFormat("2.0");

	/*
	 * Because process_no_xmi_vesion has already been active,
	 * we have to skip the first xmlTextReaderRead()
	 */
	bool skip = true;
	for (;;) {
		if (!skip) {
			ret = xmlTextReaderRead(reader);
			if (ret != 1) break;
		}
		skip = false;
		try {
			ret = processNode(*this);
		} catch (handled& h) {
			; // ok, should be ignored
			ret = 0;
		} catch (std::exception& e) {
			dlog << DEBUGLEVEL_ERROR
			     << "ERROR Caught an exception: " << e.what() << "!" << std::endl;
			ret = 1; 
		}
		if (ret) { break; }
	}

	if (ret) {
		dlog << DEBUGLEVEL_ERROR
		     << "ERROR an error occured during the XML parsing!"
		     << std::endl
	// BUG I wonder when it was first introduced
#if LIBXML_VERSION > 20507
		     << "      Occurred on line "
		     << xmlGetLineNo(xmlTextReaderCurrentNode(reader))
#else
		     << "      Couldn't determine line number, please upgrade libxml2"
#endif
		     << "." << std::endl;
		return 1;
	}

	if (reading_elem.size() != 1) {
		dlog << DEBUGLEVEL_WARNING
		     << "WARNING XML file doesn't seem to end properly, "
		     << std::endl
		     << "        Coral was still expecting "
		     << reading_elem.size()-1 << " elements."
		     << " (ignored)"
		     << std::endl;
	}

	if (parent_slots.size() != 0) {
		dlog << DEBUGLEVEL_WARNING
		     << "WARNING Possibly internal problem, "
		     << std::endl
		     << "        Coral still has "
		     << parent_slots.size() << " parent slots."
		     << " (ignored)"
		     << std::endl;
		for (parent_slots_type::const_iterator i
			     = parent_slots.begin();
		     i != parent_slots.end(); ++i) {
			dlog << "        Property: " << (*i)->getProperty()->getName() << std::endl;
		}
		dlog << "        (End)" << std::endl;
	}

	if (parent_elems.size() != 0) {
		dlog << DEBUGLEVEL_WARNING
		     << "WARNING Possibly internal problem, "
		     << std::endl
		     << "        Coral still has "
		     << parent_elems.size() << " parent elements."
		     << " (ignored)"
		     << std::endl;
		for (parent_elems_type::const_iterator i
			     = parent_elems.begin();
		     i != parent_elems.end(); ++i) {
			dlog << "        Type: " << (*i)->__type__()->getName() << std::endl;
		}
		dlog << "        (End)" << std::endl;
	}

	return 0;
}

NAMESPACE_END();
NAMESPACE_END();

