//#define TEST_ASYNC
using System;
using Xceed.Wpf.AvalonDock;
using Xceed.Wpf.AvalonDock.Layout;
using System.IO;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Windows.Threading;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Runtime.Serialization.Formatters.Binary;
using Microsoft.WindowsAPICodePack.Taskbar;
using Microsoft.WindowsAPICodePack.Dialogs;
using Prop = FooEditor.Properties;
using FooEditEngine.WPF;
using FooEditor.Plugin;
using System.Runtime.CompilerServices;

namespace FooEditor
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window,INotifyPropertyChanged
    {
        ExplorerBar<FindReplaceWindow> findWindow;
        IPCServer Server;
        PluginManager<IPlugin> plugins;
        TaskbarManager Taskbar;

        public MainWindow()
        {
            InitializeComponent();

            this.CreatedDocument += new EventHandler<DocumentEventArgs>((s, e) => { });
            this.RemovedDocument += new EventHandler<DocumentEventArgs>((s, e) => { });
            this.ActiveDocumentChanged += new EventHandler((s, e) => { });

            this.DataContext = this;
            this.Documents = new ObservableCollection<DocumentWindow>();
            this.Tools = new ObservableCollection<IToolWindow>();

            InputMethod.Current.StateChanged +=Current_StateChanged;

            this.CommandBindings.Add(new CommandBinding(ApplicationCommands.New, NewCommand));
            this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Open, OpenCommand, CanExecute));
            this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Save, SaveCommand, CanExecute));
            this.CommandBindings.Add(new CommandBinding(ApplicationCommands.SaveAs, SaveAsCommand, CanExecute));
            this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Print, PrintCommand, CanExecute));
            this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Find, FindCommand, CanExecute));
            this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Replace, FindCommand, CanExecute));
            this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Properties, PropertiesCommand, CanExecute));
            this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Help, HelpCommand));
            this.CommandBindings.Add(new CommandBinding(FooEditorCommands.SelectDocumentType, SelectDocumentTypeCommand, CanExecute));
            this.CommandBindings.Add(new CommandBinding(FooEditorCommands.OpenRecentFile, OpenRecentFileCommand));
            this.CommandBindings.Add(new CommandBinding(FooEditorCommands.Grep, GrepCommand));
            this.CommandBindings.Add(new CommandBinding(FooEditorCommands.SaveWorkSpace, SaveWorkSpace, CanExecute));
            this.CommandBindings.Add(new CommandBinding(FooEditorCommands.Quit, QuitCommand));
            this.CommandBindings.Add(new CommandBinding(FooEditorCommands.About, AboutCommand));
            this.CommandBindings.Add(new CommandBinding(FooEditorCommands.LineJump, LineJumpCommand, CanExecute));
            this.CommandBindings.Add(new CommandBinding(FooEditorCommands.GenerateFolding, GenerateFolding, CanExecute));

            this.DockManager.ActiveContentChanged += new EventHandler(DockManager_ActiveContentChanged);
            this.DockManager.DocumentClosed += new EventHandler<DocumentClosedEventArgs>(DockManager_DocumentClosed);
            this.DockManager.DocumentClosing += new EventHandler<DocumentClosingEventArgs>(DockManager_DocumentClosing);
            this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
            this.Closing += new CancelEventHandler(MainWindow_Closing);
            this.Closed += new EventHandler(MainWindow_Closed);
            this.Activated += new EventHandler(MainWindow_Activated);
            this.PreviewDragOver += new DragEventHandler(MainWindow_PreviewDragOver);
            this.PreviewDrop += new DragEventHandler(MainWindow_PreviewDrop);

            this.findWindow = new ExplorerBar<FindReplaceWindow>();
            this.findWindow.Content = new FindReplaceWindow(this);
            this.RegisterExploereBar(this.findWindow);
            this.findWindow.IsVisible = false;

            Config config = Config.GetInstance();
            this.Width = config.Width;
            this.Height = config.Height;

            this.plugins = new PluginManager<IPlugin>(config.DontLoadPlugins);
            foreach (IPlugin plugin in this.plugins)
                plugin.Initalize(this);

            Application.Current.DispatcherUnhandledException += Current_DispatcherUnhandledException;

            Version ver = Environment.OSVersion.Version;
            if (ver.Major >= 6 && ver.Minor >= 1)
                this.Taskbar = TaskbarManager.Instance;
        }

        void Current_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
        {
            ExceptionDialog dialog = new ExceptionDialog();
            dialog.Exception = e.Exception;
            dialog.Owner = this;
            if (dialog.ShowDialog() == true)
                this.SaveWorkSpace(this, null);
            else
                e.Handled = true;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged([CallerMemberName] string name = "")
        {
            if (this.PropertyChanged != null)
                this.PropertyChanged(this, new PropertyChangedEventArgs(name));
        }

        private void Current_StateChanged(object sender, InputMethodStateChangedEventArgs e)
        {
            if (e.IsImeConversionModeChanged)
                this.OnPropertyChanged("ImeConversionMode");
            if (e.IsImeStateChanged)
                this.OnPropertyChanged("ImeState");
        }

        public InputMethodState ImeState
        {
            get
            {
                return InputMethod.Current.ImeState;
            }
        }

        public ImeConversionModeValues ImeConversionMode
        {
            get
            {
                return InputMethod.Current.ImeConversionMode;
            }
        }

        public DocumentWindow ActiveDocument
        {
            get { return (DocumentWindow)GetValue(ActiveDocumentProperty); }
            private set { SetValue(ActiveDocumentProperty, value); }
        }

        public static readonly DependencyProperty ActiveDocumentProperty =
            DependencyProperty.Register("ActiveDocument", typeof(DocumentWindow), typeof(MainWindow), new FrameworkPropertyMetadata(null));

        public event EventHandler ActiveDocumentChanged;

        public event EventHandler<DocumentEventArgs> CreatedDocument;

        public event EventHandler<DocumentEventArgs> RemovedDocument;

        public ObservableCollection<DocumentWindow> Documents
        {
            get;
            private set;
        }

        public ObservableCollection<IToolWindow> Tools
        {
            get;
            private set;
        }

        public RecentFileCollection RecentFiles
        {
            get
            {
                Config config = Config.GetInstance();
                return config.RecentFile;
            }
        }

        public DocumentTypeCollection DocumentTypes
        {
            get
            {
                Config config = Config.GetInstance();
                return config.SyntaxDefinitions;
            }
        }

        public static string PipeServerName
        {
            get
            {
                return Prop.Resources.IPCServerName + "." + Process.GetCurrentProcess().SessionId;
            }
        }

        public void RegisterExploereBar<T>(ExplorerBar<T> bar) where T : IToolWindow
        {
            this.Tools.Add(bar.Content);
        }

        public void ActivateDocument(DocumentWindow doc)
        {
            this.DockManager.ActiveContent = doc;
        }

        public DocumentWindow CreateDocument()
        {
            DocumentWindow newWindow = new DocumentWindow(this.Documents.Count);
            this.CreatedDocument(this, new DocumentEventArgs(newWindow));
            this.Documents.Add(newWindow);
            newWindow.Progress += Document_Progress;
            return newWindow;
        }

        void Document_Progress(object sender, FooEditEngine.ProgressEventArgs e)
        {
            if (this.Taskbar == null || this.IsActive == false)
                return;
            switch (e.state)
            {
                case FooEditEngine.ProgressState.Start:
                    this.Taskbar.SetProgressState(TaskbarProgressBarState.Indeterminate);
                    this.ProgressBar.IsIndeterminate = true;
                    this.ProgressBar.Visibility = Visibility.Visible;
                    break;
                case FooEditEngine.ProgressState.Complete:
                    this.Taskbar.SetProgressState(TaskbarProgressBarState.NoProgress);
                    this.ProgressBar.IsIndeterminate = false;
                    this.ProgressBar.Visibility = Visibility.Collapsed;
                    break;
            }
        }

        void MainWindow_PreviewDragOver(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
                e.Effects = DragDropEffects.Copy;
        }

        async void MainWindow_PreviewDrop(object sender, DragEventArgs e)
        {
            string[] filepaths = (string[])e.Data.GetData(DataFormats.FileDrop);
            if (filepaths == null)
                return;
            foreach (string filepath in filepaths)
            {
                DocumentWindow document = this.CreateDocument();
                await document.LoadAsync(filepath, null);
            }
        }

        void MainWindow_Closing(object sender, CancelEventArgs e)
        {
            bool hasDirtyDoc = false;
            foreach (DocumentWindow doc in this.Documents)
                if (doc.Dirty)
                    hasDirtyDoc = true;
            if (hasDirtyDoc == false)
                return;

            CustomMessageBox dialog = new CustomMessageBox(Prop.Resources.ConfirmDocumentClose,this.Title);
            dialog.AddButton("save", Prop.Resources.ConfirmDialogSaveButton);
            dialog.AddButton("nosave", Prop.Resources.ConfirmDialogNoSaveButton);
            dialog.AddButton("cancle", Prop.Resources.ConfirmDialogCancelButton);
            string result = dialog.Show();
            if (result == "cancle")
            {
                e.Cancel = true;
                return;
            }
            else if (result == "nosave")
            {
                return;
            }
            foreach (DocumentWindow doc in this.Documents)
                if (doc.Dirty)
                {
                    Task t = this.Save(doc);
                    t.Wait();
                }
        }

        void MainWindow_Activated(object sender, EventArgs e)
        {
            if (this.ActiveDocument != null)
                this.ActiveDocument.TextBox.Focus();
        }

        void MainWindow_Closed(object sender, System.EventArgs e)
        {
            if (this.plugins != null)
            {
                foreach (IPlugin plugin in this.plugins)
                    plugin.ClosedApp();
            }
            Config config = Config.GetInstance();
            config.Width = this.Width;
            config.Height = this.Height;
            config.Save();
            if(this.Server != null)
                this.Server.Dispose();
        }

        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            DocumentWindow document = null;
            if(this.LoadWorkSpace())
                document = this.CreateDocument();

            Server = new IPCServer(MainWindow.PipeServerName);
            Server.Recive += new ServerReciveEventHandler(Server_Recive);

            App.ParseArgs(Environment.GetCommandLineArgs());

            if (document != null)
                document.TextBox.Document.Load(Console.In);
        }

        void Server_Recive(object sender, ServerReciveEventArgs e)
        {
            string data = e.StreamReader.ReadLine();

            PipeCommandListener listener = new PipeCommandListener(this);

            listener.Execute(data);
        }

        void DockManager_DocumentClosing(object sender, DocumentClosingEventArgs e)
        {
            DocumentWindow document = (DocumentWindow)e.Document.Content;
            if (document.Dirty == false)
                return;
            CustomMessageBox dialog = new CustomMessageBox(Prop.Resources.ConfirmDocumentClose, document.Title);
            dialog.AddButton("save", Prop.Resources.ConfirmDialogSaveButton);
            dialog.AddButton("nosave", Prop.Resources.ConfirmDialogNoSaveButton);
            dialog.AddButton("cancle", Prop.Resources.ConfirmDialogCancelButton);
            string result = dialog.Show();
            if (result == "save")
            {
                Task t;
                if (document.FilePath == null || document.FilePath == string.Empty)
                    t = this.SaveAs(document);
                else
                    t = document.SaveAsync(document.FilePath, document.Encoding);
                t.Wait();
                return;
            }
            else if (result == "cancle")
            {
                e.Cancel = true;
                return;
            }
        }

        void DockManager_DocumentClosed(object sender, DocumentClosedEventArgs e)
        {
            DocumentWindow document = (DocumentWindow)e.Document.Content;
            this.Documents.Remove(document);
            this.RemovedDocument(this, new DocumentEventArgs(document));
            document.Dispose();

            if (this.Documents.Count == 0)
            {
                this.ActiveDocument = null;
                this.ActiveDocumentChanged(this, null);
            }

            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
        }

        void DockManager_ActiveContentChanged(object sender, EventArgs e)
        {
            DocumentWindow document = this.DockManager.ActiveContent as DocumentWindow;
            if (document == null)
                return;

            this.ActiveDocument = document;
            this.ActiveDocumentChanged(this, null);
        }

        bool LoadWorkSpace()
        {
            string recoveryStateFilePattern = string.Format(Prop.Resources.RecoveryState, "*", "");
            if (Directory.Exists(Config.ApplicationFolder) == false)
                return true;
            string[] files = Directory.GetFiles(Config.ApplicationFolder, recoveryStateFilePattern);
            if (files.Length > 0 && MessageBox.Show(Prop.Resources.RecoveryConfirm, "", MessageBoxButton.YesNo) == MessageBoxResult.No)
            {
                for (int i = 0; i < files.Length; i++)
                    File.Delete(files[i]);
                return true;
            }
            else if(files.Length == 0)
            {
                return true;
            }
            for (int i = 0; i < files.Length; i++)
            {
                BinaryFormatter formatter = new BinaryFormatter();
                FileStream fs = new FileStream(files[i], FileMode.Open, FileAccess.Read);
                DocumentWindow document = (DocumentWindow)formatter.Deserialize(fs);
                fs.Close();
                File.Delete(files[i]);
                this.CreatedDocument(this, new DocumentEventArgs(document));
                this.Documents.Add(document);
            }
            return false;
        }

        #region Command
        void CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = this.ActiveDocument != null;
            if (this.ActiveDocument != null)
                e.CanExecute = this.ActiveDocument.TextBox.Document.State == FooEditEngine.AsyncState.None;
            else
                e.CanExecute = false;
        }

        void GenerateFolding(object sender, ExecutedRoutedEventArgs e)
        {
            DocumentWindow document = this.ActiveDocument;
            if (document == null)
                return;
            document.TextBox.LayoutLineCollection.GenerateFolding(true);
            document.TextBox.Refresh();
        }

        void LineJumpCommand(object sender, ExecutedRoutedEventArgs e)
        {
            DocumentWindow document = this.ActiveDocument;
            if (document == null)
                return;
            LineJumpDialog dialog = new LineJumpDialog();
            dialog.TextBox = document.TextBox;
            dialog.Owner = this;
            dialog.ShowDialog();
        }

        void PrintCommand(object sender, ExecutedRoutedEventArgs e)
        {
            DocumentWindow document = this.ActiveDocument;
            if (document == null)
                return;
            PrintDialog pd = new PrintDialog();
            pd.PageRangeSelection = PageRangeSelection.AllPages;
            pd.UserPageRangeEnabled = true;
            if (pd.ShowDialog() == false)
                return;
            Config config = Config.GetInstance();
            FooPrintText printtext = new FooPrintText();
            PrintHFParser Parser = new PrintHFParser(document);
            printtext.Document = document.TextBox.Document;
            printtext.Font = document.TextBox.FontFamily;
            printtext.FontSize = document.TextBox.FontSize;
            printtext.DrawLineNumber = document.TextBox.DrawLineNumber;
            printtext.Header = config.Header;
            printtext.Footer = config.Footer;
            printtext.ParseHF = Parser.Parse;
            printtext.LineBreakMethod = document.TextBox.LineBreakMethod;
            printtext.LineBreakCharCount = document.TextBox.LineBreakCharCount;
            printtext.MarkURL = document.TextBox.MarkURL;
            printtext.Hilighter = document.TextBox.Hilighter;
            printtext.Foreground = document.TextBox.Foreground;
            printtext.URL = document.TextBox.URL;
            printtext.Comment = document.TextBox.Comment;
            printtext.Keyword1 = document.TextBox.Keyword1;
            printtext.Keyword2 = document.TextBox.Keyword2;
            printtext.Litral = document.TextBox.Literal;
            if (pd.PageRangeSelection == PageRangeSelection.AllPages)
            {
                printtext.StartPage = -1;
                printtext.EndPage = -1;
            }
            else
            {
                printtext.StartPage = pd.PageRange.PageFrom;
                printtext.EndPage = pd.PageRange.PageTo;
            }
            printtext.PageRect = new Rect(config.LeftSpace,
                config.TopSpace,
                pd.PrintableAreaWidth - config.RightSpace,
                pd.PrintableAreaHeight - config.BottomSpace);
            printtext.Print(pd);
        }

        void SelectDocumentTypeCommand(object sender, ExecutedRoutedEventArgs e)
        {
            DocumentWindow document = this.ActiveDocument;
            if (document == null)
                return;
            string type = (string)e.Parameter;
            document.DocumentType = (string)type;
            this.DocumentTypes.Select(type);
        }

        void PropertiesCommand(object sender, ExecutedRoutedEventArgs e)
        {
            ConfigDialog cd = new ConfigDialog(this.plugins);
            cd.Owner = this;
            if (cd.ShowDialog() == false)
                return;
            Config config = Config.GetInstance();
            foreach (DocumentWindow document in this.Documents)
            {
                document.ApplyConfig(config);
            }
        }

        void QuitCommand(object sender, ExecutedRoutedEventArgs e)
        {
            this.Close();
        }

        void GrepCommand(object sender, ExecutedRoutedEventArgs e)
        {
            Config config = Config.GetInstance();
            if (!File.Exists(config.GrepPath))
                return;
            ProcessStartInfo info = new ProcessStartInfo();
            info.FileName = config.GrepPath;
            info.Verb = "open";
            info.UseShellExecute = true;
            Process process = Process.Start(info);
        }

        void NewCommand(object sender, ExecutedRoutedEventArgs e)
        {
            this.CreateDocument();
        }

        async void OpenRecentFileCommand(object sender, ExecutedRoutedEventArgs e)
        {
            DocumentWindow document = this.ActiveDocument;
            string filepath = (string)e.Parameter;
            if (document == null || document.FilePath != null || document.Dirty)
                document = this.CreateDocument();
            await document.LoadAsync(filepath, null);
        }

        async void OpenCommand(object sender, ExecutedRoutedEventArgs e)
        {
            DocumentWindow document = this.ActiveDocument;
            CustomOpenFileDialog ofd = new CustomOpenFileDialog();
            ofd.addFilter(Prop.Resources.AllFileLable, "*.*");
            if (ofd.ShowDialog() == CommonFileDialogResult.Ok)
            {
                if (document == null || document.FilePath != null || document.Dirty)
                    document = this.CreateDocument();
                await document.LoadAsync(ofd.FileName, ofd.FileEncoding);
            }
            ofd.Dispose();
        }

        async void SaveCommand(object sender, ExecutedRoutedEventArgs e)
        {
            DocumentWindow document = this.ActiveDocument;
            await this.Save(document);
        }
        
        async void SaveAsCommand(object sender, ExecutedRoutedEventArgs e)
        {
            DocumentWindow document = this.ActiveDocument;
            await this.SaveAs(document);
        }

        async Task Save(DocumentWindow document)
        {
            if (document == null)
                return;
            if (document.FilePath == string.Empty || document.FilePath == null)
                await this.SaveAs(document);
            else
                await document.SaveAsync(document.FilePath, document.Encoding);
        }

        async Task SaveAs(DocumentWindow document)
        {
            if (document == null)
                return;
            CustomSaveFileDialog sfd = new CustomSaveFileDialog();
            sfd.addFilter(Prop.Resources.AllFileLable, "*.*");
            sfd.FileEncoding = document.Encoding;
            sfd.LineFeed = document.LineFeed;
            if (sfd.ShowDialog() == CommonFileDialogResult.Ok)
            {
                await document.SaveAsync(sfd.FileName, sfd.FileEncoding);
                document.FilePath = sfd.FileName;
            }
            sfd.Dispose();
        }

        void FindCommand(object sender, ExecutedRoutedEventArgs e)
        {
            DocumentWindow document = this.ActiveDocument;
            if (document == null)
                return;
            this.findWindow.IsVisible = true;
        }

        void SaveWorkSpace(object sender, ExecutedRoutedEventArgs e)
        {
            foreach (DocumentWindow document in this.Documents)
            {
                if (document.TextBox.Document.State != FooEditEngine.AsyncState.None)
                    continue;
                string stateFilePath = string.Format(Config.ApplicationFolder + "\\" + Prop.Resources.RecoveryState, Process.GetCurrentProcess().Id, document.Title);

                if (Directory.Exists(Config.ApplicationFolder) == false)
                    Directory.CreateDirectory(Config.ApplicationFolder);

                BinaryFormatter formatter = new BinaryFormatter();
                FileStream fs = new FileStream(stateFilePath, FileMode.Create, FileAccess.Write);
                formatter.Serialize(fs, document);
                fs.Close();
            }
        }

        void HelpCommand(object sender, ExecutedRoutedEventArgs e)
        {
            Process.Start(Path.Combine(Config.ExecutablePath, Prop.Resources.HelpFileName));
        }

        void AboutCommand(object sender, ExecutedRoutedEventArgs e)
        {
            FileVersionInfo versionInfo = FileVersionInfo.GetVersionInfo(Environment.GetCommandLineArgs()[0]);
            string str = string.Format("{0} version{1}\n{2}", versionInfo.ProductName, versionInfo.ProductVersion, versionInfo.CompanyName);
            MessageBox.Show(str, "FooEditorについて");
#if DEBUG
            throw new NotImplementedException();
#endif
        }
        #endregion

    }

    sealed class CustomMessageBox
    {
        TaskDialog Dialog;
        string ClickedButtonName;
        public CustomMessageBox(string text, string title)
        {
            this.Dialog = new TaskDialog();
            this.Dialog.Caption = title;
            this.Dialog.Text = text;
            this.Dialog.Cancelable = true;
        }
        public void AddButton(string name, String text,bool elevate = false)
        {
            TaskDialogButton button = new TaskDialogButton(name, text);
            button.Click += button_Click;
            button.UseElevationIcon = elevate;
            this.Dialog.Controls.Add(button);
        }

        void button_Click(object sender, EventArgs e)
        {
            TaskDialogButton button = (TaskDialogButton)sender;
            this.ClickedButtonName = button.Name;
            this.Dialog.Close();
        }

        public string Show()
        {
            this.Dialog.Show();
            return this.ClickedButtonName;
        }
    }

    public static class FooEditorMenuItemName
    {
        public static string FileMenuName = "FileMenuItem";
        public static string EditMenuName = "EditMenuItem";
        public static string LookMenuName = "LookMenuItem";
        public static string ToolMenuName = "ToolMenuItem";
    }

    public static class FooEditorResourceName
    {
        public static string ContextMenuName = "ContextMenu";
    }

    public sealed class DocumentEventArgs : EventArgs
    {
        public DocumentWindow Document;
        public DocumentEventArgs(DocumentWindow doc)
        {
            this.Document = doc;
        }
    }
}
