//#define TEST_PROGRESS

using System;
using System.Text;
using System.Xml.Serialization;
using System.Collections.ObjectModel;
using System.IO;
using Windows.ApplicationModel.DataTransfer;
using Windows.Foundation;
using Windows.Graphics.Printing;
using Windows.Storage;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Media;
using System.Threading.Tasks;
using FooEditEngine;
using FooEditEngine.Metro;
using EncodeDetect;

namespace FooEditor
{
    class DocumentControlViewModel : ViewModelBase,IDisposable
    {
        FooPrintText printText = new FooPrintText();
        FooTextBox Textbox;
        DocumentModel docModel = new DocumentModel();
        public DocumentControlViewModel(FooTextBox textbox)
        {
            this.Textbox = textbox;
            this.Textbox.Document.Update += Document_Update;

            AppSettings setting = AppSettings.Current;
            setting.ChangedSetting += setting_ChangedSetting;
            setting.OnChangedSetting();

            InputPane currentView = InputPane.GetForCurrentView();
            currentView.Showing += currentView_Showing;
            currentView.Hiding += currentView_Hiding;

            this.docModel.LoadFileImpl = new Func<FileModel, Encoding, Task>(async (file, enc) =>{
                using (Stream stream = await file.GetReadStreamAsync())
                using (StreamReader reader = new StreamReader(stream, enc))
                {
                    await Task.Delay(100);  //SizeChangedイベントがコンポーネント内で発生すると落ちるのでしばらく待つ
                    await this.Textbox.LoadFileAsync(reader, null);
                    this.Textbox.Refresh();
                }
            });
            this.docModel.SaveFileImpl = new Func<FileModel, Encoding, LineFeedType, Task>(async (file, enc, linefeed) =>{
                using (Stream stream = await file.GetWriteStreamAsync())
                {
                    stream.SetLength(0);    //上書き時にゴミが残らないようにする
                    using (StreamWriter writer = new StreamWriter(stream, enc))
                    {
                        writer.NewLine = LineFeedHelper.ToString(linefeed);
                        await this.Textbox.SaveFile(writer, null);
                    }
                }
            });
            this.docModel.DocumentTypeChanged += (s, e) =>
            {
                AppSettings.Current.FileType = e.newFileType;
                this.ApplySetting(AppSettings.Current);

                if (e.hilighter == null)
                {
                    this.Textbox.Hilighter = null;
                    this.Textbox.LayoutLineCollection.ClearHilight();
                }
                else
                {
                    this.Textbox.Hilighter = e.hilighter;
                    this.Textbox.LayoutLineCollection.HilightAll();
                }

                if (e.folding == null)
                {
                    this.Textbox.FoldingStrategy = null;
                    this.Textbox.LayoutLineCollection.ClearFolding();
                }
                else
                {
                    this.Textbox.FoldingStrategy = e.folding;
                    this.Textbox.LayoutLineCollection.ClearFolding();
                    this.Textbox.LayoutLineCollection.GenerateFolding();
                }
                this.Textbox.Refresh();
            };
        }

        void Document_Update(object sender, DocumentUpdateEventArgs e)
        {
            if (e.type == UpdateType.Replace && !this.IsDirty)
                this.IsDirty = true;
        }

        public void Dispose()
        {
            AppSettings setting = AppSettings.Current;
            setting.ChangedSetting -= setting_ChangedSetting;

            InputPane currentView = InputPane.GetForCurrentView();
            currentView.Showing -= currentView_Showing;
            currentView.Hiding -= currentView_Hiding;
        }

        public string Title
        {
            get
            {
                return this.docModel.Title;
            }
            set
            {
                this.docModel.Title = value;
                this.OnPropertyChanged();
            }
        }

        public bool IsProgressNow
        {
            get
            {
                return this.docModel.IsProgressNow;
            }
            set
            {
                this.docModel.IsProgressNow = value;
                this.OnPropertyChanged();
            }
        }

        public bool IsDirty
        {
            get
            {
                return this.docModel.IsDirty;
            }
            set
            {
                this.docModel.IsDirty = value;
                this.OnPropertyChanged();
            }
        }

        public FileType DocumentType
        {
            get
            {
                return this.docModel.DocumentType;
            }
        }

        public Encoding Encode
        {
            get
            {
                return this.docModel.Encode;
            }
            set
            {
                this.docModel.Encode = value;
                this.OnPropertyChanged();
            }
        }

