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

namespace FooEditEngine
{
    enum AdjustFlow
    {
        Row,
        Col,
        Both,
    }

    struct Margin
    {
        /// <summary>
        /// 左マージン
        /// </summary>
        public int Left;
        /// <summary>
        /// 上マージン
        /// </summary>
        public int Top;
        /// <summary>
        /// 右マージン
        /// </summary>
        public int Right;
        /// <summary>
        /// 下マージン
        /// </summary>
        public int Bottom;
        public Margin(int left, int top, int right, int bottom)
        {
            this.Left = left;
            this.Top = top;
            this.Right = right;
            this.Bottom = bottom;
        }
    }
    /// <summary>
    /// キャレットとドキュメントの表示を担当します。レイアウト関連もこちらで行います
    /// </summary>
    sealed class EditView : ViewBase
    {
        Point _CaretLocation = new Point();
        TextPoint _CaretPostion = new TextPoint();
        long tickCount;
        bool _CaretBlink,_HideRuler = true;
        internal const int LineNumberLength = 6;
        Margin Margin;

        /// <summary>
        /// コンストラクター
        /// </summary>
        public EditView(Document doc, ITextRender r,int MarginLeftAndRight = 5)
            : base(doc,r)
        {
            this.Margin = new Margin(MarginLeftAndRight, 0, MarginLeftAndRight, 0);
            this.CaretBlinkTime = 500;
            this.CaretWidthOnInsertMode = 1;
            this.CalculateClipRect();
            this._CaretLocation.X = this.render.TextArea.X;
            this._CaretLocation.Y = this.render.TextArea.Y;
            this.LayoutLines.FoldingCollection.StatusChanged += FoldingCollection_StatusChanged;
            this.InsertPoint = null;
        }

        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <param name="doc">ドキュメント</param>
        /// <param name="r">レンダー</param>
        /// <param name="margin">マージン（１番目：左、２番目：上、３番目：右、４番目：下）</param>
        public EditView(Document doc, ITextRender r, Margin margin)
            : this(doc,r,margin.Left)
        {
            this.Margin = margin;
        }

        /// <summary>
        /// ラインマーカーを描くなら偽。そうでなければ真
        /// </summary>
        public bool HideLineMarker
        {
            get;
            set;
        }

        /// <summary>
        /// キャレットを描くなら偽。そうでなければ真
        /// </summary>
        public bool HideCaret
        {
            get;
            set;
        }

        /// <summary>
        /// 挿入モードなら真を返し、上書きモードなら偽を返す
        /// </summary>
        public bool InsertMode
        {
            get;
            set;
        }

        /// <summary>
        /// キャレットの点滅間隔
        /// </summary>
        public int CaretBlinkTime
        {
            get;
            set;
        }

        /// <summary>
        /// 挿入モード時のキャレットの幅
        /// </summary>
        public double CaretWidthOnInsertMode
        {
            get;
            set;
        }

        /// <summary>
        /// キャレットを点滅させるなら真。そうでないなら偽
        /// </summary>
        /// <remarks>キャレット点滅タイマーもリセットされます</remarks>
        public bool CaretBlink
        {
            get { return this._CaretBlink; }
            set
            {
                this._CaretBlink = value;
                if (value)
                    this.tickCount = DateTime.Now.Ticks + this.To100nsTime(this.CaretBlinkTime);
            }
        }

        /// <summary>
        /// 一ページの高さに収まる行数を返す（こちらは表示されていない行も含みます）
        /// </summary>
        public int LineCountOnScreenWithInVisible
        {
            get;
            private set;
        }

        /// <summary>
        /// スクロール時に確保するマージン幅
        /// </summary>
        public double ScrollMarginWidth
        {
            get { return this.PageBound.Width * 20 / 100; }
        }

        /// <summary>
        /// ルーラーを表示しないなら真、そうでないなら偽
        /// </summary>
        public bool HideRuler
        {
            get { return this._HideRuler; }
            set
            {
                this._HideRuler = value;
                this.LayoutLines.ClearLayoutCache();
                CalculateClipRect();
                CalculateLineCountOnScreen();
            }
        }

