// 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: The stock plain text editing control.
//

namespace System.Windows.Controls
{
    using MS.Internal;
    using System.Threading;
    using System.Collections; // IEnumerator
    using System.ComponentModel; // DefaultValue
    using System.Globalization;
    using System.Windows;
    using System.Windows.Media;
    using System.Windows.Data; // Binding
    using System.Windows.Documents;
    using System.Windows.Automation.Peers;
    using System.Windows.Input; // CanExecuteRoutedEventArgs, ExecuteRoutedEventArgs

    using System.Windows.Controls.Primitives; // TextBoxBase
    using System.Windows.Navigation;
    using System.Windows.Markup; // IAddChild, XamlDesignerSerializer, ContentPropertyAttribute
    using MS.Utility;
    using MS.Internal.Text;
    using MS.Internal.Automation;   // TextAdaptor
    using MS.Internal.Documents;    // Undo
    using MS.Internal.Commands;     // CommandHelpers
    using MS.Internal.Telemetry.PresentationFramework;

    /// <summary>
    /// The stock text editing control.
    /// </summary>
    [Localizability(LocalizationCategory.Text)]
    [ContentProperty("Text")]
    public class TextBox : TextBoxBase, IAddChild, ITextBoxViewHost
    {
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------

        #region Constructors

        /// <summary>
        /// Static constructor for TextBox.
        /// </summary>
        static TextBox()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(TextBox), new FrameworkPropertyMetadata(typeof(TextBox)));
            _dType = DependencyObjectType.FromSystemTypeInternal(typeof(TextBox));

            // Add handlers for height properties so we can manage min/maxLines
            PropertyChangedCallback callback = new PropertyChangedCallback(OnMinMaxChanged);

            HeightProperty.OverrideMetadata(typeof(TextBox), new FrameworkPropertyMetadata(callback));
            MinHeightProperty.OverrideMetadata(typeof(TextBox), new FrameworkPropertyMetadata(callback));
            MaxHeightProperty.OverrideMetadata(typeof(TextBox), new FrameworkPropertyMetadata(callback));
            FontFamilyProperty.OverrideMetadata(typeof(TextBox), new FrameworkPropertyMetadata(callback));
            FontSizeProperty.OverrideMetadata(typeof(TextBox), new FrameworkPropertyMetadata(callback));

            // Registering typography properties metadata
            PropertyChangedCallback onTypographyChanged = new PropertyChangedCallback(OnTypographyChanged);
            DependencyProperty[] typographyProperties = Typography.TypographyPropertiesList;
            for (int i = 0; i < typographyProperties.Length; i++)
            {
                typographyProperties[i].OverrideMetadata(typeof(TextBox), new FrameworkPropertyMetadata(onTypographyChanged));
            }

            HorizontalScrollBarVisibilityProperty.OverrideMetadata(typeof(TextBox), new FrameworkPropertyMetadata(
             ScrollBarVisibility.Hidden,
             new PropertyChangedCallback(OnScrollViewerPropertyChanged), // PropertyChangedCallback
             new CoerceValueCallback(CoerceHorizontalScrollBarVisibility)));

            ControlsTraceLogger.AddControl(TelemetryControls.TextBox);
            