        public LineFeedType LineFeed
        {
            get
            {
                return this.docModel.LineFeed;
            }
            set
            {
                this.docModel.LineFeed = value;
                this.OnPropertyChanged();
            }
        }

        public FileModel CurrentFile
        {
            get
            {
                return this.docModel.currentFile;
            }
        }

        public bool IsHitTextArea(Point point)
        {
            var collection = VisualTreeHelper.FindElementsInHostCoordinates(point, this.Textbox);
            foreach (UIElement element in collection)
                if (element is FooTextBox)
                    return true;
            return false;
        }

        public void Print(PrintTaskRequestedEventArgs args)
        {
            this.printText.Header = AppSettings.Current.Header;
            this.printText.Fotter = AppSettings.Current.Footer;
            this.printText.ParseHF = (s, e) =>
            {
                PrintInfomation info = new PrintInfomation() { Title = this.Title, PageNumber = e.PageNumber };
                return EditorHelper.ParseHF(e.Original, info);
            };
            this.printText.Padding = new Padding(AppSettings.Current.LeftMargin, 
                AppSettings.Current.TopMargin, 
                AppSettings.Current.RightMargin, 
                AppSettings.Current.BottomMargin);
            this.printText.Print(args.Request, "textprint", this.Textbox);
        }

        public void GetData(DataRequestedEventArgs args)
        {
            if (this.Textbox.Selection.Length == 0 && this.docModel.currentFile != null)
            {
                args.Request.Data.Properties.Title = this.Title;
                args.Request.Data.SetStorageItems(new StorageFile[] { this.docModel.currentFile.File });
            }
            else if (this.Textbox.Selection.Length != 0)
            {
                var loader = new Windows.ApplicationModel.Resources.ResourceLoader();
                args.Request.Data.Properties.Title = loader.GetString("SharedDataTitle");
                args.Request.Data.SetText(this.Textbox.SelectedText);
            }
        }

        public void ApplySetting(AppSettings setting)
        {
            setting.FileType = this.DocumentType;   //ドキュメントが切り替わった場合にも呼ばれるのでドキュメントのタイプを設定しておく
            if (this.Textbox.DrawCaretLine != setting.ShowLineMarker)
                this.Textbox.DrawCaretLine = setting.ShowLineMarker;
            if (this.Textbox.TabChars != setting.TabChar)
                this.Textbox.TabChars = setting.TabChar;
            if (this.Textbox.FontSize != setting.FontSize)
                this.Textbox.FontSize = setting.FontSize;
            if (this.Textbox.FontFamily.Source != setting.FontFamily)
                this.Textbox.FontFamily = new Windows.UI.Xaml.Media.FontFamily(setting.FontFamily);
            FlowDirection flowdir = setting.IsRTL ? FlowDirection.RightToLeft : FlowDirection.LeftToRight;
            if (this.Textbox.FlowDirection != flowdir)
                this.Textbox.FlowDirection = flowdir;
            if (!setting.ShowFoundPattern)
                this.Textbox.MarkerPatternSet.Remove(FindFlyout.FoundMarkerID);
            IndentMode indentmode = setting.IndentBySpace ? IndentMode.Space : IndentMode.Tab;
            if (this.Textbox.IndentMode != indentmode)
                this.Textbox.IndentMode = indentmode;
            if (this.Textbox.DrawRuler != setting.ShowRuler)
                this.Textbox.DrawRuler = setting.ShowRuler;
            if (this.Textbox.DrawLineNumber != setting.ShowLineNumber)
                this.Textbox.DrawLineNumber = setting.ShowLineNumber;
            if (this.Textbox.ShowFullSpace != setting.ShowFullSpace)
                this.Textbox.ShowFullSpace = setting.ShowFullSpace;
            if (this.Textbox.ShowTab != setting.ShowTab)
                this.Textbox.ShowTab = setting.ShowTab;
            if (this.Textbox.ShowLineBreak != setting.ShowLineBreak)
                this.Textbox.ShowLineBreak = setting.ShowLineBreak;
            if (this.Textbox.TabChars != setting.TabChar)
                this.Textbox.TabChars = setting.TabChar;
            if (this.Textbox.IndentMode != indentmode)
                this.Textbox.IndentMode = indentmode;
            bool rebuildLayout = false;
            if (this.Textbox.LineBreakMethod != setting.LineBreakMethod)
            {
                this.Textbox.LineBreakMethod = setting.LineBreakMethod;
                rebuildLayout = true;
            }
            if (this.Textbox.LineBreakCharCount != setting.LineBreakCount)
            {
                this.Textbox.LineBreakCharCount = setting.LineBreakCount;
                rebuildLayout = true;
            }
            if (rebuildLayout)
                this.Textbox.PerfomLayouts();
            this.Textbox.Refresh();
        }

