/* AbiWord
 * Copyright (C) 2002 Tomas Frydrych <tomas@frydrych.uklinux.net>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

#include "pp_Revision.h"
#include "pp_AttrProp.h"
#include "pd_Style.h"
#include "pd_Document.h"
#include "ut_debugmsg.h"
#include "ut_misc.h"

//#include <limits.h>

PP_Revision::PP_Revision(UT_uint32 Id, PP_RevisionType eType, const XML_Char * props, const XML_Char * attrs):
	m_iID(Id), m_eType(eType), m_bDirty(true)
{
	if(!props && !attrs)
		return;

	const char * empty = "";
	
	if(props)
	{
		char * pProps = UT_strdup(props);
		UT_return_if_fail (pProps);

		char * p = strtok(pProps, ":");

		while(p)
		{
			char * n = p;

			// skip over spaces ...
			while(n && *n == ' ')
				++n;
			
			p = strtok(NULL, ";");

			// if we have no p, that means the property is being removed ...
			const char * v = p ? p : empty;
			if(! UT_strcmp(v, "-/-"))
				v = empty;
		
			if(n)
			{
				setProperty(n,v);
				p = strtok(NULL,":");
			}
			else
			{
				// malformed property
				UT_DEBUGMSG(("PP_Revision::PP_Revision: malformed props string [%s]\n", props));
				// if we have not reached the end, we will keep trying ...
				if(p)
					p = strtok(NULL,":");
			}
		}

		FREEP(pProps);
	}

	if(attrs)
	{
		char * pAttrs = UT_strdup(attrs);

		UT_ASSERT_HARMLESS(pAttrs);
		if(!pAttrs)
		{
			UT_DEBUGMSG(("PP_Revision::PP_Revision: out of memory\n"));
			return;
		}

		char * p = strtok(pAttrs, ":");

		while(p)
		{
			char * n = p;
			p = strtok(NULL, ";");

			const char * v = p ? p : empty;
			if(! UT_strcmp(v, "-/-"))
				v = empty;
			
			if(n)
			{
				setAttribute(n,v);
				p = strtok(NULL,":");
			}
			else
			{
				// malformed property
				UT_DEBUGMSG(("PP_Revision::PP_Revision: malformed props string [%s]\n", props));
				// if we have not reached the end, we will keep trying ...
				if(p)
					p = strtok(NULL,":");
			}
		}

		FREEP(pAttrs);
	}
}

PP_Revision::PP_Revision(UT_uint32 Id, PP_RevisionType eType, const XML_Char ** props, const XML_Char ** attrs):
	m_iID(Id), m_eType(eType), m_bDirty(true)
{
	if(!props && !attrs)
		return;

	if(props)
	{
		setProperties(props);
	}

	if(attrs)
	{
		setAttributes(attrs);
	}
}

/*!
    Sets attributes taking care of any nested revision attribute (which needs to be parsed
    and combined with the current AP set.
*/
bool PP_Revision::setAttributes(const XML_Char ** attributes)
{
	if(!PP_AttrProp::setAttributes(attributes))
		return false;
	
	return _handleNestedRevAttr();
}



bool PP_Revision::_handleNestedRevAttr()
{
	const XML_Char * pNestedRev = NULL;
	getAttribute("revision", pNestedRev);
	
	if(pNestedRev)
	{
		PP_RevisionAttr NestedAttr(pNestedRev);

		// now remove "revision"
		setAttribute("revision", NULL);
		prune();

		// overlay the attrs and props from the revision attribute
		for(UT_uint32 i = 0; i < NestedAttr.getRevisionsCount(); ++i)
		{
			const PP_Revision * pRev = NestedAttr.getNthRevision(i);
			UT_return_val_if_fail( pRev, false );
					
			// ignore inserts and deletes
			if(pRev->getType() == PP_REVISION_ADDITION || pRev->getType() == PP_REVISION_DELETION)
				continue;

			setProperties(pRev->getProperties());
			setAttributes(pRev->getAttributes());
		}

		prune();
	}

	return true;
}


