//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES.
//
/*
 *	@doc	INTERNAL
 *
 *	@module	M_UNDO.C	|
 *
 *	Purpose:
 *		Implementation of the global mutli-undo stack
 *
 * 	Author:
 *		alexgo  3/25/95
 */

#include "_common.h"
#include "_m_undo.h"
#include "_edit.h"
#include "_disp.h"
#include "_urlsup.h"
#include "_antievt.h"

ASSERTDATA

//
// PUBLIC METHODS
//

/*
 *	CUndoStack::CUndoStack (ped)
 *
 *	@mfunc	Constructor
 */
CUndoStack::CUndoStack( 
	CTxtEdit *ped,		//@parm	the CTxtEdit parent
	DWORD & rdwLim,		//@parm the initial limit
	USFlags	flags )		//@parm flags for this undo stack
{
	TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::CUndoStack");

	_ped = ped;

	_prgActions = NULL;
	_index = 0;
	_dwLim = 0;

	//we should be creating an undo stack if there's nothing to
	//put in it!
	Assert(rdwLim);
	SetUndoLimit(rdwLim);

	if( flags & US_REDO )
	{
		_fRedo = TRUE;
	}
}

/*
 *	CUndoStack::~CUndoStack
 *
 *	@mfunc Destructor
 *
 *	@comm
 *		deletes any remaining anti-events.  The anti event dispenser
 *		should *not* clean up because of this!!
 */
CUndoStack::~CUndoStack()
{
	TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::~CUndoStack");

	// clear out any remaining antievents
	ClearAll();

	delete _prgActions;
}

/*
 *	CUndoStack::Destroy 
 *
 *	@mfunc
 *		deletes this instance
 */
void CUndoStack::Destroy()
{
	TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::Destroy");

	delete this;
}

/*
 * 	CUndoStack::SetUndoLimit (dwLim)
 *
 *	@mfunc
 *		allows the undo stack to be enlarged or reduced
 *
 *	@rdesc
 *		the size to which the stack is actually set.
 *
 *	@comm
 *		the algorithm we use is the following:	 <nl>
 *			try to allocate space for the requested size.
 *			if there's not enough memory then we try to recover
 *			with the largest block possible.
 *
 *			if the requested size is bigger than the default,
 *			and the current size is less than the default, go 
 *			ahead and try to allocate the default.
 *
 *			if that fails then just stick with the existing stack
 */
DWORD CUndoStack::SetUndoLimit( 
	DWORD dwLim )			//@parm	the new undo limit.  May not
							// be zero
{
	TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::SetUndoLimit");

	UndoAction *prgnew = NULL;

	// if the undo limit is zero, we should get rid of the entire
	// undo stack instead.

	Assert(dwLim);

	if( _fSingleLevelMode )
	{
		// if fSingleLevelMode is on, we can't be the redo stack
		Assert(_fRedo == FALSE);

		if( dwLim != 1 )
		{
			TRACEERRORSZ("Trying to grow/shrink the undo buffer while in"
				"single level mode");
			
			dwLim = 1;
		}
	}

	prgnew = new UndoAction[dwLim];

	if( prgnew )
	{
		TransferToNewBuffer(prgnew, dwLim);
	}
	else if( dwLim > DEFAULT_UNDO_SIZE && _dwLim < DEFAULT_UNDO_SIZE )
	{
		// we are trying to grow past the default but failed.  So
		// try to allocate the default
		prgnew = new UndoAction[DEFAULT_UNDO_SIZE];

		if( prgnew )
		{
			TransferToNewBuffer(prgnew, DEFAULT_UNDO_SIZE);
		}
	}
	
	// in either success or failure, _dwLim will be set correctly.	
	return _dwLim;
}

/*
 *	CUndoStack::GetUndoLimit 
 *
 *	@mfunc
 *		gets the current limit size
 *
 *	@rdesc	
 *		the current undo limit
 */
DWORD CUndoStack::GetUndoLimit()
{
	TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::GetUndoLimit");

	return _dwLim;
}

/*
 *	CUndoStack::PushAntiEvent (pae)
 *
 *	@mfunc
 *		adds an undoable event to the event stack
 *
 *	@rdesc	HRESULT
 *
 *	@comm
 *	Algorithm:
 *
 *		if merging is set, then we to merge the given anti-event
 *		list *into* the current list (assuming it's a typing
 *		undo action).
 *
 */