        public async Task SetDocumentType(FileType type)
        {
            await this.docModel.SetDocumentType(type);
        }

        public async Task LoadFile(FileModel file, Encoding enc = null)
        {
            await this.docModel.LoadFile(file, enc);
        }

        public async Task ReloadFile(Encoding enc)
        {
            await this.docModel.ReloadFile(enc);
        }

        public Popup CreatePopup(Type t)
        {
            if (t == typeof(GoToFlyout))
            {
                var flyout = new GoToFlyout(this.Textbox);
                return FlyoutUtils.CreateFlyoutUnderTopAppBar(flyout);
            }
            throw new ArgumentOutOfRangeException();
        }

        private FileType GetFileType(string file)
        {
            ObservableCollection<FileType> collection = AppSettings.Current.FileTypeCollection;
            foreach (FileType type in collection)
                foreach (string ext in type.ExtensionCollection)
                    if (Path.GetExtension(file) == ext)
                        return type;
            return null;
        }

        public async Task SaveFile(FileModel file)
        {
            await this.docModel.SaveFile(file);
        }

        public void Undo()
        {
            this.Textbox.Document.UndoManager.undo();
            this.Textbox.Refresh();
        }

        public void Redo()
        {
            this.Textbox.Document.UndoManager.redo();
            this.Textbox.Refresh();
        }

        void setting_ChangedSetting(object sender, EventArgs e)
        {
            this.ApplySetting((AppSettings)sender);
        }

        void currentView_Hiding(InputPane sender, InputPaneVisibilityEventArgs args)
        {
            this.Textbox.Margin = new Thickness(0);
            args.EnsuredFocusedElementInView = true;
        }

        void currentView_Showing(InputPane sender, InputPaneVisibilityEventArgs args)
        {
            this.Textbox.Margin = new Thickness(0, 0, 0, args.OccludedRect.Height);
            args.EnsuredFocusedElementInView = true;
        }

        public async Task ReadXml(System.Xml.XmlReader reader)
        {
            reader.ReadStartElement("DocumentControl");

            reader.ReadStartElement("Title");
            this.Title = reader.ReadContentAsString();
            reader.ReadEndElement();

            reader.ReadStartElement("CaretPostionRow");
            int row = reader.ReadContentAsInt();
            reader.ReadEndElement();

            reader.ReadStartElement("CaretPostionCol");
            int col = reader.ReadContentAsInt();
            reader.ReadEndElement();

            string documentType = string.Empty;
            if (reader.IsEmptyElement)
            {
                reader.Skip();
            }else
            {
                reader.ReadStartElement("DocumentType");
                documentType = reader.ReadContentAsString();
                reader.ReadEndElement();
            }

            FileModel currentFile = null;
            try
            {
                reader.ReadStartElement("CurrentFile");
                currentFile = new FileModel(null);
                currentFile.ReadXml(reader);
                reader.ReadEndElement();
            }
            catch (System.Xml.XmlException)
            {
                //存在しない可能性があるので無視する
            }

            await this.docModel.RestoreCurrentFile(this.Title, currentFile, documentType);
            this.Textbox.JumpCaret(row, col);
        }

        public async Task WriteXml(System.Xml.XmlWriter writer)
        {
            writer.WriteStartElement("Title");
            writer.WriteValue(this.Title);
            writer.WriteEndElement();

            writer.WriteStartElement("CaretPostionRow");
            writer.WriteValue(this.Textbox.CaretPostion.row);
            writer.WriteEndElement();

            writer.WriteStartElement("CaretPostionCol");
            writer.WriteValue(this.Textbox.CaretPostion.col);
            writer.WriteEndElement();

            writer.WriteStartElement("DocumentType");
            writer.WriteValue(this.DocumentType == null ? string.Empty : this.DocumentType.DocumentType);
            writer.WriteEndElement();

            if (CurrentFile != null)
            {
                writer.WriteStartElement("CurrentFile");
                this.CurrentFile.WriteXml(writer);
                writer.WriteEndElement();
            }

            await this.docModel.SaveCurrentFile();
        }
    }
}
