using System;
using System.Collections.Generic;
using FastReport.Table;
using FastReport.Utils;

namespace FastReport.AdvMatrix
{
    /// <summary>
    /// The descriptor that is used to describe one element of the matrix header.
    /// </summary>
    /// <remarks>
    /// The class is used to define one header element of the matrix
    /// (either the column element or row element). The key properties are 
    /// <see cref="Expression"/> and <see cref="Sort"/>. 
    /// <para/>The collection of descriptors used to represent the matrix header is stored
    /// in the <b>AdvMatrixObject.Data.Columns</b> and <b>AdvMatrixObject.Data.Rows</b> properties.
    /// </remarks>
    public class HeaderDescriptor : IFRSerializable
    {
        /// <summary>
        /// Gets or sets the name of this descriptor.
        /// </summary>
        /// <remarks>
        /// This property is used by the TopN engine to find linked items such as TopNTotal, Others, OtherTotal.
        /// All others descriptors have empty name.
        /// </remarks>
        public string Name { get; set; }
        
        /// <summary>
        /// Gets the parent descriptor of this descriptor.
        /// </summary>
        public HeaderDescriptor Parent { get; private set; }
        
        /// <summary>
        /// Gets child items of this descriptor.
        /// </summary>
        public List<HeaderDescriptor> Items { get; }

        /// <summary>
        /// Gets or sets an expression which value will be used to fill the matrix.
        /// </summary>
        /// <remarks>
        /// <b>Expression</b> may be any valid expression. Usually it's a data column:
        /// <c>[DataSource.Column]</c>.
        /// </remarks>
        public string Expression { get; set; }

        /// <summary>
        /// Gets or sets a text which will be displayed in this item.
        /// </summary>
        /// <remarks>
        /// <b>DisplayText</b> may contain text mixed with expressions just like in the TextObject, e.g. "Some text: [expr]".
        /// The default property value is empty for group descriptors. 
        /// In this case the group value returned by the <see cref="Expression"/> property will be displayed in this item.
        /// </remarks>
        public string DisplayText { get; set; }

        /// <summary>
        /// Gets or sets the filter expression.
        /// </summary>
        public string Filter { get; set; }

        /// <summary>
        /// Gets or sets the sort order of header values.
        /// </summary>
        /// <remarks>
        /// This property determines how the values displayed in this element are sorted. The default sort is ascending.
        /// </remarks>
        public SortOrder Sort { get; set; }

        /// <summary>
        /// Gets or sets the sort button name which toggles the sort order of this item.
        /// </summary>
        public string SortToggledBy { get; set; }

        /// <summary>
        /// Gets or sets an expression which value will be used to sort the header values.
        /// </summary>
        /// <remarks>This expression is used to sort header by its total value. The expression must contain single aggregate, e.g. "Sum([Table1.Field1])".
        /// The empty expression (by default) indicates that the header should be sorted by its value.
        /// </remarks>
        public string SortByTotal { get; set; }

        /// <summary>
        /// Gets or sets a value indicating that this item can be sorted interactively.
        /// </summary>
        public bool InteractiveSort { get; set; }
        
        /// <summary>
        /// Gets or sets an expression which value will be used to sort the header values interactively.
        /// </summary>
        /// <remarks>This expression is used to sort header values when interactive sorting is on. 
        /// The expression must contain single aggregate, e.g. "Sum([Table1.Field1])".
        /// The empty expression (by default) indicates automatic mode.
        /// </remarks>
        public string InteractiveSortBy { get; set; }
        
        /// <summary>
        /// Gets or sets the visibility of this item.
        /// </summary>
        public bool Visible { get; set; }

        /// <summary>
        /// Gets or sets the expression that returns the visibility of this item.
        /// </summary>
        public string VisibleExpression { get; set; }

        /// <summary>
        /// Gets or sets the collapse button name which toggles the visibility of this item.
        /// </summary>
        public string VisibleToggledBy { get; set; }

        /// <summary>
        /// Gets or sets a value indicating that the page break must be printed before this element.
        /// </summary>
        /// <remarks>
        /// Page break is not printed before the very first element.
        /// </remarks>
        public bool PageBreak { get; set; }

        /// <summary>
        /// Determines whether the item should merge itself with single subitem.
        /// </summary>
        public bool MergeSingleItem { get; set; }

        /// <summary>
        /// Determines whether this item and its subitems will be displayed stepped.
        /// </summary>
        public bool Stepped { get; set; }

        /// <summary>
        /// Gets TopN settings for this item.
        /// </summary>
        public TopNInfo TopN { get; private set; }

        /// <summary>
        /// Gets or sets column span of this item. 0 means span is set automatically.
        /// </summary>
        public int ColSpan { get; set; }

        /// <summary>
        /// Gets or sets row span of this item. 0 means span is set automatically.
        /// </summary>
        public int RowSpan { get; set; }

        // these items set by TemplateBuilder.UpdateDescriptors
        internal AdvMatrixObject Matrix { get; set; }
        internal bool IsColumn { get; set; }
        internal TableColumn TemplateColumn { get; set; }
        internal TableRow TemplateRow { get; set; }
        internal TableCell TemplateCell { get; set; }
        
