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

namespace FooEditEngine
{
    internal interface ITextLayout : IDisposable
    {
        /// <summary>
        /// 文字列の幅
        /// </summary>
        double Width
        {
            get;
        }

        /// <summary>
        /// 文字列の高さ
        /// </summary>
        double Height
        {
            get;
        }

        /// <summary>
        /// Disposeされているなら真を返す
        /// </summary>
        bool Disposed
        {
            get;
        }

        /// <summary>
        /// 破棄すべきなら真。そうでなければ偽
        /// </summary>
        bool Invaild
        {
            get;
        }

        /// <summary>
        /// 桁方向の座標に対応するインデックスを得る
        /// </summary>
        /// <param name="colpos">桁方向の座標</param>
        /// <returns>インデックス</returns>
        /// <remarks>行番号の幅は考慮されてないのでView以外のクラスは呼び出さないでください</remarks>
        int GetIndexFromColPostion(double colpos);

        /// <summary>
        /// インデックスに対応する文字の幅を得る
        /// </summary>
        /// <param name="index">インデックス</param>
        /// <returns>文字の幅</returns>
        double GetWidthFromIndex(int index);

        /// <summary>
        /// インデックスに対応する桁方向の座標を得る
        /// </summary>
        /// <param name="index">インデックス</param>
        /// <returns>桁方向の座標</returns>
        /// <remarks>行頭にEOFが含まれている場合、0が返ります</remarks>
        double GetColPostionFromIndex(int index);

        /// <summary>
        /// 適切な位置にインデックスを調整する
        /// </summary>
        /// <param name="index">インデックス</param>
        /// <param name="flow">真の場合は隣接するクラスターを指すように調整し、
        /// そうでない場合は対応するクラスターの先頭を指すように調整します</param>
        /// <returns>調整後のインデックス</returns>
        int AlignIndexToNearestCluster(int index, AlignDirection flow);
    }

    internal class SpilitStringEventArgs : EventArgs
    {
        public Document buffer;
        public int index;
        public int length;
        public int row;
        public SpilitStringEventArgs(Document buf, int index, int length,int row)
        {
            this.buffer = buf;
            this.index = index;
            this.length = length;
            this.row = row;
        }
    }

    internal struct SyntaxInfo
    {
        public TokenType type;
        public int index;
        public int length;
        public SyntaxInfo(int index, int length, TokenType type)
        {
            this.type = type;
            this.index = index;
            this.length = length;
        }
    }

    internal enum EncloserType
    {
        None,
        Begin,
        Now,
        End,
    }

    internal class LineToIndexTableData : IDisposable
    {
        /// <summary>
        /// 行の先頭。正しい行の先頭位置を取得するにはGetLineHeadIndex()を使用してください
        /// </summary>
        public int Index;
        /// <summary>
        /// 行の長さ
        /// </summary>
        public int Length;
        /// <summary>
        /// 改行マークかEOFなら真を返す
        /// </summary>
        public bool LineEnd;
        public SyntaxInfo[] Syntax;
        public EncloserType EncloserType;
        internal ITextLayout Layout;
        public bool Dirty = false;

        /// <summary>
        /// コンストラクター。LineToIndexTable以外のクラスで呼び出さないでください
        /// </summary>
        public LineToIndexTableData()
        {
        }

        /// <summary>
        /// コンストラクター。LineToIndexTable以外のクラスで呼び出さないでください
        /// </summary>
        public LineToIndexTableData(int index, int length, bool lineend,bool dirty, SyntaxInfo[] syntax)
        {
            this.Index = index;
            this.Length = length;
            this.LineEnd = lineend;
            this.Syntax = syntax;
            this.EncloserType = EncloserType.None;
            this.Dirty = dirty;
        }

        public void Dispose()
        {
            if(this.Layout != null)
                this.Layout.Dispose();
        }
    }

    internal delegate IList<LineToIndexTableData> SpilitStringEventHandler(object sender, SpilitStringEventArgs e);

    internal sealed class CreateLayoutEventArgs
    {
        /// <summary>
        /// 開始インデックス
        /// </summary>
        public int Index
        {
            get;
            private set;
        }
        /// <summary>
        /// 長さ
        /// </summary>
        public int Length
        {
            get;
            private set;
        }
        /// <summary>
        /// 文字列
        /// </summary>
        public string Content
        {
            get;
            private set;
        }
        public CreateLayoutEventArgs(int index, int length,string content)
        {
            this.Index = index;
            this.Length = length;
            this.Content = content;
        }
    }

