// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

//
// Description:
//
//     See spec at Undo spec.htm
//

using System;
using System.Windows;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics;
using MS.Utility;
using System.Windows.Markup;
using System.Windows.Documents;
using System.Windows.Controls.Primitives;

namespace MS.Internal.Documents
{
    /// <summary>
    /// Enum for the state of the undo manager
    /// </summary>
    /// <ExternalAPI Inherit="true"/>
    internal enum UndoState
    {
        /// <summary>
        /// Ready to accept new undo units; not currently undoing or redoing
        /// </summary>
        /// <ExternalAPI/>
        Normal,

        /// <summary>
        /// In the process of undoing
        /// </summary>
        /// <ExternalAPI/>
        Undo,

        /// <summary>
        /// In the process of redoing
        /// </summary>
        /// <ExternalAPI/>
        Redo,

        /// <summary>
        /// In the process of rolling back an aborted undo unit
        /// </summary>
        /// <ExternalAPI/>
        Rollback
    };

    /// <summary>
    /// Undo Manager
    /// </summary>
    internal class UndoManager
    {
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------

        #region Constructors

        /// <summary>
        /// UndoManager constructor
        /// </summary>
        internal UndoManager() : base()
        {
            _scope = null;
            _state = UndoState.Normal;
            _isEnabled = false;
            _undoStack = new List<IUndoUnit>(4);
            _redoStack = new Stack(2);
            _topUndoIndex = -1;
            _bottomUndoIndex = 0;
            _undoLimit = _undoLimitDefaultValue;
        }

        #endregion Constructors

        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------

        #region Internal Methods

        /// <summary>
        /// Defines a given FrameworkElement as a scope for undo service.
        /// New instance of UndoManager created and attached to this element.
        /// </summary>
        /// <param name="scope">
        /// FrameworkElement to which new instance of UndoManager is attached.
        /// </param>
        /// <param name="undoManager">
        /// </param>
        internal static void AttachUndoManager(DependencyObject scope, UndoManager undoManager)
        {
            ArgumentNullException.ThrowIfNull(scope);
            ArgumentNullException.ThrowIfNull(undoManager);

            if (undoManager is UndoManager && ((UndoManager)undoManager)._scope != null)
            {
                throw new InvalidOperationException(SR.UndoManagerAlreadyAttached);
            }

            // Detach existing instance of undo manager if any
            DetachUndoManager(scope);

            // Attach the service to the scope via private dependency property
            scope.SetValue(UndoManager.UndoManagerInstanceProperty, undoManager);
            if (undoManager is UndoManager)
            {
                Debug.Assert(((UndoManager)undoManager)._scope == null);
                ((UndoManager)undoManager)._scope = scope;
            }

            undoManager.IsEnabled = true;
        }

        /// <summary>
        /// Detaches an undo service from the given FrameworkElement.
        /// </summary>
        /// <param name="scope">
        /// A FrameworkElement with UndoManager attached to it.
        /// </param>
        /// <remarks>
        /// Throws an exception if the scope does not have undo service attached to it.
        /// </remarks>
        internal static void DetachUndoManager(DependencyObject scope)
        {
            UndoManager undoManager;

            ArgumentNullException.ThrowIfNull(scope);

            // Detach existing undo service if any
            undoManager = scope.ReadLocalValue(UndoManager.UndoManagerInstanceProperty) as UndoManager;
            if (undoManager != null)
            {
                // Disable the service while in detached state
                undoManager.IsEnabled = false;

                // Remove the service from a tre
                scope.ClearValue(UndoManager.UndoManagerInstanceProperty);

                // Break the linkage to its scope
                if (undoManager is UndoManager)
                {
                    Debug.Assert(((UndoManager)undoManager)._scope == scope);
                    ((UndoManager)undoManager)._scope = null;
                }
            }
        }

        /// <summary>
        /// Finds the nearest undo service for a given uielement as a target.
        /// </summary>
        /// <param name="target">
        /// A ui element which is a descendant (or self) of an element
        /// to which undo service is attached.
        /// </param>
        /// <returns></returns>
        internal static UndoManager GetUndoManager(DependencyObject target)
        {
            if (target == null)
            {
                return null;
            }
            else
            {
                return target.GetValue(UndoManager.UndoManagerInstanceProperty) as UndoManager;
            }
        }