        /// <summary>
        /// 矩形選択モード中に文字列が挿入される位置を表す
        /// </summary>
        public SelectCollection InsertPoint
        {
            get;
            set;
        }

        /// <summary>
        /// キャレットがある領域を示す
        /// </summary>
        public Point CaretLocation
        {
            get { return this._CaretLocation; }
        }

        /// <summary>
        /// レイアウト行のどこにキャレットがあるかを表す
        /// </summary>
        public TextPoint CaretPostion
        {
            get { return this._CaretPostion; }
        }

        /// <summary>
        /// ヒットテストを行う
        /// </summary>
        /// <param name="x">x座標</param>
        /// <param name="y">y座標</param>
        /// <returns>テキストエリア内にあれば真。そうでなければ偽</returns>
        public bool HitTextArea(double x, double y)
        {
            if (x >= this.render.TextArea.X && x <= this.render.TextArea.Right &&
                y >= this.render.TextArea.Y && y <= this.render.TextArea.Bottom)
                return true;
            else
                return false;
        }

        /// <summary>
        /// ヒットテストを行う
        /// </summary>
        /// <param name="x">x座標</param>
        /// <param name="row">行</param>
        /// <returns>ヒットした場合はFoldingDataオブジェクトが返され、そうでない場合はnullが返る</returns>
        public FoldingItem HitFoldingData(double x, int row)
        {
            if (x >= this.GetRealtiveX(AreaType.FoldingArea) && x <= this.GetRealtiveX(AreaType.FoldingArea) + this.render.FoldingWidth)
            {
                LineToIndexTableData lineData = this.LayoutLines.GetData(row);
                FoldingItem foldingData = this.LayoutLines.FoldingCollection.Get(lineData.Index, lineData.Length);
                if (foldingData != null && foldingData.IsFirstLine(this.LayoutLines,row))
                    return foldingData;
            }
            return null;
        }

        /// <summary>
        /// Rectで指定された範囲にドキュメントを描く
        /// </summary>
        /// <param name="updateRect">描写する範囲</param>
        /// <remarks>キャレットを点滅させる場合、定期的のこのメソッドを呼び出してください</remarks>
        public override void Draw(Rectangle updateRect)
        {
            if (this.LayoutLines.Count == 0)
                return;

            if ((updateRect.Height < this.PageBound.Height ||
                updateRect.Width < this.PageBound.Width) && 
                this.render.IsVaildCache())
            {
                this.render.DrawCachedBitmap(updateRect);
            }
            else
            {
                Rectangle background = this.PageBound;
                this.render.FillBackground(background);

                if (this.HideLineMarker == false)
                    this.DrawLineMarker(this.CaretPostion.row);

                if (this.HideRuler == false)
                    this.DrawRuler();

                Point pos = this.render.TextArea.TopLeft;
                pos.X -= this.Src.X;
                double endposy = this.render.TextArea.Bottom;
                Size lineNumberSize = new Size(this.render.LineNemberWidth,this.render.TextArea.Height);
                for (int i = this.Src.Row; i < this.LayoutLines.Count; i++)
                {
                    LineToIndexTableData lineData = this.LayoutLines.GetData(i);

                    if (pos.Y + lineData.Layout.Height > endposy)
                        break;

                    FoldingItem foldingData = this.LayoutLines.FoldingCollection.Get(lineData.Index,lineData.Length);

                    if (foldingData != null)
                    {
                        if ((foldingData.Parent == null || foldingData.Parent.Expand) && foldingData.IsFirstLine(this.LayoutLines, i))
                            this.render.DrawFoldingMark(foldingData.Expand, this.PageBound.X + this.GetRealtiveX(AreaType.FoldingArea), pos.Y);
                        if (foldingData.IsHidden(lineData.Index))
                            continue;
                    }

                    var selectRange = from s in this.Document.Selections.Get(lineData.Index, lineData.Length)
                                      let n = Util.ConvertAbsIndexToRelIndex(s, lineData.Index,lineData.Length)
                                      select n;

                    this.render.DrawOneLine(this.LayoutLines, i, pos.X, pos.Y, selectRange);

                    if (this.DrawLineNumber)
                    {
                        this.render.DrawString((i + 1).ToString(), this.PageBound.X + this.GetRealtiveX(AreaType.LineNumberArea), pos.Y, StringAlignment.Right, lineNumberSize);
                    }

                    pos.Y += this.LayoutLines.GetLayout(i).Height;
                }

                if (this.InsertPoint != null)
                    this.DrawInsertPoint();

                this.render.CacheContent();
            }

            if (this.HideCaret == false)
                this.DrawCaret();
        }