    /// <summary>
    /// 行番号とインデックスを相互変換するためのクラス
    /// </summary>
    public sealed class LineToIndexTable : IEnumerable<string>
    {
        const int MaxEntries = 100;
        Queue<ITextLayout> CacheEntries = new Queue<ITextLayout>();
        GapBuffer<LineToIndexTableData> Lines = new GapBuffer<LineToIndexTableData>();
        Document Document;
        bool _IsSync;
        ITextRender render;
        int stepRow = -1,stepLength = 0;
        const int STEP_ROW_IS_NONE = -1;

        internal LineToIndexTable(Document buf, ITextRender r)
        {
            this.Document = buf;
            this.Document.Markers.Updated += Markers_Updated;
            this.render = r;
            this.FoldingCollection = new FoldingCollection();
            this._IsSync = true;
#if DEBUG && !NETFX_CORE
            if (!Debugger.IsAttached)
            {
                Guid guid = Guid.NewGuid();
                string path = string.Format("{0}\\footextbox_lti_debug_{1}.log", System.IO.Path.GetTempPath(), guid);
                Debug.Listeners.Add(new TextWriterTraceListener(path));
                Debug.AutoFlush = true;
            }
#endif
        }

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

        internal SpilitStringEventHandler SpilitString;

        /// <summary>
        /// 行数を返す
        /// </summary>
        public int Count
        {
            get { return this.Lines.Count; }
        }

        /// <summary>
        /// 折り畳み関係の情報を収めたコレクション
        /// </summary>
        public FoldingCollection FoldingCollection
        {
            get;
            private set;
        }

        /// <summary>
        /// シンタックスハイライター
        /// </summary>
        internal IHilighter Hilighter { get; set; }

        internal IFoldingStrategy FoldingStrategy { get; set; }

        /// <summary>
        /// 保持しているレイアウトキャッシュをクリアーする
        /// </summary>
        public void ClearLayoutCache()
        {
            foreach (ITextLayout data in this.CacheEntries)
            {
                data.Dispose();
            }
            this.CacheEntries.Clear();
        }

        /// <summary>
        /// 行番号に対応する文字列を返します
        /// </summary>
        /// <param name="n"></param>
        /// <returns></returns>
        public string this[int n]
        {
            get
            {
                LineToIndexTableData data = this.Lines[n];
                string str = this.Document.ToString(this.GetLineHeadIndex(n), data.Length);

                return str;
            }
        }

        /// <summary>
        /// 更新フラグを更新しないなら真
        /// </summary>
        public bool IsFrozneDirtyFlag
        {
            get;
            set;
        }

        int GetLineHeadIndex(int row)
        {
            if (this.Lines.Count == 0)
                return 0;
            if (this.stepRow != STEP_ROW_IS_NONE && row > this.stepRow)
                return this.Lines[row].Index + this.stepLength;
            else
                return this.Lines[row].Index;
        }

        internal LineToIndexTableData CreateLineToIndexTableData(int index, int length, bool lineend, SyntaxInfo[] syntax)
        {
            LineToIndexTableData result = new LineToIndexTableData(index, length, lineend,this.IsFrozneDirtyFlag == false, syntax);
            return result;
        }