/*! converts the internal vector of properties into XML string */
const XML_Char * PP_Revision::getPropsString()
{
	if(m_bDirty)
		_refreshString();

	return (const XML_Char*) m_sXMLProps.c_str();
}

/*! converts the internal vector of attributes into XML string */
const XML_Char * PP_Revision::getAttrsString()
{
	if(m_bDirty)
		_refreshString();

	return (const XML_Char*) m_sXMLAttrs.c_str();
}

void PP_Revision::_refreshString()
{
	m_sXMLProps.clear();
	m_sXMLAttrs.clear();

	UT_uint32 i;
	UT_uint32 iCount = getPropertyCount();
	const XML_Char * n, *v;

	for(i = 0; i < iCount; i++)
	{
		if(!getNthProperty(i,n,v))
		{
			// UT_ASSERT_HARMLESS( UT_SHOULD_NOT_HAPPEN );
			continue;
		}
		
		if(!v || !*v) v = "-/-";
		
		m_sXMLProps += n;
		m_sXMLProps += ":";
		m_sXMLProps += v;
		if(i < iCount - 1)
			m_sXMLProps += ";";
	}

	iCount = getAttributeCount();
	for(i = 0; i < iCount; i++)
	{
		if(!getNthAttribute(i,n,v))
		{
			// UT_ASSERT_HARMLESS( UT_SHOULD_NOT_HAPPEN );
			continue;
		}
		
		if(!v || !*v) v = "-/-";

		m_sXMLAttrs += n;
		m_sXMLAttrs += ":";
		m_sXMLAttrs += v;
		if(i < iCount - 1)
			m_sXMLAttrs += ";";
	}

	m_bDirty = false;
}

bool PP_Revision::operator == (const PP_Revision &op2) const
{
	// this is quite involved, but we will start with the simple
	// non-equality cases

	if(getId() != op2.getId())
		return false;

	if(getType() != op2.getType())
		return false;


	// OK, so we have the same type and id, do we have the same props ???
	UT_uint32 iPCount1 = getPropertyCount();
	UT_uint32 iPCount2 = op2.getPropertyCount();
	UT_uint32 iACount1 = getAttributeCount();
	UT_uint32 iACount2 = op2.getAttributeCount();

	if((iPCount1 != iPCount2) || (iACount1 != iACount2))
		return false;

	// now the lengthy comparison
	UT_uint32 i;
	const XML_Char * n;
	const XML_Char * v1, * v2;

	for(i = 0; i < iPCount1; i++)
	{

		getNthProperty(i,n,v1);
		op2.getProperty(n,v2);

		if(UT_strcmp(v1,v2))
			return false;
	}

	for(i = 0; i < iACount1; i++)
	{

		getNthAttribute(i,n,v1);
		op2.getAttribute(n,v2);

		if(UT_strcmp(v1,v2))
			return false;
	}
	return true;
}


/************************************************************
 ************************************************************/

/*! create class instance from an XML attribute string
 */
PP_RevisionAttr::PP_RevisionAttr(const XML_Char * r):
	m_pLastRevision(NULL)
{
	_init(r);
}

/*! create class instance from a single revision data */
PP_RevisionAttr::PP_RevisionAttr(UT_uint32 iId, PP_RevisionType eType,
								 const XML_Char ** pAttrs, const XML_Char ** pProps)
{
	PP_Revision * pRevision = new PP_Revision((UT_uint32)iId, eType, pProps, pAttrs);
	m_vRev.addItem((void*)pRevision);
}


PP_RevisionAttr::~PP_RevisionAttr()
{
	_clear();
}

/*! initialize instance with XML attribute string
 */
void PP_RevisionAttr::setRevision(const XML_Char * r)
{
	_clear();
	_init(r);
}

/*! destroys all internal data */
void PP_RevisionAttr::_clear()
{
	for(UT_uint32 i = 0; i < m_vRev.getItemCount(); i++)
	{
		delete (PP_Revision *) m_vRev.getNthItem(i);
	}

	m_vRev.clear();
	m_bDirty = true;
	m_pLastRevision = NULL;
}