HRESULT CUndoStack::PushAntiEvent( 
	UNDONAMEID idName,		//@parm	name for this AE collection
	IAntiEvent *pae )		//@parm the AE collection
{
	TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::PushAntiEvent");

	// Fix for WinCEOS RAID #12736
	if (_ped->_DeleteBeforeConvert == 2) {
		_ped->_DeleteBeforeConvert = 0;
		idName = UID_TYPING;
		_fMerge = TRUE;
	}
	if (_ped->_DeleteBeforeConvert == 1) {
		_ped->_DeleteBeforeConvert = 2;
		idName = UID_TYPING;
	}

	// _index should be at next available position

	if( !_fMerge )
	{
		// clear out any existing event	
		if( _prgActions[_index].pae != NULL )
		{
			DestroyAEList(_prgActions[_index].pae);
			_prgActions[_index].pae = NULL;
		}

		if( _fRedo )
		{
			_ped->GetCallMgr()->SetNewRedo();
		}
		else
		{
			_ped->GetCallMgr()->SetNewUndo();
		}
	}

	if( _fMerge )
	{
		IAntiEvent *paetemp = pae, *paeNext;
		DWORD i = GetPrev();

		// if these asserts fail, then somebody did not call 
		// StopGroupTyping

		Assert(_prgActions[i].id == idName);
		Assert(idName == UID_TYPING);

		// put the existing anti-event chain onto the *end* of the
		// current one.

		while( (paeNext = paetemp->GetNext()) != NULL )
		{
			paetemp = paeNext;
		}

		paetemp->SetNext(_prgActions[i].pae);
		
		_index = i;

		// fall through!
	}
	else if( _fGroupTyping )
	{
		// in this case, we are *starting* a group typing session.
		// Any subsequent push'es of anti events should be merged
		_fMerge = TRUE;

		// fall through!!
	}

	_prgActions[_index].pae = pae;
	_prgActions[_index].id = idName;
	
	Next();


	return NOERROR;
}

/*
 *	CUndoStack::PopAndExecuteAntiEvent( void )
 *
 *	@mfunc
 *		Undo!  Takes the most recent anti-event and executes it
 *
 *	@rdesc	HRESULT from invoking the anti-events (AEs)
 */

HRESULT CUndoStack::PopAndExecuteAntiEvent( 
	DWORD dwDoToCookie)		//@parm if non-NULL, undo up to this point.
{
	TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::PopAndExecuteAntiEvent");

	HRESULT hresult = NOERROR;
	IAntiEvent *pae, *paeDoTo;
	DWORD i, j;
	CCallMgr *pcallmgr = _ped->GetCallMgr();

	// we need to check to see if there are any non-empty undo builders
	// higher on the stack.  In this case, we have been re-entered
	if( pcallmgr->IsReEntered() )
	{
		IUndoBuilder *publdr;

		// there are two cases to handle: we are invoking redo or we
		// are invoking undo.  If we are invoking undo and there are 
		// existing undo actions in the undo builder, then simply commit
		// those actions and undo them.  We can assert in this case
		// that the redo stack is empty.
		//
		// In the second case if we are invoking redo while there are
		// undo actions in progress, simply cancel the call.  When the
		// undo actions are added, they will clear the redo stack.
		// 
		// We never need to check for a redo builder as that _only_
		// gets created in this routine and it's use is carefully guarded.


		publdr = (CGenUndoBuilder *)pcallmgr->GetComponent(COMP_UNDOBUILDER);

		
		// commit the anti-events to this undo stack, so that we will simply
		// undo them first.
		if( publdr )
		{			
			TRACEWARNSZ("Undo/Redo Invoked with uncommitted anti-events");
			TRACEWARNSZ("		Recovering....");

			if( _fRedo )
			{
				// if we are the redo stack, simply fail the redo call
				return NOERROR;
			}
			else
			{
				// just commit the anti-events and the routine below
				// will take of the rest.
				publdr->Done();
			}
		}
	}

	// if we are in single level mode, check to see if our current buffer is
	// empty.  If so, simply delegate to the redo stack if it exists.  We only
	// support this mode for dwDoToCookies being NULL.  Note that we can't call
	// CanUndo here as it will consider the redo stack as well

	if( _fSingleLevelMode && !_prgActions[GetPrev()].pae )
	{
		Assert(_fRedo == FALSE);
		Assert(dwDoToCookie == 0);

		if( _ped->GetRedoMgr() )
		{
			return _ped->GetRedoMgr()->PopAndExecuteAntiEvent(0);
		}
		// nothing to redo && nothing to do here; don't bother continuing	
		return NOERROR;
	}

	// this next bit of logic is tricky.  What is says is create
	// an undo builder for the stack *opposite* of the current one
	// (namely, undo actions go on the redo stack and vice versa).
	// Also, if we are the redo stack, then we don't want to flush
	// the redo stack as anti-events are added to the undo stack.

	CGenUndoBuilder undobldr(_ped, 
					(!_fRedo ? UB_REDO : UB_DONTFLUSHREDO) | UB_AUTOCOMMIT);
					
	// obviously, we can't be grouping typing if we're undoing!
	StopGroupTyping();

	// _index by default points to the next available slot
	// so we need to back up to the previous one.
	Prev();

	// do some verification on the cookie--make sure it's one
	// of ours
	paeDoTo = (IAntiEvent *)dwDoToCookie;

	if( paeDoTo != NULL )
	{
		for( i = 0, j = _index; i < _dwLim; i++ )
		{
			if( IsCookieInList(_prgActions[j].pae, (IAntiEvent *)paeDoTo) )
			{
				paeDoTo = _prgActions[j].pae;
				break;
			}
			// go backwards through the ring buffer; typically
			// paeDoTo will be "close" to the top
			
			if( !j )
			{
				j = _dwLim - 1;
			}
			else
			{
				j--;
			}
		}
		
		if( i == _dwLim )
		{
			TRACEERRORSZ("Invalid Cookie passed into Undo; cookie ignored");
			hresult = E_INVALIDARG;
			paeDoTo = NULL;
		}
	}
	else
	{
		paeDoTo = _prgActions[_index].pae;
	}

	undobldr.SetNameID(_prgActions[_index].id);


	while( paeDoTo )
	{
		CUndoStackGuard guard(_ped);

		pae = _prgActions[_index].pae;
		Assert(pae);

		// fixup our state _before_ calling Undo, so 
		// that we can handle being re-entered.
		_prgActions[_index].pae = NULL;

		hresult = guard.SafeUndo(pae, &undobldr);

		DestroyAEList(pae);

		if( pae == paeDoTo || guard.WasReEntered() )
		{
			paeDoTo = NULL;
		}
		
		Prev();
	}

	// put _index at the next unused slot
	Next();

	return hresult;
}