        internal void UpdateAsReplace(int index, int removedLength, int insertedLength)
        {
#if DEBUG
            Debug.WriteLine("Replaced Index:{0} RemoveLength:{1} InsertLength:{2}", index, removedLength, insertedLength);
#endif
            //削除すべき行の開始位置を求める
            int startRow = this.GetLineNumberFromIndex(index);
            while(startRow > 0 && this.Lines[startRow - 1].LineEnd == false)
                startRow--;

            //削除すべき行の終了位置を求める
            int endRow = this.GetLineNumberFromIndex(index + removedLength);
            while (endRow < this.Lines.Count && this.Lines[endRow].LineEnd == false)
                endRow++;
            if (endRow >= this.Lines.Count)
                endRow = this.Lines.Count - 1;

            //SpilitStringの対象となる範囲を求める
            int HeadIndex = this.GetIndexFromLineNumber(startRow);
            int LastIndex = this.GetIndexFromLineNumber(endRow) + this.GetLengthFromLineNumber(endRow) - 1;
            int fisrtPartLength = index - HeadIndex;
            int secondPartLength = LastIndex - (index + removedLength - 1);
            int analyzeLength = fisrtPartLength + secondPartLength + insertedLength;
            Debug.Assert(analyzeLength <= this.Document.Length - 1 - HeadIndex + 1);

            //行を削除する
            int removeCount = endRow - startRow + 1;
            this.RemoveLine(startRow, removeCount);

            //挿入範囲内のドキュメントから行を生成する
            SpilitStringEventArgs e = new SpilitStringEventArgs(this.Document, HeadIndex, analyzeLength, startRow);
            IList<LineToIndexTableData> newLines = SpilitString(this, e);

            //行を挿入する
            int newCount = newLines.Count;
            if (this.stepRow > startRow && newCount > 0 && newCount != removeCount)
            {
                this.stepRow = Math.Max(this.stepRow - (removeCount - newCount),startRow);
#if DEBUG
                if (this.stepRow < 0 || this.stepRow > this.Lines.Count + newCount)
                {
                    Debug.WriteLine("step row < 0 or step row >= lines.count");
                    Debugger.Break();
                }
#endif
            }
            int deltaLength = insertedLength - removedLength;
            this.InsertLine(startRow, newLines, deltaLength);
            this.UpdateLineHeadIndex(deltaLength, startRow, newLines.Count);
            this.AddDummyLine();

            this.FoldingCollection.UpdateData(this.Document, index, insertedLength, removedLength);            
            this.FoldingCollection.CollectEmptyFolding(index, Document.Length - 1);

            this.Hilight(startRow, newLines.Count);

            this._IsSync = false;
        }

        void RemoveLine(int startRow, int removeCount)
        {
            for (int i = startRow; i < startRow + removeCount; i++)
                this.Lines[i].Dispose();

            this.Lines.RemoveRange(startRow, removeCount);
        }

        void InsertLine(int startRow, IList<LineToIndexTableData> collection, int deltaLength)
        {
            //startRowが挿入した行の開始位置なのであらかじめ引いておく
            for (int i = 1; i < collection.Count; i++)
            {
                if (this.stepRow != STEP_ROW_IS_NONE && startRow + i > this.stepRow)
                    collection[i].Index -= deltaLength + this.stepLength;
                else
                    collection[i].Index -= deltaLength;
            }

            this.Lines.InsertRange(startRow, collection);
        }

        void AddDummyLine()
        {
            LineToIndexTableData dummyLine = null;
            if (this.Lines.Count == 0)
            {
                dummyLine = new LineToIndexTableData();
                this.Lines.Add(dummyLine);
                return;
            }

            int lastLineRow = this.Lines.Count > 0 ? this.Lines.Count - 1 : 0;
            int lastLineHeadIndex = this.GetIndexFromLineNumber(lastLineRow);
            int lastLineLength = this.GetLengthFromLineNumber(lastLineRow);

            if (lastLineLength != 0 && this.Document[Document.Length - 1] == Document.NewLine)
            {
                int realIndex = lastLineHeadIndex + lastLineLength;
                if (lastLineRow >= this.stepRow)
                    realIndex -= this.stepLength;
                dummyLine = new LineToIndexTableData(realIndex, 0, true,false, null);
                this.Lines.Add(dummyLine);
            }
        }

        void UpdateLineHeadIndex(int deltaLength,int startRow,int insertedLineCount)
        {
            if (this.Lines.Count == 0)
            {
                this.stepRow = STEP_ROW_IS_NONE;
                this.stepLength = 0;
                return;
            }

            if (this.stepRow == STEP_ROW_IS_NONE)
            {
                this.stepRow = startRow;
                this.stepLength = deltaLength;
                return;
            }


            if (startRow < this.stepRow)
            {
                //ドキュメントの後半部分をごっそり削除した場合、this.stepRow >= this.Lines.Countになる可能性がある
                if (this.stepRow >= this.Lines.Count)
                    this.stepRow = this.Lines.Count - 1;
                for (int i = this.stepRow; i > startRow; i--)
                    this.Lines[i].Index -= this.stepLength;
            }
            else if (startRow > this.stepRow)
            {
                for (int i = this.stepRow + 1; i < startRow; i++)
                    this.Lines[i].Index += this.stepLength;
            }

            this.stepRow = startRow;
            this.stepLength += deltaLength;

            this.ValidateLines();
        }