        void DrawRuler()
        {
            Point pos,from,to;
            Size emSize = this.render.emSize;
            Rectangle clipRect = this.render.TextArea;
            int count = 0;
            double markerHeight = emSize.Height / 2;
            if (this.render.RightToLeft)
            {
                pos = new Point(clipRect.TopRight.X, clipRect.TopRight.Y - emSize.Height);
                for (; pos.X >= clipRect.TopLeft.X; pos.X -= emSize.Width, count++)
                {
                    from = pos;
                    to = new Point(pos.X, pos.Y + emSize.Height);
                    int mod = count % 10;
                    if (mod == 0)
                    {
                        string countStr = (count / 10).ToString();
                        double counterWidth = emSize.Width * countStr.Length;
                        this.render.DrawString(countStr, pos.X - counterWidth, pos.Y, StringAlignment.Right, new Size(counterWidth, double.MaxValue));
                    }
                    else if (mod == 5)
                        from.Y = from.Y + emSize.Height / 2;
                    else
                        from.Y = from.Y + emSize.Height * 3 / 4;
                    this.render.DrawLine(from, to);
                    if (this.CaretLocation.X >= pos.X && this.CaretLocation.X < pos.X + emSize.Width)
                        this.render.FillRectangle(new Rectangle(pos.X, pos.Y + markerHeight, emSize.Width, markerHeight), FillRectType.OverwriteCaret);
                }
            }
            else
            {
                pos = new Point(clipRect.TopLeft.X, clipRect.TopLeft.Y - emSize.Height);
                for (; pos.X < clipRect.TopRight.X; pos.X += emSize.Width, count++)
                {
                    from = pos;
                    to = new Point(pos.X, pos.Y + emSize.Height);
                    int mod = count % 10;
                    if (mod == 0)
                        this.render.DrawString((count / 10).ToString(), pos.X, pos.Y, StringAlignment.Left, new Size(double.MaxValue, double.MaxValue));
                    else if (mod == 5)
                        from.Y = from.Y + emSize.Height / 2;
                    else
                        from.Y = from.Y + emSize.Height * 3 / 4;
                    this.render.DrawLine(from, to);
                    if (this.CaretLocation.X >= pos.X && this.CaretLocation.X < pos.X + emSize.Width)
                        this.render.FillRectangle(new Rectangle(pos.X, pos.Y + markerHeight, emSize.Width, markerHeight), FillRectType.OverwriteCaret);
                }
            }
            this.render.DrawLine(clipRect.TopLeft, clipRect.TopRight);
        }

        void DrawInsertPoint()
        {
            foreach (Selection sel in this.InsertPoint)
            {
                if (sel.length == 0)
                {
                    TextPoint tp = this.GetLayoutLineFromIndex(sel.start);
                    Point left = this.GetPostionFromTextPoint(tp);
                    double lineHeight = this.LayoutLines.GetLayout(tp.row).Height;
                    Rectangle InsertRect = new Rectangle(left.X,
                        left.Y,
                        CaretWidthOnInsertMode,
                        lineHeight);
                    render.FillRectangle(InsertRect, FillRectType.InsertPoint);
                }
            }
        }

