/**
 * Message generation utilities
 * 
 * Authors:
 * David Yip <yipdw@rose-hulman.edu>
 * Jonas Collaros, Stephen Montgomery
 *
 * Copyright (c) 2004-2005 Authors
 *
 * Released under GNU GPL, read the file 'COPYING' for more information
 */

#include <glibmm/i18n.h>

#include "util/share.h"
#include "util/list.h"

#include "xml/node.h"
#include "xml/attribute-record.h"
#include "xml/repr.h"

#include "jabber_whiteboard/defines.h"
#include "jabber_whiteboard/typedefs.h"
#include "jabber_whiteboard/node-utilities.h"
#include "jabber_whiteboard/message-utilities.h"
#include "jabber_whiteboard/node-tracker.h"

#include <iostream>

namespace Inkscape {

namespace Whiteboard {

// This method can be instructed to not build a message string but only collect nodes that _would_ be transmitted
// and subsequently added to the tracker.  This can be useful in the case where an Inkboard user is the only one
// in a chatroom and therefore needs to fill out the node tracker, but does not need to build the message string.
// This can be controlled with the only_collect_nodes flag, which will only create pointers to new XML::Nodes 
// in the maps referenced by newidsbuf and newnodesbuf.  Passing NULL as the message buffer has the same effect.
//
// only_collect_nodes defaults to false because most invocations of this method also use the message string.
void
MessageUtilities::newObjectMessage(Glib::ustring* msgbuf, KeyToNodeMap& newidsbuf, NodeToKeyMap& newnodesbuf, NewChildObjectMessageList& childmsgbuf, XMLNodeTracker* xmt, Inkscape::XML::Node const* node, bool only_collect_nodes, bool collect_children)
{
	// Initialize pointers
	std::string id, refid, parentid;

	gchar const* name = NULL;
	XML::Node* parent = NULL;
	XML::Node* ref = NULL;

	bool only_add_children = false;


	if (node != NULL) {
		parent = sp_repr_parent(node);
		if (parent != NULL) {
			parentid = NodeUtilities::findNodeID(*parent, xmt, newnodesbuf);
			if (parentid.empty()) {
				g_warning("Parent %p is not being tracked, creating new ID", parent);
				parentid = xmt->generateKey();
				newidsbuf[parentid] = parent;
				newnodesbuf[parent] = parentid;
			}

			if ( node != parent->firstChild() && parent != NULL ) {
				ref = parent->firstChild();
				while (ref->next() != node) {
					ref = ref->next();
				}
			}	
		}

		if (ref != NULL) {
			refid = NodeUtilities::findNodeID(*ref, xmt, newnodesbuf);
			if (refid.empty() && ref != NULL) {
				g_warning("Ref %p is not being tracked, creating new ID", ref);
				refid = xmt->generateKey();
				newidsbuf[refid] = ref;
				newnodesbuf[ref] = refid;
			}
		}

		name = static_cast< gchar const* >(node->name());
	}

	// Generate an id for this object and append it onto the list, if 
	// it's not already in the tracker
	if (!xmt->isSpecialNode(node->name())) {
		if (!xmt->isTracking(*node)) {
			id = xmt->generateKey();	
			newidsbuf[id] = node;
			newnodesbuf[node] = id;
		} else {
			id = xmt->get(*node);
		}
	} else {
		id = xmt->get(*node);
		if (id.empty()) {
			g_warning("Node %p (name %s) is a special node, but it could not be found in the node tracker (possible unexpected duplicate?)  Generating unique ID anyway.", node, node->name());
			id = xmt->generateKey();
			newidsbuf[id] = node;
			newnodesbuf[node] = id;
		}
		only_add_children = true;
	}

	// If we're only adding children (i.e. this is a special node)
	// don't process the given node.
	if( !only_add_children && !id.empty() && msgbuf != NULL && !only_collect_nodes ) {
		// <MESSAGE_NEWOBJ>
		(*msgbuf) = (*msgbuf) + "<" + MESSAGE_NEWOBJ + ">";

		// <MESSAGE_PARENT>
		(*msgbuf) = (*msgbuf) + "<" + MESSAGE_PARENT + ">";

		if(!parentid.empty()) {
			(*msgbuf) += parentid;
		}

		// </MESSAGE_NEWOBJ><MESSAGE_CHILD>id</MESSAGE_CHILD>
		(*msgbuf) = (*msgbuf) + "</" + MESSAGE_PARENT + ">";

		(*msgbuf) = (*msgbuf) + "<" + MESSAGE_CHILD + ">";
		(*msgbuf) += id;

		(*msgbuf) = (*msgbuf) + "</" + MESSAGE_CHILD + ">";

		if(!refid.empty()) {
			// <MESSAGE_REF>refid</MESSAGE_REF>
			(*msgbuf) = (*msgbuf) + "<" + MESSAGE_REF + ">";
			
			(*msgbuf) += refid;

			(*msgbuf) = (*msgbuf) + "</" + MESSAGE_REF + ">";
		}

		// <MESSAGE_NODETYPE>*node.type()</MESSAGE_NODETYPE>
		(*msgbuf) = (*msgbuf) + "<" + MESSAGE_NODETYPE + ">" + NodeUtilities::nodeTypeToString(*node);
		(*msgbuf) = (*msgbuf) + "</" + MESSAGE_NODETYPE + ">";

		if (node->content() != NULL) {
			// <MESSAGE_CONTENT>node->content()</MESSAGE_CONTENT>
			(*msgbuf) = (*msgbuf) + "<" + MESSAGE_CONTENT + ">" + node->content();
			(*msgbuf) = (*msgbuf) + "</" + MESSAGE_CONTENT + ">";
		}

		// <MESSAGE_NAME>name</MESSAGE_NAME>
		(*msgbuf) = (*msgbuf) + "<" + MESSAGE_NAME + ">";

		if( name != NULL ) {
			(*msgbuf) += name;
		}

		(*msgbuf) = (*msgbuf) + "</" + MESSAGE_NAME + ">";

		// </MESSAGE_NEWOBJ>
		(*msgbuf) = (*msgbuf) + "</" + MESSAGE_NEWOBJ + ">";
	} else if (id.empty()) {
		// if ID is NULL, then we have a real problem -- we were not able to find a key
		// nor generate one.  The only thing we can really do here is abort, since we have
		// no way to let the other client(s) uniquely identify this object.
		/* FIXME: If this indicates a programming bug, then don't request translation with
		 * _(...): it is most useful in untranslated form so that developers may search for
		 * it when someone reports it in a bug report (as we want users to do for all bugs,
		 * as indicated by it being a g_warning string).
		 * 
		 * Otherwise, if it is not a programming bug but a network error or a bug in the
		 * remote peer (perhaps running different software) or whatever, then present it in
		 * an alert box, and avoid use of technical jargon `NULL'.
		 */
		g_warning(_("ID for new object is NULL even after generation and lookup attempts: the new object will NOT be sent, nor will any of its child objects!"));
		return;
	} else {

	}


	if (!only_collect_nodes && msgbuf != NULL && !id.empty()) {
		// Collect new object's attributes and append them onto the msgbuf
		Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attrlist = node->attributeList();

		for(; attrlist; attrlist++) {
			MessageUtilities::objectChangeMessage(msgbuf, xmt, id, g_quark_to_string(attrlist->key), NULL, attrlist->value, false);
		}
	}

	if (!only_collect_nodes) {
		childmsgbuf.push_back(*msgbuf);
	}

	if (!id.empty() && collect_children) {
		Glib::ustring childbuf;
		// Collect any child objects of this new object
		for ( Inkscape::XML::Node const *child = node->firstChild(); child != NULL; child = child->next() ) {
			childbuf.clear();
			MessageUtilities::newObjectMessage(&childbuf, newidsbuf, newnodesbuf, childmsgbuf, xmt, child, only_collect_nodes);
			if (!only_collect_nodes) {
				// we're recursing down the tree, so we're picking up child nodes first
				// and parents afterwards
//				childmsgbuf.push_front(childbuf);
			}

		}
	}
}

void
MessageUtilities::objectChangeMessage(ustring* msgbuf, XMLNodeTracker* xmt, std::string const id, gchar const* key, gchar const* oldval, gchar const* newval, bool is_interactive)
{
	// Construct message
	
	// <MESSAGE_CHANGE><MESSAGE_ID>id</MESSAGE_ID>
	(*msgbuf) = (*msgbuf) + "<" + MESSAGE_CHANGE + ">";
	(*msgbuf) = (*msgbuf) + "<" + MESSAGE_ID + ">";
	(*msgbuf) += id;
	(*msgbuf) = (*msgbuf) + "</" + MESSAGE_ID + ">";

	// <MESSAGE_KEY>key</MESSAGE_KEY>
	(*msgbuf) = (*msgbuf) + "<" + MESSAGE_KEY + ">";
	if (key != NULL) {
		(*msgbuf) += key;
	}
	(*msgbuf) = (*msgbuf) + "</" + MESSAGE_KEY + ">";

	// <MESSAGE_OLDVAL>oldval</MESSAGE_OLDVAL>
	(*msgbuf) = (*msgbuf) + "<" + MESSAGE_OLDVAL + ">";
	if (oldval != NULL) {
		(*msgbuf) += oldval;
	}
	(*msgbuf) = (*msgbuf) + "</" + MESSAGE_OLDVAL + ">";

	// <MESSAGE_NEWVAL>newval</MESSAGE_NEWVAL>
	(*msgbuf) = (*msgbuf) + "<" + MESSAGE_NEWVAL + ">";
	if (newval != NULL) {
		(*msgbuf) += newval;
	}
	(*msgbuf) = (*msgbuf) + "</" + MESSAGE_NEWVAL + ">";

	// <MESSAGE_ISINTERACTIVE>is_interactive</MESSAGE_ISINTERACTIVE>
	(*msgbuf) = (*msgbuf) + "<" + MESSAGE_ISINTERACTIVE + ">";
	if (is_interactive) {
		(*msgbuf) += "true";
	} else {
		(*msgbuf) += "false";
	}
	(*msgbuf) = (*msgbuf) + "</" + MESSAGE_ISINTERACTIVE + ">";

	// </MESSAGE_CHANGE>
	(*msgbuf) = (*msgbuf) + "</" + MESSAGE_CHANGE + ">";
}

void
MessageUtilities::objectDeleteMessage(Glib::ustring* msgbuf, XMLNodeTracker* xmt, Inkscape::XML::Node const& parent, Inkscape::XML::Node const& child, Inkscape::XML::Node const* prev)
{
	/*
	gchar const* parentid = NULL;
	gchar const* previd = NULL;
	gchar const* childid = NULL;

	childid = child.attribute("id");
	parentid = parent.attribute("id");
	if (prev != NULL) {
		previd = prev->attribute("id");
	}*/

	std::string parentid, previd, childid;

	childid = xmt->get(child);
	parentid = xmt->get(parent);
	previd = xmt->get(*prev);

	if (!childid.empty()) {
		// <MESSAGE_DELETE><MESSAGE_PARENT>parentid</MESSAGE_PARENT>
		(*msgbuf) = (*msgbuf) + "<" + MESSAGE_DELETE + ">" + "<" + MESSAGE_PARENT + ">";
		if (!parentid.empty()) {
			(*msgbuf) += parentid;
		}
		(*msgbuf) = (*msgbuf) + "</" + MESSAGE_PARENT + ">";

		// <MESSAGE_CHILD>childid</MESSAGE_CHILD>
		(*msgbuf) = (*msgbuf) + "<" + MESSAGE_CHILD + ">";
		if (!childid.empty()) {
			(*msgbuf) += childid;
		}
		(*msgbuf) = (*msgbuf) + "</" + MESSAGE_CHILD + ">";

		// <MESSAGE_REF>previd</MESSAGE_REF>
		(*msgbuf) = (*msgbuf) + "<" + MESSAGE_REF + ">";
		if (!previd.empty()) {
			(*msgbuf) += previd;
		}
		(*msgbuf) = (*msgbuf) + "</" + MESSAGE_REF + ">";

		// </MESSAGE_DELETE>
		(*msgbuf) = (*msgbuf) + "</" + MESSAGE_DELETE + ">";
	}
}

void
MessageUtilities::contentChangeMessage(Glib::ustring& msgbuf, std::string const nodeid, Util::shared_ptr<char> old_value, Util::shared_ptr<char> new_value)
{
	if (!nodeid.empty()) {
		// <MESSAGE_NODECONTENT>
		msgbuf = msgbuf + "<" + MESSAGE_NODECONTENT + ">";

		// <MESSAGE_ID>nodeid</MESSAGE_ID>
		msgbuf = msgbuf + "<" + MESSAGE_ID + ">";
		msgbuf += nodeid;
		msgbuf = msgbuf + "</" + MESSAGE_ID + ">";

		// <MESSAGE_OLDVAL>old_value</MESSAGE_OLDVAL>
		msgbuf = msgbuf + "<" + MESSAGE_OLDVAL + ">";
		msgbuf += old_value.pointer();
		msgbuf = msgbuf + "</" + MESSAGE_OLDVAL + ">";

		// <MESSAGE_NEWVAL>new_value</MESSAGE_NEWVAL>
		msgbuf = msgbuf + "<" + MESSAGE_NEWVAL + ">";
		msgbuf += new_value.pointer();
		msgbuf = msgbuf + "</" + MESSAGE_NEWVAL + ">";

		// </MESSAGE_NODECONTENT>
		msgbuf = msgbuf + "</" + MESSAGE_NODECONTENT + ">";
	}
}

void
MessageUtilities::childOrderChangeMessage(Glib::ustring& msgbuf, std::string const childid, std::string const oldprevid, std::string const newprevid)
{
	if (!childid.empty()) {
		// <MESSAGE_ORDERCHANGE>
		msgbuf = msgbuf + "<" + MESSAGE_ORDERCHANGE + ">";

		// <MESSAGE_ID>nodeid</MESSAGE_ID>
		msgbuf = msgbuf + "<" + MESSAGE_CHILD + ">";
		msgbuf += childid;
		msgbuf = msgbuf + "</" + MESSAGE_CHILD + ">";

		// <MESSAGE_OLDVAL>oldprevid</MESSAGE_OLDVAL>
		/*
		msgbuf = msgbuf + "<" + MESSAGE_OLDVAL + ">";
		msgbuf += (*oldprevid);
		msgbuf = msgbuf + "</" + MESSAGE_OLDVAL + ">";
		*/

		// <MESSAGE_NEWVAL>newprevid</MESSAGE_NEWVAL>
		msgbuf = msgbuf + "<" + MESSAGE_NEWVAL + ">";
		msgbuf += newprevid;
		msgbuf = msgbuf + "</" + MESSAGE_NEWVAL + ">";

		// </MESSAGE_ORDERCHANGE>
		msgbuf = msgbuf + "</" + MESSAGE_ORDERCHANGE + ">";
	}
}


bool
MessageUtilities::getFirstMessageTag(struct Node& buf, Glib::ustring const& msg)
{
	if (msg.empty()) {
		return false;
	}

	// See if we have a valid start tag, i.e. < ... >.  If we do,
	// continue; if not, stop and return NULL.
	//
	// find_first_of returns ULONG_MAX when it cannot find the first
	// instance of the given character.

	Glib::ustring::size_type startDelim = msg.find_first_of('<');
	if (startDelim != ULONG_MAX) {
		Glib::ustring::size_type endDelim = msg.find_first_of('>');
		if (endDelim != ULONG_MAX) {
			if (endDelim > startDelim) {
				buf.tag = msg.substr(startDelim+1, (endDelim-startDelim)-1);
				if (buf.tag.find_first_of('/') == ULONG_MAX) { // start tags should not be end tags


					// construct end tag (</buf.data>)
					Glib::ustring endTag(buf.tag);
					endTag.insert(0, "/");

					Glib::ustring::size_type endTagLoc = msg.find(endTag, endDelim);
					if (endTagLoc != ULONG_MAX) {
						buf.data = msg.substr(endDelim+1, ((endTagLoc - 1) - (endDelim + 1)));
						buf.next_pos = endTagLoc + endTag.length() + 1;

						return true;
					}
				}
			}
		}
	}

	return false;
}

bool
MessageUtilities::findTag(struct Node& buf, Glib::ustring const& msg)
{
	if (msg.empty()) {
		return false;
	}

	// Read desired tag type out of buffer, and append
	// < > to it 
	
	Glib::ustring searchterm("<");
	searchterm += buf.tag;
	searchterm + ">";

	Glib::ustring::size_type tagStart = msg.find(searchterm, 0);
	if (tagStart != ULONG_MAX) {
		// Find ending tag starting at the point at the end of
		// the start tag.
		searchterm.insert(1, "/");
		Glib::ustring::size_type tagEnd = msg.find(searchterm, tagStart + searchterm.length());
		if (tagEnd != ULONG_MAX) {
			Glib::ustring::size_type start = tagStart + searchterm.length();
			buf.data = msg.substr(start, tagEnd - start);
			return true;
		}
	}
	return false;
}

Glib::ustring
MessageUtilities::makeTagWithContent(Glib::ustring tagname, Glib::ustring content)
{
	Glib::ustring buf;
	buf = "<" + tagname + ">";
	buf += content;
	buf += "</" + tagname + ">";
	return buf;
}


}

}



/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