        void ValidateLines()
        {
#if DEBUG
            int nextIndex = 0;
            for (int i = 0; i < this.Lines.Count; i++)
            {
                int lineHeadIndex = this.GetLineHeadIndex(i);
                if (lineHeadIndex != nextIndex)
                {
                    Debug.WriteLine("Invaild Line");
                    System.Diagnostics.Debugger.Break();
                }
                nextIndex = lineHeadIndex + this.Lines[i].Length;
            }
#endif
        }

        /// <summary>
        /// 行番号をインデックスに変換します
        /// </summary>
        /// <param name="row">行番号</param>
        /// <returns>0から始まるインデックスを返す</returns>
        public int GetIndexFromLineNumber(int row)
        {
            if (row < 0 || row > this.Lines.Count)
                throw new ArgumentOutOfRangeException();
            return this.GetLineHeadIndex(row);
        }

        /// <summary>
        /// 行の長さを得ます
        /// </summary>
        /// <param name="row">行番号</param>
        /// <returns>行の文字長を返します</returns>
        public int GetLengthFromLineNumber(int row)
        {
            if (row < 0 || row > this.Lines.Count)
                throw new ArgumentOutOfRangeException();
            return this.Lines[row].Length;
        }

        /// <summary>
        /// 更新フラグを取得します
        /// </summary>
        /// <param name="row">行番号</param>
        /// <returns>更新されていれば真。そうでなければ偽</returns>
        public bool GetDirtyFlag(int row)
        {
            if (row < 0 || row > this.Lines.Count)
                throw new ArgumentOutOfRangeException();
            return this.Lines[row].Dirty;
        }

        internal ITextLayout GetLayout(int row)
        {
            if (this.Lines[row].Layout != null && this.Lines[row].Layout.Invaild)
            {
                this.Lines[row].Layout.Dispose();
                this.Lines[row].Layout = null;
            }
            if (this.Lines[row].Layout == null || this.Lines[row].Layout.Disposed)
                this.Lines[row].Layout = this.CreateLayout(row);
            return this.Lines[row].Layout;
        }

        internal event EventHandler<CreateLayoutEventArgs> CreateingLayout;

        ITextLayout CreateLayout(int row)
        {
            ITextLayout layout;
            LineToIndexTableData lineData = this.Lines[row];
            if (lineData.Length == 0)
            {
                layout = this.render.CreateLaytout("", null, null);
            }
            else
            {
                int lineHeadIndex = this.GetLineHeadIndex(row);

                string content = this.Document.ToString(lineHeadIndex, lineData.Length);

                if (this.CreateingLayout != null)
                    this.CreateingLayout(this, new CreateLayoutEventArgs(lineHeadIndex, lineData.Length,content));
                
                var markerRange = from id in this.Document.Markers.IDs
                                  from s in this.Document.Markers.Get(id,lineHeadIndex,lineData.Length)
                                  let n = Util.ConvertAbsIndexToRelIndex(s, lineHeadIndex, lineData.Length)
                                  select n;
                layout = this.render.CreateLaytout(content, lineData.Syntax, markerRange);
            }

            if (this.CacheEntries.Count > MaxEntries)
            {
                ITextLayout oldItem = this.CacheEntries.Dequeue();
                oldItem.Dispose();
            }
            this.CacheEntries.Enqueue(layout);

            return layout;
        }