/* 
 *	CUndoStack::GetNameIDFromTopAE
 *
 *	@mfunc
 *		retrieves the name of the most recent undo-able operation
 *
 *	@rdesc	the name ID of the most recent collection of anti-events
 */
UNDONAMEID CUndoStack::GetNameIDFromAE( 
	DWORD dwAECookie)		//@parm the anti-event whose name is desired;
							// 0 for the the top
{
	IAntiEvent *pae = (IAntiEvent *)dwAECookie;
	DWORD	i, j = GetPrev();	// _index by default points to the next available
							// slot

	TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::GetNameIDFromTopAE");

	if( pae == NULL )
	{
		pae = _prgActions[j].pae;
	}

	if( _fSingleLevelMode && !pae)
	{
		// if fSingleLevelMode is on, we can't be the redo stack
		Assert(_fRedo == FALSE);

		// if pae is NULL, our answer may be on the redo stack.  Note that
		// we if somebody tries to pass in a cookie while in SingleLevelMode,
		// they won't be able to get actions off the redo stack.
		if( _ped->GetRedoMgr() )
		{
			return _ped->GetRedoMgr()->GetNameIDFromAE(0);
		}
	}		

	for( i = 0; i < _dwLim; i++ )
	{
		if( _prgActions[j].pae == pae )
		{
			return _prgActions[j].id;
		}

		if( j == 0 )
		{
			j = _dwLim - 1;
		}
		else
		{
			j--;
		}
	}

	return UID_UNKNOWN;
}

/*
 *	CUndoStack::GetMergeAntiEvent (void)
 *
 *	@mfunc	If we are in merge typing mode, then return the topmost
 *			anti-event
 *
 *	@rdesc	NULL or the current AntiEvent if in merge mode
 */
IAntiEvent *CUndoStack::GetMergeAntiEvent(void)
{
	TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::GetMergeAntiEvent");

	DWORD i;

	if( _fMerge == TRUE )
	{
		i = GetPrev();	// _index by default points to the next available
						// slot

		Assert(_prgActions[i].pae);		//can't be in merge anti event mode
										//if there is no anti-event to merge
										//with!!!

		return _prgActions[i].pae;
	}
	return NULL;
}

/*
 *	CUndoStack::GetTopAECookie
 *
 *	@mfunc	Returns a cookie to the topmost anti-event.
 *
 *	@rdesc	A cookie value.  Note that this cookie is just the anti-event
 *			pointer, but clients shouldn't really know that.
 */		
 DWORD CUndoStack::GetTopAECookie( void )
 {
 	TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::GetTopAECookie");

	DWORD i = GetPrev();

	return (DWORD)_prgActions[i].pae;
}


/*
 *	CUndoStack::ClearAll ()
 *
 *	@mfunc
 *		removes any anti-events that are currently in the undo stack
 */

void CUndoStack::ClearAll( void )
{
	TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::ClearAll");

	DWORD i;	 
 	for( i = 0; i < _dwLim; i++ )
	{
		if( _prgActions[i].pae != NULL )
		{
			DestroyAEList(_prgActions[i].pae);
			_prgActions[i].pae = NULL;
		}
	}

	// just in case we've been grouping typing; clear the
	// state.
	StopGroupTyping();
}

