using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

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

        protected Document Document;
        protected LineToIndexTable _LayoutLines;
        protected Point2 _Src = new Point2();
        protected Rectangle _Rect;
        protected double _LongestWidth;
        bool _DrawLineNumber, _isLineBreak;

        public ViewBase(Document doc, ITextRender r)
        {
            this.Document = doc;
            this.Document.UpdateCalledAlways += new DocumentUpdateEventHandler(doc_Update);
            this._LayoutLines = new LineToIndexTable(this.Document, r);
            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.LineBreakChanged += new EventHandler((s, e) => { });
            this.PageBoundChanged += new EventHandler((s, e) => { });
        }

        public event EventHandler SrcChanged;

        public event EventHandler LineBreakChanged;

        public event EventHandler PageBoundChanged;

        /// <summary>
        /// URLをハイパーリンクとして表示するなら真。そうでないなら偽
        /// </summary>
        public bool UrlMark
        {
            get { return this.LayoutLines.UrlMark; }
            set { this.LayoutLines.UrlMark = value; }
        }

        /// <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 bool isLineBreak
        {
            get
            {
                return this._isLineBreak;
            }
            set
            {
                this._isLineBreak = value;
                if (value)
                    this._LayoutLines.SpilitString = new SpilitStringEventHandler(LayoutLines_SpilitStringByPixelbase);
                else
                    this._LayoutLines.SpilitString = new SpilitStringEventHandler(LayoutLines_SpilitStringByChar);
                this.PerfomLayouts();
                this.LineBreakChanged(this, null);
            }
        }

        /// <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.doc_Update(this.Document, new DocumentUpdateEventArgs(UpdateType.Clear, -1, -1, -1));
            this.doc_Update(this.Document, new DocumentUpdateEventArgs(UpdateType.Replace, 0, 0, this.Document.Length));
        }

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

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

        protected virtual void CalculateClipRect()
        {
        }

        protected virtual void CalculateLineCountOnScreen()
        {
        }

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

        protected virtual void OnLineBreakChanged(EventArgs e)
        {
            EventHandler handler = this.LineBreakChanged;
            if (handler != null)
                this.LineBreakChanged(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._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.Replace:
                    this._LayoutLines.UpdateAsReplace(e.startIndex, e.removeLength, e.insertLength);
                    break;
                case UpdateType.Clear:
                    this._LayoutLines.Clear();
                    this._LongestWidth = 0;
                    break;
            }
        }

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

            if (WrapWidth < 0 && this.isLineBreak)
                throw new InvalidOperationException();

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

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

        IList<LineToIndexTableData> LayoutLines_SpilitStringByChar(object sender, SpilitStringEventArgs e)
        {
            int startIndex = e.index;
            int endIndex = e.index + e.length - 1;
            List<LineToIndexTableData> output = new List<LineToIndexTableData>();

            foreach (string str in e.buffer.GetLines(startIndex, endIndex, SpiltCharCount))
            {
                char c = str[str.Length - 1];
                bool hasNewLine = c == Document.NewLine;
                output.Add(new LineToIndexTableData(startIndex, str.Length, hasNewLine, null));
                startIndex += str.Length;
            }

            if (output.Count > 0)
                output.Last().LineEnd = true;

            return output;
        }
    }
}