        void DrawCaret()
        {
            long diff = DateTime.Now.Ticks - this.tickCount;
            long blinkTime = this.To100nsTime(this.CaretBlinkTime);

            if (this._CaretBlink && diff % blinkTime >= blinkTime / 2)
                return;

            Rectangle CaretRect = new Rectangle();

            int row = this.CaretPostion.row;
            double lineHeight = this.LayoutLines.GetLayout(row).Height;
            double charWidth = this.LayoutLines.GetLayout(row).GetWidthFromIndex(this.CaretPostion.col);

            if (this.InsertMode || charWidth == 0)
            {
                CaretRect.Size = new Size(CaretWidthOnInsertMode, lineHeight);
                CaretRect.Location = new Point(this.CaretLocation.X, this.CaretLocation.Y);
                render.FillRectangle(CaretRect, FillRectType.InsertCaret);
            }
            else
            {
                double height = lineHeight / 3;
                CaretRect.Size = new Size(charWidth, height);
                CaretRect.Location = new Point(this.CaretLocation.X, this.CaretLocation.Y + lineHeight - height);
                render.FillRectangle(CaretRect, FillRectType.OverwriteCaret);
            }
        }

        long To100nsTime(int ms)
        {
            return ms * 10000;
        }

        public void DrawLineMarker(int row)
        {
            Point p = this.CaretLocation;
            double height = this.LayoutLines.GetData(this.CaretPostion.row).Layout.Height;
            double width = this.render.TextArea.Width;
            this.render.FillRectangle(new Rectangle(this.PageBound.X + this.render.TextArea.X, this.CaretLocation.Y, width, height), FillRectType.LineMarker);
        }

        /// <summary>
        /// 指定した座標の一番近くにあるTextPointを取得する
        /// </summary>
        /// <param name="p">テキストエリアを左上とする相対位置</param>
        /// <returns>レイアウトラインを指し示すTextPoint</returns>
        public TextPoint GetTextPointFromPostion(Point p)
        {
            if (p.Y < this.render.TextArea.TopLeft.Y || 
                p.Y > this.render.TextArea.BottomRight.Y)
                return TextPoint.Null;
            TextPoint tp = new TextPoint();

            if (this.LayoutLines.Count == 0)
                return tp;

            p.Y -= this.render.TextArea.Y;

            double y = 0;
            tp.row = this.LayoutLines.Count - 1;
            for (int i = this.Src.Row; i < this.LayoutLines.Count; i++)
            {
                double height = this.LayoutLines.GetData(i).Layout.Height;
                
                LineToIndexTableData lineData = this.LayoutLines.GetData(i);
                FoldingItem foldingData = this.LayoutLines.FoldingCollection.Get(lineData.Index,lineData.Length);

                if (foldingData != null && foldingData.IsHidden(lineData.Index))
                    continue;

                if (y + height > p.Y)
                {
                    tp.row = i;
                    break;
                }
                y += height;
            }

            if (p.X < this.render.TextArea.X)
                return tp;

            tp.col = GetIndexFromX(tp.row, p.X);

            int lineLength = this.LayoutLines[tp.row].Length;
            if (tp.col > lineLength)
                tp.col = lineLength;

            return tp;
        }

        /// <summary>
        /// X座標に対応するインデックスを取得する
        /// </summary>
        /// <param name="row">対象となる行</param>
        /// <param name="x">テキストエリアからの相対位置</param>
        /// <returns></returns>
        public int GetIndexFromX(int row, double x)
        {
            x -= this.render.TextArea.X;
            LineToIndexTableData data = this.LayoutLines.GetData(row);
            if (data.Length == 0)
                return 0;
            int index = data.Layout.GetIndexFromX(this.Src.X + x);
            return index;
        }

        /// <summary>
        /// インデックスに対応するＸ座標を得る
        /// </summary>
        /// <param name="row">対象となる行</param>
        /// <param name="index">インデックス</param>
        /// <returns>テキストエリアからの相対位置を返す</returns>
        public double GetXFromIndex(int row, int index)
        {
            double x = this.LayoutLines.GetData(row).Layout.GetXFromIndex(index);
            return x - Src.X + this.render.TextArea.X;
        }