/*! parse given XML attribute string and fill the
    instance with the data
*/
void PP_RevisionAttr::_init(const XML_Char *r)
{
	if(!r)
		return;

	// the string we are parsing looks like
	// "+1,-2,!3{font-family: Times New Roman}"

	// first duplicate the string so we can play with it ...
	char * s = (char*) UT_strdup(r);
	char * end_s = s + strlen(s); // we need to remember where this
								  // string ends because we cannot use strtok(NULL,...)

	UT_sint32 iId;
	PP_RevisionType eType;
	XML_Char * pProps, * pAttrs,
		     * cl_brace = 0, * op_brace = 0,
		     * cl_brace2 = 0;

	char * t = strtok(s,",");

	// we have to remember the end of this token for future calls to
	// strtok since strtok is also used in the PP_Revision class and
	// it screws us up, so we have to start always with explicit
	// string
	char * next_s = s;

	while(t)
	{
		next_s = next_s + strlen(t) + 1; // 1 for the token separator

		if(*t == '!')
		{
			eType = PP_REVISION_FMT_CHANGE;
			t++;
		}
		else if(*t == '-')
		{
			eType = PP_REVISION_DELETION;
			t++; // so we do not need to deal with sign later
		}

		else
			eType = PP_REVISION_ADDITION; // this value is only
										  // temporary because this
										  // could equally be addition
										  // + format

		cl_brace = strchr(t, '}');
		op_brace = strchr(t, '{');

		if(!cl_brace || !op_brace)
		{
			// no props
			if(eType == PP_REVISION_FMT_CHANGE)
			{
				// malformed token, move onto the next one
				UT_DEBUGMSG(("PP_RevisionAttr::_init: invalid ! token [%s]\n",t));
				goto skip_this_token;
			}
			pProps = NULL;
			pAttrs = NULL;
		}
		else
		{
			// OK this is a case where we have some props, i.e., it
			// must be either fmt change or addition
			if(eType == PP_REVISION_DELETION)
			{
				// malformed token, move onto the next one
				UT_DEBUGMSG(("PP_RevisionAttr::_init: invalid - token [%s]\n",t));
				goto skip_this_token;
			}

			// insert null as needed to be able to parse the id and props
			*op_brace = 0;
			*cl_brace = 0;
			pProps = op_brace+1;

			// now see if the props are followed by attributes
			if(*(cl_brace + 1) == '{')
			{
				cl_brace2 = strchr(cl_brace + 2,'}');
				if(cl_brace2)
				{
					pAttrs = cl_brace + 2;
					*cl_brace2 = 0;
				}
				else
				{
					UT_DEBUGMSG(( "PP_RevisionAttr::_init: invalid token - [%s]\n", t ));
					pAttrs = NULL;
				}
			}
			else
				pAttrs = NULL;

			if(eType == PP_REVISION_ADDITION)
				eType = PP_REVISION_ADDITION_AND_FMT;
		}

		// now we can retrieve the id
		iId = atol(t);

		{
			PP_Revision * pRevision = new PP_Revision((UT_uint32)iId, eType, pProps, pAttrs);

			m_vRev.addItem((void*)pRevision);
		}
		
	skip_this_token:
		if(next_s < end_s)
			t = strtok(next_s,",");
		else
			t = NULL;
	}

	FREEP(s);
	m_bDirty = true;
	m_iSuperfluous = 0;
	m_pLastRevision = NULL;
}

/*!
    changes the type of revision with id iId to eType; if revision
    with that id is not present, returns false
 */
bool PP_RevisionAttr::changeRevisionType(UT_uint32 iId, PP_RevisionType eType)
{
	for(UT_uint32 i = 0; i < m_vRev.getItemCount(); i++)
	{
		PP_Revision * r = (PP_Revision *)m_vRev.getNthItem(i);

		if(iId == r->getId())
		{
			r->setType(eType);
			m_bDirty = true;
			return true;
		}
	}

	return false;
}

