/*
 * Copyright (C) 2013 FooProject
 * * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
#if METRO
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Automation;
using Windows.UI.Xaml.Automation.Peers;
using Windows.UI.Xaml.Automation.Provider;
using Windows.UI.Xaml.Automation.Text;
using FooEditEngine.Metro;
#endif
#if WPF
using System.Windows.Automation;
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;
using System.Windows.Automation.Text;
using FooEditEngine.WPF;
#endif

namespace FooEditEngine
{
    /// <summary>
    /// Automation Peer class for CustomInputBox2.  
    /// 
    /// Note: The difference between this and CustomControl1AutomationPeer is that this one implements
    /// Text Pattern (ITextProvider) and Value Pattern (IValuePattern) interfaces.  So Touch keyboard shows 
    /// automatically when user taps on the control with Touch or Pen.
    /// </summary>
    sealed class FooTextBoxAutomationPeer : FrameworkElementAutomationPeer, ITextProvider, IValueProvider
    {
        private FooTextBox fooTextBox;
        private string accClass = "FooTextBox";

        /// <summary>
        /// 
        /// </summary>
        /// <param name="owner"></param>
        public FooTextBoxAutomationPeer(FooTextBox owner)
            : base(owner)
        {
            this.fooTextBox = owner;
            this.fooTextBox.Document.Update += Document_Update;
        }

        void Document_Update(object sender, DocumentUpdateEventArgs e)
        {
            this.RaiseAutomationEvent(AutomationEvents.TextPatternOnTextChanged);
        }

        public void OnNotifyCaretChanged()
        {
            this.RaiseAutomationEvent(AutomationEvents.TextPatternOnTextSelectionChanged);
        }

#if METRO
        /// <summary>
        /// Override GetPatternCore to return the object that supports the specified pattern.  In this case the Value pattern, Text
        /// patter and any base class patterns.
        /// </summary>
        /// <param name="patternInterface"></param>
        /// <returns>the object that supports the specified pattern</returns>
        protected override object GetPatternCore(PatternInterface patternInterface)
        {
            if (patternInterface == PatternInterface.Value)
            {
                return this;
            }
            else if (patternInterface == PatternInterface.Text)
            {
                return this;
            }
            return base.GetPatternCore(patternInterface);
        }
#endif
#if WPF
        public override object GetPattern(PatternInterface patternInterface)
        {
            if (patternInterface == PatternInterface.Value)
            {
                return this;
            }
            else if (patternInterface == PatternInterface.Text)
            {
                return this;
            }
            return base.GetPattern(patternInterface);
        }
#endif

        /// <summary>
        /// Override GetClassNameCore and set the name of the class that defines the type associated with this control.
        /// </summary>
        /// <returns>The name of the control class</returns>
        protected override string GetClassNameCore()
        {
            return this.accClass;
        }

        protected override AutomationControlType GetAutomationControlTypeCore()
        {
            return AutomationControlType.Edit;
        }

        protected override bool IsContentElementCore()
        {
            return true;
        }

#if METRO
        protected override Windows.Foundation.Rect GetBoundingRectangleCore()
        {
            return Util.GetScreentRect(new Windows.Foundation.Rect(0, 0, this.fooTextBox.ActualWidth, this.fooTextBox.ActualHeight),this.fooTextBox);
        }
#endif
#if WPF
        protected override System.Windows.Rect GetBoundingRectangleCore()
        {
            System.Windows.Point left = this.fooTextBox.PointToScreen(new System.Windows.Point(0, 0));
            System.Windows.Point bottom = this.fooTextBox.PointToScreen(new System.Windows.Point(this.fooTextBox.ActualWidth, this.fooTextBox.ActualHeight));
            return new System.Windows.Rect(left, bottom);
        }
#endif

        #region Implementation for ITextPattern interface
        // Complete implementation of the ITextPattern is beyond the scope of this sample.  The implementation provided
        // is specific to this sample's custom control, so it is unlikely that they are directly transferable to other 
        // custom control.

        ITextRangeProvider ITextProvider.DocumentRange
        {
            // A real implementation of this method is beyond the scope of this sample.
            // If your custom control has complex text involving both readonly and non-readonly ranges, 
            // it will need a smarter implementation than just returning a fixed range
            get
            {
                return new FooTextBoxRangeProvider(this.fooTextBox, this);
            }
        }

        ITextRangeProvider[] ITextProvider.GetSelection()
        {
            ITextRangeProvider[] ret = new ITextRangeProvider[1];
            int selStart = this.fooTextBox.Selection.Index;
            int selLength = this.fooTextBox.Selection.Length;
            ret[0] = new FooTextBoxRangeProvider(this.fooTextBox, selStart, selLength, this);
            return ret;
        }

        ITextRangeProvider[] ITextProvider.GetVisibleRanges()
        {
            ITextRangeProvider[] ret = new ITextRangeProvider[1];
            if (this.fooTextBox.LayoutLineCollection.Count == 0)
            {
                ret[0] = new FooTextBoxRangeProvider(this.fooTextBox, 0, 0, this);
            }
            else
            {
#if METRO
                int startIndex = this.fooTextBox.GetIndexFromPostion(new Windows.Foundation.Point(0,0));
                int endIndex = this.fooTextBox.GetIndexFromPostion(new Windows.Foundation.Point(this.fooTextBox.ActualWidth, this.fooTextBox.ActualHeight));
#endif
#if WPF
                int startIndex = this.fooTextBox.GetIndexFromPostion(new System.Windows.Point(0, 0));
                int endIndex = this.fooTextBox.GetIndexFromPostion(new System.Windows.Point(this.fooTextBox.ActualWidth, this.fooTextBox.ActualHeight));
#endif
                ret[0] = new FooTextBoxRangeProvider(this.fooTextBox, startIndex, endIndex - startIndex, this);
            }
            return ret;
        }

        ITextRangeProvider ITextProvider.RangeFromChild(IRawElementProviderSimple childElement)
        {
            return new FooTextBoxRangeProvider(this.fooTextBox,0,0, this);
        }

#if METRO
        ITextRangeProvider ITextProvider.RangeFromPoint(Windows.Foundation.Point screenLocation)
        {
            Windows.Foundation.Point pt = Util.GetClientPoint(screenLocation, this.fooTextBox);

            int index = this.fooTextBox.GetIndexFromPostion(pt);
            int length = 1;
            if (index == this.fooTextBox.Document.Length)
                length = 0;
            
            return new FooTextBoxRangeProvider(this.fooTextBox, index, length, this);
        }
#endif
#if WPF
        ITextRangeProvider ITextProvider.RangeFromPoint(System.Windows.Point screenLocation)
        {
            System.Windows.Point pt = this.fooTextBox.PointFromScreen(screenLocation);

            int index = this.fooTextBox.GetIndexFromPostion(pt);
            int length = 1;
            if (index == this.fooTextBox.Document.Length)
                length = 0;

            return new FooTextBoxRangeProvider(this.fooTextBox, index, length, this);
        }
#endif

        SupportedTextSelection ITextProvider.SupportedTextSelection
        {
            get { return SupportedTextSelection.Single; }
        }

        #endregion

        #region Implementation for IValueProvider interface
        // Complete implementation of the IValueProvider is beyond the scope of this sample.  The implementation provided
        // is specific to this sample's custom control, so it is unlikely that they are directly transferable to other 
        // custom control.

        /// <summary>
        /// The value needs to be false for the Touch keyboard to be launched automatically because Touch keyboard
        /// does not appear when the input focus is in a readonly UI control.
        /// </summary>
        bool IValueProvider.IsReadOnly
        {
            get { return false; }
        }

        void IValueProvider.SetValue(string value)
        {
            string oldText = this.fooTextBox.Document.ToString(0);
            this.fooTextBox.Document.Replace(0,this.fooTextBox.Document.Length,value);
            this.RaisePropertyChangedEvent(ValuePatternIdentifiers.ValueProperty, oldText, this.fooTextBox.Document.ToString(0));
        }

        string IValueProvider.Value
        {
            get
            {
                return this.fooTextBox.Document.ToString(0,this.fooTextBox.Document.Length);
            }
        }

        #endregion //Implementation for IValueProvider interface

        public IRawElementProviderSimple GetRawElementProviderSimple()
        {
            return ProviderFromPeer(this);
        }
    }

    /// <summary>
    /// A minimal implementation of ITextRangeProvider, used by CustomControl2AutomationPeer
    /// A real implementation is beyond the scope of this sample
    /// </summary>
    sealed class FooTextBoxRangeProvider : ITextRangeProvider
    {
        private FooTextBox textbox;
        private Document document;
        private FooTextBoxAutomationPeer _peer;
        private int start, end;

        public FooTextBoxRangeProvider(FooTextBox textbox, FooTextBoxAutomationPeer peer)
            : this(textbox,0,textbox.Document.Length,peer)
        {
        }
        public FooTextBoxRangeProvider(FooTextBox textbox, int start, int length, FooTextBoxAutomationPeer peer)
        {
            this.textbox = textbox;
            this.document = textbox.Document;
            this.start = start;
            this.end = start + length;
            _peer = peer;
        }

        public void AddToSelection()
        {
            throw new InvalidOperationException();
        }

        public ITextRangeProvider Clone()
        {
            return new FooTextBoxRangeProvider(this.textbox,this.start,this.end - this.start, _peer);
        }

        public bool Compare(ITextRangeProvider o)
        {
            FooTextBoxRangeProvider other = o as FooTextBoxRangeProvider;
            if (other == null)
                throw new ArgumentNullException("null以外の値を指定してください");
            if (this.start == other.start && this.end == other.end)
                return true;
            else
                return false;
        }

        public int CompareEndpoints(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint)
        {
            FooTextBoxRangeProvider other = targetRange as FooTextBoxRangeProvider;
            
            if (other == null)
                throw new ArgumentException("");

            if (endpoint == TextPatternRangeEndpoint.Start)
            {
                if (targetEndpoint == TextPatternRangeEndpoint.Start)
                    return this.Compare(this.start,other.start);
                if(targetEndpoint == TextPatternRangeEndpoint.End)
                    return this.Compare(this.start,other.end);
            }
            if (endpoint == TextPatternRangeEndpoint.End)
            {
                if (targetEndpoint == TextPatternRangeEndpoint.Start)
                    return this.Compare(this.start, other.end);
                if (targetEndpoint == TextPatternRangeEndpoint.End)
                    return this.Compare(this.end, other.end);
            }
            throw new ArgumentException("endpointに未知の値が指定されました");
        }

        int Compare(int self, int other)
        {
            if (self < other)
                return -1;
            else if (self > other)
                return 1;
            else
                return 0;
        }

        public void ExpandToEnclosingUnit(TextUnit unit)
        {
            if (unit == TextUnit.Character)
            {
                if (this.start == this.end)
                {
                    if (this.start < this.document.Length)
                        this.end += 1;
                }
                return;
            }
            if (unit == TextUnit.Format || unit == TextUnit.Word || unit == TextUnit.Line)
            {
                LineToIndexTable layoutLineCollection = this.textbox.LayoutLineCollection;
                int row = layoutLineCollection.GetLineNumberFromIndex(this.start);
                this.start = layoutLineCollection.GetIndexFromLineNumber(row);
                this.end = this.start + layoutLineCollection.GetLengthFromLineNumber(row);
                return;
            }
            if (unit == TextUnit.Paragraph || unit == TextUnit.Page || unit == TextUnit.Document)
            {
                this.start = 0;
                this.end = this.document.Length;
                return;
            }
            throw new NotImplementedException();
        }

        public ITextRangeProvider FindAttribute(int attribute, Object value, bool backward)
        {
            return null;
        }

        public ITextRangeProvider FindText(String text, bool backward, bool ignoreCase)
        {
            if (backward)
                throw new NotImplementedException();
            document.SetFindParam(text, false, ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None);
            IEnumerator<SearchResult> it = document.Find();
            if (it.MoveNext())
            {
                SearchResult sr = it.Current;
                return new FooTextBoxRangeProvider(this.textbox, sr.Start, sr.End - sr.Start + 1, _peer);
            }
            return null;
        }

        public Object GetAttributeValue(int attribute)
        {
            return null;
        }

#if METRO
        public void GetBoundingRectangles(out double[] rectangles)
#endif
#if WPF
        public double[] GetBoundingRectangles()
#endif
        {
            LineToIndexTable layoutLineCollection = this.textbox.LayoutLineCollection;
            TextPoint topLeft = layoutLineCollection.GetTextPointFromIndex(this.start);
            TextPoint bottomRight = this.textbox.LayoutLineCollection.GetTextPointFromIndex(IsNewLine(this.end) ? this.end - 1 : this.end);


#if METRO
            Windows.Foundation.Point topLeftPos = this.textbox.GetPostionFromTextPoint(topLeft);
            Windows.Foundation.Point bottomRightPos = this.textbox.GetPostionFromTextPoint(bottomRight);
            topLeftPos = Util.GetScreentPoint(topLeftPos, this.textbox);
            bottomRightPos = Util.GetScreentPoint(bottomRightPos, this.textbox);
#endif
#if WPF
            System.Windows.Point topLeftPos = this.textbox.GetPostionFromTextPoint(topLeft);
            System.Windows.Point bottomRightPos = this.textbox.GetPostionFromTextPoint(bottomRight);
            topLeftPos = this.textbox.PointToScreen(topLeftPos);
            bottomRightPos = this.textbox.PointToScreen(bottomRightPos);
#endif

            double width = bottomRightPos.X - topLeftPos.X;
            if (width == 0)
                width = 1;
            Rectangle rect = new Rectangle(topLeftPos.X, topLeftPos.Y,
                 width,
                 bottomRightPos.Y - topLeftPos.Y + layoutLineCollection.GetLayout(bottomRight.row).Height);

#if METRO
            rectangles = new double[4]{
                rect.X,
                rect.Y,
                rect.Width,
                rect.Height
            };
#endif
#if WPF
            return new double[4]{
                rect.X,
                rect.Y,
                rect.Width,
                rect.Height
            };
#endif
        }

        bool IsNewLine(int index)
        {
            if (this.document.Length > 0)
                return this.document[index == 0 ? 0 : index - 1] == Document.NewLine;
            else
                return false;
        }

        public IRawElementProviderSimple[] GetChildren()
        {
            return new IRawElementProviderSimple[0];
        }

        public IRawElementProviderSimple GetEnclosingElement()
        {
            return _peer.GetRawElementProviderSimple();
        }

        public String GetText(int maxLength)
        {
            if (this.document.Length == 0)
                return "";
            int length = this.end - this.start;
            if (maxLength < 0)
                return this.document.ToString(this.start, length);
            else
                return this.document.ToString(this.start, (int)Math.Min(length, maxLength));
        }

        public int Move(TextUnit unit, int count)
        {
            if (count == 0)
                return 0;
            switch (unit)
            {
                case TextUnit.Character:
                    {
                        int moved;
                        this.start = this.MoveEndpointByCharacter(this.start,count,out moved);
                        this.end = this.start + 1;
                        return moved;
                    }
                case TextUnit.Format:
                case TextUnit.Word:
                case TextUnit.Line:
                    {
                        return this.MoveEndPointByLine(this.start, count, out this.start, out this.end);
                    }
                case TextUnit.Paragraph:
                case TextUnit.Page:
                case TextUnit.Document:
                    {
                        this.start = 0;
                        this.end = this.document.Length;
                        return 1;
                    }
            }
            throw new NotImplementedException();
        }

        public void MoveEndpointByRange(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint)
        {
            FooTextBoxRangeProvider other = targetRange as FooTextBoxRangeProvider;

            if (other == null)
                throw new ArgumentException("");

            if (endpoint == TextPatternRangeEndpoint.Start)
            {
                if (targetEndpoint == TextPatternRangeEndpoint.Start)
                    this.start = other.start;
                if (targetEndpoint == TextPatternRangeEndpoint.End)
                    this.start = other.end;
                if (this.start > this.end)
                    this.end = this.start;
                return;
            }
            if (endpoint == TextPatternRangeEndpoint.End)
            {
                if (targetEndpoint == TextPatternRangeEndpoint.Start)
                    this.end = other.start;
                if (targetEndpoint == TextPatternRangeEndpoint.End)
                    this.end = other.end;
                return;
            }
            throw new ArgumentException("endpointに未知の値が指定されました");
        }

        public int MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count)
        {
            if (count == 0)
                return 0;
            int moved = 0;
            if (unit == TextUnit.Character)
            {
                if (endpoint == TextPatternRangeEndpoint.Start)
                {
                    this.start = this.MoveEndpointByCharacter(this.start, count, out moved);
                    if(this.start > this.end)
                        this.end = this.start;
                }
                else if (endpoint == TextPatternRangeEndpoint.End)
                {
                    this.end = this.MoveEndpointByCharacter(this.end, count, out moved);
                    if (this.end < this.start)
                        this.start = this.end;
                }
            }
            if (unit == TextUnit.Format || unit == TextUnit.Word || unit == TextUnit.Line)
            {
                if (endpoint == TextPatternRangeEndpoint.Start)
                    return this.MoveEndPointByLine(this.start, count, out this.start, out this.end);
                else if (endpoint == TextPatternRangeEndpoint.End)
                    return this.MoveEndPointByLine(this.end, count, out this.start, out this.end);
            }
            if (unit == TextUnit.Paragraph || unit == TextUnit.Page || unit == TextUnit.Document)
            {
                this.start = 0;
                this.end = this.document.Length - 1;
                moved = 1;
            }
            return moved;
        }

        int MoveEndpointByCharacter(int index, int count,out int moved)
        {
            int newIndex = index + count - 1;
            if (newIndex > this.document.Length)
                newIndex = this.document.Length;
            if (newIndex < 0)
                newIndex = 0;
            moved = newIndex - index;
            return newIndex;
        }

        int MoveEndPointByLine(int index, int count,out int newStartInex,out int newEndInex)
        {
            LineToIndexTable layoutLineCollection = this.textbox.LayoutLineCollection;
            Controller controller = this.textbox.Controller;
            TextPoint currentPoint = layoutLineCollection.GetTextPointFromIndex(index);
            TextPoint newPoint = controller.GetTextPointAfterMoveLine(count, currentPoint);
            int lineLength = layoutLineCollection.GetLengthFromLineNumber(newPoint.row);
            newStartInex = layoutLineCollection.GetIndexFromLineNumber(newPoint.row);
            newEndInex = newStartInex + lineLength;
            return newPoint.row - currentPoint.row;
        }

        public void RemoveFromSelection()
        {
            throw new InvalidOperationException();
        }

        public void ScrollIntoView(bool alignToTop)
        {
            int row = this.textbox.LayoutLineCollection.GetLineNumberFromIndex(alignToTop ? this.start : this.end);
            this.textbox.ScrollIntoView(row, alignToTop);
        }

        public void Select()
        {
            this.textbox.Select(this.start, this.end - this.start + 1);
        }
    }
}