        int lastLineNumber;
        /// <summary>
        /// インデックスを行番号に変換します
        /// </summary>
        /// <param name="index">インデックス</param>
        /// <returns>行番号を返します</returns>
        public int GetLineNumberFromIndex(int index)
        {
            if (index < 0)
                throw new ArgumentOutOfRangeException("indexに負の値を設定することはできません");

            if (index == 0 && this.Lines.Count > 0)
                return 0;

            LineToIndexTableData line;
            int lineHeadIndex;

            if (lastLineNumber < this.Lines.Count - 1)
            {
                line = this.Lines[lastLineNumber];
                lineHeadIndex = this.GetLineHeadIndex(lastLineNumber);
                if (index >= lineHeadIndex && index < lineHeadIndex + line.Length)
                    return lastLineNumber;
            }

            int left = 0, right = this.Lines.Count - 1, mid;
            while (left <= right)
            {
                mid = (left + right) / 2;
                line = this.Lines[mid];
                lineHeadIndex = this.GetLineHeadIndex(mid);
                if (index >= lineHeadIndex && index < lineHeadIndex + line.Length)
                {
                    lastLineNumber = mid;
                    return mid;
                }
                if (index < lineHeadIndex)
                {
                    right = mid - 1;
                }
                else
                {
                    left = mid + 1;
                }
            }

            int lastRow = this.Lines.Count - 1;
            line = this.Lines[lastRow];
            lineHeadIndex = this.GetLineHeadIndex(lastRow);
            if (index >= lineHeadIndex && index <= lineHeadIndex + line.Length)   //最終行長+1までキャレットが移動する可能性があるので
            {
                lastLineNumber = this.Lines.Count - 1;
                return lastLineNumber;
            }

            throw new ArgumentOutOfRangeException("該当する行が見つかりませんでした");
        }

        /// <summary>
        /// インデックスからテキストポイントに変換します
        /// </summary>
        /// <param name="index">インデックス</param>
        /// <returns>TextPoint構造体を返します</returns>
        public TextPoint GetTextPointFromIndex(int index)
        {
            TextPoint tp = new TextPoint();
            tp.row = GetLineNumberFromIndex(index);
            tp.col = index - this.GetLineHeadIndex(tp.row);
            Debug.Assert(tp.row < this.Lines.Count && tp.col <= this.Lines[tp.row].Length);
            return tp;
        }

        /// <summary>
        /// テキストポイントからインデックスに変換します
        /// </summary>
        /// <param name="tp">TextPoint構造体</param>
        /// <returns>インデックスを返します</returns>
        public int GetIndexFromTextPoint(TextPoint tp)
        {
            if (tp == TextPoint.Null)
                throw new ArgumentNullException("TextPoint.Null以外の値でなければなりません");
            if(tp.row < 0 || tp.row > this.Lines.Count)
                throw new ArgumentOutOfRangeException("tp.rowが設定できる範囲を超えています");
            if (tp.col < 0 || tp.col > this.Lines[tp.row].Length)
                throw new ArgumentOutOfRangeException("tp.colが設定できる範囲を超えています");
            return this.GetLineHeadIndex(tp.row) + tp.col;
        }

        /// <summary>
        /// フォールディングを再生成します
        /// </summary>
        /// <param name="force">ドキュメントが更新されていなくても再生成する</param>
        /// <returns>生成された場合は真を返す</returns>
        /// <remarks>デフォルトではドキュメントが更新されている時にだけ再生成されます</remarks>
        public bool GenerateFolding(bool force = false)
        {
            if (force)
                this._IsSync = false;
            if (this.Document.Length == 0 || this._IsSync)
                return false;
            this.GenerateFolding(0, this.Document.Length - 1);
            this._IsSync = true;
            return true;
        }

        void GenerateFolding(int start, int end)
        {
            if (start > end)
                throw new ArgumentException("start <= endである必要があります");
            if (this.FoldingStrategy != null)
            {
                var items = this.FoldingStrategy.AnalyzeDocument(this.Document, start, end)
                    .Where((item) =>
                    {
                        int startRow = this.GetLineNumberFromIndex(item.Start);
                        int endRow = this.GetLineNumberFromIndex(item.End);
                        return startRow != endRow;
                    })
                    .Select((item) => item);
                this.FoldingCollection.AddRange(items);
            }
        }

        /// <summary>
        /// フォールディングをすべて削除します
        /// </summary>
        public void ClearFolding()
        {
            this.FoldingCollection.Clear();
            this._IsSync = false;
        }

        /// <summary>
        /// すべての行に対しシンタックスハイライトを行います
        /// </summary>
        public void HilightAll()
        {
            this.Hilight(0, this.Lines.Count);
            this.ClearLayoutCache();
        }

        /// <summary>
        /// ハイライト関連の情報をすべて削除します
        /// </summary>
        public void ClearHilight()
        {
            foreach (LineToIndexTableData line in this.Lines)
                line.Syntax = null;
            this.ClearLayoutCache();
        }

        /// <summary>
        /// すべて削除します
        /// </summary>
        internal void Clear()
        {
            this.ClearLayoutCache();
            this.FoldingCollection.Clear();
            this.Lines.Clear();
            LineToIndexTableData dummy = new LineToIndexTableData();
            this.Lines.Add(dummy);
            this.stepRow = STEP_ROW_IS_NONE;
            this.stepLength = 0;
            Debug.WriteLine("Clear");
        }

