/*
 * 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/>.
 */

//#define TEST_ASYNC

using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace FooEditEngine
{
    /// <summary>
    /// Documentの拡張クラス
    /// </summary>
    public static class DocumentExtend
    {
        static Progress<ProgressEventArgs> _Progress = new Progress<ProgressEventArgs>();
        /// <summary>
        /// 進捗処理を表す
        /// </summary>
        public static event EventHandler<ProgressEventArgs> Progress
        {
            add
            {
                _Progress.ProgressChanged += value;
            }
            remove
            {
                _Progress.ProgressChanged -= value;
            }
        }

#if !METRO
        /// <summary>
        /// ファイルからドキュメントを構築します
        /// </summary>
        /// <param name="doc">Documentオブジェクト</param>
        /// <param name="filepath">読み取り先のファイル</param>
        /// <param name="enc">エンコーディング</param>
        /// <remarks>
        /// 非同期操作中はこのメソッドを実行することはできません。
        /// </remarks>
        public static void Load(this Document doc, string filepath, Encoding enc)
        {
            if (doc.State != AsyncState.None)
                throw new InvalidOperationException();
            using (StreamReader sr = new StreamReader(filepath, enc))
            {
                Load(doc,sr);
            }
        }

        /// <summary>
        /// ファイルからドキュメントを非同期的に構築します
        /// </summary>
        /// <param name="doc">Documentオブジェクト</param>
        /// <param name="filepath">読み取り先のファイル</param>
        /// <param name="enc">エンコーディング</param>
        /// <param name="token">キャンセル用のトークン</param>
        /// <remarks>
        /// 読み取り操作は別スレッドで行われます。
        /// また、非同期操作中にこのメソッドを読みだすことはできません
        /// </remarks>
        public static async Task LoadAsync(this Document doc, string filepath, Encoding enc, CancellationTokenSource token)
        {
            if (doc.State != AsyncState.None)
                throw new InvalidOperationException();
            using (StreamReader sr = new StreamReader(filepath, enc))
            {
                await LoadAsync(doc,sr, token);
            }
        }
#endif

        /// <summary>
        /// ストリームからドキュメントを構築します
        /// </summary>
        /// <param name="doc">Documentオブジェクト</param>
        /// <param name="sr">読み取り先のストリーム</param>
        /// <remarks>
        /// 非同期操作中はこのメソッドを実行することはできません
        /// </remarks>
        public static void Load(this Document doc, TextReader sr)
        {
            Task t = LoadAsync(doc,sr, null);
            t.Wait();
        }

        /// <summary>
        /// ストリームからドキュメントを非同期的に構築します
        /// </summary>
        /// <param name="doc">Documentオブジェクト</param>
        /// <param name="sr">読み取り先のストリーム</param>
        /// <param name="tokenSource">キャンセルトークン</param>
        /// <returns>Taskオブジェクト</returns>
        /// <remarks>
        /// 読み取り操作は別スレッドで行われます。
        /// また、非同期操作中はこのメソッドを実行することはできません。
        /// </remarks>
        public static async Task LoadAsync(this Document doc, TextReader sr, CancellationTokenSource tokenSource = null)
        {
            if (doc.State != AsyncState.None)
                throw new InvalidOperationException();
            if (sr.Peek() == -1)
            {
                return;
            }

            IProgress<ProgressEventArgs> progress = _Progress;
            try
            {
                progress.Report(new ProgressEventArgs(ProgressState.Start));
                doc.State = AsyncState.Loading;
                doc.FireUpdateEvent = false;
                doc.StringBuffer.Clear();
                doc.UndoManager.BeginLock();
                string str;
                for (int i = 0; (str = await sr.ReadLineAsync().ConfigureAwait(false)) != null; i++)
                {
                    int index = doc.StringBuffer.Length;
                    if (index < 0)
                        index = 0;

                    doc.StringBuffer.Replace(index, 0, Util.GetEnumrator(str + Document.NewLine),str.Length + 1);

                    if (tokenSource != null)
                        tokenSource.Token.ThrowIfCancellationRequested();
#if TEST_ASYNC
                    System.Threading.Thread.Sleep(10);
#endif
                }
            }
            finally
            {
                doc.FireUpdateEvent = true;
                doc.UndoManager.EndLock();
                doc.State = AsyncState.None;
                progress.Report(new ProgressEventArgs(ProgressState.Complete));
            }
        }

#if !METRO
        /// <summary>
        /// ドキュメントをファイルに保存します
        /// </summary>
        /// <param name="doc">Documentオブジェクト</param>
        /// <param name="filepath">保存先のファイル</param>
        /// <param name="enc">保存したいエンコード</param>
        /// <param name="newline">改行を表す文字列</param>
        /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
        public static void Save(this Document doc, string filepath, Encoding enc, string newline)
        {
            if (doc.State != AsyncState.None)
                throw new InvalidOperationException();
            using (StreamWriter sw = new StreamWriter(filepath, false, enc))
            {
                sw.NewLine = newline;
                Save(doc,sw);
            }
        }

        /// <summary>
        /// ドキュメントをファイルに保存します
        /// </summary>
        /// <param name="doc">Documentオブジェクト</param>
        /// <param name="filepath">保存先のファイル</param>
        /// <param name="enc">保存したいエンコード</param>
        /// <param name="newline">改行を表す文字列</param>
        /// <param name="token">CancellationTokenSourceオブジェクト</param>
        /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
        public static async Task SaveAsync(this Document doc, string filepath, Encoding enc, string newline,CancellationTokenSource token)
        {
            if (doc.State != AsyncState.None)
                throw new InvalidOperationException();
            using (StreamWriter sw = new StreamWriter(filepath, false, enc))
            {
                sw.NewLine = newline;
                await SaveAsync(doc,sw, token);
            }
        }
#endif

        /// <summary>
        /// ストリームに保存します
        /// </summary>
        /// <param name="doc">Documentオブジェクト</param>
        /// <param name="sw">保存先のストリーム</param>
        /// <returns>Taskオブジェクト</returns>
        /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
        public static void Save(this Document doc, StreamWriter sw)
        {
            Task t = SaveAsync(doc,sw, null);
            t.Wait();
        }

        /// <summary>
        /// ストリームに非同期モードで保存します
        /// </summary>
        /// <param name="doc">Documentオブジェクト</param>
        /// <param name="sw">保存先のストリーム</param>
        /// <param name="tokenSource">キャンセルトークン</param>
        /// <returns>Taskオブジェクト</returns>
        /// <remarks>非同期操作中はこのメソッドを実行することはできません</remarks>
        public static async Task SaveAsync(this Document doc,StreamWriter sw, CancellationTokenSource tokenSource = null)
        {
            if (doc.State != AsyncState.None)
                throw new InvalidOperationException();
            IProgress<ProgressEventArgs> progress = _Progress;
            try
            {
                progress.Report(new ProgressEventArgs(ProgressState.Start));
                doc.State = AsyncState.Saving;
                StringBuilder line = new StringBuilder();
                for (int i = 0; i < doc.Length; i++)
                {
                    char c = doc[i];
                    line.Append(c);
                    if (c == Document.NewLine || i == doc.Length - 1)
                    {
                        string str = line.ToString();
                        str = str.Replace(Document.NewLine.ToString(), sw.NewLine);
                        await sw.WriteAsync(str).ConfigureAwait(false);
                        line.Clear();
                        if (tokenSource != null)
                            tokenSource.Token.ThrowIfCancellationRequested();
#if TEST_ASYNC
                    System.Threading.Thread.Sleep(10);
#endif
                    }
                }
            }
            finally
            {
                doc.State = AsyncState.None;
                progress.Report(new ProgressEventArgs(ProgressState.Complete));
            }
        }
    }
}
