using System;
using System.Windows;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using C1.Silverlight.FlexGrid;

namespace C1.Silverlight.FlexGridExcel
{
    /// <summary>
    /// Interface implemented by classes that contain flattened views of hierarchical data.
    /// </summary>
    interface IFlatList
    {
        GroupInformation GetGroupInformation(object item);
        int GetMaxLevels();
    }
    /// <summary>
    /// Class that contains hierarchical information for data items.
    /// </summary>
    public class GroupInformation
    {
        /// <summary>
        /// Gets or sets a value that determines whether this group has child items.
        /// </summary>
        public bool HasChildren { get; set; }
        /// <summary>
        /// Gets or sets the level of this group.
        /// </summary>
        public int Level { get; set; }
    }
    /// <summary>
    /// List that contains a flattened view of a hierarchical list with items that
    /// contain child items of the same type.
    /// </summary>
    /// <typeparam name="T">Type of objects in the original list.</typeparam>
    /// <remarks>
    /// <para>This class contains the information about the data hierarchy, so you don't
    /// have to create a CollectionView and add group descriptors to it.</para>
    /// <para>If you do create a CollectionView to wrap this class, then the built-in
    /// hierarchy is not taken into account when creating groups, and all the usual
    /// CollectionView semantics apply instead.</para>
    /// <para>The built-in hierarchy assumes parent above child items, so in order
    /// to see the hierarchy in a C1FlexGrid, you have to set the GroupRowPosition
    /// property to AboveData (the default setting).</para>
    /// </remarks>
    public class FlatList<T> : ObservableCollection<T>, IFlatList
        where T : class
    {
        //----------------------------------------------------------------------------------
        #region ** fields

        IEnumerable<T> _sourceList;
        Dictionary<T, GroupInformation> _itemInfo = new Dictionary<T, GroupInformation>();
        PropertyInfo _piChildren;
        bool _parentsBelow;
        bool _updating;

        #endregion

        //----------------------------------------------------------------------------------
        #region ** ctor

        /// <summary>
        /// Initializes a new instance of a FlatList.
        /// </summary>
        /// <param name="sourceList">List of objects with child elements of the same type.</param>
        public FlatList(IEnumerable<T> sourceList)
        {
            // find property that contains sub-objects
            foreach (var pi in typeof(T).GetProperties())
            {
                // is this an IEnumerable?
                var children = pi.PropertyType.GetInterface(typeof(IEnumerable<>).Name, true);
                if (children != null)
                {
                    // is this an IEnumerable<type>?
                    var enumType = children.GetGenericArguments()[0];
                    if (enumType == typeof(T))
                    {
                        // yes, we're done here
                        _piChildren = pi;
                        break;
                    }
                }
            }

            // populate the list
            _sourceList = sourceList;
            GenerateFlatList();
        }

        #endregion

        //----------------------------------------------------------------------------------
        #region ** IFlatList

        /// <summary>
        /// Gets or sets a value that determines whether parent rows should be placed above 
        /// their children.
        /// </summary>
        public bool ParentsBelowChildren
        {
            get { return _parentsBelow; }
            set
            {
                _parentsBelow = value;
                GenerateFlatList();
            }
        }

        #endregion

        //----------------------------------------------------------------------------------
        #region ** IFlatList

        /// <summary>
        /// Gets the maximum number of levels in the outline.
        /// </summary>
        /// <returns>The maximum number of levels in the outline.</returns>
        public int GetMaxLevels()
        {
            var maxLevel = 0;
            foreach (GroupInformation info in _itemInfo.Values)
            {
                maxLevel = Math.Max(maxLevel, info.Level);
            }
            return maxLevel;
        }
        /// <summary>
        /// Gets the <see cref="GroupInformation"/> for a specific item.
        /// </summary>
        /// <param name="dataItem">Data item to retrieve the information for.</param>
        /// <returns>The <see cref="GroupInformation"/> for the <paramref name="dataItem"/> item.</returns>
        public GroupInformation GetGroupInformation(object dataItem)
        {
            // get item
            T item = dataItem as T;

            // return cached info for this item
            GroupInformation info;
            return _itemInfo.TryGetValue(item, out info)
                ? info
                : null;
        }

        #endregion

        //----------------------------------------------------------------------------------
        #region ** implementation

        // generate the flat list from the source hierarchical list
        void GenerateFlatList()
        {
            _updating = true;
            Clear();
            if (_sourceList != null)
            {
                // create new (flat) list, add each item to it
                foreach (T item in _sourceList)
                {
                    AddItem(item, 0);
                }
            }
            _updating = false;

            // done updating, notify any listeners
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }

        // add an item and its children to the collection
        void AddItem(T item, int level)
        {
            // keep track of new item information
            var info = new GroupInformation();
            info.Level = level;
            _itemInfo[item] = info;

            // add the object (parents above children)
            if (!_parentsBelow)
            {
                this.Add(item);
            }

            // does it have sub-objects?
            if (_piChildren != null)
            {
                // get the sub-objects
                var children = _piChildren.GetValue(item, null) as IEnumerable;
                if (children != null)
                {
                    // and add them to the list (flattening as we go)
                    foreach (T subItem in children)
                    {
                        AddItem(subItem, level + 1);
                        info.HasChildren = true;
                    }
                }
            }

            // add the object (parents below children)
            if (_parentsBelow)
            {
                this.Add(item);
            }
        }
        /// <summary>
        /// Overridden to suppress notifications while updating the list.
        /// </summary>
        /// <param name="e"><see cref="NotifyCollectionChangedEventArgs"/> that contains the event data.</param>
        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (!_updating)
            {
                base.OnCollectionChanged(e);
            }
        }

        #endregion
    }
}