        // Aggregate is used if "sort by total" is on
        internal AggregateExpressionPair SortAggregate { get; set; }
        // Aggregate is used if interactive sort is on
        internal AggregateExpressionPair InteractiveSortAggregate { get; set; }

        internal bool IsGroup { get { return !String.IsNullOrEmpty(Expression); } }
        
        internal bool IsTopNItem { get { return !String.IsNullOrEmpty(Name); } }

        internal HeaderDescriptor Root 
        { 
            get
            {
                HeaderDescriptor item = this;
                while (item.Parent != null)
                {
                    item = item.Parent;
                }
                return item;
            }
        }

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

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

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

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

        private void GetTerminalItems(HeaderDescriptor root, List<HeaderDescriptor> list)
        {
            if (root.Items.Count == 0 || root.Stepped)
                list.Add(root);
            foreach (HeaderDescriptor d in root.Items)
            {
                GetTerminalItems(d, list);
            }
        }

        internal void SetParent(HeaderDescriptor parent)
        {
            Parent = parent;
        }

        internal void InitAggregates()
        {
            ExpressionParser parser = new ExpressionParser(Matrix);
            if (!String.IsNullOrEmpty(SortByTotal))
            {
                SortAggregate = parser.Parse(SortByTotal);
            }
            if (InteractiveSort && !String.IsNullOrEmpty(InteractiveSortBy))
            {
                // use existing cell aggregate here
                InteractiveSortAggregate = Matrix.Data.CellData.FindAggregate(parser.Parse(InteractiveSortBy));
            }
        }

        internal object CalcValue()
        {
            if (String.IsNullOrEmpty(Expression))
                return null;
            return Matrix.Report.Calc(Expression);
        }

        /// <summary>
        /// Adds new child descriptor.
        /// </summary>
        /// <returns>The new descriptor.</returns>
        public HeaderDescriptor Add()
        {
            return Add(new HeaderDescriptor());
        }

        /// <summary>
        /// Adds a child descriptor.
        /// </summary>
        /// <param name="descr">The new descriptor.</param>
        /// <returns>The new descriptor.</returns>
        public HeaderDescriptor Add(HeaderDescriptor descr)
        {
            Items.Add(descr);
            descr.Parent = this;
            return descr;
        }

        /// <inheritdoc/>
        public void Serialize(FRWriter writer)
        {
            writer.ItemName = "Descriptor";
            HeaderDescriptor c = writer.DiffObject as HeaderDescriptor;
            if (Name != c.Name)
                writer.WriteStr("Name", Name);
            if (Expression != c.Expression)
                writer.WriteStr("Expression", Expression);
            if (DisplayText != c.DisplayText)
                writer.WriteStr("DisplayText", DisplayText);
            if (Filter != c.Filter)
                writer.WriteStr("Filter", Filter);
            if (Sort != c.Sort)
                writer.WriteValue("Sort", Sort);
            if (SortToggledBy != c.SortToggledBy)
                writer.WriteStr("SortToggledBy", SortToggledBy);
            if (SortByTotal != c.SortByTotal)
                writer.WriteStr("SortByTotal", SortByTotal);
            if (InteractiveSort != c.InteractiveSort)
                writer.WriteBool("InteractiveSort", InteractiveSort);
            if (InteractiveSortBy != c.InteractiveSortBy)
                writer.WriteStr("InteractiveSortBy", InteractiveSortBy);
            if (Visible != c.Visible)
                writer.WriteBool("Visible", Visible);
            if (VisibleExpression != c.VisibleExpression)
                writer.WriteStr("VisibleExpression", VisibleExpression);
            if (VisibleToggledBy != c.VisibleToggledBy)
                writer.WriteStr("VisibleToggledBy", VisibleToggledBy);
            TopN.Serialize(writer, "TopN", c.TopN);
            if (Stepped != c.Stepped)
                writer.WriteBool("Stepped", Stepped);
            if (PageBreak != c.PageBreak)
                writer.WriteBool("PageBreak", PageBreak);
            if (MergeSingleItem != c.MergeSingleItem)
                writer.WriteBool("MergeSingleItem", MergeSingleItem);
            if (ColSpan != c.ColSpan)
                writer.WriteInt("ColSpan", ColSpan);
            if (RowSpan != c.RowSpan)
                writer.WriteInt("RowSpan", RowSpan);

            foreach (HeaderDescriptor d in Items)
            {
                writer.Write(d);
            }
        }

        /// <inheritdoc/>
        public void Deserialize(FRReader reader)
        {
            reader.ReadProperties(this);

            Items.Clear();
            while (reader.NextItem())
            {
                reader.Read(Add());
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="HeaderDescriptor"/> class.
        /// </summary>
        public HeaderDescriptor()
        {
            Name = "";
            Items = new List<HeaderDescriptor>();
            Expression = "";
            DisplayText = "";
            Filter = "";
            Sort = SortOrder.Ascending;
            SortToggledBy = "";
            SortByTotal = "";
            InteractiveSort = true;
            InteractiveSortBy = "";
            Visible = true;
            VisibleExpression = "";
            VisibleToggledBy = "";
            TopN = new TopNInfo(this);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="HeaderDescriptor"/> class with the group expression specified.
        /// </summary>
        public HeaderDescriptor(string expression) : this()
        {
            Expression = expression;
        }
    }
}