bool PP_RevisionAttr::changeRevisionId(UT_uint32 iOldId, UT_uint32 iNewId)
{
	UT_return_val_if_fail(iNewId >= iOldId, false);
	
	for(UT_uint32 i = 0; i < m_vRev.getItemCount(); i++)
	{
		PP_Revision * r = (PP_Revision *)m_vRev.getNthItem(i);

		if(iOldId == r->getId())
		{
			r->setId(iNewId);
			m_bDirty = true;
			return true;
		}
	}

	return false;
}

/*!
    this function removes any revisions that no-longer contribute to the cumulative effect
    it is used in full-history mode when transfering attrs and props from the revision attribute
    into the main attrs and props
*/
void PP_RevisionAttr::pruneForCumulativeResult(PD_Document * pDoc)
{
	// first we are looking for any deletions which cancel anything below them
	bool bDelete = false;
	UT_sint32 i;
	if(m_vRev.getItemCount() == 0)
	{
		return;
	}
	
	for(i = m_vRev.getItemCount()-1; i >=0; --i)
	{
		PP_Revision * r = (PP_Revision *)m_vRev.getNthItem(i);
		
		if(!bDelete && r->getType() == PP_REVISION_DELETION)
		{
			bDelete = true;
			continue; // we do not want the top revision deleted
		}
		

		if(bDelete)
		{
			delete r;
			m_vRev.deleteNthItem(i);
		}
	}

	// now we merge props and attrs in what is left
	if(m_vRev.getItemCount() == 0)
	{
		return;
	}

	PP_Revision * r0 = (PP_Revision *)m_vRev.getNthItem(0);
	UT_return_if_fail(r0);
	
	for(i = 1; i < (UT_sint32)m_vRev.getItemCount(); ++i)
	{
		PP_Revision * r = (PP_Revision *)m_vRev.getNthItem(i);
		UT_return_if_fail( r );
		
		r0->setProperties(r->getProperties());
		r0->setAttributes(r->getAttributes());

		delete r;
		m_vRev.deleteNthItem(i);
		--i;
	}

    // explode the style if present
	if(pDoc)
		r0->explodeStyle(pDoc);

#if 0
	// I do not think we should do this -- the emptiness indicates that the props and
	// attributes should be removed from the format; if we remove them, we irreversibly
	// lose this information
	
	// get rid of any empty props and attrs
	r0->prune();
#endif
	
	// finally, remove the revision attribute if present
	const XML_Char * v;
	if(r0->getAttribute("revision", v))
		r0->setAttribute("revision", NULL);

	UT_ASSERT_HARMLESS( m_vRev.getItemCount() == 1 );
}


/*! return highest revision associated with this revision attribute
    that has ID at most equal to id; the returned PP_Revision can be
    used to determine whether this text should be displayed or hidden
    in revision with the original id

    \param UT_uint32 id : the id of this revision
    \param PP_Revision ** ppR: location where to store pointer to one
                               of the special revisions in case return
                               value is NULL
                              
    \return : pointer to PP_Revision, or NULL if the revision
    attribute should be ignored for the present level
*/

// these are special instances of PP_Revision that are used to in the
// following function to handle special cases
static const PP_Revision s_del(0, PP_REVISION_DELETION, (XML_Char*)0, (XML_Char*)0);
static const PP_Revision s_add(0, PP_REVISION_ADDITION, (XML_Char*)0, (XML_Char*)0);

