/* AbiWord
 * Copyright (C) 1998 AbiSource, Inc.
 * 
 * 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 "ut_types.h"
#include "ut_assert.h"
#include "ut_debugmsg.h"
#include "pt_Types.h"
#include "pt_VarSet.h"


pt_VarSet::pt_VarSet()
{
	m_currentVarSet = 0;
	m_bInitialized = UT_FALSE;
}

pt_VarSet::~pt_VarSet()
{
}

UT_Bool pt_VarSet::_finishConstruction(void)
{
	// finish the construction -- C++ doesn't let us return failures
	// in a constructor, so we do the malloc's here.
		
	// create a default A/P as entry zero in each AP table.

	PT_AttrPropIndex foo;
		
	if (   !m_tableAttrProp[0].createAP(&foo)
		|| !m_tableAttrProp[1].createAP(&foo))
		return UT_FALSE;
	((PP_AttrProp *)getAP(_makeAPIndex(0,0)))->markReadOnly();
	((PP_AttrProp *)getAP(_makeAPIndex(1,0)))->markReadOnly();
	
	m_bInitialized = UT_TRUE;
	return UT_TRUE;
}

void pt_VarSet::setPieceTableState(PTState pts)
{
	if (pts == PTS_Editing)
		m_currentVarSet = 1;
}

UT_Bool pt_VarSet::appendBuf(const UT_UCSChar * pBuf, UT_uint32 length, PT_BufIndex * pbi)
{
	UT_uint32 bufOffset = m_buffer[m_currentVarSet].getLength();
	if (m_buffer[m_currentVarSet].ins(bufOffset,pBuf,length))
	{
		*pbi = _makeBufIndex(m_currentVarSet,bufOffset);
		return UT_TRUE;
	}

	UT_DEBUGMSG(("could not appendBuf\n"));
	return UT_FALSE;
}

UT_Bool pt_VarSet::storeAP(const XML_Char ** attributes, PT_AttrPropIndex * papi)
{
	if (!m_bInitialized)
		if (!_finishConstruction())
			return UT_FALSE;

	// create an AP for this set of attributes -- iff unique.
	// return the index for the new one (or the one we found).

	if (!attributes || !*attributes)
	{
		// we preloaded the zeroth cell (of both tables)
		// with empty attributes.  return index back to
		// the first table.
		*papi = _makeAPIndex(0,0);
		return UT_TRUE;
	}

	// This is a bit expensive, but it can't be helped.
	// We synthesize a (possibly temporary) AP to organize
	// and store the values given in the args.  We then use
	// it to find one that matches it.  If we find it, we
	// destroy the one we just created.  Otherwise, we add
	// the new one to the correct table and return it.

	PP_AttrProp * pTemp = new PP_AttrProp();
	if (!pTemp)
		return UT_FALSE;

	if (pTemp->setAttributes(attributes))
	{
		pTemp->markReadOnly();
		return addIfUniqueAP(pTemp,papi);
	}
	
	delete pTemp;
	return UT_FALSE;
}

UT_Bool pt_VarSet::storeAP(const UT_Vector * pVecAttributes, PT_AttrPropIndex * papi)
{
	if (!m_bInitialized)
		if (!_finishConstruction())
			return UT_FALSE;

	// create an AP for this set of attributes -- iff unique.
	// return the index for the new one (or the one we found).
	
	if (!pVecAttributes || pVecAttributes->getItemCount()==0)
	{
		// we preloaded the zeroth cell (of both tables)
		// with empty attributes.  return index back to
		// the first table.
		*papi = _makeAPIndex(0,0);
		return UT_TRUE;
	}

	// This is a bit expensive, but it can't be helped.
	// We synthesize a (possibly temporary) AP to organize
	// and store the values given in the args.  We then use
	// it to find one that matches it.  If we find it, we
	// destroy the one we just created.  Otherwise, we add
	// the new one to the correct table and return it.

	PP_AttrProp * pTemp = new PP_AttrProp();
	if (!pTemp)
		return UT_FALSE;
	
	if (pTemp->setAttributes(pVecAttributes))
	{
		pTemp->markReadOnly();
		return addIfUniqueAP(pTemp,papi);
	}

	delete pTemp;
	return UT_FALSE;
}

const UT_UCSChar * pt_VarSet::getPointer(PT_BufIndex bi) const
{
	return m_buffer[_varsetFromBufIndex(bi)].getPointer(_subscriptFromBufIndex(bi));
}

PT_BufIndex pt_VarSet::getBufIndex(PT_BufIndex bi, UT_uint32 offset) const
{
	return _makeBufIndex(_varsetFromBufIndex(bi),
						 _subscriptFromBufIndex(bi)+offset);
}

UT_Bool pt_VarSet::isContiguous(PT_BufIndex bi, UT_uint32 length, PT_BufIndex bi2) const
{
	return ((getPointer(bi)+length) == getPointer(bi2));
}

const PP_AttrProp * pt_VarSet::getAP(PT_AttrPropIndex api) const
{
	return m_tableAttrProp[_varsetFromAPIndex(api)].getAP(_subscriptFromAPIndex(api));
}

UT_uint32 pt_VarSet::_subscriptFromBufIndex(PT_BufIndex bi) const
{
	return (bi & 0x7fffffff);
}

UT_uint32 pt_VarSet::_subscriptFromAPIndex(PT_AttrPropIndex api) const
{
	return (api & 0x7fffffff);
}

UT_uint32 pt_VarSet::_varsetFromBufIndex(PT_BufIndex bi) const
{
	return (bi >> 31);
}

UT_uint32 pt_VarSet::_varsetFromAPIndex(PT_AttrPropIndex api) const
{
	return (api >> 31);
}

PT_BufIndex pt_VarSet::_makeBufIndex(UT_uint32 varset, UT_uint32 subscript) const
{
	return ((varset<<31)|subscript);
}

PT_AttrPropIndex pt_VarSet::_makeAPIndex(UT_uint32 varset, UT_uint32 subscript) const
{
	return ((varset<<31)|subscript);
}

UT_Bool pt_VarSet::mergeAP(PTChangeFmt ptc, PT_AttrPropIndex apiOld,
						   const XML_Char ** attributes, const XML_Char ** properties,
						   PT_AttrPropIndex * papiNew)
{
	// merge the given attr/props with set referenced by apiOld
	// under the operator ptc giving a new set to be returned in
	// papiNew.  if the resulting merger is the same as the set
	// referenced in apiOld, just return it.
	// return UT_FALSE only if we had an error.

	const PP_AttrProp * papOld = getAP(apiOld);
	switch (ptc)
	{
	case PTC_AddFmt:
		{
			if (papOld->areAlreadyPresent(attributes,properties))
			{
				*papiNew = apiOld;
				return UT_TRUE;
			}

			// create a new AP that is the merger of the existing
			// one and the a/p given in the args.  we then use it
			// to find an existing match or use the new one.
			
			PP_AttrProp * pNew = papOld->cloneWithReplacements(attributes,properties, UT_FALSE);
			if (!pNew)
				return UT_FALSE;

			pNew->markReadOnly();
			return addIfUniqueAP(pNew,papiNew);
		}

	case PTC_AddStyle:
		{
			if (!papOld->hasProperties() && 
				papOld->areAlreadyPresent(attributes,properties))
			{
				*papiNew = apiOld;
				return UT_TRUE;
			}

			// create a new AP that is the merger of the existing
			// one (with its props cleared) and the a/p given in 
			// the args.  we then use it to find an existing match 
			// or use the new one.
			
			PP_AttrProp * pNew = papOld->cloneWithReplacements(attributes,properties, UT_TRUE);
			if (!pNew)
				return UT_FALSE;

			pNew->markReadOnly();
			return addIfUniqueAP(pNew,papiNew);
		}

	case PTC_RemoveFmt:
		{
			if (!papOld->areAnyOfTheseNamesPresent(attributes,properties))
			{
				// none of the given attributes/properties are present in this set.
				// the remove has no effect.
				*papiNew = apiOld;
				return UT_TRUE;
			}

			// create a new AP that is the existing set minus the
			// ones given in the args.  we then use it to find an
			// existing match or use the new one.

			PP_AttrProp * pNew = papOld->cloneWithElimination(attributes,properties);
			if (!pNew)
				return UT_FALSE;

			pNew->markReadOnly();
			return addIfUniqueAP(pNew,papiNew);
		}
		
	default:
		UT_ASSERT(0);
		return UT_FALSE;
	}
}

UT_Bool pt_VarSet::addIfUniqueAP(PP_AttrProp * pAP, PT_AttrPropIndex * papi)
{
	// Add the AP to our tables iff it is unique.
	// If not unique, delete it and return the index
	// of the one that matches.  If it is unique, add
	// it and return the index where we added it.
	// return false if we have any errors.

	UT_ASSERT(pAP && papi);
	UT_uint32 subscript = 0;
	UT_uint32 table = 0;
	
	for (table=0; table<2; table++)
		if (m_tableAttrProp[table].findMatch(pAP,&subscript))
		{
			// use the one that we already have in the table.
			delete pAP;
			*papi = _makeAPIndex(table,subscript);
			return UT_TRUE;
		}

	// we did not find a match, so we store our new one.
	
	if (m_tableAttrProp[m_currentVarSet].addAP(pAP,&subscript))
	{
		*papi = _makeAPIndex(m_currentVarSet,subscript);
		return UT_TRUE;
	}
	
	// memory error of some kind.
	
	delete pAP;
	return UT_FALSE;
}
