/*
 * 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.Threading.Tasks;

namespace FooEditEngine
{
    /// <summary>
    /// 新しく作成されるフォールティングアイテムを表す
    /// </summary>
    public class FoldingItem : IRangeProvider<int>
    {
        /// <summary>
        /// 開始インデックス
        /// </summary>
        public int Start
        {
            get
            {
                return this.Range.From;
            }
        }

        /// <summary>
        /// 終了インデックス
        /// </summary>
        public int End
        {
            get
            {
                return this.Range.To;
            }
        }

        /// <summary>
        /// 展開されているなら真。そうでないなら偽
        /// </summary>
        public bool Expand
        {
            get;
            internal set;
        }

        /// <summary>
        /// 内部で使用しているメンバーです。外部から参照しないでください
        /// </summary>
        public Range<int> Range
        {
            get;
            set;
        }

        internal FoldingItem Parent;

        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <param name="start">開始インデックス</param>
        /// <param name="end">終了インデックス</param>
        public FoldingItem(int start, int end)
        {
            if (start >= end)
                throw new ArgumentException("start < endである必要があります");
            this.Range = new Range<int>(start, end);
            this.Expand = true;
        }

        internal bool IsParentHidden()
        {
            if (this.Parent != null && !this.Parent.Expand)
                return true;
            else
                return false;
        }

        internal bool IsHidden(int index)
        {
            if (this.Parent != null && !this.Parent.Expand)
                return true;
            if (!this.Expand && index > this.Start && index <= this.End)
                return true;
            return false;
        }

        internal bool IsFirstLine(LineToIndexTable layoutLines, int row)
        {
            int firstRow = layoutLines.GetLineNumberFromIndex(this.Start);
            return row == firstRow;
        }
    }

    sealed class RangeItemComparer : IComparer<FoldingItem>
    {
        public int Compare(FoldingItem x, FoldingItem y)
        {
            return x.Range.CompareTo(y.Range);
        }
    }

    /// <summary>
    /// イベントデーター
    /// </summary>
    public sealed class FoldingItemStatusChangedEventArgs : EventArgs
    {
        /// <summary>
        /// 状態に変化があったアイテム
        /// </summary>
        public FoldingItem Item;
        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <param name="item">FoldingItemオブジェクト</param>
        public FoldingItemStatusChangedEventArgs(FoldingItem item)
        {
            this.Item = item;
        }
    }

    /// <summary>
    /// 折り畳み関係のコレクションを表す
    /// </summary>
    public sealed class FoldingCollection
    {
        RangeTree<int, FoldingItem> collection = new RangeTree<int, FoldingItem>(new RangeItemComparer());

        internal FoldingCollection()
        {
            this.collection.AutoRebuild = false;
            this.StatusChanged += (s, e) => { };
        }

        internal void UpdateData(Document doc,int startIndex,int insertLength,int removeLength)
        {
            if (this.collection.Count == 0)
                return;
            int delta = insertLength - removeLength;
            foreach (FoldingItem item in this.collection.Items)
            {
                int endIndex = startIndex + removeLength - 1;
                if (startIndex <= item.Start)
                {
                    if ((endIndex >= item.Start && endIndex <= item.End) || endIndex > item.End)
                        item.Range = new Range<int>(item.Start, item.Start);    //ここで削除すると例外が発生する
                    else
                        item.Range = new Range<int>(item.Start + delta, item.End + delta);
                }
                else if (startIndex >= item.Start && startIndex <= item.End)
                {
                    if (endIndex > item.End)
                        item.Range = new Range<int>(item.Start, item.Start);    //ここで削除すると例外が発生する
                    else
                        item.Range = new Range<int>(item.Start, item.End + delta);
                }
            }
            this.collection.Rebuild();
        }

        internal void CollectEmptyFolding(int startIndex,int endIndex)
        {
            foreach (FoldingItem foldingData in this.GetRange(startIndex, endIndex - startIndex + 1))
                if (foldingData.Start == foldingData.End)
                    this.Remove(foldingData);
        }

        /// <summary>
        /// 状態が変わったことを表す
        /// </summary>
        public event EventHandler<FoldingItemStatusChangedEventArgs> StatusChanged;

        /// <summary>
        /// 折り畳みを追加する
        /// </summary>
        /// <param name="data">FoldingItemオブジェクト</param>
        public void Add(FoldingItem data)
        {
            foreach (FoldingItem item in this.collection.Items)
            {
                if (item.Start == data.Start && item.End == data.End)
                    return;
                if (item.Parent != null && data.Start < item.Parent.Start && data.End >= item.Parent.End)
                    continue;
                else if (item.Start < data.Start && item.End > data.End)
                    data.Parent = item;
                else if (item.Start > data.Start && item.End <= data.End)
                    item.Parent = data;
            }
            this.collection.Add(data);
        }

        /// <summary>
        /// 折り畳みを追加する
        /// </summary>
        /// <param name="collection">FoldingItemのコレクション</param>
        public void AddRange(IEnumerable<FoldingItem> collection)
        {
            foreach (FoldingItem data in collection)
            {
                this.Add(data);
            }
        }
        
        /// <summary>
        /// 折り畳みを削除する
        /// </summary>
        /// <param name="data">FoldingItemオブジェクト</param>
        public void Remove(FoldingItem data)
        {
            this.collection.Remove(data);
        }

        /// <summary>
        /// 指定した範囲の折り畳みを取得する
        /// </summary>
        /// <param name="index">開始インデックス</param>
        /// <param name="length">長さ</param>
        /// <returns>FoldingItemイテレーター</returns>
        public IEnumerable<FoldingItem> GetRange(int index, int length)
        {
            if (this.collection.Count == 0)
                yield break;

            this.collection.Rebuild();

            List<FoldingItem> items = this.collection.Query(new Range<int>(index, index + length - 1));
            foreach (FoldingItem item in items)
                yield return item;
        }

        /// <summary>
        /// 指定した範囲に最も近い折り畳みを取得する
        /// </summary>
        /// <param name="index">開始インデックス</param>
        /// <param name="length">長さ</param>
        /// <returns>FoldingItemオブジェクト</returns>
        public FoldingItem Get(int index, int length)
        {
            if (this.collection.Count == 0)
                return null;
            
            this.collection.Rebuild();

            List<FoldingItem> items = this.collection.Query(new Range<int>(index, index + length - 1));

            int minLength = Int32.MaxValue;
            FoldingItem minItem = null;
            foreach (FoldingItem item in items)
                if (index - item.Start < minLength)
                    minItem = item;
            return minItem;
        }

        /// <summary>
        /// すべて削除する
        /// </summary>
        public void Clear()
        {
            this.collection.Clear();
        }

        /// <summary>
        /// 展開する
        /// </summary>
        /// <param name="foldingData">foldingItemオブジェクト</param>
        /// <remarks>親ノードも含めてすべて展開されます</remarks>
        public void Expand(FoldingItem foldingData)
        {
            while (foldingData != null)
            {
                foldingData.Expand = true;
                if (foldingData.Parent == null || foldingData.Parent.Expand)
                    break;
                else
                    foldingData = foldingData.Parent;
            }
            this.StatusChanged(this, new FoldingItemStatusChangedEventArgs(foldingData));
        }

        /// <summary>
        /// 折りたたむ
        /// </summary>
        /// <param name="foldingData">foldingItemオブジェクト</param>
        /// <remarks>全ての子ノードは折りたたまれます</remarks>
        public void Collapse(FoldingItem foldingData)
        {
            if (foldingData == null)
                return;
            this.collection.Rebuild();
            List<FoldingItem> items = this.collection.Query(foldingData.Range);
            foldingData.Expand = false;
            foreach (FoldingItem item in items)
                if (item.Start > foldingData.Start && item.End <= foldingData.End)
                    item.Expand = false;
            this.StatusChanged(this, new FoldingItemStatusChangedEventArgs(foldingData));
        }

        /// <summary>
        /// 指定した範囲に属する親ノードを取得する
        /// </summary>
        /// <param name="index">開始インデックス</param>
        /// <param name="length">長さ</param>
        /// <returns>FoldingItemオブジェクト</returns>
        /// <remarks>指定した範囲には属する中で隠された親ノードだけが取得される</remarks>
        public FoldingItem GetFarestHiddenFoldingData(int index, int length)
        {
            FoldingItem foldingData = this.Get(index, length);
            if (foldingData == null)
                return null;
            while (foldingData.Parent != null)
            {
                if (foldingData.Parent.Expand)
                    break;
                else
                    foldingData = foldingData.Parent;
            }
            return foldingData;
        }
    }
}