const PP_Revision *  PP_RevisionAttr::getGreatestLesserOrEqualRevision(UT_uint32 id,
																	   const PP_Revision ** ppR)
{
	if(ppR)
		*ppR = NULL;
	
	if(id == 0)
		return getLastRevision();

	const PP_Revision *r = NULL; // this will be the revision we are looking for
	UT_uint32 r_id = 0;

	const PP_Revision *m = NULL; // this will be the lowest revision present
	UT_uint32 m_id = 0xFFFF;

	for(UT_uint32 i = 0; i < m_vRev.getItemCount(); i++)
	{
		const PP_Revision * t = (const PP_Revision *) m_vRev.getNthItem(i);
		UT_uint32 t_id = t->getId();

		// the special case speedup - if we hit our id, then we can return immediately
		if(t_id == id)
			return t;

		if(t_id < m_id)
		{
			m = t;
			m_id = t_id;
		}

		if((t_id < id) && (t_id > r_id))
		{
			r = t;
			r_id = t_id;
		}
	}

	// now that we have the biggest revision with ID lesser or equal
	// id, we have to deal with the special case when this is NULL
	// i.e., this fragment only figures in revisions > id; the problem
	// with NULL is that it is visible if the smallest revision ID is
	// negative, and hidden in the opposite case -- we use the special
	// static variables s_del and s_add to indicate what should
	// be done

	if(r == NULL && ppR)
	{
		if(!m)
		{
			//UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN);
			// this happens when there was no revision attribute
			return NULL;
		}

		if(m->getType() == PP_REVISION_DELETION)
			*ppR = &s_del;
		else if((m->getType() == PP_REVISION_ADDITION)
				||(m->getType() == PP_REVISION_ADDITION_AND_FMT))
			*ppR = &s_add;
		else // the initial revision was fmt change, so ignore it
			*ppR = NULL;
	}

	return r;
}

const PP_Revision * PP_RevisionAttr::getLowestGreaterOrEqualRevision(UT_uint32 id)
{
	if(id == 0)
		return NULL;

	const PP_Revision *r = NULL; // this will be the revision we are looking for
	UT_uint32 r_id = PD_MAX_REVISION;

	for(UT_uint32 i = 0; i < m_vRev.getItemCount(); i++)
	{
		const PP_Revision * t = (const PP_Revision *) m_vRev.getNthItem(i);
		UT_uint32 t_id = t->getId();

		// the special case speedup - if we hit our id, then we can return immediately
		if(t_id == id)
			return t;

		if((t_id > id) && (t_id < r_id))
		{
			r = t;
			r_id = t_id;
		}
	}

	return r;
}


/*! finds the highest revision number in this attribute
 */
const PP_Revision * PP_RevisionAttr::getLastRevision()
{
	// since this is rather involved, we will chache the result and
	// use the cache if it is uptodate
	if(m_pLastRevision)
		return m_pLastRevision;

	//const PP_Revision * r = NULL;
	UT_uint32 r_id = 0;

	for(UT_uint32 i = 0; i < m_vRev.getItemCount(); i++)
	{
		const PP_Revision * t = (const PP_Revision *)m_vRev.getNthItem(i);
		UT_uint32 t_id = t->getId();

		if(t_id > r_id)
		{
			r_id = t_id;
			m_pLastRevision = t;
		}
	}

	// UT_ASSERT_HARMLESS( m_pLastRevision );
	// it is legal for this to be NULL -- it happens when the revision was pruned for
	// cumulative effect and the last revision was a deletion.
	return m_pLastRevision;
}

/*!
   find revision with id == iId; if revision is not found minId
   contains the smallest id in this set greater than iId; if return value is and minId
   is PD_MAX_REVISION then there are revisions preset
*/
const PP_Revision * PP_RevisionAttr::getRevisionWithId(UT_uint32 iId, UT_uint32 &minId)
{
	minId = PD_MAX_REVISION;

	for(UT_uint32 i = 0; i < m_vRev.getItemCount(); i++)
	{
		const PP_Revision * t = (const PP_Revision *)m_vRev.getNthItem(i);
		UT_uint32 t_id = t->getId();

		if(t_id == iId)
		{
			return t;
		}

		if(minId > t_id && t_id > iId)
			minId = t_id;
	}

	return NULL;
}


/*! given revision level id, this function returns true if given
    segment of text is to be visible, false if it is to be hidden
*/
bool PP_RevisionAttr::isVisible(UT_uint32 id)
{
	if(id == 0)
	{
		// id 0 means show all revisions
		return true;
	}

	const PP_Revision * pSpecial;
	const PP_Revision * pR = getGreatestLesserOrEqualRevision(id, &pSpecial);

	if(pR)
	{
		// found compliant revision ...
		return true;
	}
	

	if(pSpecial)
	{
		// pSpecial is of the same type as the revision with the
		// lowest id
		PP_RevisionType eType = pSpecial->getType();

		// deletions and fmt changes can be ignored; insertions need
		// to be hidden
		return ((eType != PP_REVISION_ADDITION) && (eType == PP_REVISION_ADDITION_AND_FMT));
	}

	// the revision with the lowest id is a change of format, this
	// text has to remain visible
	return true;
}