        /// <summary>
        /// Add the given parent undo unit to the undo manager, making it the OpenedUnit of the
        /// innermost open parent unit (or the undo manager itself, if no parents are open).
        /// </summary>
        /// <param name="unit">
        /// parent unit to add
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// Thrown if UndoManager is disabled or passed unit is already open.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// Thrown if passed unit is null.
        /// </exception>
        internal void Open(IParentUndoUnit unit)
        {
            IParentUndoUnit deepestOpen;

            if (!IsEnabled)
            {
                throw new InvalidOperationException(SR.UndoServiceDisabled);
            }

            ArgumentNullException.ThrowIfNull(unit);

            deepestOpen = DeepestOpenUnit;
            if (deepestOpen == unit)
            {
                throw new InvalidOperationException(SR.UndoUnitCantBeOpenedTwice);
            }

            if (deepestOpen == null)
            {
                if (unit != LastUnit)
                {
                    // Don't want to add the unit again if we're just reopening it
                    Add(unit as IUndoUnit);
                    SetLastUnit(unit as IUndoUnit);
                }
                SetOpenedUnit(unit);
                unit.Container = this;
            }
            else
            {
                unit.Container = deepestOpen;
                deepestOpen.Open(unit);
            }
        }

        /// <summary>
        /// Opens a closed undo unit on the top of the stack.
        /// </summary>
        /// <param name="unit">
        /// IParentUndoUnit to reopen
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// Thrown if:
        ///     UndoManager is disabled
        ///     another unit is already open
        ///     the given unit is locked
        ///     the given unit is not on top of the stack
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// Thrown if passed unit is null.
        /// </exception>
        internal void Reopen(IParentUndoUnit unit)
        {
            if (!IsEnabled)
            {
                throw new InvalidOperationException(SR.UndoServiceDisabled);
            }

            ArgumentNullException.ThrowIfNull(unit);

            if (OpenedUnit != null)
            {
                throw new InvalidOperationException(SR.UndoUnitAlreadyOpen);
            }

            switch (State)
            {
                case UndoState.Normal:
                case UndoState.Redo:
                    {
                        if (UndoCount == 0 || PeekUndoStack() != unit)
                        {
                            throw new InvalidOperationException(SR.UndoUnitNotOnTopOfStack);
                        }

                        break;
                    }

                case UndoState.Undo:
                    {
                        if (RedoStack.Count == 0 || (IParentUndoUnit)RedoStack.Peek() != unit)
                        {
                            throw new InvalidOperationException(SR.UndoUnitNotOnTopOfStack);
                        }

                        break;
                    }

                case UndoState.Rollback:
                default:
                    // should only happen if someone changes the UndoState enum or parameter validation
                    Debug.Assert(false);
                    break;
            }
            if (unit.Locked)
            {
                throw new InvalidOperationException(SR.UndoUnitLocked);
            }

            Open(unit);
            _lastReopenedUnit = unit;
        }

        /// <summary>
        /// Closes the current open unit, adding it to the containing unit's undo stack if committed.
        /// </summary>
        internal void Close(UndoCloseAction closeAction)
        {
            Close(OpenedUnit, closeAction);
        }