/*
 *	CUndoStack::CanUndo
 *
 *	@mfunc
 *		indicates whether or not can undo operation can be performed
 *		(in other words, are there any anti-events in our buffer)
 *
 *	@rdesc
 *		TRUE	-- anti-events exist 	<nl>
 *		FALSE 	-- no anti-events		<nl>
 */
BOOL CUndoStack::CanUndo()
{
	TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::CanUndo");

	DWORD i = GetPrev();	// _index by default points to the next available
							// slot

	if( _prgActions[i].pae != NULL )
	{
		return TRUE;
	}
	else if( _fSingleLevelMode )
	{
		// if fSingleLevelMode is on, we can't be the redo stack
		Assert(_fRedo == FALSE);

		// if we are in single level mode, we are the undo stack.  Check to
		// see if the redo stack can do something here.
		if( _ped->GetRedoMgr() )
		{
			return _ped->GetRedoMgr()->CanUndo();
		}
	}

	return FALSE;
}

/*
 *	CUndoStack::StartGroupTyping
 *
 *	@mfunc
 *		TOGGLES the group typing flag on.  If fGroupTyping is set, then
 *		all *typing* events will be merged together
 *
 *	@comm
 *	Algorithm:
 *
 *		There are three interesting states:	<nl>
 *			-no group merge; every action just gets pushed onto the stack <nl>
 *			-group merge started; the first action is pushed onto the stack<nl>
 *			-group merge in progress; every action (as long as it's "typing")
 *			is merged into the prior state	<nl>
 *
 *		See the state diagram in the implemenation doc for more details
 */

void CUndoStack::StartGroupTyping()
{
	TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::StartGroupTyping");

	if( _fGroupTyping == TRUE )
	{
		_fMerge = TRUE;
	}
	else
	{
		Assert(_fMerge == FALSE);
		_fGroupTyping = TRUE;
	}
}

/*
 *	CUndoStack::StopGroupTyping
 *
 *	@mfunc
 *		TOGGLES the group typing flag off.  If fGroupTyping is not set,
 *		then no merging of typing anti-events will be done
 */

void CUndoStack::StopGroupTyping()
{
	TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::StopGroupTyping");

	_fGroupTyping = FALSE;
	_fMerge = FALSE;
}

/*
 *	CUndoStack::EnableSingleLevelMode
 *
 *	@mfunc	Turns on single level undo mode; in this mode, we behave just like
 *			RichEdit 1.0 w.r.t. to Undo.
 *
 *	@comm	This special mode means that undo is 1 level deep and everything 
 *			is accessed via UNDO messages.  Thus, instead of redo to undo an 
 *			undo action, you simply use another undo message. 
 *
 *	@devnote	This call is _ONLY_ allowed for the UndoStack; the redo 
 *			stack simply tags along.  Note that caller is responsible for
 *			ensuring that we are in an empty state.
 */
HRESULT CUndoStack::EnableSingleLevelMode()
{
	Assert(_ped->GetRedoMgr() == NULL || 
		_ped->GetRedoMgr()->CanUndo() == FALSE);
	Assert(CanUndo() == FALSE);
	Assert(_fRedo == FALSE);

	_fSingleLevelMode = TRUE;

	// for single level undo mode, it is very important to get
	// just 1 entry in the undo stack.  If we can't do that,
	// then we better just fail.
	if( SetUndoLimit(1) != 1 )
	{
		_fSingleLevelMode = FALSE;
		return E_OUTOFMEMORY;
	}

	if( _ped->GetRedoMgr() )
	{
		// doesn't matter if the redo manager fails to reset
		_ped->GetRedoMgr()->SetUndoLimit(1);
	}

	return NOERROR;
}

/*
 *	CUndoStack::DisableSingleLevelMode
 *
 *	@mfunc	This turns off the 1.0 undo compatibility mode and restores us to 
 *			the RichEdit 2.0 default undo state
 */
void CUndoStack::DisableSingleLevelMode()
{
	Assert(_ped->GetRedoMgr() == NULL || 
		_ped->GetRedoMgr()->CanUndo() == FALSE);
	Assert(CanUndo() == FALSE);
	Assert(_fRedo == FALSE);

	_fSingleLevelMode = FALSE;

	// we don't care about failures here; multi-level undo mode
	// can handle any sized undo stack
	SetUndoLimit(DEFAULT_UNDO_SIZE);

	if( _ped->GetRedoMgr() )
	{
		// doesn't matter if the redo manager can't grow back in
		// size; it just means that we won't have full redo capability.
		_ped->GetRedoMgr()->SetUndoLimit(DEFAULT_UNDO_SIZE);
	}
}