        void Hilight(int row, int rowCount)
        {
            if (this.Hilighter == null || rowCount == 0)
                return;

            //シンタックスハイライトの開始行を求める
            int startRow = row;
            EncloserType type = this.Lines[startRow].EncloserType;
            EncloserType prevLineType = startRow > 0 ? this.Lines[startRow - 1].EncloserType : EncloserType.None;
            if (type == EncloserType.Now || type == EncloserType.End ||
                prevLineType == EncloserType.Now || prevLineType == EncloserType.End)
                startRow = SearchStartRow(startRow);
            else if (prevLineType == EncloserType.Begin)
                startRow = startRow - 1;

            //シンタックスハイライトの終了行を求める
            int endRow = row + rowCount - 1;
            type = this.Lines[endRow].EncloserType;
            prevLineType = endRow > 0 ? this.Lines[endRow - 1].EncloserType : EncloserType.None;
            if (type == EncloserType.Begin || type == EncloserType.Now ||
                prevLineType == EncloserType.Begin || prevLineType == EncloserType.Now)
                endRow = SearchEndRow(endRow);
            else if (endRow + 1 <= this.Lines.Count - 1 && this.Lines[endRow + 1].EncloserType == EncloserType.Now)
                endRow = SearchEndRow(endRow + 1);

            //シンタックスハイライトを行う
            bool hasBeginEncloser = false;
            int i;
            for (i = startRow; i <= endRow; i++)
            {
                this.HilightLine(i, ref hasBeginEncloser);
            }

            if (hasBeginEncloser)   //終了エンクロージャーが見つかったかどうか
            {
                for (; i < this.Lines.Count; i++)
                {
                    if (this.HilightLine(i, ref hasBeginEncloser) < 0)
                        break;
                }
            }

            this.Hilighter.Reset();
        }

        private int HilightLine(int row, ref bool hasBeginEncloser)
        {
            //シンタックスハイライトを行う
            List<SyntaxInfo> syntax = new List<SyntaxInfo>();
            string str = this[row];
            int level = this.Hilighter.DoHilight(str, str.Length, (s) =>
            {
                if (s.type == TokenType.None || s.type == TokenType.Control)
                    return;
                if (str[s.index + s.length - 1] == Document.NewLine)
                    s.length--;
                syntax.Add(new SyntaxInfo(s.index, s.length, s.type));
            });

            LineToIndexTableData lineData = this.Lines[row];
            lineData.Syntax = syntax.ToArray();

            if (level > 0 && hasBeginEncloser == false)  //開始エンクロージャー
            {
                lineData.EncloserType = EncloserType.Begin;
                hasBeginEncloser = true;
            }
            else if (level < 0) //終了エンクロージャー
            {
                lineData.EncloserType = EncloserType.End;
                hasBeginEncloser = false;
            }
            else if (hasBeginEncloser) //エンクロージャーの範囲内
                lineData.EncloserType = EncloserType.Now;
            else
                lineData.EncloserType = EncloserType.None;

            return level;
        }

        private int SearchStartRow(int startRow)
        {
            for (startRow--; startRow >= 0; startRow--)
            {
                EncloserType type = this.Lines[startRow].EncloserType;
                if (type == EncloserType.Begin || type == EncloserType.None)
                    return startRow;
            }
            return 0;
        }

        private int SearchEndRow(int startRow)
        {
            for (startRow++ ; startRow < this.Lines.Count; startRow++)
            {
                EncloserType type = this.Lines[startRow].EncloserType;
                if (type == EncloserType.End)
                    return startRow;
            }
            return this.Lines.Count - 1;
        }

        #region IEnumerable<string> メンバー

        /// <summary>
        /// コレクションを反復処理するためのIEnumeratorを返す
        /// </summary>
        /// <returns>IEnumeratorオブジェクト</returns>
        public IEnumerator<string> GetEnumerator()
        {
            for (int i = 0; i < this.Lines.Count; i++)
                yield return this[i];
        }

        #endregion

        #region IEnumerable メンバー

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            for (int i = 0; i < this.Lines.Count; i++)
                yield return this[i];
        }

        #endregion
    }

}