/*! adds id to the revision vector handling the special cases where id
    is already present in this attribute.
*/
void PP_RevisionAttr::addRevision(UT_uint32 iId, PP_RevisionType eType,
								  const XML_Char ** pAttrs, const XML_Char ** pProps)
{
	UT_uint32 i;

	for(i = 0; i < m_vRev.getItemCount(); i++)
	{
		PP_Revision * r = (PP_Revision*) m_vRev.getNthItem(i);
		UT_uint32 r_id = r->getId();
		PP_RevisionType r_type = r->getType();

		if(iId != r_id)
			continue;
		
		if(eType != r_type)
		{
			// we are trying to add a revision id already in the vector
			// but of a different -- this is legal, i.e., the
			// editor just changed his mind
			// we need to make distinction between different cases

			if((eType == PP_REVISION_DELETION) && (   r_type == PP_REVISION_ADDITION
												   || r_type == PP_REVISION_ADDITION_AND_FMT))
			{
				// the editor originally inserted a new segment of
				// text but now wants it out; we cannot just remove
				// the id, because the original operation resulted in
				// a new fragment in the piece table; rather we will
				// mark this with '-' and will remember the superfluous
				// id, so if queried later we can work out if this
				// whole fragment should in fact go

				delete r;
				m_vRev.deleteNthItem(i);

				m_iSuperfluous = iId;

				const PP_Revision * pRevision = new PP_Revision(iId, eType, (XML_Char*)0, (XML_Char*)0);
				m_vRev.addItem((void*)pRevision);
			}
			else if((eType == PP_REVISION_ADDITION) && (r_type == PP_REVISION_DELETION))
			{
				// in the opposite case, when the editor originally
				// marked the text for removal and now wants it back,
				// we just remove the attribute (which we have done)
				// this also happens when we have been left with a
				// superfluous deletion id in the vector; if that is
				// the case we need to reset m_iSuperfluous, since
				// this fragment can no more be superfluous

				delete r;
				m_vRev.deleteNthItem(i);

				if(m_iSuperfluous == iId)
				{
					// the editor has had another change of heart
					m_iSuperfluous  = 0;
				}
			}
			else if((eType == PP_REVISION_DELETION) && (r_type == PP_REVISION_FMT_CHANGE))
			{
				// this is the case when the editor changed
				// formatting, but now wants the whole fragment out
				// instead -- we simly replace the old revision with
				// the new, since the original action did not result
				// in inserting new text

				delete r;
				m_vRev.deleteNthItem(i);

				const PP_Revision * pRevision = new PP_Revision(iId, eType, (XML_Char*)0, (XML_Char*)0);
				m_vRev.addItem((void*)pRevision);
			}
			else if((eType == PP_REVISION_FMT_CHANGE) && (r_type == PP_REVISION_DELETION))
			{
				// originally the editor marked the text for deletion,
				// but now he just wants a format change, in this case
				// we just replace the old revision with the new

				delete r;
				m_vRev.deleteNthItem(i);

				const PP_Revision * pRevision = new PP_Revision(iId, eType, pProps, pAttrs);
				
				m_vRev.addItem((void*)pRevision);
			}
			else if((eType == PP_REVISION_FMT_CHANGE) && (r_type == PP_REVISION_ADDITION))
			{
				// the editor first added this fragment, and now wants
				// to apply a format change on the top of that
				// so we will keep the old revision record, but need
				// to merge any existing props in the revision with
				// the new ones
				r->setProperties(pProps);
				r->setAttributes(pAttrs);
			}
			else if((eType == PP_REVISION_FMT_CHANGE) && (r_type == PP_REVISION_ADDITION_AND_FMT))
			{
				// addition with a fmt change, just add our changes to it
				r->setProperties(pProps);
				r->setAttributes(pAttrs);
			}

			m_bDirty = true;
			m_pLastRevision = NULL;
			return;
		}
		else //(eType == r_type)
		{
			// we are trying to add a type already in the vector; this is legal but makes sense only
			// if both are fmt changes
			if(!((eType == PP_REVISION_FMT_CHANGE) && (r_type == PP_REVISION_FMT_CHANGE)))
				return;
			
			r->setProperties(pProps);
			r->setAttributes(pAttrs);
			
			m_bDirty = true;
			m_pLastRevision = NULL;
			return;
		}
	}

	// if we got here then the item is not in our vector so add it
	const PP_Revision * pRevision = new PP_Revision(iId, eType, pProps, pAttrs);
	
	m_vRev.addItem((void*)pRevision);
	m_bDirty = true;
	m_pLastRevision = NULL;
}

