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

        Document _Document;
        protected Rectangle _Rect;
        protected double _LongestWidth;
        Padding _Padding;

        public ViewBase(Document doc, ITextRender r,Padding padding)
        {
            this._Padding = padding;
            this.Document = doc;
            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.PageBoundChanged += new EventHandler((s, e) => { });
        }

        public Document Document
        {
            get
            {
                return this._Document;
            }
            set
            {
                if(this._Document != null)
                {
                    this._Document.Update -= new DocumentUpdateEventHandler(doc_Update);
                    this._Document.LineBreakChanged -= Document_LineBreakChanged;
                    this._Document.StatusUpdate -= Document_StatusUpdate;
                    this._Document.PerformLayouted -= _Document_PerformLayouted;
                }

                this._Document = value;

                this._Document.Update += new DocumentUpdateEventHandler(doc_Update);
                this._Document.LineBreakChanged += Document_LineBreakChanged;
                this._Document.StatusUpdate += Document_StatusUpdate;
                this._Document.PerformLayouted += _Document_PerformLayouted;

                this.Document_LineBreakChanged(this, null);

                this.Document_StatusUpdate(this, null);
            }
        }

        private void _Document_PerformLayouted(object sender, EventArgs e)
        {
            CalculateLineCountOnScreen();
            if(this.PerformLayouted != null)
                this.PerformLayouted(this, e);
        }

        private void Document_StatusUpdate(object sender, EventArgs e)
        {
            if (this.render == null)
                return;
            if (this.render.TabWidthChar != this.Document.TabStops)
                this.render.TabWidthChar = this.Document.TabStops;
            if (this.render.RightToLeft != this.Document.RightToLeft)
                this.render.RightToLeft = this.Document.RightToLeft;
            if (this.render.ShowFullSpace != this.Document.ShowFullSpace)
                this.render.ShowFullSpace = this.Document.ShowFullSpace;
            if (this.render.ShowHalfSpace != this.Document.ShowHalfSpace)
                this.render.ShowHalfSpace = this.Document.ShowHalfSpace;
            if (this.render.ShowTab != this.Document.ShowTab)
                this.render.ShowTab = this.Document.ShowTab;
            if (this.render.ShowLineBreak != this.Document.ShowLineBreak)
                this.render.ShowLineBreak = this.Document.ShowLineBreak;
            CalculateClipRect();
            CalculateLineCountOnScreen();
            this._LayoutLines.ClearLayoutCache();
        }

        private void Document_LineBreakChanged(object sender, EventArgs e)
        {
            if (this.Document.LineBreak != LineBreakMethod.None)
                this._LayoutLines.SpilitString = new SpilitStringEventHandler(LayoutLines_SpilitStringByPixelbase);
            else
                this._LayoutLines.SpilitString = new SpilitStringEventHandler(LayoutLines_SpilitStringByChar);
        }

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

        public event EventHandler SrcChanged;

        [Obsolete]
        public event EventHandler PerformLayouted;

        public event EventHandler PageBoundChanged;

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

        public double LineNumberMargin
        {
            get
            {
                return this.render.emSize.Width;
            }
        }

        /// <summary>
        /// シンタックスハイライター
        /// </summary>
        /// <remarks>差し替えた場合、再構築する必要があります</remarks>
        public IHilighter Hilighter
        {
            get { return this._LayoutLines.Hilighter; }
            set { this._LayoutLines.Hilighter = value; this._LayoutLines.ClearLayoutCache(); }
        }

        /// <summary>
        /// すべてのレイアウト行を破棄し、再度レイアウトをやり直す
        /// </summary>
        [Obsolete]
        public virtual void PerfomLayouts()
        {
            //互換性を保つために残しておく
            this.Document.PerformLayout();
        }

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

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

        public virtual void Draw(Rectangle updateRect, bool force = false)
        {
            return;
        }

        public virtual bool TryScroll(double x, int row)
        {
            if (row < 0)
                return true;
            if (row > this.LayoutLines.Count - 1)
                return true;
            this.Document.Src = new SrcPoint(x, row, row * this.render.emSize.Height);
            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.Update -= new DocumentUpdateEventHandler(this.doc_Update);    //これをしないと複数のビューを作成した時に妙なエラーが発生する
                this._Document.LineBreakChanged -= Document_LineBreakChanged;
                this._Document.StatusUpdate -= Document_StatusUpdate;
                this._Document.PerformLayouted -= _Document_PerformLayouted;
            }
            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 render_ChangedRightToLeft(object sender, EventArgs e)
        {
            this.Document.Src = new SrcPoint(0, this.Document.Src.Row, this.Src.Y);
        }

        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 (this.Document.LineBreak == LineBreakMethod.PageBound)
                WrapWidth = this.render.TextArea.Width - LineBreakingMarginWidth;  //余白を残さないと欠ける
            else
                WrapWidth = this.render.emSize.Width * this.Document.LineBreakCharCount;

            if (WrapWidth < 0 && this.Document.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, Document.MaximumLineLength);
        }
    }
}
