using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using FooEditEngine;
using FooEditEngine.WPF;
using Prop = FooEditor.Properties;
using FooEditor.Plugin;

namespace FooEditor
{
    /// <summary>
    /// FindReplaceWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class FindReplaceWindow : UserControl,IToolWindow
    {
        static FindReplaceModel model;

        /// <summary>
        /// コンストラクター
        /// </summary>
        public FindReplaceWindow()
        {
            InitializeComponent();
            this.Title = "検索と置き換え";

            this.CommandBindings.Add(new CommandBinding(FindReplaceCommands.FindStart, FindStartCommand));
            this.CommandBindings.Add(new CommandBinding(FindReplaceCommands.ReplaceStart, ReplaceStartCommand,ReplaceCanExecute));
            this.CommandBindings.Add(new CommandBinding(FindReplaceCommands.ReplaceAllStart, ReplaceAlStartlCommand));
            this.DataContext = this;
        }

        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <param name="main">MainWindowオブジェクト</param>
        public FindReplaceWindow(MainWindow main) : this()
        {
            model = new FindReplaceModel(main);
        }
        /// <summary>
        /// 検索対象の文字列
        /// </summary>
        public string FindPattern
        {
            get { return (string)GetValue(FindPatternProperty); }
            set { SetValue(FindPatternProperty, value); }
        }

        // Using a DependencyProperty as the backing store for FindPattern.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty FindPatternProperty =
            DependencyProperty.Register("FindPattern", typeof(string), typeof(FindReplaceWindow), new PropertyMetadata(string.Empty, FindPatternChanged));

        /// <summary>
        /// 置き換え対象の文字列
        /// </summary>
        public string ReplacePattern
        {
            get { return (string)GetValue(ReplacePatternProperty); }
            set { SetValue(ReplacePatternProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ReplacePattern.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ReplacePatternProperty =
            DependencyProperty.Register("ReplacePattern", typeof(string), typeof(FindReplaceWindow), new PropertyMetadata(string.Empty));

        /// <summary>
        /// 正規表現を使用するなら真
        /// </summary>
        public bool UseRegEx
        {
            get { return (bool)GetValue(UseRegExProperty); }
            set { SetValue(UseRegExProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UseRegEx.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UseRegExProperty =
            DependencyProperty.Register("UseRegEx", typeof(bool), typeof(FindReplaceWindow), new PropertyMetadata(false));

        /// <summary>
        /// 大文字と小文字を区別するなら真
        /// </summary>
        public bool RestrictSearch
        {
            get { return (bool)GetValue(RestrictSearchProperty); }
            set { SetValue(RestrictSearchProperty, value); }
        }

        // Using a DependencyProperty as the backing store for RestrictSearch.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty RestrictSearchProperty =
            DependencyProperty.Register("RestrictSearch", typeof(bool), typeof(FindReplaceWindow), new PropertyMetadata(false));

        /// <summary>
        /// グループ置き換えを使用するなら真
        /// </summary>
        public bool UseGroup
        {
            get { return (bool)GetValue(UseGroupProperty); }
            set { SetValue(UseGroupProperty, value); }
        }

        // Using a DependencyProperty as the backing store for UseGroup.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty UseGroupProperty =
            DependencyProperty.Register("UseGroup", typeof(bool), typeof(FindReplaceWindow), new PropertyMetadata(false));


        /// <summary>
        /// すべてのドキュメントを対象にするなら真
        /// </summary>
        public bool AllDocuments
        {
            get { return (bool)GetValue(AllDocumentsProperty); }
            set { SetValue(AllDocumentsProperty, value); }
        }

        // Using a DependencyProperty as the backing store for AllDocuments.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AllDocumentsProperty =
            DependencyProperty.Register("AllDocuments", typeof(bool), typeof(FindReplaceWindow), new PropertyMetadata(false,AllDocumentChanged));

        /// <summary>
        /// アクティブかどうかを指定する
        /// </summary>
        public bool IsActive
        {
            get { return (bool)GetValue(IsActiveProperty); }
            set { SetValue(IsActiveProperty, value); }
        }

        // Using a DependencyProperty as the backing store for IsActive.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsActiveProperty =
            DependencyProperty.Register("IsActive", typeof(bool), typeof(FindReplaceWindow), new PropertyMetadata(false));

        
        static void FindPatternChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (model != null)
                model.Reset();
        }

        static void AllDocumentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if(model != null)
                model.Reset();
            model.AllDocuments = (bool)e.NewValue;
        }

        private void ReplaceCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = model.IsCanReplace();
        }

        /// <summary>
        /// ダイアログのタイトルを表す
        /// </summary>
        public string Title
        {
            get;
            set;
        }

        private RegexOptions DecideRegexOpt()
        {
            RegexOptions opt = RegexOptions.None;
            if (this.RestrictSearch == false)
                opt |= RegexOptions.IgnoreCase;
            return opt;
        }

        void FindStartCommand(object sender, ExecutedRoutedEventArgs e)
        {
            try
            {
                string err = model.FindNext(this.FindPattern,this.UseRegEx,DecideRegexOpt());
                if (err != null)
                    MessageBox.Show(err);
            }
            catch (ArgumentException ex)
            {
                MessageBox.Show(ex.Message);
                return;
            }
        }

        void ReplaceStartCommand(object sender, ExecutedRoutedEventArgs e)
        {
            model.Replace(this.ReplacePattern,this.UseGroup);
            this.FindStartCommand(sender, e);
        }
        
        void ReplaceAlStartlCommand(object sender, ExecutedRoutedEventArgs e)
        {
            string err = model.ReplaceAll(this.FindPattern,this.ReplacePattern,this.UseGroup,this.UseRegEx,DecideRegexOpt());
            if (err == null)
                MessageBox.Show(Prop.Resources.ReplaceAllComplete);
            else
                MessageBox.Show(err);
        }
    }

    public static class FindReplaceCommands
    {
        public static RoutedCommand FindStart = new RoutedCommand("FindStart", typeof(FindReplaceWindow));
        public static RoutedCommand ReplaceStart = new RoutedCommand("ReplaceStart", typeof(FindReplaceWindow));
        public static RoutedCommand ReplaceAllStart = new RoutedCommand("ReplaceAllStart", typeof(FindReplaceWindow));
    }

    class FindReplaceModel
    {
        MainWindow Main;
        DocumentWindow CurrentDocument;
        int currentIndex;
        const string SearchResultIteratorName = "SearchResult";
        bool canExecute = false;

        public const int FoundMarkerID = 2;

        public FindReplaceModel(MainWindow main)
        {
            this.Main = main;
            this.Main.ActiveDocumentChanged += Main_ActiveDocumentChanged;
            this.Main.Documents.CollectionChanged += Documents_CollectionChanged;
        }
        
        IEnumerator<SearchResult> GetSearchResultIterator(DocumentWindow doc)
        {
            object value;
            if (doc.ExtraDataCollection.TryGetValue("SearchResult", out value))
                return (IEnumerator<SearchResult>)value;
            else
                return null;
        }

        public bool AllDocuments
        {
            get;
            set;
        }

        void Documents_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            this.Reset();
        }

        void Main_ActiveDocumentChanged(object sender, EventArgs e)
        {
            this.CurrentDocument = this.Main.ActiveDocument;
        }

        public bool IsCanReplace()
        {
            return this.canExecute;
        }

        public void Reset(DocumentWindow targetDoc = null)
        {
            if (targetDoc == null)
            {
                foreach (DocumentWindow doc in this.Main.Documents)
                    doc.ExtraDataCollection.Remove(SearchResultIteratorName);
            }
            else
            {
                targetDoc.ExtraDataCollection.Remove(SearchResultIteratorName);
            }
            this.currentIndex = 0;
            this.CurrentDocument = null;
            this.canExecute = false;
        }
        
        public string FindNext(string pattern, bool useregex, RegexOptions opt)
        {
            if (this.CurrentDocument == null)
            {
                if (this.AllDocuments)
                    this.CurrentDocument = this.Main.Documents.First();
                else
                    this.CurrentDocument = this.Main.ActiveDocument;
                this.Main.ActivateDocument(this.CurrentDocument);
            }

        findstartbegin:

            if (this.AllDocuments && this.CurrentDocument != this.Main.Documents[this.currentIndex])
                this.Main.ActivateDocument(this.Main.Documents[this.currentIndex]);

            DocumentWindow docwnd = this.CurrentDocument;

            if (docwnd.TextBox.RectSelectMode)
            {
                return Prop.Resources.MustBeDisableRectSelect;
            }

            IEnumerator<SearchResult> it = this.GetSearchResultIterator(docwnd);
            if (it == null)
            {
                docwnd.TextBox.Document.SetFindParam(pattern, useregex, opt);
                Config config = Config.GetInstance();
                if (config.ShowFoundPattern)
                {
                    Color color = new Color(config.FoundMarker.A, config.FoundMarker.R, config.FoundMarker.G, config.FoundMarker.B);
                    docwnd.TextBox.MarkerPatternSet.Add(FoundMarkerID, docwnd.TextBox.Document.CreateWatchDogByFindParam(HilightType.Select, color));
                }
                it = docwnd.TextBox.Document.Find();
                docwnd.ExtraDataCollection.Add(SearchResultIteratorName, it);
            }

            if (!it.MoveNext())
            {
                docwnd.TextBox.DeSelectAll();
                if ((bool)this.AllDocuments)
                {
                    this.currentIndex++;
                    if (this.currentIndex < this.Main.Documents.Count)
                    {
                        this.Main.ActivateDocument(this.Main.Documents[this.currentIndex]);
                        goto findstartbegin;    //スタックの浪費を防ぐため
                    }
                    this.Reset();
                }
                else
                {
                    this.Reset(docwnd);
                }
                this.canExecute = true;
                return Prop.Resources.FindDialogNotFound;
            }

            SearchResult sr = it.Current;
            docwnd.TextBox.JumpCaret(sr.End);
            docwnd.TextBox.Select(sr.Start, sr.End - sr.Start + 1);
            docwnd.TextBox.Refresh();
            this.canExecute = true;
            return null;
        }

        public void Replace(string newpattern, bool usegroup)
        {
            DocumentWindow docwnd = this.CurrentDocument;
            IEnumerator<SearchResult> it = this.GetSearchResultIterator(docwnd);
            if (usegroup)
                docwnd.TextBox.SelectedText = it.Current.Result(newpattern);
            else
                docwnd.TextBox.SelectedText = newpattern;
        }

        public string ReplaceAll(string pattern, string newpattern, bool usegroup, bool useregex, RegexOptions opt)
        {
            IEnumerable<DocumentWindow> docwndit;

            if (this.AllDocuments)
                docwndit = this.Main.Documents;
            else
                docwndit = new DocumentWindow[] { this.Main.ActiveDocument };

            foreach (DocumentWindow docwnd in docwndit)
            {
                if (docwnd.TextBox.RectSelectMode)
                {
                    return Prop.Resources.MustBeDisableRectSelect;
                }
            }

            foreach (DocumentWindow docwnd in docwndit)
            {
                docwnd.TextBox.Document.FireUpdateEvent = false;
                if (useregex)
                {
                    docwnd.TextBox.Document.SetFindParam(pattern, useregex, opt);
                    docwnd.TextBox.Document.ReplaceAll(newpattern, usegroup);
                }
                else
                {
                    docwnd.TextBox.Document.ReplaceAll2(pattern, newpattern,(opt & RegexOptions.IgnoreCase) == RegexOptions.IgnoreCase);
                }
                docwnd.TextBox.Document.FireUpdateEvent = true;
                docwnd.TextBox.Refresh();
            }
            return null;
        }
    }
}