        /// <summary>
        /// TextPointに対応する座標を得る
        /// </summary>
        /// <param name="tp">レイアウトライン上の位置</param>
        /// <returns>テキストエリアを左上とする相対位置</returns>
        public Point GetPostionFromTextPoint(TextPoint tp)
        {
            Point p = new Point();
            for (int i = this.Src.Row; i < tp.row; i++)
            {
                LineToIndexTableData lineData = this.LayoutLines.GetData(i);
                FoldingItem foldingData = this.LayoutLines.FoldingCollection.Get(lineData.Index,lineData.Length);
                if (foldingData != null && foldingData.IsHidden(lineData.Index))
                    continue;
                p.Y += this.LayoutLines.GetData(i).Layout.Height;
            }
            p.X = this.GetXFromIndex(tp.row, tp.col);
            p.Y += this.render.TextArea.Y;
            return p;
        }

        /// <summary>
        /// キャレットを指定した位置に移動させる
        /// </summary>
        /// <param name="row"></param>
        /// <param name="col"></param>
        /// <param name="autoExpand">折り畳みを展開するなら真</param>
        public void JumpCaret(int row, int col, bool autoExpand = true)
        {
            if (autoExpand)
            {
                LineToIndexTableData lineData = this.LayoutLines.GetData(row);
                FoldingItem foldingData = this.LayoutLines.FoldingCollection.Get(lineData.Index,lineData.Length);
                if (foldingData != null && (!foldingData.IsFirstLine(this.LayoutLines, row) || foldingData.IsParentHidden()))
                {
                    this.LayoutLines.FoldingCollection.Expand(foldingData);
                    CalculateLineCountOnScreen();
                }
            }

            this._CaretPostion.row = row;
            this._CaretPostion.col = col;
        }

        /// <summary>
        /// index上の文字が表示されるようにSrcを調整する
        /// </summary>
        /// <param name="index">インデックス</param>
        /// <returns>調整されたら真。そうでなければ偽</returns>
        public bool AdjustSrc(int index)
        {
            TextPoint startTextPoint = this.GetLayoutLineFromIndex(index);
            double x = this.LayoutLines.GetData(startTextPoint.row).Layout.GetXFromIndex(startTextPoint.col);
            if (x < this.Src.X ||
                x > this.Src.X + this.PageBound.Width)
            {
                this.TryScroll(x, this.Src.Row);
                return true;
            }
            if (startTextPoint.row < this.Src.Row ||
                startTextPoint.row > this.Src.Row + this.LineCountOnScreenWithInVisible)
            {
                this.TryScroll(this.Src.X, startTextPoint.row);
                return true;
            }
            return false;
        }

        /// <summary>
        /// キャレットがあるところまでスクロールする
        /// </summary>
        /// <return>再描写する必要があるなら真を返す</return>
        /// <remarks>Document.Update(type == UpdateType.Clear)イベント時に呼び出した場合、例外が発生します</remarks>
        public bool AdjustCaretAndSrc(AdjustFlow flow = AdjustFlow.Both)
        {
            if (this.PageBound.Width == 0 || this.PageBound.Height == 0)
            {
                this.SetCaretPostion(this.Margin.Left + this.render.FoldingWidth, 0);
                return false;
            }

            bool result = false;
            TextPoint tp = this.CaretPostion;
            double x = this.CaretLocation.X;
            double y = this.CaretLocation.Y;

            if (flow == AdjustFlow.Col || flow == AdjustFlow.Both)
            {
                x = this.LayoutLines.GetData(tp.row).Layout.GetXFromIndex(tp.col);

                double left = this.Src.X;
                double right = this.Src.X + this.render.TextArea.Width;

                if (x >= left && x <= right)    //xは表示領域にないにある
                {
                    x -= left;
                }
                else if (x > right) //xは表示領域の右側にある
                {
                    this._Src.X = x - this.render.TextArea.Width + this.ScrollMarginWidth;
                    if (this.render.RightToLeft && this._Src.X > 0)
                    {
                        System.Diagnostics.Debug.Assert(x > 0);
                        this._Src.X = 0;
                    }
                    else
                    {
                        x = this.render.TextArea.Width - this.ScrollMarginWidth;
                    }
                    result = true;
                }
                else if (x < left)    //xは表示領域の左側にある
                {
                    this._Src.X = x - this.ScrollMarginWidth;
                    if (!this.render.RightToLeft && this._Src.X < this.render.TextArea.X)
                    {
                        this._Src.X = 0;
                    }
                    else
                    {
                        x = this.ScrollMarginWidth;
                    }
                    result = true;
                }
                x += this.render.TextArea.X;
            }

            if (flow == AdjustFlow.Row || flow == AdjustFlow.Both)
            {
                int caretRow = 0;
                int lineCount = this.LineCountOnScreenWithInVisible;
                if (tp.row >= this.Src.Row && tp.row < this.Src.Row + lineCount)
                    caretRow = tp.row - this.Src.Row;
                else if (tp.row >= this.Src.Row + lineCount)
                {
                    this._Src.Row = this.GetSrcRow(tp.row, this.LineCountOnScreen);
                    caretRow = tp.row - this._Src.Row;
                    result = true;
                    CalculateLineCountOnScreen();
                }
                else if (tp.row < this.Src.Row)
                {
                    this._Src.Row = tp.row;
                    result = true;
                    CalculateLineCountOnScreen();
                }

                y = 0;

                if (caretRow > 0)
                {
                    for (int i = 0; i < caretRow; i++)
                    {
                        LineToIndexTableData lineData = this.LayoutLines.GetData(this.Src.Row + i);

                        FoldingItem foldingData = this.LayoutLines.FoldingCollection.Get(lineData.Index, lineData.Length);

                        if (foldingData != null && foldingData.IsHidden(lineData.Index))
                            continue;

                        y += lineData.Layout.Height;
                    }
                }
                y += this.render.TextArea.Y;
            }

            this.SetCaretPostion(x, y);

            if (result)
                this.OnSrcChanged(null);

            return result;
        }

