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

#include "xmi_writer_1.h"

#include <coral/core/Element.h>
#include "xmi_reader_1.h"
#include <coral/lang/XMI/XMI.h>
#include <coral/core/config.h>
#include "xmi_writer_common.h"

#include <string>
#include <sstream>
#include <fstream>

#include <coral/core/Project.h>
#include "c_time.h"

NAMESPACE_BEGIN(coral);
NAMESPACE_BEGIN(io);

XMIStreamer::XMIStreamer() {
	m_is_poseidon_hack = true;
}

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

void XMIStreamer::saveModel(const Collection* roots, std::ostream& out)
	throw (std::exception) {
	coral_assert(roots);

	const coral::Package* themetamodel = NULL;

	autounref<coral::Set> all(new coral::Set());

	/*
	 * Remember what we've saved so elements with multiple
	 * parents are recognized and we can throw...
	 */
	std::set<const coral::Element*> saved_elements;

	Iterator i(roots);
	ref_counted* _t;

	autounref<Package> poseidon_hack_lang(NULL);
	m_is_poseidon_hack = true;

	while ((_t = i.next()) != NULL) {
		Element* e = dynamic_cast<Element*>(_t);
		coral_assert(e);

		autounref<coral::Set> o(e->__getAllParts__());
		all->unionWith(o.get());

		Language* l = e->__type__()->__language__();
		if (l->getXMLNS() == "http://schema.omg.org/spec/UML/1.4") {
			;
		} else if (l->getXMLNS() == "org.omg.xmi.namespace.XMIDI") {
			;
		} else {
			m_is_poseidon_hack = false;
		}
		if (!themetamodel) {
			themetamodel = l;
		} else if (l != themetamodel) {
			if (m_is_poseidon_hack) {
				poseidon_hack_lang = Language::__getLanguageByXMLNS__("org.omg.xmi.namespace.UML");
				themetamodel = poseidon_hack_lang.get();
				continue;
			}
			throw save_error("Can't save model with multiple metamodels using XMI 1. Try XMI 2 instead!");
		}
	}

	/*
	 * Get RFC 822 local time string in the C locale
	 */
	std::string curtime(coral::io::getCurrentRFC822Time());

	/*
	 * XMI header
	 */

	/* BUG xmlns not correct, but we set it just so
	 * loaders are happy... */
	out << "<?xml version='1.0' encoding='UTF-8'?>\n"
	    << "<XMI xmlns:XMI=\"http://schema.omg.org/spec/XMI/1.2\"";
	if (themetamodel) {
		std::string ns(themetamodel->getName());
		std::string url(themetamodel->getXMLNS());
		if (m_is_poseidon_hack) {
			ns = "UML";
		}
		out << "\n     xmlns:" << ns << "=";
		if (url.size() != 0) {
			out << qescape(url);
		} else {
			out << "\"unknown.xml\"";
		}
	}
	out << "\n     xmi.version='1.2' timestamp='" << curtime << "'>\n"
	    << " <XMI.header>\n";
	if (themetamodel) {
		const Language* ltmp = dynamic_cast<const Language*>(themetamodel);
		coral_assert(ltmp);
		if (!m_is_poseidon_hack) {
			int version = ltmp->getVersion();
			int revision = ltmp->getRevision();
			out << "  <XMI.metamodel xmi.name="
			    << qescape(themetamodel->getName())
			    << " xmi.version='" << version << "."
			    << revision << "'/>\n";
		}
	}
	out << "  <XMI.documentation>\n"
	    << "   <XMI.exporter>Coral</XMI.exporter>\n"
	    << "   <XMI.exporterVersion>"
	    << coral::version
	    << "</XMI.exporterVersion>\n"
	    << "  </XMI.documentation>\n"
	    << " </XMI.header>\n"
	    << " <XMI.content>\n"
		;

	/*
	 * Save elements recursively
	 */
	kind_t kind;
	kind.kind = kind_t::KIND_COMPOSITION;
	Iterator i2(roots);
	while ((_t = i2.next()) != NULL) {
		coral::Element* e = dynamic_cast<Element*>(_t);
		coral_assert(e);
		saveElement(saved_elements, all.get(), out, e, 2, kind, false);
	}

	/*
	 * XMI footer
	 */
	out << " </XMI.content>\n"
	    << "</XMI>\n"
		;
}