        /// <summary>
        /// Closes an open child parent unit, adding it to the containing unit's undo stack if committed.
        /// </summary>
        /// <param name="unit">
        /// IParentUndoUnit to close.  If NULL, this unit's OpenedUnit is closed.
        /// </param>
        /// <param name="closeAction">
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// Thrown if:
        ///     UndoManager is disabled
        ///     no undo unit is currently open
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// Thrown if unit is null
        /// </exception>
        internal void Close(IParentUndoUnit unit, UndoCloseAction closeAction)
        {
            if (!IsEnabled)
            {
                throw new InvalidOperationException(SR.UndoServiceDisabled);
            }

            ArgumentNullException.ThrowIfNull(unit);

            if (OpenedUnit == null)
            {
                throw new InvalidOperationException(SR.UndoNoOpenUnit);
            }

            // find the parent of the given unit
            if (OpenedUnit != unit)
            {
                IParentUndoUnit closeParent;

                closeParent = OpenedUnit;

                while (closeParent.OpenedUnit != null && closeParent.OpenedUnit != unit)
                {
                    closeParent = closeParent.OpenedUnit;
                }

                if (closeParent.OpenedUnit == null)
                {
                    throw new ArgumentException(SR.UndoUnitNotFound, nameof(unit));
                }

                closeParent.Close(closeAction);
                return;
            }

            //
            // Close our open unit
            //
            if (closeAction != UndoCloseAction.Commit)
            {
                // discard unit
                SetState(UndoState.Rollback);
                if (unit.OpenedUnit != null)
                {
                    unit.Close(closeAction);
                }

                if (closeAction == UndoCloseAction.Rollback)
                {
                    unit.Do();
                }

                PopUndoStack();

                SetOpenedUnit(null);
                OnNextDiscard();
                SetLastUnit(_topUndoIndex == -1 ? null : PeekUndoStack()); // can be null, which is fine
                SetState(UndoState.Normal);
            }
            else
            {
                // commit unit
                if (unit.OpenedUnit != null)
                {
                    unit.Close(UndoCloseAction.Commit);
                }

                // flush redo stack
                if (State != UndoState.Redo && State != UndoState.Undo && RedoStack.Count > 0)
                {
                    RedoStack.Clear();
                }

                SetOpenedUnit(null);
            }
        }

        /// <summary>
        /// Adds an undo unit to the undo/redo stack, depending on current state.
        /// </summary>
        /// <param name="unit">
        /// IUndoUnit to add
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// Thrown if:
        ///     UndoManager is disabled
        ///     unit is not IParentUndoUnit and there is no open IParentUndoUnit
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// Thrown if unit is null
        /// </exception>
        internal void Add(IUndoUnit unit)
        {
            IParentUndoUnit parent;

            if (!IsEnabled)
            {
                throw new InvalidOperationException(SR.UndoServiceDisabled);
            }

            ArgumentNullException.ThrowIfNull(unit);

            parent = DeepestOpenUnit;
            if (parent != null)
            {
                parent.Add(unit);
            }
            else if (unit is IParentUndoUnit)
            {
                ((IParentUndoUnit)unit).Container = this;
                if (LastUnit is IParentUndoUnit)
                {
                    ((IParentUndoUnit)LastUnit).OnNextAdd();
                }

                SetLastUnit(unit);
                if (State == UndoState.Normal || State == UndoState.Redo)
                {
                    if (++_topUndoIndex == UndoLimit)
                    {
                        _topUndoIndex = 0;
                    }
                    if (!(_topUndoIndex < UndoStack.Count && PeekUndoStack() == null) // Non-null topmost stack item
                        && (UndoLimit == -1 || UndoStack.Count < UndoLimit))
                    {
                        UndoStack.Add(unit);
                    }
                    else
                    {
                        if (PeekUndoStack() != null)
                        {
                            if (++_bottomUndoIndex == UndoLimit)
                            {
                                _bottomUndoIndex = 0;
                            }
                        }
                        UndoStack[_topUndoIndex] = unit;
                    }
                }
                else if (State == UndoState.Undo)
                {
                    RedoStack.Push(unit);
                }
                else if (State == UndoState.Rollback)
                {
                    // do nothing, throwing out the unit
                }
            }
            else
            {
                throw new InvalidOperationException(SR.UndoNoOpenParentUnit);
            }
        }

        /// <summary>
        /// Clear the undo and redo stacks, as well as LastUnit.
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// Thrown if UndoManager is disabled
        /// </exception>
        internal void Clear()
        {
            if (!IsEnabled)
            {
                throw new InvalidOperationException(SR.UndoServiceDisabled);
            }

            // In practice, we only clear when the public IsUndoEnabled property is set false.
            // We'll check that property again when _imeSupportModeEnabled transitions to false.
            // While _imeSupportModeEnabled == true, we must leave undo enabled.
            if (!_imeSupportModeEnabled)
            {
                DoClear();
            }
        }