        int GetSrcRow(int row,int count)
        {
            if (this.LayoutLines.FoldingStrategy == null)
                return row - count;
            for (int i = row; i >= 0; i--)
            {
                LineToIndexTableData lineData = this.LayoutLines.GetData(i);
                FoldingItem foldingData = this.LayoutLines.FoldingCollection.GetFarestHiddenFoldingData(lineData.Index, lineData.Length);
                if (foldingData != null && foldingData.IsHidden(lineData.Index))
                    continue;
                if (count <= 0)
                    return i;
                count--;
            }
            return 0;
        }

        /// <summary>
        /// レイアウト行をテキストポイントからインデックスに変換する
        /// </summary>
        /// <param name="tp">テキストポイント表す</param>
        /// <returns>インデックスを返す</returns>
        public int GetIndexFromLayoutLine(TextPoint tp)
        {
            return this.LayoutLines.GetIndexFromTextPoint(tp);
        }

        /// <summary>
        /// インデックスからレイアウト行を指し示すテキストポイントに変換する
        /// </summary>
        /// <param name="index">インデックスを表す</param>
        /// <returns>テキストポイント返す</returns>
        public TextPoint GetLayoutLineFromIndex(int index)
        {
            return this.LayoutLines.GetTextPointFromIndex(index);
        }

        /// <summary>
        /// 指定した座標までスクロールする
        /// </summary>
        /// <param name="x"></param>
        /// <param name="row"></param>
        /// <remarks>
        /// 範囲外の座標を指定した場合、範囲内に収まるように調整されます
        /// </remarks>
        public void Scroll(double x, int row)
        {
            if (x < 0)
                x = 0;
            if(row < 0)
                row = 0;
            int endRow = this.LayoutLines.Count - 1 - this.LineCountOnScreen;
            if (endRow < 0)
                endRow = 0;
            if (row > endRow)
                row = endRow;
            base.TryScroll(x, row);
        }

        public int AdjustRow(int row, bool isMoveNext)
        {
            if (this.LayoutLines.FoldingStrategy == null)
                return row;
            LineToIndexTableData lineData = this.LayoutLines.GetData(row);
            FoldingItem foldingData = this.LayoutLines.FoldingCollection.GetFarestHiddenFoldingData(lineData.Index, lineData.Length);
            if (foldingData != null && !foldingData.Expand)
            {
                if (foldingData.End == this.Document.Length)
                    return row;
                if (isMoveNext && lineData.Index > foldingData.Start)
                    row = this.LayoutLines.GetLineNumberFromIndex(foldingData.End) + 1;
                else
                    row = this.LayoutLines.GetLineNumberFromIndex(foldingData.Start);
            }
            return row;
        }

