/*
 * 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/>.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace FooEditEngine
{
    /// <summary>
    /// LineBreakMethod列挙体
    /// </summary>
    public enum LineBreakMethod
    {
        /// <summary>
        /// 折り返さない
        /// </summary>
        None = 0,
        /// <summary>
        /// 右端で折り返す
        /// </summary>
        PageBound = 1,
        /// <summary>
        /// 文字数で折り返す
        /// </summary>
        CharUnit = 2
    }

    /// <summary>
    /// 余白を表す
    /// </summary>
    public struct Padding
    {
        /// <summary>
        /// 左余白
        /// </summary>
        public int Left;
        /// <summary>
        /// 上余白
        /// </summary>
        public int Top;
        /// <summary>
        /// 右余白
        /// </summary>
        public int Right;
        /// <summary>
        /// 下余白
        /// </summary>
        public int Bottom;
        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <param name="left">左</param>
        /// <param name="top">上</param>
        /// <param name="right">右</param>
        /// <param name="bottom">下</param>
        public Padding(int left, int top, int right, int bottom)
        {
            this.Left = left;
            this.Top = top;
            this.Right = right;
            this.Bottom = bottom;
        }
    }

    abstract class ViewBase : IDisposable
    {
        const int SpiltCharCount = 1024;

        protected Document Document;
        protected Point2 _Src = new Point2();
        protected Rectangle _Rect;
        protected double _LongestWidth;
        Padding _Padding;
        bool _DrawLineNumber,_UrlMark;
        LineBreakMethod _LineBreak;
        int _LineBreakCharCount = 80;

        public ViewBase(Document doc, ITextRender r,Padding padding)
        {
            this._Padding = padding;
            this.Document = doc;
            this.Document.UpdateCalledAlways += new DocumentUpdateEventHandler(doc_Update);
            this._LayoutLines.SpilitString = new SpilitStringEventHandler(LayoutLines_SpilitStringByChar);
            this.render = r;
            this.render.ChangedRenderResource += new ChangedRenderResourceEventHandler(render_ChangedRenderResource);
            this.render.ChangedRightToLeft += render_ChangedRightToLeft;
            this.SrcChanged += new EventHandler((s, e) => { });
            this.PerformLayouted += new EventHandler((s, e) => { });
            this.PageBoundChanged += new EventHandler((s, e) => { });
            this.MarkerPatternSet = new MarkerPatternSet(this._LayoutLines, doc.Markers);
            this.MarkerPatternSet.Updated += WacthDogPattern_Updated;
        }

        protected LineToIndexTable _LayoutLines
        {
            get
            {
                return this.Document.LayoutLines;
            }
        }

        public event EventHandler SrcChanged;

        public event EventHandler PerformLayouted;

        public event EventHandler PageBoundChanged;

        /// <summary>
        /// マーカーパターンセット
        /// </summary>
        public MarkerPatternSet MarkerPatternSet
        {
            get;
            private set;
        }

        /// <summary>
        /// URLをハイパーリンクとして表示するなら真。そうでないなら偽
        /// </summary>
        public bool UrlMark
        {
            get { return this._UrlMark; }
            set
            {
                this._UrlMark = value;
                if (value)
                {
                    Regex regex = new Regex("(http|https|ftp)(:\\/\\/[-_.!~*\\'()a-zA-Z0-9;\\/?:\\@&=+\\$,%#]+)");
                    this.MarkerPatternSet.Add(MarkerIDs.URL, new RegexMarkerPattern(regex, HilightType.Url,new Color()));
                }
                else
                {
                    this.MarkerPatternSet.Remove(MarkerIDs.URL);
                }
            }
        }

        /// <summary>
        /// テキストレンダラ―
        /// </summary>
        public ITextRender render
        {
            get;
            set;
        }

        /// <summary>
        /// 一ページの高さに収まる行数を返す
        /// </summary>
        public int LineCountOnScreen
        {
            get;
            protected set;
        }

        /// <summary>
        /// 折り返し時の右マージン
        /// </summary>
        public double LineBreakingMarginWidth
        {
            get;
            protected set;
        }

        /// <summary>
        /// 保持しているレイアウト行
        /// </summary>
        public LineToIndexTable LayoutLines
        {
            get { return this._LayoutLines; }
        }

        /// <summary>
        /// 最も長い行の幅
        /// </summary>
        public double LongestWidth
        {
            get { return this._LongestWidth; }
        }

        /// <summary>
        /// 桁折り処理の方法を指定する
        /// </summary>
        /// <remarks>
        /// 変更した場合、呼び出し側で再描写とレイアウトの再構築を行う必要があります
        /// </remarks>
        public LineBreakMethod LineBreak
        {
            get
            {
                return this._LineBreak;
            }
            set
            {
                this._LineBreak = value;
                if (value != LineBreakMethod.None)
                    this._LayoutLines.SpilitString = new SpilitStringEventHandler(LayoutLines_SpilitStringByPixelbase);
                else
                    this._LayoutLines.SpilitString = new SpilitStringEventHandler(LayoutLines_SpilitStringByChar);
            }
        }

        /// <summary>
        /// 折り返し行う文字数。実際に折り返しが行われる幅はem単位×この値となります
        /// </summary>
        public int LineBreakCharCount
        {
            get
            {
                return this._LineBreakCharCount;
            }
            set
            {
                this._LineBreakCharCount = value;
            }
        }

        /// <summary>
        /// シンタックスハイライター
        /// </summary>
        public IHilighter Hilighter
        {
            get { return this._LayoutLines.Hilighter; }
            set { this._LayoutLines.Hilighter = value; }
        }

        /// <summary>
        /// タブの幅
        /// </summary>
        /// <remarks>変更した場合、呼び出し側で再描写する必要があります</remarks>
        public int TabStops
        {
            get { return this.render.TabWidthChar; }
            set { this.render.TabWidthChar = value; }
        }

        /// <summary>
        /// すべてのレイアウト行を破棄し、再度レイアウトをやり直す
        /// </summary>
        public virtual void PerfomLayouts()
        {
            this.Document.FireUpdate(new DocumentUpdateEventArgs(UpdateType.Clear, -1, -1, -1));
            this.Document.FireUpdate(new DocumentUpdateEventArgs(UpdateType.Replace, 0, 0, this.Document.Length));
            CalculateLineCountOnScreen();
            this.PerformLayouted(this, null);
        }

        /// <summary>
        /// 余白を表す
        /// </summary>
        public Padding Padding
        {
            get {
                return this._Padding;
            }
            set {
                this._Padding = value;
                CalculateClipRect();
                CalculateLineCountOnScreen();
                if (this.render.RightToLeft)
                    this._LayoutLines.ClearLayoutCache();
                this.PageBoundChanged(this, null);
            }
        }

        /// <summary>
        /// ページ全体を表す領域
        /// </summary>
        public Rectangle PageBound
        {
            get { return this._Rect; }
            set
            {
                if (value.Width < 0 || value.Height < 0)
                    throw new ArgumentOutOfRangeException("");
                this._Rect = value;
                CalculateClipRect();
                CalculateLineCountOnScreen();
                if (this.render.RightToLeft)
                    this._LayoutLines.ClearLayoutCache();
                this.PageBoundChanged(this, null);
            }
        }

        /// <summary>
        /// Draw()の対象となる領域の左上を表す
        /// </summary>
        public Point2 Src
        {
            get { return this._Src; }
            set { this._Src = value; }
        }

        /// <summary>
        /// 行番号を表示するかどうか
        /// </summary>
        public bool DrawLineNumber
        {
            get { return this._DrawLineNumber; }
            set
            {
                this._DrawLineNumber = value;
                this._LayoutLines.ClearLayoutCache();
                CalculateClipRect();
            }
        }

        public virtual void Draw(Rectangle updateRect)
        {
        }

        public virtual bool TryScroll(double x, int row)
        {
            if (row < 0)
                return true;
            if (row > this.LayoutLines.Count - 1)
                return true;
            this._Src.X = x;
            this._Src.Row = row;
            CalculateLineCountOnScreen();
            this.SrcChanged(this,null);
            return false;
        }

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        public virtual void CalculateLineCountOnScreen()
        {
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                this.Document.UpdateCalledAlways -= new DocumentUpdateEventHandler(this.doc_Update);    //これをしないと複数のビューを作成した時に妙なエラーが発生する
            }
            this._LayoutLines.Clear();
        }

        protected virtual void CalculateClipRect()
        {
        }


        protected virtual void OnSrcChanged(EventArgs e)
        {
            EventHandler handler = this.SrcChanged;
            if (handler != null)
                this.SrcChanged(this, e);
        }

        protected virtual void OnPerformLayoutedChanged(EventArgs e)
        {
            EventHandler handler = this.PerformLayouted;
            if (handler != null)
                this.PerformLayouted(this, e);
        }

        protected virtual void OnPageBoundChanged(EventArgs e)
        {
            EventHandler handler = this.PageBoundChanged;
            if (handler != null)
                this.PageBoundChanged(this, e);
        }

        void WacthDogPattern_Updated(object sender, EventArgs e)
        {
            this._LayoutLines.ClearLayoutCache();
        }

        void render_ChangedRightToLeft(object sender, EventArgs e)
        {
            this._Src.X = 0;
            this._LayoutLines.ClearLayoutCache();
            this.CalculateClipRect();
        }

        void render_ChangedRenderResource(object sender, ChangedRenderRsourceEventArgs e)
        {
            this._LayoutLines.ClearLayoutCache();
            if (e.type == ResourceType.Font)
            {
                this.CalculateClipRect();
                this.CalculateLineCountOnScreen();
            }
        }

        void doc_Update(object sender, DocumentUpdateEventArgs e)
        {
            switch (e.type)
            {
                case UpdateType.Clear:
                    this._LongestWidth = 0;
                    break;
            }
        }

        IList<LineToIndexTableData> LayoutLines_SpilitStringByPixelbase(object sender, SpilitStringEventArgs e)
        {
            double WrapWidth;
            if (_LineBreak == LineBreakMethod.PageBound)
                WrapWidth = this.render.TextArea.Width - LineBreakingMarginWidth;  //余白を残さないと欠ける
            else
                WrapWidth = this.render.emSize.Width * this._LineBreakCharCount;

            if (WrapWidth < 0 && this._LineBreak != LineBreakMethod.None)
                throw new InvalidOperationException();

            int startIndex = e.index;
            int endIndex = e.index + e.length - 1;

            LineToIndexTable layoutLineCollection = (LineToIndexTable)sender;

            return this.render.BreakLine(e.buffer,layoutLineCollection, startIndex, endIndex, WrapWidth);
        }

        IList<LineToIndexTableData> LayoutLines_SpilitStringByChar(object sender, SpilitStringEventArgs e)
        {
            return this.Document.CreateLineList(e.index, e.length,1000);
        }
    }
}