        /// <summary>
        /// Instructs the undo manager to invoke the given number of undo actions.
        /// </summary>
        /// <param name="count">
        /// Number of undo units to undo
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// Thrown if UndoManager is disabled
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Thrown if count is out of range
        /// </exception>
        /// <exception cref="Exception">
        /// Thrown if there's an error performing the undo
        /// </exception>
        internal void Undo(int count)
        {
            if (!IsEnabled)
            {
                throw new InvalidOperationException(SR.UndoServiceDisabled);
            }

            ArgumentOutOfRangeException.ThrowIfGreaterThan(count, UndoCount);
            ArgumentOutOfRangeException.ThrowIfNegativeOrZero(count);

            if (State != UndoState.Normal)
            {
                throw new InvalidOperationException(SR.UndoNotInNormalState);
            }

            if (OpenedUnit != null)
            {
                throw new InvalidOperationException(SR.UndoUnitOpen);
            }

            Invariant.Assert(UndoCount > _minUndoStackCount);

            SetState(UndoState.Undo);

            bool exceptionThrown = true;

            try
            {
                while (count > 0)
                {
                    IUndoUnit unit;

                    unit = PopUndoStack();
                    unit.Do();
                    count--;
                }
                exceptionThrown = false;
            }
            finally
            {
                if (exceptionThrown)
                {
                    Clear();
                }
            }

            SetState(UndoState.Normal);
        }


        /// <summary>
        /// Instructs the undo manager to invoke the given number of redo actions.
        /// </summary>
        /// <param name="count">
        /// Number of redo units to redo
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// Thrown if UndoManager is disabled
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Thrown if count is out of range
        /// </exception>
        /// <exception cref="Exception">
        /// Thrown if there's an error performing the redo
        /// </exception>
        internal void Redo(int count)
        {
            if (!IsEnabled)
            {
                throw new InvalidOperationException(SR.UndoServiceDisabled);
            }

            ArgumentOutOfRangeException.ThrowIfGreaterThan(count, RedoStack.Count);
            ArgumentOutOfRangeException.ThrowIfNegativeOrZero(count);

            if (State != UndoState.Normal)
            {
                throw new InvalidOperationException(SR.UndoNotInNormalState);
            }

            if (OpenedUnit != null)
            {
                throw new InvalidOperationException(SR.UndoUnitOpen);
            }

            SetState(UndoState.Redo);

            bool exceptionThrown = true;

            try
            {
                while (count > 0)
                {
                    IUndoUnit unit;

                    unit = (IUndoUnit)RedoStack.Pop();
                    unit.Do();
                    count--;
                }
                exceptionThrown = false;
            }
            finally
            {
                if (exceptionThrown)
                {
                    Clear();
                }
            }
            SetState(UndoState.Normal);
        }

        /// <summary>
        /// Called when a unit is discarded.  Unlocks the most recent unit added before the discarded one.
        /// </summary>
        internal virtual void OnNextDiscard()
        {
            if (UndoCount > 0)
            {
                IParentUndoUnit lastParent = (IParentUndoUnit)PeekUndoStack();

                lastParent.OnNextDiscard();
            }
        }

        // Peek the unit of UndoStack and the return the top unit of UndoStack.
        internal IUndoUnit PeekUndoStack()
        {
            if (_topUndoIndex < 0 || _topUndoIndex == UndoStack.Count)
            {
                return null;
            }
            else
            {
                return UndoStack[_topUndoIndex] as IUndoUnit;
            }
        }

        /// <summary>
        /// Explicitly sets the redo stack.
        /// </summary>
        /// <remark>
        /// DANGER!  This method is internal (and not private) only so it can be accessed
        /// by IMEs.  Using this method can result in instability and is strongly discouraged.
        /// </remark>
        internal Stack SetRedoStack(Stack value)
        {
            Stack previousValue = _redoStack;

            if (value == null)
            {
                value = new Stack(2);
            }

            _redoStack = value;

            return previousValue;
        }

        #endregion Internal Methods

        //------------------------------------------------------
        //
        //  Internal Properties
        //
        //------------------------------------------------------

        #region Internal Properties