//
// PRIVATE METHODS
//

/*
 *	CUndoStack::Next
 *
 *	@mfunc
 *		sets _index to the next available slot
 */
void CUndoStack::Next( void )
{
	TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::Next");

	_index++;
	
	if( _index == _dwLim )
	{	
		_index = 0;
	}
}

/*
 *	CUndoStack::Prev
 *
 *	@mfunc
 *		sets _index to the previous slot
 */
void CUndoStack::Prev( void )
{
	TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::Prev");

	_index = GetPrev();
}

/*
 *	CUndoStack::GetPrev
 *
 *	@mfunc
 *		figures out what the index to the previous slot
 *		*should* be (but does not set it)
 *
 *	@rdesc	the index of what the previous slot would be
 */

DWORD CUndoStack::GetPrev( void )
{
	TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::GetPrev");

	DWORD i = _index;

	if( i == 0 )
	{
		i = _dwLim - 1;
	}
	else
	{
		i--;
	}

	return i;
}

/*
 *	CUndoStack::IsCookieInList
 *
 *	@mfunc	
 *		determines whether or not the given DoTo cookie is in
 *		the list of anti-events.
 *
 *	@rdesc	TRUE/FALSE
 */
BOOL CUndoStack::IsCookieInList(
	IAntiEvent *pae,		//@parm	the list to check
	IAntiEvent *paeCookie)	//@parm the cookie to check
{
	while( pae )
	{
		if( pae == paeCookie )
		{
			return TRUE;
		}

		pae = pae->GetNext();
	}
	return FALSE;
}

/*
 *	CUndoStack::TransferToNewBuffer
 *
 *	@mfunc	
 *		transfers existing anti-events to the given buffer and
 *		swaps this undo stack to use the new buffer
 *
 *	@rdesc	void
 *
 *	@comm	The algorithm is very straightforward; go backwards in
 *			the ring buffer copying antievents over until either there
 *			are no more anti-events or the new buffer is full.  Discard
 *			any remaining anti-events.
 */
void CUndoStack::TransferToNewBuffer(UndoAction *prgnew, DWORD dwLimNew)
{
	TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CUndoStack::TransferToNewBuffer");

	DWORD 	iOld = 0, 
			iNew = 0,
			iCopyStart = 0;

	// first, clear the new buffer.
	memset(prgnew, 0, dwLimNew * sizeof(UndoAction));

	// if there is nothing to copy, don't bother

	if( NULL == _prgActions || NULL == _prgActions[GetPrev()].pae )
	{
		goto SetState;
	}

	// this is a bit counter-intuitive, but since the stack is really
	// a ring buffer, go *forwards* until you hit a non-NULL slot.
	// This will be the _end_ of the existing antievents.
	//
	// However, we need to make sure that if dwLimNew is 
	// _smaller_ than _dwLim we only copy the final dwLimNew
	// anti-events.  We'll set iCopyStart to indicate when
	// we can start copying stuff.

	if( dwLimNew < _dwLim )
	{
		iCopyStart = _dwLim - dwLimNew;
	}

	for(; iOld < _dwLim; iOld++, Next() )
	{
		if( NULL == _prgActions[_index].pae )
		{
			continue;
		}
		else if( iOld >= iCopyStart )
		{
			Assert(iNew < dwLimNew );
			// copy anti-events over 
			prgnew[iNew] = _prgActions[_index];
			iNew++;
		}
		else
		{
			// otherwise, get rid of them
			DestroyAEList(_prgActions[_index].pae);
			_prgActions[_index].pae = NULL;
		}
	}

SetState:
	
	//we start at index iNew
	_index = (iNew == dwLimNew ) ? 0 : iNew;
	Assert(iNew <= dwLimNew);

	_dwLim = dwLimNew;
	
	if( _prgActions )
	{
		delete _prgActions;
	}
	_prgActions = prgnew;
}	

//
//	CGenUndoBuilder implementation
//

//
//	Public methods
//

/*
 *	CGenUndoBuilder::CGenUndoBuilder (pstack)
 *
 *	@mfunc	Constructor
 *
 *	@comm
 *		This is a *PUBLIC* constructor
 *	
 */

CGenUndoBuilder::CGenUndoBuilder( 
	CTxtEdit *ped, 			//@parm	the overall context
	DWORD flags) :			//@parm flags (usually UB_AUTOCOMMIT)

	_fAutoCommit(1),
	_pfirstae(NULL)
	