            CommandHelpers.RegisterCommandHandler(typeof(TextBox), EditingCommands.Clear, OnClearCommand, new CanExecuteRoutedEventHandler(OnCanExecuteClearCommand));
        }

        /// <summary>
        /// Constructor
        /// </summary>
        public TextBox() : base()
        {
            // Register static editing command handlers.
            // This only has an effect that first time we make the call.
            // We don't use the static ctor because there are cases
            // where another control will want to alias our properties
            // but doesn't need this overhead.
            TextEditor.RegisterCommandHandlers(typeof(TextBox), /*acceptsRichContent:*/false, /*readOnly*/false, /*registerEventListeners*/false);

            // Create TextContainer and TextEditor associated with it
            TextContainer container = new TextContainer(this, true /* plainTextOnly */);
            container.CollectTextChanges = true;
            InitializeTextContainer(container);

            // TextBox only accepts plain text, so change TextEditor's default to that.
            this.TextEditor.AcceptsRichContent = false;

        }

        #endregion Constructors

        //------------------------------------------------------
        //
        //  Public Methods
        //
        //------------------------------------------------------

        #region Public Methods

        // -----------------------------------------------------------
        //
        // IAddChild interface
        //
        // -----------------------------------------------------------

        ///<summary>
        /// Called to Add the object as a Child.
        ///</summary>
        ///<param name="value">
        /// Object to add as a child
        ///</param>
        ///<remarks>
        /// This method will always throw InvalidOperationException because
        /// the TextBox only accepts plain text.
        ///</remarks>
        void IAddChild.AddChild(Object value)
        {
            ArgumentNullException.ThrowIfNull(value);

            // TextBox only accepts plain text, via IAddChild.AddText.
            throw new InvalidOperationException(SR.Format(SR.TextBoxInvalidChild, value.ToString()));
        }

        ///<summary>
        /// Called when text appears under the tag in markup.
        ///</summary>
        ///<param name="text">
        /// Text to Add to the Object
        ///</param>
        void IAddChild.AddText(string text)
        {
            ArgumentNullException.ThrowIfNull(text);

            this.TextContainer.End.InsertTextInRun(text);
        }

        /// <summary>
        /// Select the text in the given position and length.
        /// </summary>
        public void Select(int start, int length)
        {
            ArgumentOutOfRangeException.ThrowIfNegative(start);
            ArgumentOutOfRangeException.ThrowIfNegative(length);

            // Identify new position for selection Start
            int maxStart = TextContainer.SymbolCount;
            if (start > maxStart)
            {
                start = maxStart;
            }
            TextPointer newStart = this.TextContainer.CreatePointerAtOffset(start, LogicalDirection.Forward);

            // Normalize new start in some particular direction, to exclude ambiguity on surrogates bounndaries
            // and to start counting length from appropriate position.
            newStart = newStart.GetInsertionPosition(LogicalDirection.Forward);

            // Identify new position for selection End
            int maxLength = newStart.GetOffsetToPosition(TextContainer.End);
            if (length > maxLength)
            {
                length = maxLength;
            }
            TextPointer newEnd = new TextPointer(newStart, length, LogicalDirection.Forward);

            // Normalize end in some particular direction to exclude ambiguity on surrogate boundaries
            newEnd = newEnd.GetInsertionPosition(LogicalDirection.Forward);

            // Set new selection
            TextSelectionInternal.Select(newStart, newEnd);
        }

        /// <summary>
        /// Clear all the content in the TextBox control.
        /// </summary>
        public void Clear()
        {
            using (this.TextSelectionInternal.DeclareChangeBlock())
            {
                this.TextContainer.DeleteContentInternal((TextPointer)this.TextContainer.Start, (TextPointer)this.TextContainer.End);
                TextSelectionInternal.Select(this.TextContainer.Start, this.TextContainer.Start);
            }
        }

        /// <summary>
        /// Return the 0-based character index of the given point.  If there is no character
        /// at that point and snapToText is false, return -1.
        /// </summary>
        /// <param name="point">Point in TextBox coordinate space</param>
        /// <param name="snapToText">if true and there is no character at the given point, will return the nearest character</param>
        /// <returns>Character index at the given point, or -1</returns>
        public int GetCharacterIndexFromPoint(Point point, bool snapToText)
        {
            if (this.RenderScope == null)
            {
                return -1;
            }

            TextPointer textPointer = GetTextPositionFromPointInternal(point, snapToText);

            if (textPointer != null)
            {
                // offset corresponds to insertion position
                int offset = textPointer.Offset;

                // return character index based on orientation of TextPointer
                return (textPointer.LogicalDirection == LogicalDirection.Backward) ? offset - 1 : offset;
            }
            else
            {
                return -1;
            }
        }

        /// <summary>
        /// Return the 0-based character index of the first character of lineIndex.
        /// </summary>
        /// <param name="lineIndex">0-based index of the line for which we want the first character index</param>
        /// <returns>0-based index of the first character of lineIndex, or -1 if no layout information is available.</returns>
        public int GetCharacterIndexFromLineIndex(int lineIndex)
        {
            if (this.RenderScope == null)
            {
                return -1;
            }

            ArgumentOutOfRangeException.ThrowIfNegative(lineIndex);
            ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(lineIndex, LineCount);

            TextPointer textPointer = GetStartPositionOfLine(lineIndex);

            // textPointer will be null if there is no layout available.
            return (textPointer == null) ? 0 : textPointer.Offset;
        }

        /// <summary>
        /// Return the 0-based index of the line containing the given character index.
        /// </summary>
        /// <param name="charIndex">index of the character for which a line index is to be returned</param>
        /// <returns>
        /// 0-based index of the line containing the character at charIndex, or -1 if no
        /// layout information is available
        /// </returns>
        public int GetLineIndexFromCharacterIndex(int charIndex)
        {
            if (this.RenderScope == null)
            {
                return -1;
            }

            ArgumentOutOfRangeException.ThrowIfNegative(charIndex);
            ArgumentOutOfRangeException.ThrowIfGreaterThan(charIndex, this.TextContainer.SymbolCount);

            int line;
            TextPointer position = this.TextContainer.CreatePointerAtOffset(charIndex, LogicalDirection.Forward);

            if (position.ValidateLayout())
            {
                TextBoxView textboxView = (TextBoxView)this.RenderScope;
                line = textboxView.GetLineIndexFromOffset(charIndex);
            }
            else
            {
                line = -1;
            }

            return line;
        }

        /// <summary>
        /// Return the number of characters in the given line.
        /// </summary>
        /// <param name="lineIndex">0-based line index</param>
        /// <returns>number of characters in the given line, or -1 if no layout information is available</returns>
        public int GetLineLength(int lineIndex)
        {
            if (this.RenderScope == null)
            {
                return -1;
            }

            ArgumentOutOfRangeException.ThrowIfNegative(lineIndex);
            ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(lineIndex, LineCount);

            TextPointer textPointerStart = GetStartPositionOfLine(lineIndex);
            TextPointer textPointerEnd = GetEndPositionOfLine(lineIndex);
            int length;

            if (textPointerStart == null || textPointerEnd == null)
            {
                // No layout available.
                length = -1;
            }
            else
            {
                length = textPointerStart.GetOffsetToPosition(textPointerEnd);
            }

            return length;
        }

        /// <summary>
        /// Return the index of the first line that is currently visible in the TextBox.
        /// </summary>
        /// <returns>0-based index of the first visible line, or -1 if no layout information is available</returns>
        public int GetFirstVisibleLineIndex()
        {
            if (this.RenderScope == null)
            {
                return -1;
            }

            // Include an epsilon in the calculation below to account for floating
            // point rounding error.  Example: suppose we're looking for line 10.
            // Because of rounding error, we calculate line 9.9999, take the
            // Floor, and get the previous line.
            const double epsilon = 0.0001;
            double lineHeight = GetLineHeight();
            return (int)Math.Floor((this.VerticalOffset / lineHeight) + epsilon);
        }

        /// <summary>
        /// Return the index of the last line that is currently visible in the TextBox.
        /// </summary>
        /// <returns>0-based index of the last visible line, or -1 if no layout information is available</returns>
        public int GetLastVisibleLineIndex()
        {
            double height;

            if (this.RenderScope == null)
            {
                return -1;
            }

            height = ((IScrollInfo)this.RenderScope).ExtentHeight;

            if (this.VerticalOffset + this.ViewportHeight >= height)
            {
                return this.LineCount - 1;
            }
            else
            {
                return (int)Math.Floor((this.VerticalOffset + this.ViewportHeight - 1) / GetLineHeight());
            }
        }

        /// <summary>
        /// Scroll the minimal amount necessary to bring the given line into full view.
        /// </summary>
        /// <param name="lineIndex">line to scroll into view</param>
        public void ScrollToLine(int lineIndex)
        {
            if (this.RenderScope == null)
            {
                return;
            }

            ArgumentOutOfRangeException.ThrowIfNegative(lineIndex);
            ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(lineIndex, LineCount);

            TextPointer textPointer = GetStartPositionOfLine(lineIndex);
            Rect rect;
            if (GetRectangleFromTextPositionInternal(textPointer, false, out rect))
            {
                this.RenderScope.BringIntoView(rect);
            }
        }

        /// <summary>
        /// Get the text displayed at the given line.
        /// </summary>
        /// <param name="lineIndex">0-based index of the desired line</param>
        /// <returns>String containing a copy of the text at the given line index, or null if no layout information
        /// is available</returns>
        public String GetLineText(int lineIndex)
        {
            string text;
            TextPointer startOfLine;
            TextPointer endOfLine;

            if (this.RenderScope == null)
            {
                return null; // sentinel value
            }

            ArgumentOutOfRangeException.ThrowIfNegative(lineIndex);
            ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(lineIndex, LineCount);

            startOfLine = GetStartPositionOfLine(lineIndex);
            endOfLine = GetEndPositionOfLine(lineIndex);

            // startOfLine/endOfLine will be null if no layout is available.
            if (startOfLine != null && endOfLine != null)
            {
                text = TextRangeBase.GetTextInternal(startOfLine, endOfLine);
            }
            else
            {
                text = this.Text;
            }

            return text;
        }

        /// <summary>
        /// Get the rectangle for the leading edge of the character at the given index.
        /// </summary>
        /// <param name="charIndex">index of the desired character</param>
        /// <returns>leading edge rectangle of the given character, or Rect.Empty if no layout information is available.</returns>
        public Rect GetRectFromCharacterIndex(int charIndex)
        {
            return GetRectFromCharacterIndex(charIndex, /*trailingEdge*/false);
        }

        /// <summary>
        /// Get the rectangle for an edge of the character at the given index.
        /// </summary>
        /// <param name="charIndex">index of the desired character</param>
        /// <param name="trailingEdge">specifies an edge of the character bounding box</param>
        /// <returns>leading or trailing edge rectangle of the given character, or Rect.Empty if no layout information is available.</returns>
        public Rect GetRectFromCharacterIndex(int charIndex, bool trailingEdge)
        {
            ArgumentOutOfRangeException.ThrowIfNegative(charIndex);
            ArgumentOutOfRangeException.ThrowIfGreaterThan(charIndex, this.TextContainer.SymbolCount);

            // Start by moving to an insertion position in backward direction.
            // This ensures that when the character at charIndex is part of a surrogate pair or multi-byte character,
            // we handle leading/trailing edge correctly.

            TextPointer textPointer = TextContainer.CreatePointerAtOffset(charIndex, LogicalDirection.Backward);
            textPointer = textPointer.GetInsertionPosition(LogicalDirection.Backward);

            if (trailingEdge && charIndex < this.TextContainer.SymbolCount)
            {
                // Get next insertion position
                textPointer = textPointer.GetNextInsertionPosition(LogicalDirection.Forward);
                Invariant.Assert(textPointer != null);

                // Backward gravity for trailing edge
                textPointer = textPointer.GetPositionAtOffset(0, LogicalDirection.Backward);
            }
            else
            {
                // Forward gravity for leading edge
                textPointer = textPointer.GetPositionAtOffset(0, LogicalDirection.Forward);
            }

            // NB: rect will be Rect.Empty if no layout is available.
            Rect rect;
            GetRectangleFromTextPositionInternal(textPointer, /*relativeToTextBox*/true, out rect);
            return rect;
        }

        /// <summary>
        /// Returns the associated IndexedSpellingError at a specified character index.
        /// </summary>
        /// <param name="charIndex">
        /// Index of text to query.
        /// </param>
        /// <remarks>
        /// The charIndex paramter specifies a character to query.
        /// If the specificed character is not part of a misspelled word (or if
        /// IsSpellCheckEnabled == false) then this method will return null.
        /// </remarks>
        public SpellingError GetSpellingError(int charIndex)
        {
            ArgumentOutOfRangeException.ThrowIfNegative(charIndex);
            ArgumentOutOfRangeException.ThrowIfGreaterThan(charIndex, this.TextContainer.SymbolCount);

            TextPointer position = this.TextContainer.CreatePointerAtOffset(charIndex, LogicalDirection.Forward);
            SpellingError spellingError = this.TextEditor.GetSpellingErrorAtPosition(position, LogicalDirection.Forward);

            if (spellingError == null && charIndex < this.TextContainer.SymbolCount - 1)
            {
                position = this.TextContainer.CreatePointerAtOffset(charIndex + 1, LogicalDirection.Forward);
                spellingError = this.TextEditor.GetSpellingErrorAtPosition(position, LogicalDirection.Backward);
            }

            return spellingError;
        }

        /// <summary>
        /// Returns the start index of the first character of a spelling error
        /// containing a specified char.
        /// </summary>
        /// <param name="charIndex">
        /// Index of the character to query.
        /// </param>
        /// <returns>
        /// Start index of the spelling error containing a specified char, or -1 if
        /// the char is not part of a spelling error.
        /// </returns>
        public int GetSpellingErrorStart(int charIndex)
        {
            SpellingError spellingError = GetSpellingError(charIndex);

            return (spellingError == null) ? -1 : spellingError.Start.Offset;
        }

        /// <summary>
        /// Returns the length of the spelling error containing a specified char.
        /// </summary>
        /// <param name="charIndex">
        /// Index of the character to query.
        /// </param>
        /// <returns>
        /// Length of the spelling error containing a specified char, or 0 if
        /// the char is not part of a spelling error.
        /// </returns>
        public int GetSpellingErrorLength(int charIndex)
        {
            SpellingError spellingError = GetSpellingError(charIndex);

            return (spellingError == null) ? 0 : spellingError.End.Offset - spellingError.Start.Offset;
        }

        /// <summary>
        /// Returns the index of the next character in a specificed direction
        /// that is the start of a misspelled word.
        /// </summary>
        /// <param name="charIndex">
        /// Index of text to query.
        /// </param>
        /// <param name="direction">
        /// Direction to query.
        /// </param>
        /// <remarks>
        /// The charIndex paramter specifies a character at which to start the query.
        /// When direction == LogicalDirection.Forward, the search includes the
        /// spelling error containing charIndex (if any).
        /// When direction == LogicalDirection.Backward, the search does not
        /// include the error containing charIndex (if any).
        ///
        /// If no misspelled word is encountered, the method returns -1.
        /// </remarks>
        public int GetNextSpellingErrorCharacterIndex(int charIndex, LogicalDirection direction)
        {
            ArgumentOutOfRangeException.ThrowIfNegative(charIndex);
            ArgumentOutOfRangeException.ThrowIfGreaterThan(charIndex, this.TextContainer.SymbolCount);

            if (this.TextContainer.SymbolCount == 0)
            {
                // Early out on an empty doc to keep logic simpler below.
                return -1;
            }

            ITextPointer position = this.TextContainer.CreatePointerAtOffset(charIndex, direction);

            position = this.TextEditor.GetNextSpellingErrorPosition(position, direction);

            return (position == null) ? -1 : position.Offset;
        }

        #endregion Public Methods

        //------------------------------------------------------
        //
        //  Public Properties
        //
        //------------------------------------------------------

        #region Public Properties

        /// <summary>
        /// DependencyProperty for <see cref="TextWrapping" /> property.
        /// </summary>
        public static readonly DependencyProperty TextWrappingProperty =
                TextBlock.TextWrappingProperty.AddOwner(
                        typeof(TextBox),
                        new FrameworkPropertyMetadata(
                                TextWrapping.NoWrap,
                                FrameworkPropertyMetadataOptions.AffectsMeasure,
                                new PropertyChangedCallback(OnTextWrappingChanged)));

        /// <summary>
        /// The TextWrapping property controls whether or not text wraps
        /// when it reaches the flow edge of its containing block box.
        /// </summary>
        public TextWrapping TextWrapping
        {
            get
            {
                return (TextWrapping)GetValue(TextWrappingProperty);
            }
            set
            {
                SetValue(TextWrappingProperty, value);
            }
        }

        /// <summary>
        /// Dependency ID for the MinLines property
        /// Default value: 1
        /// </summary>
        public static readonly DependencyProperty MinLinesProperty =
                DependencyProperty.Register(
                        "MinLines", // Property name
                        typeof(int), // Property type
                        typeof(TextBox), // Property owner
                        new FrameworkPropertyMetadata(
                                1,
                                FrameworkPropertyMetadataOptions.AffectsMeasure,
                                new PropertyChangedCallback(OnMinMaxChanged)),
                        new ValidateValueCallback(MinLinesValidateValue));

        /// <summary>
        /// Minimum number of lines to size to.
        /// </summary>
        [DefaultValue(1)]
        public int MinLines
        {
            get { return (int) GetValue(MinLinesProperty); }
            set { SetValue(MinLinesProperty, value); }
        }

        /// <summary>
        /// Dependency ID for the MaxLines property
        /// Default value: MaxInt
        /// </summary>
        public static readonly DependencyProperty MaxLinesProperty =
                DependencyProperty.Register(
                        "MaxLines", // Property name
                        typeof(int), // Property type
                        typeof(TextBox), // Property owner
                        new FrameworkPropertyMetadata(
                                Int32.MaxValue,
                                FrameworkPropertyMetadataOptions.AffectsMeasure,
                                new PropertyChangedCallback(OnMinMaxChanged)),
                        new ValidateValueCallback(MaxLinesValidateValue));

        /// <summary>
        /// Minimum number of lines to size to.
        /// </summary>
        [DefaultValue(Int32.MaxValue)]
        public int MaxLines
        {
            get { return (int) GetValue(MaxLinesProperty); }
            set { SetValue(MaxLinesProperty, value); }
        }

        /// <summary>
        /// The DependencyID for the Text property.
        /// Default Value:      ""
        /// </summary>
        public static readonly DependencyProperty TextProperty =
                DependencyProperty.Register(
                        "Text", // Property name
                        typeof(string), // Property type
                        typeof(TextBox), // Property owner
                        new FrameworkPropertyMetadata( // Property metadata
                                string.Empty, // default value
                                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | // Flags
                                    FrameworkPropertyMetadataOptions.Journal,
                                new PropertyChangedCallback(OnTextPropertyChanged),    // property changed callback
                                new CoerceValueCallback(CoerceText),
                                true, // IsAnimationProhibited
                                UpdateSourceTrigger.LostFocus   // DefaultUpdateSourceTrigger
                                ));

        /// <summary>
        /// Contents of the TextBox.
        /// </summary>
        [DefaultValue("")]
        [Localizability(LocalizationCategory.Text)]
        public string Text
        {
            get { return (string) GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        /// <summary>
        /// The DependencyID for the CharacterCasing property.
        /// Controls whether or not input text is converted to upper or lower case
        /// Flags:              Can be used in style rules
        /// Default Value:      CharacterCasing.Normal
        /// </summary>
        public static readonly DependencyProperty CharacterCasingProperty =
                DependencyProperty.Register(
                        "CharacterCasing", // Property name
                        typeof(CharacterCasing), // Property type
                        typeof(TextBox), // Property owner
                        new FrameworkPropertyMetadata(CharacterCasing.Normal /*default value*/),
                        new ValidateValueCallback(CharacterCasingValidateValue) /*validation callback*/);

        /// <summary>
        /// Character casing of the TextBox
        /// </summary>
        public CharacterCasing CharacterCasing
        {
            get { return (CharacterCasing) GetValue(CharacterCasingProperty); }
            set { SetValue(CharacterCasingProperty, value); }
        }

        /// <summary>
        /// The limit number of characters that the textbox or other editable controls can contain.
        /// if it is 0, means no-limitation.
        /// User can set this value for some simple single line textbox to restrict the text number.
        /// RichTextBox doesn't have this limitation.
        /// By default it is 0.
        /// </summary>
        /// <remarks>
        /// When this property is set to zero, the maximum length of the text that can be entered
        /// in the control is limited only by available memory. You can use this property to restrict
        /// the length of text entered in the control for values such as postal codes and telephone numbers.
        /// You can also use this property to restrict the length of text entered when the data is to be entered
        /// in a database.
        /// You can limit the text entered into the control to the maximum length of the corresponding field in the database.
        /// Note:   In code, you can set the value of the Text property to a value that is larger than
        /// the value specified by the MaxLength property.
        /// This property only affects text entered into the control at runtime.
        /// </remarks>
        public static readonly DependencyProperty MaxLengthProperty =
                DependencyProperty.Register(
                    "MaxLength", // Property name
                    typeof(int), // Property type
                    typeof(TextBox), // Property owner
                    new FrameworkPropertyMetadata(0), /*default value*/
                    new ValidateValueCallback(MaxLengthValidateValue));


        /// <summary>
        /// Maximum number of characters the TextBox can accept
        /// </summary>
        [DefaultValue((int)0)]
        [Localizability(LocalizationCategory.None, Modifiability = Modifiability.Unmodifiable)] // cannot be modified by localizer
        public int MaxLength
        {
            get { return (int) GetValue(MaxLengthProperty); }
            set { SetValue(MaxLengthProperty, value); }
        }

        /// <summary>
        /// DependencyProperty for <see cref="TextAlignment" /> property.
        /// </summary>
        public static readonly DependencyProperty TextAlignmentProperty = Block.TextAlignmentProperty.AddOwner(typeof(TextBox));

        /// <summary>
        /// The TextAlignment property specifies horizontal alignment of the content.
        /// </summary>
        public TextAlignment TextAlignment
        {
            get
            {
                return (TextAlignment)GetValue(TextAlignmentProperty);
            }
            set
            {
                SetValue(TextAlignmentProperty, value);
            }
        }

        /// <summary>
        /// Selected Text
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public string SelectedText
        {
            get
            {
                return TextSelectionInternal.Text;
            }
            set
            {
                using (this.TextSelectionInternal.DeclareChangeBlock())
                {
                    TextSelectionInternal.Text = value;
                }
            }
        }

        /// <summary>
        /// Character number of the selected text
        /// </summary>
        /// <remarks>
        /// Length is calculated as unicode count, so it counts
        /// eacn \r\n combination as 2 - even though it is actially
        /// one caret position, and it would be illegal to insert
        /// any characters between them or expect selection ends
        /// to stay between them.
        /// Because of that after setting SelectionLength to some value
        /// it can be automatically corrected (by adding 1)
        /// if selection end happens to be between \r and \n.
        /// </remarks>
        [DefaultValue((int)0)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public int SelectionLength
        {
            get
            {
                return TextSelectionInternal.Start.GetOffsetToPosition(TextSelectionInternal.End);
            }
            set
            {
                ArgumentOutOfRangeException.ThrowIfNegative(value);

                // Identify new position for selection end
                int maxLength = TextSelectionInternal.Start.GetOffsetToPosition(TextContainer.End);
                if (value > maxLength)
                {
                    value = maxLength;
                }
                TextPointer newEnd = new TextPointer(TextSelectionInternal.Start, value, LogicalDirection.Forward);

                // Normalize end in some particular direction to exclude ambiguity on surrogate boundaries
                newEnd = newEnd.GetInsertionPosition(LogicalDirection.Forward);

                // Set new selection
                TextSelectionInternal.Select(TextSelectionInternal.Start, newEnd);
            }
        }

        /// <summary>
        /// The start position of the selection.
        /// </summary>
        /// <remarks>
        /// Index is calculated as unicode offset, so it counts
        /// eacn \r\n combination as 2 - even though it is actially
        /// one caret position, and it would be illegal to insert
        /// any characters between them or expect selection ends
        /// to stay between them.
        /// Because of that after setting SelectionStart to some value
        /// it can be automatically corrected (by adding 1)
        /// if it happens to be between \r and \n.
        /// </remarks>
        [DefaultValue((int)0)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public int SelectionStart
        {
            get
            {
                return this.TextSelectionInternal.Start.Offset;
            }
            set
            {
                ArgumentOutOfRangeException.ThrowIfNegative(value);

                // Store current length of the selection
                int selectionLength = TextSelectionInternal.Start.GetOffsetToPosition(TextSelectionInternal.End);

                // Identify new position for selection Start
                int maxStart = TextContainer.SymbolCount;
                if (value > maxStart)
                {
                    value = maxStart;
                }
                TextPointer newStart = TextContainer.CreatePointerAtOffset(value, LogicalDirection.Forward);

                // Normalize new start in some particular direction, to exclude ambiguity on surrogates bounndaries
                // and to start counting length from appropriate position.
                newStart = newStart.GetInsertionPosition(LogicalDirection.Forward);

                // Identify new position for selection End
                int maxLength = newStart.GetOffsetToPosition(TextContainer.End);
                if (selectionLength > maxLength)
                {
                    selectionLength = maxLength;
                }
                TextPointer newEnd = new TextPointer(newStart, selectionLength, LogicalDirection.Forward);

                // Normalize end in some particular direction to exclude ambiguity on surrogate boundaries
                newEnd = newEnd.GetInsertionPosition(LogicalDirection.Forward);

                // Set new selection
                TextSelectionInternal.Select(newStart, newEnd);
            }
        }

        /// <summary>
        /// Position of the caret.
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public int CaretIndex
        {
            get
            {
                return SelectionStart;
            }

            set
            {
                Select(value, 0);
            }
        }

        /// <summary>
        /// Number of lines in the TextBox.
        /// </summary>
        /// <value>number of lines in the TextBox, or -1 if no layout information is available</value>
        /// <remarks>
        /// If Wrap == true, changing the width of the TextBox may change this value.
        /// The value returned is the number of lines in the entire TextBox, regardless of how many are
        /// currently in view.
        /// </remarks>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public int LineCount
        {
            get
            {
                if (this.RenderScope == null)
                {
                    return -1;
                }

                return GetLineIndexFromCharacterIndex(this.TextContainer.SymbolCount) + 1;
            }
        }

        /// <summary>
        /// DependencyProperty for the TextDecorations property.
        /// </summary>
        public static readonly DependencyProperty TextDecorationsProperty =
                Inline.TextDecorationsProperty.AddOwner(
                        typeof(TextBox),
                        new FrameworkPropertyMetadata(
                            new FreezableDefaultValueFactory(TextDecorationCollection.Empty),
                            FrameworkPropertyMetadataOptions.AffectsRender));

        /// <summary>
        /// Property used to apply decorations such as underline to content.
        /// </summary>
        public TextDecorationCollection TextDecorations
        {
            get { return (TextDecorationCollection)GetValue(TextDecorationsProperty); }
            set { SetValue(TextDecorationsProperty, value); }
        }


        /// <summary>
        /// Access to all text typography properties.
        /// </summary>
        public Typography Typography
        {
            get
            {
                return new Typography(this);
            }
        }

        /// <summary>
        /// Property for <see cref="TemplateButtonCommand"/>.
        /// </summary>
        internal static readonly DependencyProperty TemplateButtonCommandProperty = DependencyProperty.Register(
            nameof(TemplateButtonCommand),
            typeof(RoutedCommand),
            typeof(TextBox),
            new PropertyMetadata(EditingCommands.Clear)
        );

        #region Properties

        /// <summary>
        /// Command triggered after clicking the button.
        /// </summary>
        internal RoutedCommand TemplateButtonCommand => (RoutedCommand)GetValue(TemplateButtonCommandProperty);

        #endregion

        /// <summary>
        /// Triggered when the user clicks the clear text button.
        /// </summary>
        private static void OnClearCommand(object target, ExecutedRoutedEventArgs args)
        {
            if (target is TextBox textBox)
                textBox.OnClearButtonClick();
        }

        private static void OnCanExecuteClearCommand(object target, CanExecuteRoutedEventArgs args)
        {
            if (target is TextBox textBox)
            {
                args.CanExecute =  !textBox.IsReadOnly
                                    && textBox.IsEnabled
                                    && textBox.Text.Length > 0;
            }
        }

        private void OnClearButtonClick()
        {
            if (Text.Length > 0)
                Text = string.Empty;
        }

        #endregion Public Properties

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

        #region Protected Methods

        /// <summary>
        /// Creates AutomationPeer (<see cref="UIElement.OnCreateAutomationPeer"/>)
        /// </summary>
        protected override AutomationPeer OnCreateAutomationPeer()
        {
            return new TextBoxAutomationPeer(this);
        }

        ///
        /// <see cref="FrameworkElement.OnPropertyChanged"/>
        ///
        protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            //  always call base.OnPropertyChanged, otherwise Property Engine will not work.
            base.OnPropertyChanged(e);

            if (this.RenderScope != null)
            {
                FrameworkPropertyMetadata fmetadata = e.Property.GetMetadata(typeof(TextBox)) as FrameworkPropertyMetadata;
                if (fmetadata != null)
                {
                    // We need to check for TextAlignmentProperty specifically since a local value change might require a render
                    // update even though e.IsAValueChange is false (see TextBoxView.CalculatedTextAlignment).
                    if (e.IsAValueChange || e.IsASubPropertyChange || e.Property == TextBox.TextAlignmentProperty)
                    {
                        if (fmetadata.AffectsMeasure || fmetadata.AffectsArrange ||
                            fmetadata.AffectsParentMeasure || fmetadata.AffectsParentArrange ||
                            e.Property == Control.HorizontalContentAlignmentProperty || e.Property == Control.VerticalContentAlignmentProperty)
                        {
                            ((TextBoxView)this.RenderScope).Remeasure();
                        }
                        else if (fmetadata.AffectsRender &&
                                (e.IsAValueChange || !fmetadata.SubPropertiesDoNotAffectRender))
                        {
                            ((TextBoxView)this.RenderScope).Rerender();
                        }

                        if (Speller.IsSpellerAffectingProperty(e.Property) &&
                            this.TextEditor.Speller != null)
                        {
                            this.TextEditor.Speller.ResetErrors();
                        }
                    }
                }
            }

            TextBoxAutomationPeer peer = UIElementAutomationPeer.FromElement(this) as TextBoxAutomationPeer;
            if (peer != null)
            {
                if (e.Property == TextProperty)
                {
                    peer.RaiseValuePropertyChangedEvent((string)e.OldValue, (string)e.NewValue);
                }

                if (e.Property == IsReadOnlyProperty)
                {
                    peer.RaiseIsReadOnlyPropertyChangedEvent((bool)e.OldValue, (bool)e.NewValue);
                }
            }
        }

        /// <summary>
        /// Returns enumerator to logical children.
        /// </summary>
        protected internal override IEnumerator LogicalChildren
        {
            get
            {
                //  We don't need this type of serialization.
                // This is an atavism from TextBoxBase gereric implementation
                // We can simply return a string.
                return new RangeContentEnumerator((TextPointer)this.TextContainer.Start, (TextPointer)this.TextContainer.End);
            }
        }

        /// <summary>
        /// Measurement override. Implement your size-to-content logic here.
        /// </summary>
        /// <param name="constraint">
        /// Sizing constraint.
        /// </param>
        protected override Size MeasureOverride(Size constraint)
        {
            if (MinLines > 1 && MaxLines < MinLines)
            {
                throw new Exception(SR.TextBoxMinMaxLinesMismatch);
            }

            Size size = base.MeasureOverride(constraint);

            if (_minmaxChanged)
            {
                // If there is a scrollViewer, we'll listen to the ScrollChanged event and
                // handle min/maxLines there.
                if (this.ScrollViewer == null)
                {
                    SetRenderScopeMinMaxHeight();
                }
                else
                {
                    SetScrollViewerMinMaxHeight();
                }
                _minmaxChanged = false;
            }

            return size;
        }

        // Called every time after Wrap property gets new value
        internal void OnTextWrappingChanged()
        {
            CoerceValue(HorizontalScrollBarVisibilityProperty);
        }

        // Allocates the initial render scope for this control.
        internal override FrameworkElement CreateRenderScope()
        {
            return new TextBoxView(this);
        }

        #endregion Protected Methods

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

        #region Internal Methods

        /// <summary>
        /// Detaches the editor from old visual tree and attaches it to a new one
        /// </summary>
        internal override void AttachToVisualTree()
        {
            base.AttachToVisualTree();

            if (this.RenderScope == null)
            {
                return;
            }

            // Set TextWrapping property for the new renderScope
            OnTextWrappingChanged();

            // We need to recalculate our min/max story.
            _minmaxChanged = true;
        }

        /// <summary>
        ///     Gives a string representation of this object.
        /// </summary>
        internal override string GetPlainText()
        {
            return this.Text;
        }

        ///
        // Returns the DependencyObjectType for the registered ThemeStyleKey's default
        // value. Controls will override this method to return approriate types.
        internal override DependencyObjectType DTypeThemeStyleKey
        {
            get
            {
                return _dType;
            }
        }

        /// <summary>
        /// Scroll content by one line to the top.
        /// </summary>
        internal override void DoLineUp()
        {
            if (this.ScrollViewer != null)
            {
                ScrollViewer.ScrollToVerticalOffset(VerticalOffset - GetLineHeight());
            }
        }

        /// <summary>
        /// Scroll content by one line to the bottom.
        /// </summary>
        internal override void DoLineDown()
        {
            if (this.ScrollViewer != null)
            {
                ScrollViewer.ScrollToVerticalOffset(VerticalOffset + GetLineHeight());
            }
        }

        /// <summary>
        /// Handler for TextContainer.Changed event.
        /// </summary>
        internal override void OnTextContainerChanged(object sender, TextContainerChangedEventArgs e)
        {
            bool resetText = false;
            string newTextValue = null;

            try
            {
                // if there are re-entrant changes, only raise public events
                // after the outermost change completes
                _changeEventNestingCount++;

                // Ignore property changes that originate from OnTextPropertyChange.
                if (!_isInsideTextContentChange)
                {
                    _isInsideTextContentChange = true;

                    // Use a DeferredTextReference instead of calculating the new
                    // value now for better performance.  Most of the time no
                    // one cares what the new value is, and loading our content into a
                    // string can be extremely expensive.
                    DeferredTextReference dtr = new DeferredTextReference(this.TextContainer);
                    _newTextValue = dtr;
                    SetCurrentDeferredValue(TextProperty, dtr);
                }
            }
            finally
            {
                _changeEventNestingCount--;
                if (_changeEventNestingCount == 0)
                {
                    // when Text is data-bound, _newTextValue is converted from a
                    // deferred reference to a string.  The binding writes the string
                    // back to the source, then computes a new value for Text (which
                    // may be different, either because the source normalizes the value
                    // or because of conversion and formatting).  Usually this raises
                    // a change notification for Text, which brings the Text property and
                    // the text container into sync.  But this doesn't happen in one
                    // case:  when the normalized value is the same as the original
                    // value for Text.  The property engine thinks that Text hasn't
                    // changed, and doesn't raise the notification.  It's true that
                    // Text hasn't changed, but we still need to update the text container,
                    // which now displays the wrong value.
                    // We detect that case by checking whether _newTextValue (the
                    // text container value) agrees with Text.
                    if (FrameworkCompatibilityPreferences.GetKeepTextBoxDisplaySynchronizedWithTextProperty())
                    {
                        newTextValue = _newTextValue as String;
                        resetText = (newTextValue != null && newTextValue != Text);
                    }

                    _isInsideTextContentChange = false;
                    _newTextValue = DependencyProperty.UnsetValue;
                }
            }

            if (resetText)
            {
                // The text container holds a new value which round-trips to the
                // old value of Text.  We need to bring the text container into sync.
                try
                {
                    _newTextValue = newTextValue;
                    _isInsideTextContentChange = true;
                    ++ _changeEventNestingCount;

                    OnTextPropertyChanged(newTextValue, Text);
                }
                finally
                {
                    -- _changeEventNestingCount;
                    _isInsideTextContentChange = false;
                    _newTextValue = DependencyProperty.UnsetValue;
                }
            }


            if (_changeEventNestingCount == 0)
            {
                // Let base raise the public TextBoxBase.TextChanged event.
                base.OnTextContainerChanged(sender, e);
            }
        }

        // if the DeferredTextReference is resolved to a string during the previous
        // method, track that here
        internal void OnDeferredTextReferenceResolved(DeferredTextReference dtr, string s)
        {
            if (dtr == _newTextValue)
            {
                _newTextValue = s;
            }
        }

        /// <summary>
        /// Handler for ScrollViewer's OnScrollChanged event.
        /// </summary>
        internal override void OnScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            base.OnScrollChanged(sender, e);

            if (e.ViewportHeightChange != 0)
            {
                SetScrollViewerMinMaxHeight();
            }
        }

        // this method is called by an editable ComboBox to raise a TextChanged event after
        // ComboBox.Text is changed outside the scope of a TextBox.Text change
        // (e.g. when an IME text composition has completed).   It's a courtesy to
        // controls and apps that assume every change to ComboBox.Text will be
        // followed by a TextBox.TextChanged event from the combobox's editable TextBox.
        internal void RaiseCourtesyTextChangedEvent()
        {
            OnTextChanged(new TextChangedEventArgs(TextChangedEvent, UndoAction.None));
        }

        //
        //  This property
        //  1. Finds the correct initial size for the _effectiveValues store on the current DependencyObject
        //  2. This is a performance optimization
        //
        internal override int EffectiveValuesInitialSize
        {
            get { return 42; }
        }

        #endregion Internal methods

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

        #region Internal Properties

        /// <summary>
        /// Text Selection (readonly)
        /// </summary>
        internal TextSelection Selection
        {
            get
            {
                return (TextSelection)TextSelectionInternal;
            }
        }

        /// <summary>
        /// TextPointer where the TextBox's text begins (readonly)
        /// </summary>
        internal TextPointer StartPosition
        {
            get
            {
                return (TextPointer)this.TextContainer.Start;
            }
        }

        /// <summary>
        /// TextPointer where the TextBox's text ends (readonly)
        /// </summary>
        internal TextPointer EndPosition
        {
            get
            {
                return (TextPointer)this.TextContainer.End;
            }
        }

        /// <summary>
        /// IsTypographyDefaultValue
        /// </summary>
        internal bool IsTypographyDefaultValue
        {
            get
            {
                return !_isTypographySet;
            }
        }

        // ITextContainer holding the Control content.
        ITextContainer ITextBoxViewHost.TextContainer
        {
            get
            {
                return this.TextContainer;
            }
        }

        // Set true when typography property values are all default values.
        bool ITextBoxViewHost.IsTypographyDefaultValue
        {
            get
            {
                return this.IsTypographyDefaultValue;
            }
        }

        #endregion Internal Properties

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

        #region Private Methods

        // Returns false if no layout is available.
        private bool GetRectangleFromTextPositionInternal(TextPointer position, bool relativeToTextBox, out Rect rect)
        {
            if (this.RenderScope == null)
            {
                rect = Rect.Empty;
                return false;
            }

            if (position.ValidateLayout())
            {
                rect = TextPointerBase.GetCharacterRect(position, position.LogicalDirection, relativeToTextBox);
            }
            else
            {
                rect = Rect.Empty;
            }

            return rect != Rect.Empty;
        }

        // Returns null if no layout is available.
        private TextPointer GetStartPositionOfLine(int lineIndex)
        {
            if (this.RenderScope == null)
            {
                return null;
            }

            Point point = new Point();

            // all lines in TextBox are the same height, so get the line height and multiply...
            double lineHeight = GetLineHeight();
            point.Y = lineHeight * lineIndex + (lineHeight / 2) - VerticalOffset;  // use a point in the middle of the line, to be safe
            point.X = -HorizontalOffset;

            TextPointer textPointer;

            if (TextEditor.GetTextView(this.RenderScope).Validate(point))
            {
                textPointer = (TextPointer)TextEditor.GetTextView(this.RenderScope).GetTextPositionFromPoint(point, /* snap to text */ true);
                textPointer = (TextPointer)TextEditor.GetTextView(this.RenderScope).GetLineRange(textPointer).Start.CreatePointer(textPointer.LogicalDirection);
            }
            else
            {
                textPointer = null;
            }

            return textPointer;
        }

        private TextPointer GetEndPositionOfLine(int lineIndex)
        {
            if (this.RenderScope == null)
            {
                return null;
            }

            // all lines in TextBox are the same height, so get the line height and multiply...
            Point point = new Point();

            double lineHeight = GetLineHeight();
            point.Y = lineHeight * lineIndex + (lineHeight / 2) - VerticalOffset;  // use a point in the middle of the line, to be safe
            point.X = 0;

            TextPointer textPointer;

            if (TextEditor.GetTextView(this.RenderScope).Validate(point))
            {
                textPointer = (TextPointer)TextEditor.GetTextView(this.RenderScope).GetTextPositionFromPoint(point, /* snap to text */ true);
                textPointer = (TextPointer)TextEditor.GetTextView(this.RenderScope).GetLineRange(textPointer).End.CreatePointer(textPointer.LogicalDirection);

                // Hit testing ignores line breaks, so the position returned will be between the last visible character
                // and the line break, if any.  We want the position AFTER the line break.
                if (TextPointerBase.IsNextToPlainLineBreak(textPointer, LogicalDirection.Forward))
                {
                    textPointer.MoveToNextInsertionPosition(LogicalDirection.Forward);
                }
            }
            else
            {
                textPointer = null;
            }

            return textPointer;
        }

        private static object CoerceHorizontalScrollBarVisibility(DependencyObject d, object value)
        {
            TextBox textBox = d as TextBox;

            if (textBox != null && (textBox.TextWrapping == TextWrapping.Wrap || textBox.TextWrapping == TextWrapping.WrapWithOverflow))
            {
                return ScrollBarVisibility.Disabled;
            }
            return value;
        }

        /// <summary>
        /// <see cref="DependencyProperty.ValidateValueCallback"/>
        /// </summary>
        private static bool MaxLengthValidateValue(object value)
        {
            return ((int)value) >= 0;
        }

        /// <summary>
        /// <see cref="DependencyProperty.ValidateValueCallback"/>
        /// </summary>
        private static bool CharacterCasingValidateValue(object value)
        {
            return (CharacterCasing.Normal <= (CharacterCasing)value && (CharacterCasing)value <= CharacterCasing.Upper);
        }

        /// <summary>
        /// <see cref="DependencyProperty.ValidateValueCallback"/>
        /// </summary>
        private static bool MinLinesValidateValue(object value)
        {
            return ((int)value > 0);
        }

        /// <summary>
        /// <see cref="DependencyProperty.ValidateValueCallback"/>
        /// </summary>
        private static bool MaxLinesValidateValue(object value)
        {
            return ((int)value > 0);
        }

        /// <summary>
        /// Callback for changes to the MinLines and MaxLines property
        /// </summary>
        private static void OnMinMaxChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TextBox textBox = (TextBox)d;

            textBox._minmaxChanged = true;
        }

        /// <summary>
        /// Callback for changes to the Text property
        /// </summary>
        private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TextBox textBox = (TextBox)d;

            if (textBox._isInsideTextContentChange)
            {
                // Ignore property changes that originate from OnTextContainerChanged,
                // unless they contain a different value (indicating that a
                // re-entrant call changed the value)
                if (textBox._newTextValue == DependencyProperty.UnsetValue ||
                    textBox._newTextValue is DeferredTextReference)
                {
                    // in this case, no re-entrant call could have happened, (it
                    // would have set _newTextValue to a string).
                    return;
                }

                // Otherwise we still need to check if the re-entrant call actually
                // changed the value.  That's done in the instance OnTextPropertyChanged method.
            }

            // if we get here, it's OK to ask for e.NewValue, which inflates the
            // deferred reference, because either (a) there is no deferred reference
            // (e.g. user set Text directly), or (b) it came from a re-entrant
            // change and we'll need the actual string anyway.
            textBox.OnTextPropertyChanged((string)e.OldValue, (string)e.NewValue);
        }

        private void OnTextPropertyChanged(string oldText, string newText)
        {
            bool inReentrantChange = false;
            int savedCaretIndex = 0;
            bool resetCaret = false;

            if (_isInsideTextContentChange)
            {
                // Ignore property changes that originate from OnTextContainerChanged,
                // unless they contain a different value (indicating that a
                // re-entrant call changed the value)
                if (Object.Equals(_newTextValue, newText))
                {
                    return;
                }

                // If we get this far, we're being called re-entrantly with a value
                // different from the one set by OnTextContainerChanged.  We should
                // honor this new value.
                inReentrantChange = true;
            }

            // CoerceText will have already converted null -> String.Empty,
            // but our default CoerceValueCallback could be overridden by a
            // derived class.  So check again here.
            if (newText == null)
            {
                newText = String.Empty;
            }

            bool hasExpression = HasExpression(LookupEntry(TextBox.TextProperty.GlobalIndex), TextBox.TextProperty);
            string oldTextForCaretIndexComputation = oldText;

            // A data-bound textbox sometimes has to display a value different from what
            // the user typed - when the value is changed by the data source or a converter
            // (this is the so-called "$10 feature").  When this happens, we should reposition
            // the caret within the new text at a position matching where it was in the old text,
            // so that the user can continue typing.  This can arise in two ways:
            //  a) re-entrant change (typing causes write-back which produces changed text)
            //  b) delayed change (binding with Delay writes back, producing changed text)
            // The old text is obtained differently in each case - this is handled by the callers.
            if (inReentrantChange)
            {
                resetCaret = true;

                // If this is a re-entrant change then the TextContainer has previously been updated
                // and the CaretIndex corresponds to the Text represented by the TextContainer.
                // Eg. If someone had an IntConverter on the two-way Binding for the TextProperty
                // which converted the string into an int and back, then this roundtripping operation
                // would automatically trim all leading zeros. So if the user typed "01" then the call
                // leading here from OnTextContainerChange's SetDeferredCurrentValue call will have
                // - the oldText as "0"
                // - the newText as "1" (after the conversion)
                // - the _newTextValue as "01" which is what the user typed in
                // - the CaretIndex here as 2 which corresponds to the _newTextValue not the oldText
                oldTextForCaretIndexComputation = (string)_newTextValue;
            }
            else if (hasExpression)
            {
                BindingExpressionBase beb = BindingOperations.GetBindingExpression(this, TextProperty);
                resetCaret = (beb != null) && beb.IsInUpdate && beb.IsInTransfer;
            }

            if (resetCaret)
            {
                savedCaretIndex = ChooseCaretIndex(CaretIndex, oldTextForCaretIndexComputation, newText);
            }

            if (inReentrantChange)
            {
                // we're about to change text container to hold newText.
                // update the cached proposed value accordingly
                _newTextValue = newText;
            }

            _isInsideTextContentChange = true;
            try
            {
                using (TextSelectionInternal.DeclareChangeBlock())
                {
                    // Update the text content with new TextProperty value.
                    TextContainer.DeleteContentInternal((TextPointer)TextContainer.Start, (TextPointer)TextContainer.End);
                    TextContainer.End.InsertTextInRun(newText);

                    // Collapse selection to the beginning of a text box
                    Select(savedCaretIndex, 0);
                }
            }
            finally
            {
                if (!inReentrantChange)
                {
                    _isInsideTextContentChange = false;
                }
            }

            // We need to clear undo stack in case when the value comes from
            // databinding or some other expression.
            if (hasExpression)
            {
                UndoManager undoManager = TextEditor._GetUndoManager();
                if (undoManager != null)
                {
                    if (undoManager.IsEnabled)
                        undoManager.Clear();
                }
            }
        }

        // return an index within newText that best approximates the old index
        // within oldText.   Called when the text container's content is changed
        // re-entrantly.
        private int ChooseCaretIndex(int oldIndex, string oldText, string newText)
        {
            // There is no exact algorithm for this.  Instead we use some heuristics.
            //   First handle some frequent special cases

            // oldText appears within newText, translate the index
            int index = newText.IndexOf(oldText, StringComparison.Ordinal);
            if (oldText.Length > 0 && index >= 0)
                return index + oldIndex;

            // caret was at one edge of oldText, return corresponding edge
            if (oldIndex == 0)
                return 0;
            if (oldIndex == oldText.Length)
                return newText.Length;

            // newText differs from oldText by a small replacement
            // (this is common when doing conversions to numeric types - adding
            // leading or trailing zeros, decimal separators, thousand separators,
            // etc.).
            // The two strings share a common prefix and suffix - find those
            int prefix, suffix;
            for (   prefix = 0;
                    prefix < oldText.Length && prefix < newText.Length;
                    ++prefix)
            {
                if (oldText[prefix] != newText[prefix])
                    break;
            }
            for (   suffix = 0;
                    suffix < oldText.Length && suffix < newText.Length;
                    ++suffix)
            {
                if (oldText[oldText.Length - 1 - suffix ] != newText[newText.Length - 1 - suffix])
                    break;
            }
            // if the prefix and suffix account for enough of the text, treat the
            // rest as a small replacement
            if ( 2*(prefix + suffix) >= Math.Min(oldText.Length, newText.Length))
            {
                // if the caret was in or next to the prefix or suffix, return the
                // corresponding position in newText
                if (oldIndex <= prefix)
                    return oldIndex;
                if (oldIndex >= oldText.Length - suffix)
                    return newText.Length - (oldText.Length - oldIndex);
            }

            // we're left with the hard case - newText is substantially different
            // from oldText.  Look for the longest matching substring that includes
            // the character just before the (old) caret - this is what the user
            // just typed, so it should participate in the match.
            char anchor = oldText[oldIndex - 1];
            int anchorIndex = newText.IndexOf(anchor);
            int bestIndex = -1;
            int bestLength = 1;     // match at least 2 chars

            while (anchorIndex >= 0)
            {
                int matchLength = 1;

                // match backward from the anchor position
                for (   index = anchorIndex - 1;
                        index >=0 && oldIndex - (anchorIndex - index) >= 0;
                        --index)
                {
                    if (newText[index] != oldText[oldIndex - (anchorIndex - index)])
                        break;
                    ++ matchLength;
                }

                // match forward from the anchor position
                for (   index = anchorIndex + 1;
                        index < newText.Length && oldIndex + (index - anchorIndex) < oldText.Length;
                        ++index)
                {
                    if (newText[index] != oldText[oldIndex + (index - anchorIndex)])
                        break;
                    ++ matchLength;
                }

                // remember the best match
                if (matchLength > bestLength)
                {
                    bestIndex = anchorIndex + 1;
                    bestLength = matchLength;
                }

                // advance to the next occurrence of the anchor character
                anchorIndex = newText.IndexOf(anchor, anchorIndex + 1);
            }

            // return the index of the best match.  If none found, put the cursor at the end
            return (bestIndex < 0) ? newText.Length : bestIndex;
        }

        /// <summary>
        /// Callback for changes to the TextWrapping property
        /// </summary>
        private static void OnTextWrappingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is TextBox)
            {
                ((TextBox)d).OnTextWrappingChanged();
            }
        }

        /// <summary>
        /// Update the value of ScrollViewer.MinHeight/MaxHeight
        /// </summary>
        private void SetScrollViewerMinMaxHeight()
        {
            if (this.RenderScope == null)
            {
                return;
            }

            if (ReadLocalValue(HeightProperty) != DependencyProperty.UnsetValue ||
                ReadLocalValue(MaxHeightProperty) != DependencyProperty.UnsetValue ||
                ReadLocalValue(MinHeightProperty) != DependencyProperty.UnsetValue)
            {
                // scrub ScrollViewer's min/max height if any height values are set on TextBox
                this.ScrollViewer.ClearValue(MinHeightProperty);
                this.ScrollViewer.ClearValue(MaxHeightProperty);
                return;
            }

            double chrome = this.ScrollViewer.ActualHeight - ViewportHeight;
            double lineHeight = GetLineHeight();
            double value = chrome + (lineHeight * MinLines);

            if (MinLines > 1 && this.ScrollViewer.MinHeight != value)
            {
                this.ScrollViewer.MinHeight = value;
            }

            value = chrome + (lineHeight * MaxLines);

            if (MaxLines < Int32.MaxValue && this.ScrollViewer.MaxHeight != value)
            {
                this.ScrollViewer.MaxHeight = value;
            }
        }


        /// <summary>
        /// Update the value of RenderScope.MinHeight/MaxHeight
        /// </summary>
        private void SetRenderScopeMinMaxHeight()
        {
            if (this.RenderScope == null)
            {
                return;
            }

            if (ReadLocalValue(HeightProperty) != DependencyProperty.UnsetValue ||
                ReadLocalValue(MaxHeightProperty) != DependencyProperty.UnsetValue ||
                ReadLocalValue(MinHeightProperty) != DependencyProperty.UnsetValue)
            {
                RenderScope.ClearValue(MinHeightProperty);
                RenderScope.ClearValue(MaxHeightProperty);
            }
            else
            {
                double lineHeight = GetLineHeight();
                double value = lineHeight * MinLines;

                if (MinLines > 1 && RenderScope.MinHeight != value)
                {
                    RenderScope.MinHeight = value;
                }

                value = lineHeight * MaxLines;

                if (MaxLines < Int32.MaxValue && RenderScope.MaxHeight != value)
                {
                    RenderScope.MaxHeight = value;
                }
            }
        }

        //
        // Called by MeasureOverride to get the height of one line of text in the current font.
        //
        private double GetLineHeight()
        {
            // change Text height based on line size
            FontFamily fontFamily = (FontFamily)this.GetValue(FontFamilyProperty);
            double fontSize = (double)this.GetValue(TextElement.FontSizeProperty);

            // If Ps Task 25254 is completed (not likely in V1), LineStackingStrategy
            // won't be constant and we'll need to call some sort of CalcLineAdvance method.
            double lineHeight;

            if (TextOptions.GetTextFormattingMode(this) == TextFormattingMode.Ideal)
            {
                lineHeight = fontFamily.LineSpacing * fontSize;
            }
            else
            {
                lineHeight = fontFamily.GetLineSpacingForDisplayMode(fontSize, GetDpi().DpiScaleY);
            }

            return lineHeight;
        }


        //
        // Only serialize Text when not using the XamlTextHostSerializer
        //
        /// <summary>
        /// This method is used by TypeDescriptor to determine if this property should
        /// be serialized.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Never)]
        public bool ShouldSerializeText(XamlDesignerSerializationManager manager)
        {
            return manager.XmlWriter == null;
        }

        //
        // Callback for command system to verify that the LineUp / LineDown commands should be enabled.
        // ScrollViewer always returns true, so we follow suit.
        //
        private static void OnQueryScrollCommand(object target, CanExecuteRoutedEventArgs args)
        {
            args.CanExecute = true;
        }

        // Callback from the property system, after a new value is set to the TextProperty.
        // Note we cannot assume value is a string here -- it may be a DeferredTextReference.
        private static object CoerceText(DependencyObject d, object value)
        {
            if (value == null)
            {
                return String.Empty;
            }

            return value;
        }

        //  typography properties changed, no cache for this, just reset the flag
        private static void OnTypographyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TextBox textbox = (TextBox)d;

            textbox._isTypographySet = true;
        }

        #endregion Private methods

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

        #region Private Fields

        private static DependencyObjectType _dType;

        //  We could pack all flags into one word and save some memory per TextBox

        // This flag is set when the MinLines or MaxLines properties are invalidated, and
        // checked in MeasureOverride.  When true, MeasureOverride calls SetMinMaxHeight to
        // make sure the change in min/max height happens immediately.
        private bool _minmaxChanged;

        // Flag used to prevent reentrancy between nested
        // OnTextPropertyChanged/OnTextContainerChanged callbacks.
        private bool _isInsideTextContentChange;
        private object _newTextValue = DependencyProperty.UnsetValue;

        // Flag used to indicate that Typography properties are not at default values
        private bool _isTypographySet;

        // depth of nested calls to OnTextContainerChanged.
        private int _changeEventNestingCount;


        #endregion Private Fields
    }
}
