using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace FastReport.AdvMatrix
{
    internal class HeaderData : IEnumerable<HeaderData>
    {
        public HeaderDescriptor Descriptor { get; private set; }
        public HeaderData Parent { get; private set; }
        public List<HeaderDataList> Items { get; }
        public object Value { get; set; }
        public int Index { get; set; }
        public int DataRowNo { get; set; }
        public int RowNo { get; private set; }

        internal HeaderDataList ParentDataList
        {
            get
            {
                if (Parent == null)
                    return null;
                foreach (HeaderDataList dl in Parent.Items)
                {
                    if (dl.Descriptor == Descriptor && dl.IndexOf(this) != -1)
                        return dl;
                }
                return null;
            }
        }

        internal int Span
        {
            get
            {
                if (Descriptor != null && Descriptor.Stepped)
                    return 1;
                int count = TerminalItems.Count;
                return count > 0 ? count : 1;
            }
        }

        internal List<HeaderDataList> AllItems
        {
            get
            {
                List<HeaderDataList> list = new List<HeaderDataList>();
                GetAllItems(this, list);
                return list;
            }
        }

        internal List<HeaderData> TerminalItems
        {
            get
            {
                List<HeaderData> list = new List<HeaderData>();
                GetTerminalItems(this, list);
                return list;
            }
        }

        // returns all terminal items including invisible ones. Needed for TopN processing
        internal List<HeaderData> AllTerminalItems
        {
            get
            {
                List<HeaderData> list = new List<HeaderData>();
                GetAllTerminalItems(this, list);
                return list;
            }
        }

        internal object[] Values
        {
            get
            {
                int count = 0;
                HeaderData item = this;

                while (item.Parent != null)
                {
                    count++;
                    item = item.Parent;
                }

                object[] values = new object[count];
                item = this;
                int index = count - 1;

                while (item.Parent != null)
                {
                    values[index] = item.Value;
                    index--;
                    item = item.Parent;
                }

                return values;
            }
        }

        internal int ItemCount
        {
            get
            {
                return this.Count();
            }
        }

        private void GetAllItems(HeaderData root, List<HeaderDataList> list)
        {
            foreach (HeaderDataList dl in root.Items)
            {
                list.Add(dl);
                foreach (HeaderData d in dl)
                {
                    GetAllItems(d, list);
                }
            }
        }

        private void GetTerminalItems(HeaderData root, List<HeaderData> list)
        {
            if (root.Items.Count == 0 || (root.Descriptor != null && root.Descriptor.Stepped))
                list.Add(root);
            foreach (HeaderData d in root)
            {
                GetTerminalItems(d, list);
            }
        }
        
        private void GetAllTerminalItems(HeaderData root, List<HeaderData> list)
        {
            if (root.Items.Count == 0 || (root.Descriptor != null && root.Descriptor.Stepped))
                list.Add(root);

            foreach (HeaderDataList dl in root.Items)
            {
                foreach (HeaderData d in dl)
                {
                    GetAllTerminalItems(d, list);
                }
            }
        }

        private HeaderDataList Find(HeaderDescriptor descr)
        {
            if (Items.Count == 0)
            {
                // Create data lists as they appear in the descriptor item list.
                // It fixes the problem when the descriptor's Filter property is set and some total items may appear before data
                foreach (HeaderDescriptor d in descr.Parent.Items)
                {
                    Items.Add(new HeaderDataList(this, d));
                }
            }
            
            foreach (HeaderDataList dataList in Items)
            {
                if (dataList.Descriptor == descr)
                    return dataList;
            }
            
            // this should never happen
            return null;
        }

        public HeaderData Add(HeaderDescriptor descr)
        {
            HeaderDataList dataList = Find(descr);
            object value = descr.CalcValue();
            int index = dataList.Find(value);

            if (index >= 0)
            {
                return dataList[index];
            }
            else
            {
                HeaderData data = new HeaderData(this, descr) { Value = value };
                dataList.Insert(~index, data);
                return data;
            }
        }

        public void Clear()
        {
            Items.Clear();
        }

        // this iterator is needed to return data from all datalists as a single list
        // it skips invisible items and sorts data if "sort by value" is on
        public IEnumerator<HeaderData> GetEnumerator()
        {
            foreach (HeaderDataList dl in Items)
            {
                if (!dl.Visible)
                    continue;

                List<HeaderData> list = dl.GetSortedList();
                for (int i = 0; i < list.Count; i++)
                {
                    HeaderData d = list[i];
                    d.RowNo = i + 1;
                    yield return d;
                }
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public HeaderData(HeaderData parent, HeaderDescriptor descr)
        {
            Parent = parent;
            Descriptor = descr;
            Items = new List<HeaderDataList>();
        }
    }
}