{
	CompName	name = COMP_UNDOBUILDER;
	CCallMgr *	pcallmgr = ped->GetCallMgr();

	_idName		= UID_UNKNOWN;
	_ped 		= ped;
	_pfirstae 	= NULL;
	_fStartGroupTyping = FALSE;
	_fRedo		= FALSE;
	_fDontFlushRedo = FALSE;

	if( (flags & UB_AUTOCOMMIT) )
	{
		_fAutoCommit = TRUE;
	}

	if( (flags & UB_REDO) )
	{
		_fRedo = TRUE;
		name = COMP_REDOBUILDER;
		_pundo = ped->GetRedoMgr();
	}
	else
	{
		_pundo = ped->GetUndoMgr();
	}

	if( (flags & UB_DONTFLUSHREDO) )
	{
		_fDontFlushRedo = TRUE;
	}

#ifdef PWD_JUPITER // GuyBark Jupiter 18411: Don't attempt to merge events during an undo
    _bNoAttemptToMerge = FALSE;
#endif //  PWD_JUPITER

	// now link ourselves to any undobuilders that are higher up on
	// the stack.  Note that is is legal for multiple undo builders
	// to live within the same call context.

	_publdrPrev = (CGenUndoBuilder *)_ped->GetCallMgr()->GetComponent(name);

	// If we are in the middle of an undo, then we'll have two undo stacks
	// active, the undo stack and the redo stack.  Don't like the two
	// together.
	if( _fDontFlushRedo )
	{
		_publdrPrev = NULL;
	}

	_ped->GetCallMgr()->RegisterComponent((IReEntrantComponent *)this,
							name);
}

/*
 *	CGenUndoBuilder::~CGenUndoBuilder
 *
 *	@mfunc	Destructor
 *
 *	@comm
 *		This is a *PUBLIC* destructor
 *
 *	Algorithm:
 *		If this builder hasn't been committed to an undo stack
 *		via ::Done, then we must be sure to free up any resources
 *		(antievents) we may be hanging onto
 *
 */

CGenUndoBuilder::~CGenUndoBuilder( )
{
	_ped->GetCallMgr()->RevokeComponent((IReEntrantComponent *)this);

	if( _fAutoCommit == TRUE )
	{
		Done();
		return;
	}

	// free resources
	if( _pfirstae )
	{
		DestroyAEList(_pfirstae);
	}
}

/*
 *	CGenUndoBuilder::SetNameID (idName)
 *
 *	@mfunc
 *		Allows a name to be assigned to this anti-event collection.
 *		The ID should be an index that can be used to retrieve a
 *		language specific string (like "Paste").  This string is
 *		typically composed into undo menu items (i.e. "Undo Paste").
 */

void CGenUndoBuilder::SetNameID( 
	UNDONAMEID idName )			//@parm	the name ID for this undo operation
{
	TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CGenUndoBuilder::SetNameID");

	// don't delegate to the higher undobuilder, even if it exists. The
	// original name should win in re-entrancy cases.
	_idName = idName;
}


/*
 *	CGenUndoBuilder::AddAntiEvent (pae)
 *
 *	@mfunc
 *		Adds an anti-event to the end of the list
 *
 *	@rdesc 	NOERROR
 */

HRESULT CGenUndoBuilder::AddAntiEvent( 
	IAntiEvent *pae )		//@parm	the anti-event to add
{
	TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CGenUndoBuilder::AddAntiEvent");

	if( _publdrPrev )
	{
		return _publdrPrev->AddAntiEvent(pae);
	}

	pae->SetNext(_pfirstae);
	_pfirstae = pae;

	return NOERROR;
}

/*
 *	CGenUndoBuilder::GetTopAntiEvent
 *
 *	@mfunc	Gets the top anti-event for this context.
 *
 *	@comm	The current context can be either the current
 *			operation *or* to a previous operation if we are in
 *			merge typing mode.
 *
 *	@rdesc	the top anti-event
 *
 */
IAntiEvent *CGenUndoBuilder::GetTopAntiEvent()
{
	TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CGenUndoBuilder::GetTopAntiEvent");

	if( _publdrPrev )
	{
		Assert(_pfirstae == NULL);
		return _publdrPrev->GetTopAntiEvent();
	}

	if( !_pfirstae && _pundo )
	{
		return _pundo->GetMergeAntiEvent();
	}

	return _pfirstae;
}

/*
 *	CGenUndoBuilder::Done
 *
 *	@mfunc
 *		puts the combined anti-events (if any) into the undo stack
 */