void XMIStreamer::saveElement(std::set<const coral::Element*>& saved_elements,
			      Set* all,
			      std::ostream& out,
			      const Element* elem,
			      int margin,
			      kind_t asskind,
			      bool multiple_mof_datatype_elems) {
	coral_assert(elem);
	const ElementDefinition* type = elem->__type__();

	bool is_xmi_language =
		(elem->__type__() == XMI::Extension);
	const char separator = is_xmi_language ? '.' : ':';
	
	std::string spaces(margin, ' '); // could be faster...
	/*
	 * Very special cases if we're a MOF Datatype
	 *          <UML:GraphEdge.waypoints>
	 *            <XMI.field>
	 *              <XMI.field>279.0</XMI.field>
	 *              <XMI.field>80.0</XMI.field>
	 *            </XMI.field>
	 *            <XMI.field>
	 *              <XMI.field>0.0</XMI.field>
	 *              <XMI.field>0.0</XMI.field>
	 *
	 * and
	 *                   <UML:GraphElement.position>
	 *                    <XMI.field>2.0</XMI.field>
	 *                    <XMI.field>2.0</XMI.field>
	 *                  </UML:GraphElement.position>
	 *
	 */
	if (type->isMOFDatatype()) {
		if (multiple_mof_datatype_elems) {
			out << spaces << "<XMI.field>\n";
			spaces += ' ';
		}
		goto cont;
	}
	/*
	 * Write element header, <UML:Class...
	 */
	out << spaces << "<"
	    << (m_is_poseidon_hack ? "UML" : type->__language__()->getName())
	    << separator;
	if (is_xmi_language) {
		out << "extension";
		goto cont;
	} else {
		out << type->getName();
	}

	if (!all->includes(elem)) {
		Project* st = elem->__getRootsProject__();
		const char* filename = "unknown.xml";
		if (st) filename = st->getHrefFilename().c_str();
		std::ostringstream name;
		name << filename
		     << "|descendent(1,#element,xmi.uuid,"
		     << elem->__XMIid__()
		     << ")";
		out << " href=" << qescape(name.str())
		    << " />\n";
		return;
	}
	if (asskind.isComposition()) {
		out << " xmi.id=" << qescape(getID(elem))
		    << " xmi.uuid=" << qescape(elem->__XMIid__());
		if (saved_elements.insert(elem).second == false) {
			std::string s("Element has multiple parents, can't save! UUID: ");
			s += elem->__XMIid__();
			throw save_error(s);
		}
	} else {
		out << " xmi.idref=" << qescape(getID(elem))
		    << " />\n";
		return;
	}
cont:
	/*
	 * Attributes and MULT_SINGLE compositions and associations
	 */
	const ElementDefinition::ordered_properties_type* mmproperties
		= type->getPropertiesInOrder();
	for (ElementDefinition::ordered_properties_type::const_iterator i
		     = mmproperties->begin();
	     i != mmproperties->end();
	     ++i) {
		const Property* mm = *i;
		if (mm->isUnserializable()) continue;
		if (mm->isAnonymous()) continue;

		const AbstractElementDefinition* mdaed
			= mm->getTarget();

		if (mm->isAtMostOne()) {
			const Slot& s =
				elem->__getSlotRO__(mm->getName());
			const Atom* attrib = s[0];
			if (!attrib) continue;

			const Property* poppo =
				mm->getOppositeProperty();
			if (poppo && 
			    (poppo->isComposition())) {
				/* Don't save ref to parent element
				 * in compositions, or the attribute
				 * element either. */
				continue;
			}

			if (type->isMOFDatatype()) {
				out << spaces << "<XMI.field>" << escape(attrib->__str__().toUTF8()) << "</XMI.field>\n";
				continue;
			}
			std::string res;
			if (mdaed->isDatatypeDefinition()) {
				/*
				 * Skip broken enumerations
				 */
				if (mdaed->isEnumerationDefinition()
				     && ((static_cast<const coral::EnumerationDefinition*>(mdaed))->size() == 0)) {
					continue;
				}
				res += attrib->__str__().toUTF8();
			} else {
				/*
				 * We take care of compositions later...
				 */
				if (mm->isComposition())
					continue;
				const Element* a = static_cast<const Element*>(attrib);
				/*
				 * Urk, special case for "attributes" that are outside of the
				 * current model, we can't use XLinks here?
				 */
				if (!all->includes(a)) {
					continue;
				}
				res += getID(a);
			}
			out << " ";
			if (is_xmi_language) {
				out << "xmi.";
			}
			out << mm->getName() << "="
			    << qescape(res);
		}
	}
	if (!type->isMOFDatatype()) {
		out << ">\n";
	}

	/*
	 * Other people's XMI extensions here
	 */

	/*
	 * Write out multivalued associations and single-valued
	 * element compositions
	 * Recurse throught compositions and attribute elements.
	 */

	for (ElementDefinition::ordered_properties_type::const_iterator i =
		     mmproperties->begin();
	     i != mmproperties->end();
	     ++i) {
		const Property* mm = *i;
		if (mm->isUnserializable()) continue;
		if (mm->isAnonymous()) continue;
		const AbstractElementDefinition* mmtype
			= mm->getTarget();
		if (mm->isAtMostOne()) {
			if (mmtype->isDatatypeDefinition())
				continue;

			const Slot* mf = elem->__hasSlotByName__(mm->getName());
			if (!mf || mf->__len__() == 0) continue;
			const Atom* a = (*mf)[0];
			if (!a) continue;
			if (!mm->isComposition() && all->includes(a)) continue;

		} else {
			/*
			 * Something wrong. We're writing a datatype, but it has properties
			 * containing multiple values. On the other hand, this _does_
			 * work if you only have one property in the datatype.
			 * For now, don't report an error.
			 */
			if (type->isMOFDatatype()) {
				//coral_assert(0);
				;
			}
		}

		/*
		 * Faster, if nothing exists, or it has
		 * no length...
		 */
		const Slot* mf = elem->__hasSlotByName__(mm->getName());
		if (!mf || mf->__len__() == 0) continue;

		/*
		 * Also, remember to skip if this is an
		 * association back to the composition
		 */
		const Property* other
			= mm->getOppositeProperty();
		if (other
		    && other->isComposition()) {
			continue;
		}

		/*
		 * Where is the feature defined?
		 * <UML:GeneralizableElement.specialization>...
		 * Feature header.
		 */
		const AbstractElementDefinition* base = mm->getOwner();

		/*
		 * Hack! Avoid "<XMI:extension.our_contents>"
		 */
		if (base != XMI::Extension && mmtype != XMI::Extension) {
			if (!type->isMOFDatatype()) {
				out << spaces << " <"
				    << (m_is_poseidon_hack ? "UML" : type->__language__()->getName())
				    << separator
				    << base->getName() << "."
				    << mm->getName() << ">\n";
				++margin;
			}
		}
		bool multiple = false;
		if (ElementDefinition* c = dynamic_cast<ElementDefinition*>(mm->getTarget())) {
			if (c->isMOFDatatype() && !mm->isAtMostOne()) {
				multiple = true;
			}
		}
		Iterator m(mf);
		const ref_counted* _one;
		while ((_one = m.next()) != NULL) {
			const Atom* _atom
				= dynamic_cast<const Atom*>(_one);
			coral_assert(_atom);
			/*
			 * This for multi-valued strings etc..
			 */
			if (const ElementPrimitive* p
			    = dynamic_cast<const ElementPrimitive*>(_atom)) {
				writeField(out, p, margin + 1);
				continue;
			}
			const Element* one
				= dynamic_cast<const Element*>(_one);
			coral_assert(one);
			saveElement(saved_elements,
				    all,
				    out,
				    one,
				    margin + 1,
				    mm->getKindT(),
				    multiple);
		}

		/*
		 * Feature footer.
		 */
		if (base != XMI::Extension && mmtype != XMI::Extension) {
			if (!type->isMOFDatatype()) {
				--margin;
				out << spaces << " </"
				    << (m_is_poseidon_hack ? "UML" : type->__language__()->getName())
				    << separator
				    << base->getName() << "."
				    << mm->getName() << ">\n";
			}
		}

	}
	/*
	 * Write footer, </UML:Class>...
	 */
	if (type->isMOFDatatype()) {
		if (multiple_mof_datatype_elems) {
			out << (spaces.c_str() + 1) << "</XMI.field>\n";

			out << (spaces.c_str() + 1) << "<XMI.field>\n";
			out << (spaces.c_str())     << "<XMI.field>0.0</XMI.field>\n";
			out << (spaces.c_str())     << "<XMI.field>0.0</XMI.field>\n";
			out << (spaces.c_str() + 1) << "</XMI.field>\n";

			out << (spaces.c_str() + 1) << "<XMI.field>\n";
			out << (spaces.c_str())     << "<XMI.field>0.0</XMI.field>\n";
			out << (spaces.c_str())     << "<XMI.field>0.0</XMI.field>\n";
			out << (spaces.c_str() + 1) << "</XMI.field>\n";
		}
		return;
	}


	out << spaces << "</"
	    << (m_is_poseidon_hack ? "UML" : type->__language__()->getName())
	    << separator;
	if (is_xmi_language) {
		out << "extension";
	} else {
		out << type->getName();
	}
	out << ">\n";
}

NAMESPACE_END();
NAMESPACE_END();