/*! removes id from this revision, respecting the sign, i.e., it will
  not remove -5 if given 5
 */
void PP_RevisionAttr::removeRevisionIdWithType(UT_uint32 iId, PP_RevisionType eType)
{
	for(UT_uint32 i = 0; i < m_vRev.getItemCount(); i++)
	{
		PP_Revision * r = (PP_Revision *)m_vRev.getNthItem(i);

		if((iId == r->getId()) && (eType == r->getType()))
		{
			delete r;
			m_vRev.deleteNthItem(i);
			m_bDirty = true;
			m_pLastRevision = NULL;
			return;
		}
	}
}

/*! removes id from the attribute disregarding sign, i.e.,
    if given 5 it will remove both -5 and +5
*/
void PP_RevisionAttr::removeRevisionIdTypeless(UT_uint32 iId)
{
	for(UT_uint32 i = 0; i < m_vRev.getItemCount(); i++)
	{
		PP_Revision * r = (PP_Revision *)m_vRev.getNthItem(i);

		if(iId == r->getId())
		{
			delete r;
			m_vRev.deleteNthItem(i);
			m_bDirty = true;
			m_pLastRevision = NULL;
			return;
		}
	}
}

/*! removes pRev unconditionally from the attribute
*/
void PP_RevisionAttr::removeRevision(const PP_Revision * pRev)
{
	for(UT_uint32 i = 0; i < m_vRev.getItemCount(); i++)
	{
		PP_Revision * r = (PP_Revision *)m_vRev.getNthItem(i);

		if(r == pRev)
		{
			delete r;
			m_vRev.deleteNthItem(i);
			m_bDirty = true;
			m_pLastRevision = NULL;
			return;
		}
	}
}


/*! removes all IDs from the attribute whose value is lesser or
    equal the given id
*/
void PP_RevisionAttr::removeAllLesserOrEqualIds(UT_uint32 iId)
{
	for(UT_sint32 i = 0; i < (UT_sint32)m_vRev.getItemCount(); i++)
	{
		PP_Revision * r = (PP_Revision *)m_vRev.getNthItem(i);

		if(iId >= r->getId())
		{
			delete r;
			m_vRev.deleteNthItem(i);
			--i; // the vector just shrunk
		}
	}

	m_bDirty = true;
	m_pLastRevision = NULL;
}

/*! removes all IDs from the attribute whose value is higher or
    equal the given id
*/
void PP_RevisionAttr::removeAllHigherOrEqualIds(UT_uint32 iId)
{
	for(UT_sint32 i = 0; i < (UT_sint32)m_vRev.getItemCount(); i++)
	{
		PP_Revision * r = (PP_Revision *)m_vRev.getNthItem(i);

		if(iId <= r->getId())
		{
			delete r;
			m_vRev.deleteNthItem(i);
			--i; // the vector just shrunk
		}
	}

	m_bDirty = true;
	m_pLastRevision = NULL;
}


/*! create XML string from our vector
 */