HRESULT CGenUndoBuilder::Done( void )
{
	HRESULT hr = NOERROR;
	DWORD dwLim = DEFAULT_UNDO_SIZE;
	IUndoMgr *predo;
	IAntiEvent *paetemp;

	if( _publdrPrev )
	{
		Assert(_pfirstae == NULL);
		return NOERROR;
	}

	if( _ped->GetDetectURL() )
	{
		_ped->GetDetectURL()->ScanAndUpdate(this);
	}

	// if nothing changed, discard any selection anti-events
	// or other no-op actions.
	if( !_ped->GetCallMgr()->GetChangeEvent() )
	{
		Discard();
		return NOERROR;
	}

	if( _pfirstae )
	{
		if( !_pundo )
		{
			// yikes!  There is no undo stack; better create one.

			// if we are a redo guy, we should create a redo
			// stack the size of the undo stack

			if( _fRedo )
			{
				Assert(_ped->GetUndoMgr());

				dwLim = _ped->GetUndoMgr()->GetUndoLimit();
			}

			_pundo = _ped->CreateUndoMgr(dwLim,	_fRedo ? US_REDO : US_UNDO );

			// FUTURE:  A NULL ptr returned from CreateUndoMgr means either
			// 	we are out of memory, or the undo limit is set to 0.  For the
			// 	latter case, we have collected AE's to push onto a non-existent
			// 	undo stack.  It may be more efficient to not generate
			// 	the AE's at all when the undo limit is 0.

			if(!_pundo)
			{
				goto CleanUp;
			}
		}

		// we may need to flush the redo stack if we are adding
		// more anti-events to the undo stack *AND* we haven't been
		// told not to flush the redo stack.  The only time we won't
		// flush the redo stack is if it's the redo stack itself
		// adding anti-events to undo.

		if( !_fRedo )
		{
			// if our destination is the undo stack, then check
			// to see if we should flush
			if( !_fDontFlushRedo )
			{
				predo = _ped->GetRedoMgr();

				if( predo )
				{
					predo->ClearAll();
				}
			}
		}
#ifdef DEBUG
		else
		{
			Assert(!_fDontFlushRedo);
		}

#endif // DEBUG

		// if we should enter into the group typing state, inform
		// the undo manager.  Note that we only do this *iff* 
		// there is actually some anti-event to put in the undo
		// manager.  This makes the undo manager easier to implement
		if( _fStartGroupTyping )
		{
			_pundo->StartGroupTyping();
		}
		
		hr = _pundo->PushAntiEvent( _idName, _pfirstae );

		// the change event flag should be set if we're adding
		// undo items!   If this test is true, it probably means
		// the somebody earlier in the call stack sent change
		// notifiations (either via SendAllNotifications or
		// the CAutonotify class) _before_ this undo context
		// was committed _or_ it means that we were re-entered
		// in some way that was not handled properly.
		//
		// Needless to say, this is not an ideal state.

CleanUp:
		Assert(_ped->GetCallMgr()->GetChangeEvent());

		paetemp = _pfirstae;
		_pfirstae = NULL;
		
		CommitAEList(paetemp, _ped);

		if(!_pundo || hr != NOERROR)
		{
			// Either we failed to add the AE's to the undo stack
			// or the undo limit is 0 in which case there won't be
			// an undo stack to push the AE's onto.
			DestroyAEList(paetemp);
		}
	}

	return hr;
}

/*
 *	CGenUndoBuilder::Discard
 *
 *	@mfunc
 *		Gets rid of any anti-events that we may be hanging onto without
 *		executing or committing them.  Typically used for recovering
 *		from certain failure or re-entrancy scenarios.  Note that
 *		an _entire_ anti-event chain will be removed in this fashion.
 *
 *	@rdesc void
 */
void CGenUndoBuilder::Discard(void)
{
	if( _pfirstae )
	{
		DestroyAEList(_pfirstae);
		_pfirstae = NULL;
	}
	else if( _publdrPrev )
	{
		_publdrPrev->Discard();
	}
}


/*
 *	CGenUndoBuilder::StartGroupTyping
 *
 *	@mfunc
 *		hangs onto the the fact that group typing should start.
 *		We'll forward the the state transition to the undo manager
 *		only if an anti-event is actually added to the undo manager.
 *
 *	@devnote
 *		group typing is disabled for redo stacks.
 */

void CGenUndoBuilder::StartGroupTyping()
{
	TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CGenUndoBuilder::StartGroupTyping");

	_fStartGroupTyping = TRUE;
}

/*
 *	CGenUndoBuilder::StopGroupTyping
 *
 *	@mfunc
 *		forwards a stop grouped typing to the undo manager
 */

void CGenUndoBuilder::StopGroupTyping()
{
	TRACEBEGIN(TRCSUBSYSUNDO, TRCSCOPEINTERN, "CGenUndoBuilder::StopGroupTyping");

	if( _pundo )
	{
		_pundo->StopGroupTyping();
	}
}

//
//	CUndoStackGuard IMPLEMENTATION
//

/*
 *	CUndoStackGuard::CUndoStackGuard
 *
 *	@mfunc	Constructor.  Registers this object with the call manager
 *
 *	@rdesc	void
 */