        protected override void CalculateClipRect()
        {
            double x, y, width, height;

            if (this.DrawLineNumber)
            {
                if (this.render.RightToLeft)
                    x = this.Margin.Left;
                else
                    x = this.render.LineNemberWidth + this.render.FoldingWidth + this.Margin.Left;
                width = this.PageBound.Width - this.render.LineNemberWidth - this.Margin.Left - this.Margin.Right - this.render.FoldingWidth;
            }
            else
            {
                if (this.render.RightToLeft)
                    x = this.Margin.Left;
                else
                    x = this.Margin.Left + this.render.FoldingWidth;
                width = this.PageBound.Width - this.Margin.Left - this.Margin.Right - this.render.FoldingWidth;
            }

            y = this.Margin.Top;
            height = this.PageBound.Height - this.Margin.Top - this.Margin.Bottom;

            if (this.HideRuler == false)
            {
                double rulerHeight = this.render.emSize.Height;
                y += rulerHeight;
                height -= rulerHeight;
            }

            if (width < 0)
                width = 0;

            if (height < 0)
                height = 0;

            this.render.TextArea = new Rectangle(x, y, width, height);

            this.LineBreakingMarginWidth = width * 5 / 100;
        }

        protected override void CalculateLineCountOnScreen()
        {
            if (this.LayoutLines.Count == 0 || this.PageBound.Height == 0)
                return;

            double y = 0;
            int i = this.Src.Row;
            int visualCount = this.Src.Row;
            for (; true; i++)
            {
                LineToIndexTableData lineData = i < this.LayoutLines.Count ?
                    this.LayoutLines.GetData(i) :
                    this.LayoutLines.GetData(this.LayoutLines.Count - 1);

                FoldingItem foldingData = this.LayoutLines.FoldingCollection.Get(lineData.Index, lineData.Length);

                if (foldingData != null && foldingData.End < this.Document.Length - 1 && foldingData.IsHidden(lineData.Index))
                    continue;

                ITextLayout layout = lineData.Layout;

                double width = layout.Width;

                if (width > this._LongestWidth)
                    this._LongestWidth = width;

                double lineHeight = layout.Height;

                y += lineHeight;

                if (y >= this.render.TextArea.Height)
                    break;
                visualCount++;
            }
            this.LineCountOnScreen = Math.Max(visualCount - this.Src.Row - 1, 0);
            this.LineCountOnScreenWithInVisible = Math.Max(i - this.Src.Row - 1, 0);
        }

        void SetCaretPostion(double x, double y)
        {
            this._CaretLocation = new Point(x + this.PageBound.X, y + this.PageBound.Y);
            this.HideCaret = false;
        }

        void FoldingCollection_StatusChanged(object sender, FoldingItemStatusChangedEventArgs e)
        {
            this.CalculateLineCountOnScreen();
        }

        enum AreaType
        {
            FoldingArea,
            LineNumberArea,
            TextArea
        }

        double GetRealtiveX(AreaType type)
        {
            switch (type)
            {
                case AreaType.FoldingArea:
                    if (this.render.RightToLeft)
                        return this.PageBound.TopRight.X - this.render.FoldingWidth;
                    if (this.DrawLineNumber)
                        return this.render.TextArea.X - this.render.LineNemberWidth - this.Margin.Left - this.render.FoldingWidth;
                    else
                        return this.render.TextArea.X - this.render.FoldingWidth - this.Margin.Left;
                case AreaType.LineNumberArea:
                    if (this.DrawLineNumber == false)
                        throw new InvalidOperationException();
                    if (this.render.RightToLeft)
                        return this.PageBound.TopRight.X - this.render.FoldingWidth - this.render.LineNemberWidth;
                    else
                        return this.render.TextArea.X - this.render.LineNemberWidth - this.Margin.Left;
                case AreaType.TextArea:
                    return this.render.TextArea.X;
            }
            throw new ArgumentOutOfRangeException();
        }
    }
}