void PP_RevisionAttr::_refreshString()
{
	char buf[30];
	m_sXMLstring.clear();
	UT_uint32 iCount = m_vRev.getItemCount();

	for(UT_uint32 i = 0; i < iCount; i++)
	{
		PP_Revision * r = (PP_Revision *)m_vRev.getNthItem(i);
		PP_RevisionType r_type = r->getType();

		if(r_type == PP_REVISION_FMT_CHANGE)
			m_sXMLstring += "!";

		// print the id with appropriate sign
		sprintf(buf,"%d",r->getId()* ((r_type == PP_REVISION_DELETION)?-1:1));
		m_sXMLstring += buf;

		if(r_type != PP_REVISION_DELETION)
		{
			// if we have no props but have attribs, we have to issue empty braces so as not to
			// confuse attribs with props
			if(r->hasProperties() || r->hasAttributes())
				m_sXMLstring += "{";
			
			if(r->hasProperties())
				m_sXMLstring += r->getPropsString();
			
			if(r->hasProperties() || r->hasAttributes())
				m_sXMLstring += "}";
			
			if(r->hasAttributes())
			{
				m_sXMLstring += "{";
				m_sXMLstring += r->getAttrsString();
				m_sXMLstring += "}";
			}
		};

		if(i != iCount - 1)
		{
			//not the last itteration, append ','
			m_sXMLstring += ",";
		}

	}
	m_bDirty = false;
}



/*! get an XML_Char string representation of this revision
 */
const XML_Char * PP_RevisionAttr::getXMLstring()
{
	if(m_bDirty)
		_refreshString();

	return (const XML_Char*) m_sXMLstring.c_str();
}

/*! returns true if the fragment marked by this attribute is
    superfluous, i.e, it was created in the process of the present
    revision but the editor has later changed his/her mind and decided
    it should go away
*/
bool PP_RevisionAttr::isFragmentSuperfluous() const
{
	// the fragment is superfluous if the superfluous flag is set
	// and the fragment belongs only to a single revision level
	if(m_iSuperfluous != 0 && m_vRev.getItemCount() == 1)
	{
		UT_return_val_if_fail (((PP_Revision *)m_vRev.getNthItem(0))->getId() == m_iSuperfluous,false);
		return true;
	}
	else
		return false;
}

bool PP_RevisionAttr::operator == (const PP_RevisionAttr &op2) const
{
	for(UT_uint32 i = 0; i < m_vRev.getItemCount(); i++)
	{
		const PP_Revision * r1 = (const PP_Revision *) m_vRev.getNthItem(i);

		for(UT_uint32 j = 0; j < op2.m_vRev.getItemCount(); j++)
		{
			const PP_Revision * r2 = (const PP_Revision *) op2.m_vRev.getNthItem(j);

			if(!(*r1 == *r2))
				return false;
		}
	}
	return true;
}

/*! returns true if after revision iId this fragment carries revised
    property pName, the value of which will be stored in pValue; see
    notes on PP_Revision::hasProperty(...)
*/
bool PP_RevisionAttr::hasProperty(UT_uint32 iId, const XML_Char * pName, const XML_Char * &pValue)
{
	const PP_Revision * s;
	const PP_Revision * r = getGreatestLesserOrEqualRevision(iId, &s);

	if(r)
		return r->getProperty(pName, pValue);

	return false;
}

/*! returns true if after the last revision this fragment carries revised
    property pName, the value of which will be stored in pValue; see
    notes on PP_Revision::hasProperty(...)
*/
bool PP_RevisionAttr::hasProperty(const XML_Char * pName, const XML_Char * &pValue)
{
	const PP_Revision * r = getLastRevision();
	return r->getProperty(pName, pValue);
}

/*! returns the type of cumulative revision up to iId represented by this attribute
 */
PP_RevisionType PP_RevisionAttr::getType(UT_uint32 iId)
{
	const PP_Revision * s;
	const PP_Revision * r = getGreatestLesserOrEqualRevision(iId,&s);

	if(!r)
	{
		// HACK need to return something
		return PP_REVISION_FMT_CHANGE;
	}
	
	return r->getType();
}

/*! returns the type of overall cumulative revision represented by this attribute
 */
PP_RevisionType PP_RevisionAttr::getType()
{
	const PP_Revision * r = getLastRevision();
	return r->getType();
}