CUndoStackGuard::CUndoStackGuard(
	CTxtEdit *ped)			//@parm the edit context
{
	_ped = ped;
	_fReEntered = FALSE;
	_hr = NOERROR;
	ped->GetCallMgr()->RegisterComponent(this, COMP_UNDOGUARD);
}

/*
 *	CUndoStackGuard::~CUndoStackGuard
 *
 *	@mfunc	Destructor.  Revokes the registration of this object
 *			with the call manager
 *
 *	@rdesc	void
 */
CUndoStackGuard::~CUndoStackGuard()
{
	_ped->GetCallMgr()->RevokeComponent(this);
}

/*
 *	CUndoStackGuard::SafeUndo
 *
 *	@mfunc	Loops through the given list of anti-events, invoking
 *			undo on each.  
 *
 *	@rdesc	HRESULT, from the undo actions
 *
 *	@devnote	This routine is coded so that OnEnterContext can pick up
 *			and continue the undo operation should we become re-entered
 */
HRESULT CUndoStackGuard::SafeUndo(
	IAntiEvent *pae,		//@parm the start of the anti-event list
	IUndoBuilder *publdr)	//@parm the undo builder to use
{
	HRESULT hr;

	_publdr = publdr;

	while( pae )
	{
		_paeNext = pae->GetNext();
		hr = pae->Undo(_ped, publdr);

		// save the first returned error.
		if( hr != NOERROR && _hr == NOERROR)
		{
			_hr = hr;
		}

		pae = (IAntiEvent *)_paeNext;
	}

	return _hr;
}

/*
 *	CUndoStackGuard::OnEnterContext
 *
 *	@mfunc	Handle re-entrancy during undo operations.
 *
 *	@rdesc	void
 *	
 *	@devnote If this method is called, it's pretty serious.  In general,
 *			we shoud never be re-entered while processing undo stuff.
 *			However, to ensure that, block the incoming call and process
 *			the remaining actions.
 */
void CUndoStackGuard::OnEnterContext()
{
	TRACEWARNSZ("ReEntered while processing undo.  Blocking call and");
	TRACEWARNSZ("	attempting to recover.");

	_fReEntered = TRUE;
	SafeUndo((IAntiEvent *)_paeNext, _publdr);
}	

//
//	PUBLIC helper functions
//

/*
 *	@func	DestroyAEList | Destroys a list of anti-events
 *
 *	@rdesc	void
 */
void DestroyAEList( 
	IAntiEvent *pae)	//@parm the anti-event from which to start
{
	IAntiEvent *pnext;

	while( pae )
	{
		pnext = pae->GetNext();
		pae->Destroy();
		pae = pnext;
	}
}

/*
 *	@func CommitAEList | Calls OnCommit to the given list of anti-events
 *
 *	@rdesc	void
 */
void CommitAEList(
	IAntiEvent *pae,	//@parm the anti-event from which to start
	CTxtEdit *ped)		//@parm the edit context
{
	IAntiEvent *pnext;

	while( pae )
	{
		pnext = pae->GetNext();
		pae->OnCommit(ped);
		pae = pnext;
	}
}

/*
 *	@func	HandleSelectionAEInfo | Tries to merge the given info with 
 *			the existing undo context; if that fails, then it allocates 
 *			a new selection anti-event to handle the info
 */
HRESULT HandleSelectionAEInfo(
	CTxtEdit *ped,			//@parm the edit context
	IUndoBuilder *publdr,	//@parm the undo context
	LONG cp,				//@parm the cp to use for the sel ae
	LONG cch,				//@parm the signed selection extension
	LONG cpNext,			//@parm the cp to use for the AE of the AE
	LONG cchNext,			//@parm the cch to use for the AE of the AE
	SELAE flags)			//@parm controls how to intepret the info
{
	IAntiEvent *pae;

	Assert(publdr);

	pae = publdr->GetTopAntiEvent();

	// first see if we can merge the selection info into any existing
	// anti-events.  Note that the selection anti-event may be anywhere
	// in the list, so go through them all
	if( pae )
	{
		SelRange sr;

		sr.cp		= cp;
		sr.cch		= cch;
		sr.cpNext	= cpNext;
		sr.cchNext	= cchNext;
		sr.flags	= flags;

		while( pae )
		{
			if( pae->MergeData(MD_SELECTIONRANGE, (void *)&sr) == NOERROR )
			{	
				break;
			}

			pae = pae->GetNext();
		}
	
		if( pae )
		{
			return NOERROR;
		}
	}

	// oops; can't do a merge.  Go ahead and create a new anti-event.

	Assert(!pae);

	pae = gAEDispenser.CreateSelectionAE(ped, cp, cch, cpNext, cchNext);

	if( pae )
	{
		publdr->AddAntiEvent(pae);
		return NOERROR;
	}

	return E_OUTOFMEMORY;
}

