//#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
    {
        DispatcherTimer timer = new DispatcherTimer();
        FooPrintText printText = new FooPrintText();
        StorageFile currentFile;
        FooTextBox Textbox;
        public DocumentControlViewModel(FooTextBox textbox)
        {
            this.Textbox = textbox;
            this.Textbox.Document.Update += Document_Update;

            this.timer.Interval = new TimeSpan(0, 0, 0, 1);
            this.timer.Tick += timer_Tick;
            this.timer.Start();

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

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

            this.Encode = Encoding.Unicode;
            this.LineFeed = LineFeedType.CRLF;
        }

        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;
        }

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

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

        public bool IsDirty
        {
            get;
            set;
        }

        public FileType DocumentType
        {
            get;
            private set;
        }

        public Encoding Encode
        {
            get;
            set;
        }

        public LineFeedType LineFeed
        {
            get;
            set;
        }
        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) =>
            {
                string str = e.Original.Replace("%f", this._Title);
                return str.Replace("%p", e.PageNumber.ToString());
            };
            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.currentFile != null)
            {
                args.Request.Data.Properties.Title = this.Title;
                args.Request.Data.SetStorageItems(new StorageFile[] { this.currentFile });
            }
            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)
        {
            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;
            this.ApplyDocumentSetting(setting, this.DocumentType);
            this.Textbox.Refresh();
        }

        public void ApplyDocumentSetting(AppSettings setting, FileType type)
        {
            bool rebuildLayout = false;
            bool showFullSpace, showTab, showRuler, showLineNumber, showLineBreak,indentbyspace;
            LineBreakMethod lineBreakMethod;
            int lineBreakCount,tabCharCount;
            if (type == null || !type.NoInherit)
            {
                showRuler = setting.ShowRuler;
                showLineNumber = setting.ShowLineNumber;
                showFullSpace = setting.ShowFullSpace;
                showTab = setting.ShowTab;
                showLineBreak = setting.ShowLineBreak;
                lineBreakMethod = setting.LineBreakMethod;
                lineBreakCount = setting.LineBreakCount;
                indentbyspace = setting.IndentBySpace;
                tabCharCount = setting.TabChar;
            }
            else
            {
                showRuler = type.ShowRuler;
                showLineNumber = type.ShowLineNumber;
                showFullSpace = type.ShowFullSpace;
                showTab = type.ShowTab;
                showLineBreak = type.ShowLineBreak;
                lineBreakMethod = type.LineBreakMethod;
                lineBreakCount = type.LineBreakCount;
                indentbyspace = type.IndentBySpace;
                tabCharCount = type.TabCharCount;
            }

            if (this.Textbox.DrawRuler != showRuler)
                this.Textbox.DrawRuler = showRuler;
            if (this.Textbox.DrawLineNumber != showLineNumber)
                this.Textbox.DrawLineNumber = showLineNumber;
            if (this.Textbox.ShowFullSpace != showFullSpace)
                this.Textbox.ShowFullSpace = showFullSpace;
            if (this.Textbox.ShowTab != showTab)
                this.Textbox.ShowTab = showTab;
            if (this.Textbox.ShowLineBreak != showLineBreak)
                this.Textbox.ShowLineBreak = showLineBreak;
            if (this.Textbox.TabChars != tabCharCount)
                this.Textbox.TabChars = tabCharCount;
            IndentMode indentmode = indentbyspace ? IndentMode.Space : IndentMode.Tab;
            if (this.Textbox.IndentMode != indentmode)
                this.Textbox.IndentMode = indentmode;
            if (this.Textbox.LineBreakMethod != lineBreakMethod)
            {
                this.Textbox.LineBreakMethod = lineBreakMethod;
                rebuildLayout = true;
            }
            if (this.Textbox.LineBreakCharCount != lineBreakCount)
            {
                this.Textbox.LineBreakCharCount = lineBreakCount;
                rebuildLayout = true;
            }
            if (rebuildLayout)
                this.Textbox.PerfomLayouts();
            this.Textbox.Refresh();
        }

        public async Task SetDocumentType(FileType type)
        {
            if (this.DocumentType != null)
                this.DocumentType.PropertyChanged -= DocumentType_ChangedSetting;

            if (type != null)
                type.PropertyChanged += DocumentType_ChangedSetting;

            this.DocumentType = type;

            if (type == null || string.IsNullOrEmpty(type.DocumentType))
            {
                this.Textbox.Hilighter = null;
                this.Textbox.FoldingStrategy = null;
                this.Textbox.LayoutLineCollection.ClearHilight();
                this.Textbox.LayoutLineCollection.ClearFolding();
                return;
            }

            StorageFile file;
            try
            {
                var uri = new System.Uri("ms-appx:///Keywords/" + type.DocumentType);
                file = await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(uri);
            }
            catch (FileNotFoundException)
            {
                this.Textbox.Hilighter = null;
                this.Textbox.FoldingStrategy = null;
                this.Textbox.LayoutLineCollection.ClearHilight();
                this.Textbox.LayoutLineCollection.ClearFolding();
                return;
            }

            SyntaxDefnition SynataxDefnition = new SyntaxDefnition();
            SynataxDefnition.generateKeywordList(file.Path);

            if (SynataxDefnition.Hilighter == FooEditor.SyntaxDefnition.XmlHilighter)
            {
                this.Textbox.Hilighter = new XmlHilighter();
            }
            else
            {
                GenericHilighter Hilighter = new GenericHilighter();
                Hilighter.KeywordManager = SynataxDefnition;
                this.Textbox.Hilighter = Hilighter;
            }

            if (SynataxDefnition.FoldingBegin != null && SynataxDefnition.FoldingEnd != null)
            {
                if (SynataxDefnition.FoldingMethod == FooEditor.SyntaxDefnition.CLangFolding)
                    this.Textbox.FoldingStrategy = new CLangFoldingGenerator(SynataxDefnition.FoldingBegin, SynataxDefnition.FoldingEnd, '{', '}');
                else
                    this.Textbox.FoldingStrategy = new RegexFoldingGenerator(SynataxDefnition.FoldingBegin, SynataxDefnition.FoldingEnd);
            }
            else
            {
                this.Textbox.FoldingStrategy = null;
            }

            this.Textbox.LayoutLineCollection.HilightAll();
            this.Textbox.LayoutLineCollection.ClearFolding();
            this.Textbox.LayoutLineCollection.GenerateFolding();
            this.Textbox.Refresh();
        }

        public async Task LoadFile(StorageFile file, Encoding enc = null)
        {
            this.IsProgressNow = true;
            this.Textbox.Refresh();
            try
            {
                if (enc == null)
                {
                    enc = await this.GetEncodeAsync(file);
                    if (enc == null)
                        enc = Encoding.Unicode;
                }
                this.Encode = enc;
                this.LineFeed = await this.GetLineFeedAsync(file, enc);
                await this.SetDocumentType(GetFileType(file.Name));
                using (Stream stream = await file.OpenStreamForReadAsync())
                using (StreamReader reader = new StreamReader(stream, enc))
                {
                    await this.Textbox.LoadFileAsync(reader, null);
                    this.Textbox.Refresh();
                }
                this.currentFile = file;
                this.IsDirty = false;
#if TEST_PROGRESS
                await Task.Delay(10000);
#endif
            }
            finally
            {
                this.IsProgressNow = false;
            }
        }

        public async Task ReloadFile(Encoding enc)
        {
            if (this.currentFile == null)
                return;
            this.Encode = enc;
            using (Stream stream = await this.currentFile.OpenStreamForReadAsync())
            using (StreamReader reader = new StreamReader(stream, enc))
            {
                await this.Textbox.LoadFileAsync(reader,null);
                this.Textbox.Refresh();
            }
            this.IsDirty = false;
        }

        async Task<Encoding> GetEncodeAsync(StorageFile file)
        {
            const int maxReadCount = 16384;
            using (Stream stream = await file.OpenStreamForReadAsync())
            {
                byte[] bs = new byte[maxReadCount];
                await stream.ReadAsync(bs, 0, maxReadCount);
                return EncodeDetect.DectingEncode.GetCode(bs);
            }
        }

        async Task<LineFeedType> GetLineFeedAsync(StorageFile file, Encoding enc)
        {
            const int maxReadCount = 16384;
            using (Stream stream = await file.OpenStreamForReadAsync())
            using (StreamReader reader = new StreamReader(stream, enc))
            {
                char[] cs = new char[maxReadCount];
                reader.Read(cs, 0, maxReadCount);
                return LineFeedHelper.GetLineFeed(cs);
            }
        }

        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(StorageFile file)
        {
            using (Stream stream = await file.OpenStreamForWriteAsync())
            {
                stream.SetLength(0);    //上書き時にゴミが残らないようにする
                using (StreamWriter writer = new StreamWriter(stream, this.Encode))
                {
                    writer.NewLine = LineFeedHelper.ToString(this.LineFeed);
                    await this.Textbox.SaveFile(writer,null);
                }
            }
            this.IsDirty = false;
        }

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

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

        void DocumentType_ChangedSetting(object sender, EventArgs e)
        {
            this.ApplyDocumentSetting(AppSettings.Current, (FileType)sender);
        }

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

        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;
        }

        void timer_Tick(object sender, object e)
        {
            if (this.Textbox.LayoutLineCollection.GenerateFolding())
                this.Textbox.Refresh();
        }

        const string prefixFileName = "save_";

        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();

            reader.ReadStartElement("DocumentType");
            string documentType = reader.ReadContentAsString();
            reader.ReadEndElement();

            StorageFile file = await ApplicationData.Current.LocalFolder.GetFileAsync(prefixFileName + this.Title);
            await this.LoadFile(file);
            this.Textbox.JumpCaret(row, col);
            await file.DeleteAsync();

            if (documentType != string.Empty)
            {
                foreach (FileType type in AppSettings.Current.FileTypeCollection)
                    if (type.DocumentType == documentType)
                    {
                        await this.SetDocumentType(type);
                        break;
                    }
            }
        }

        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();

            StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync(prefixFileName + this.Title, CreationCollisionOption.ReplaceExisting);
            await this.SaveFile(file);
        }
    }
}