        /// <summary>
        /// IME mode switch.
        /// </summary>
        /// <remarks>
        /// In the context of IME events (TextInputStart/TextInputUpdated/TextInput)
        /// the undo stack is used internally to juggle document state such that
        /// the IMEs perceive no reentrancy.
        ///
        /// While IsImeSupportModeEnabled is set by our IME handler code
        ///  - The undo stack must be enabled.
        ///  - The undo stack must not be limited in size.
        /// </remarks>
        internal bool IsImeSupportModeEnabled
        {
            get { return _imeSupportModeEnabled; }
            set
            {
                if (value != _imeSupportModeEnabled)
                {
                    if (value)
                    {
                        // While _imeSupportModeEnabled is in effect, the undo
                        // stack is not constrained.  If necessary, force it
                        // into the expected infinite stack configuration: growing from index 0.
                        if (_bottomUndoIndex != 0 && _topUndoIndex >= 0)
                        {
                            List<IUndoUnit> undoStack = new List<IUndoUnit>(UndoCount);
                            int i;

                            if (_bottomUndoIndex > _topUndoIndex)
                            {
                                for (i = _bottomUndoIndex; i < UndoLimit; i++)
                                {
                                    undoStack.Add(_undoStack[i]);
                                }
                                _bottomUndoIndex = 0;
                            }

                            for (i = _bottomUndoIndex; i <= _topUndoIndex; i++)
                            {
                                undoStack.Add(_undoStack[i]);
                            }

                            _undoStack = undoStack;
                            _bottomUndoIndex = 0;
                            _topUndoIndex = undoStack.Count - 1;
                        }

                        _imeSupportModeEnabled = value;
                    }
                    else
                    {
                        // Transitioning false.

                        _imeSupportModeEnabled = value;

                        if (!this.IsEnabled)
                        {
                            // If the stack was originally disabled, remove all content added.
                            DoClear();
                        }
                        else
                        {
                            // Free up units that exceed the original undo limit.
                            //
                            // NB: we are not clearing the undo stack here if UndoLimit changed
                            // while _imeSupportMode was true, which is at odds
                            // with behavior from the UndoLimit setter.  It always clears the
                            // stack when the UndoLimit changes.  We're skipping that step
                            // because supporting it would require adding an additional piece
                            // of state to this class (to delay the reset from happening
                            // until we get here).
                            //
                            // it would be more efficient to null out truncated values
                            // here rather than always reallocating the copying the entire
                            // stack.
                            if (UndoLimit >= 0 && _topUndoIndex >= UndoLimit)
                            {
                                List<IUndoUnit> undoStack = new List<IUndoUnit>(UndoLimit);

                                for (int i = _topUndoIndex + 1 - UndoLimit; i <= _topUndoIndex; i++)
                                {
                                    undoStack.Add(_undoStack[i]);
                                }

                                _undoStack = undoStack;
                                _bottomUndoIndex = 0;
                                _topUndoIndex = UndoLimit - 1;
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Maximum number of undo units allowed on the stack
        /// </summary>
        internal int UndoLimit
        {
            get
            {
                return _imeSupportModeEnabled ? -1 : _undoLimit;
            }
            set
            {
                _undoLimit = value;

                // While _imeSupportModeEnabled == true, we must leave undo enabled and
                // the undo limit may not be cleared.
                if (!_imeSupportModeEnabled)
                {
                    DoClear();
                }
            }
        }

        /// <summary>
        /// Returns the current state of the undo manager
        /// </summary>
        internal UndoState State
        {
            get
            {
                return _state;
            }
        }

        /// <summary>
        /// Whether or not the Undo Manager is enabled.  If it isn't, any changes submitted
        /// to the undo manager will be ignored.
        /// </summary>
        /// <remarks>
        /// IsEnabled will evaluate to false even when explicitly set true, if the
        /// current UndoLimit is zero.
        /// </remarks>
        internal bool IsEnabled
        {
            get
            {
                return _imeSupportModeEnabled || (_isEnabled && _undoLimit != 0);
            }

            set
            {
                _isEnabled = value;
            }
        }

        /// <summary>
        /// Returns the topmost opened ParentUndoUnit on the current stack
        /// </summary>
        internal IParentUndoUnit OpenedUnit
        {
            get
            {
                return _openedUnit;
            }
        }

        /// <summary>
        /// Readonly access to the last unit added to the IParentUndoUnit
        /// </summary>
        internal IUndoUnit LastUnit
        {
            get
            {
                return _lastUnit;
            }
        }

        /// <summary>
        /// Readonly access to the most recently reopened unit.  Note that this unit is not
        /// guaranteed to still exist in the stack-- it's just the last unit to be reopened.
        /// </summary>
        /// <remarks>
        /// Used by TextBox to determine if a text change causes a new unit to be opened or
        /// an existing unit to be reopened.
        /// </remarks>
        internal IParentUndoUnit LastReopenedUnit
        {
            get
            {
                return _lastReopenedUnit;
            }
        }

        /// <summary>
        /// Number of top-level units in the undo stack
        /// </summary>
        internal int UndoCount
        {
            get
            {
                int count;
                if (UndoStack.Count == 0 || _topUndoIndex < 0)
                {
                    count = 0;
                }
                else if (_topUndoIndex == _bottomUndoIndex - 1 && PeekUndoStack() == null)
                {
                    count = 0;
                }
                else if (_topUndoIndex >= _bottomUndoIndex)
                {
                    count = _topUndoIndex - _bottomUndoIndex + 1;
                }
                else
                {
                    count = _topUndoIndex + (UndoLimit - _bottomUndoIndex) + 1;
                }
                return count;
            }
        }

        /// <summary>
        /// Number of top-level units in the redo stack
        /// </summary>
        internal int RedoCount
        {
            get
            {
                return RedoStack.Count;
            }
        }

        // Default value for UndoLimitProperty.
        internal static int UndoLimitDefaultValue
        {
            get
            {
                return _undoLimitDefaultValue;
            }
        }

        /// <summary>
        /// Returns the zero based unit in the undo stack.
        /// </summary>
        /// <remarks>
        /// DANGER!  This method is internal (and not private) only so it can be accessed
        /// by IMEs.  Using this method can result in instability and is strongly discouraged.
        ///
        /// This method may only be called while ImeSupportModeEnabled == true.
        /// It does not handle circular undo stacks (_bottomUndoIndex > _topUndoIndex).
        /// </remarks>
        internal IUndoUnit GetUndoUnit(int index)
        {
            Invariant.Assert(index < this.UndoCount);
            Invariant.Assert(index >= 0);
            Invariant.Assert(_bottomUndoIndex == 0);
            Invariant.Assert(_imeSupportModeEnabled);

            return _undoStack[index];
        }

        /// <summary>
        /// Removes a range of units in the undo stack.
        /// </summary>
        /// <remarks>
        /// DANGER!  This method is internal (and not private) only so it can be accessed
        /// by IMEs.  Using this method can result in instability and is strongly discouraged.
        ///
        /// This method may only be called while ImeSupportModeEnabled == true.
        /// It does not handle circular undo stacks (_bottomUndoIndex > _topUndoIndex).
        /// </remarks>
        internal void RemoveUndoRange(int index, int count)
        {
            Invariant.Assert(index >= 0);
            Invariant.Assert(count >= 0);
            Invariant.Assert(count + index <= this.UndoCount);
            Invariant.Assert(_bottomUndoIndex == 0);
            Invariant.Assert(_imeSupportModeEnabled);

            int i;

            // Slide following units backward to fill the gap.
            for (i = index + count; i <= _topUndoIndex; i++)
            {
                _undoStack[i - count] = _undoStack[i];
            }

            // null out old references.
            for (i = _topUndoIndex - (count - 1); i <= _topUndoIndex; i++)
            {
                _undoStack[i] = null;
            }

            // Decrement the top index.
            _topUndoIndex -= count;
        }

        /// <summary>
        /// The minimum allowed depth of the undo stack.
        /// </summary>
        /// <remarks>
        /// DANGER!  This method is internal (and not private) only so it can be accessed
        /// by IMEs.  Using this method can result in instability and is strongly discouraged.
        ///
        /// Calling Undo when UndoCount == MinUndoStackCount is an error (and
        /// will trigger an Invariant failure).
        ///
        /// This property is set during IME composition handling,
        /// to ensure that applications cannot undo IME changes
        /// inside the scope of TextInputEvent handlers.
        /// </remarks>
        internal int MinUndoStackCount
        {
            get
            {
                return _minUndoStackCount;
            }

            set
            {
                _minUndoStackCount = value;
            }
        }

        #endregion Internal Properties

        //------------------------------------------------------
        //
        //  Protected Methods
        //
        //------------------------------------------------------

        #region Protected Methods

        /// <summary>
        /// State of the Undo Service
        /// </summary>
        /// <param name="value">
        /// UndoState to which State is to be set
        /// </param>
        protected void SetState(UndoState value)
        {
            _state = value;
        }

        /// <summary>
        /// current opened unit
        /// </summary>
        /// <param name="value">
        /// IParentUndoUnit to which OpenedUnit is to bet set
        /// </param>
        protected void SetOpenedUnit(IParentUndoUnit value)
        {
            _openedUnit = value;
        }

        /// <summary>
        /// Set LastUnit
        /// </summary>
        /// <param name="value">
        /// IUndoUnit to which LastUnit is to be set
        /// </param>
        protected void SetLastUnit(IUndoUnit value)
        {
            _lastUnit = value;
        }

        /// <summary>
        /// Returns the deepest open parent undo unit contained within this one.
        /// </summary>
        protected IParentUndoUnit DeepestOpenUnit
        {
            get
            {
                IParentUndoUnit openedUnit;

                openedUnit = OpenedUnit;
                if (openedUnit != null)
                {
                    while (openedUnit.OpenedUnit != null)
                    {
                        openedUnit = openedUnit.OpenedUnit;
                    }
                }
                return openedUnit;
            }
        }

        #endregion Protected Methods

        //------------------------------------------------------
        //
        //  Protected Properties
        //
        //------------------------------------------------------

        #region Protected Properties

        /// <summary>
        /// the undo stack
        /// </summary>
        protected List<IUndoUnit> UndoStack
        {
            get
            {
                return _undoStack;
            }
        }

        /// <summary>
        /// the redo stack
        /// </summary>
        protected Stack RedoStack
        {
            get
            {
                return _redoStack;
            }
        }

        #endregion Protected Properties

        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------

        #region Private Methods

        /// <summary>
        /// Performs the work of the clear operation.  Called by the public Clear() method,
        /// or by UndoManager itself if it wants to clear its stacks without regard to
        /// whether or not it's enabled.
        /// </summary>
        private void DoClear()
        {
            Invariant.Assert(!_imeSupportModeEnabled); // We can't clear the undo stack while ime code depends on it.

            if (UndoStack.Count > 0)
            {
                UndoStack.Clear();
                UndoStack.TrimExcess();
            }

            if (RedoStack.Count > 0)
            {
                RedoStack.Clear();
            }

            SetLastUnit(null);
            SetOpenedUnit(null);
            _topUndoIndex = -1;
            _bottomUndoIndex = 0;
        }

        private IUndoUnit PopUndoStack()
        {
            int undoCount = UndoCount - 1;
            IUndoUnit unit = (IUndoUnit)UndoStack[_topUndoIndex];
            UndoStack[_topUndoIndex--] = null;
            if (_topUndoIndex < 0 && undoCount > 0)
            {
                Invariant.Assert(UndoLimit > 0);
                _topUndoIndex = UndoLimit - 1;  // This should never be possible with an unlimited stack
            }

            return unit;
        }

        #endregion Private methods

        //------------------------------------------------------
        //
        //  Private Properties
        //
        //------------------------------------------------------

        #region Private Properties

        /// <summary>
        /// Property that identifies this service in ui element tree.
        /// </summary>
        private static readonly DependencyProperty UndoManagerInstanceProperty = DependencyProperty.RegisterAttached( //
            "UndoManagerInstance", typeof(UndoManager), typeof(UndoManager), //
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits));

        #endregion Private Properties

        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------

        #region Private Fields

        private DependencyObject _scope; // an element to which this instance of scope is attached
        private IParentUndoUnit _openedUnit;
        private IUndoUnit _lastUnit;
        private List<IUndoUnit> _undoStack;   // stack of undo units
        private Stack _redoStack;   // stack of redo units
        private UndoState _state;
        private bool _isEnabled;
        private IParentUndoUnit _lastReopenedUnit;
        private int _topUndoIndex;      // index of the topmost unit in the undo stack
        private int _bottomUndoIndex;   // index of the bottommost unit in the undo stack
        private int _undoLimit;         // maximum size of undo stack, -1 means infinite.
        private int _minUndoStackCount;
        private bool _imeSupportModeEnabled;

        // Default value for UndoLimitProperty. This is the same as the default for Win32 edit controls.
        // See http://msdn.microsoft.com/en-us/library/ms652191.aspx
        private const int _undoLimitDefaultValue = 100;

        #endregion Private Fields
    }
}
