using System;

namespace FastReport.AdvMatrix
{
    /// <summary>
    /// Holds context required for aggregate functions evaluation.
    /// </summary>
    public class Context
    {
        internal CellDescriptor Descriptor;
        internal HeaderData ColumnItem;
        internal HeaderData RowItem;
        internal AggregateExpressionPair Aggregate;

        private dynamic Div(dynamic value, dynamic divBy)
        {
            if (divBy == null || divBy == 0)
                return null;
            if (value is float || value is double || value is decimal)
                return value / divBy;
            return value / (double)divBy;
        }

        private bool CheckAggregate()
        {
            // in case we provide no arguments to the special method call, use the first aggregate in a cell
            if (Aggregate == null)
            {
                if (Descriptor != null && Descriptor.Aggregates.Count > 0)
                    Aggregate = Descriptor.Aggregates[0];
            }
            return Aggregate != null;
        }

        private dynamic GetAggregateValue(int columnIndex, int rowIndex)
        {
            return Aggregate.GetValue(columnIndex, rowIndex);
        }

        /// <summary>
        /// Gets aggregate value. This method is for internal use only.
        /// </summary>
        /// <param name="aggregateName">Name of aggregate.</param>
        /// <param name="expression">Expression.</param>
        /// <returns>Aggregate value.</returns>
        public dynamic GetAggregate(string aggregateName, string expression)
        {
            if (Descriptor == null)
                return null;
            Aggregate = Descriptor.Aggregates.Find(aggregateName, expression);
            if (Aggregate == null)
                return null;
            return GetAggregateValue(ColumnItem.Index, RowItem.Index);
        }

        /// <summary>
        /// Gets value of the specific column. This method is for internal use only.
        /// </summary>
        /// <param name="value">The column value.</param>
        /// <param name="dummy">Dummy parameter.</param>
        /// <returns>The value or null.</returns>
        public dynamic GetSpecificColumn(object value, object dummy = null)
        {
            HeaderData columnItem = ColumnItem.GetSpecificItem(value);
            if (columnItem == null)
                return null;

            if (!CheckAggregate())
                return null;

            return GetAggregateValue(columnItem.Index, RowItem.Index);
        }

        /// <summary>
        /// Gets value of the specific row. This method is for internal use only.
        /// </summary>
        /// <param name="value">The row value.</param>
        /// <param name="dummy">Dummy parameter.</param>
        /// <returns>The value or null.</returns>
        public dynamic GetSpecificRow(object value, object dummy = null)
        {
            HeaderData rowItem = RowItem.GetSpecificItem(value);
            if (rowItem == null)
                return null;

            if (!CheckAggregate())
                return null;

            return GetAggregateValue(ColumnItem.Index, rowItem.Index);
        }

        /// <summary>
        /// Gets value of the first column. This method is for internal use only.
        /// </summary>
        /// <param name="dummy">Dummy parameter.</param>
        /// <param name="useIneractiveSort">Determines if the interactive sort must be respected.</param>
        /// <param name="useThisGroup">Determines if the same level group should be used to search for item.</param>
        /// <returns>The value or null.</returns>
        public dynamic GetFirstColumn(object dummy = null, bool useIneractiveSort = false, bool useThisGroup = true)
        {
            HeaderData firstColumnItem = ColumnItem.GetFirstItem(useIneractiveSort, useThisGroup);
            if (firstColumnItem == null)
                return null;

            if (!CheckAggregate())
                return null;

            return GetAggregateValue(firstColumnItem.Index, RowItem.Index);
        }

        /// <summary>
        /// Gets value of the last column. This method is for internal use only.
        /// </summary>
        /// <param name="dummy">Dummy parameter.</param>
        /// <param name="useIneractiveSort">Determines if the interactive sort must be respected.</param>
        /// <param name="useThisGroup">Determines if the same level group should be used to search for item.</param>
        /// <returns>The value or null.</returns>
        public dynamic GetLastColumn(object dummy = null, bool useIneractiveSort = false, bool useThisGroup = true)
        {
            HeaderData lastColumnItem = ColumnItem.GetLastItem(useIneractiveSort, useThisGroup);
            if (lastColumnItem == null)
                return null;

            if (!CheckAggregate())
                return null;

            return GetAggregateValue(lastColumnItem.Index, RowItem.Index);
        }

        /// <summary>
        /// Gets value of the first row. This method is for internal use only.
        /// </summary>
        /// <param name="dummy">Dummy parameter.</param>
        /// <param name="useIneractiveSort">Determines if the interactive sort must be respected.</param>
        /// <param name="useThisGroup">Determines if the same level group should be used to search for item.</param>
        /// <returns>The value or null.</returns>
        public dynamic GetFirstRow(object dummy = null, bool useIneractiveSort = false, bool useThisGroup = true)
        {
            HeaderData firstRowItem = RowItem.GetFirstItem(useIneractiveSort, useThisGroup);
            if (firstRowItem == null)
                return null;

            if (!CheckAggregate())
                return null;

            return GetAggregateValue(ColumnItem.Index, firstRowItem.Index);
        }

        /// <summary>
        /// Gets value of the last row. This method is for internal use only.
        /// </summary>
        /// <param name="dummy">Dummy parameter.</param>
        /// <param name="useIneractiveSort">Determines if the interactive sort must be respected.</param>
        /// <param name="useThisGroup">Determines if the same level group should be used to search for item.</param>
        /// <returns>The value or null.</returns>
        public dynamic GetLastRow(object dummy = null, bool useIneractiveSort = false, bool useThisGroup = true)
        {
            HeaderData lastRowItem = RowItem.GetLastItem(useIneractiveSort, useThisGroup);
            if (lastRowItem == null)
                return null;

            if (!CheckAggregate())
                return null;

            return GetAggregateValue(ColumnItem.Index, lastRowItem.Index);
        }

        /// <summary>
        /// Gets value of previous column. This method is for internal use only.
        /// </summary>
        /// <param name="dummy">Dummy parameter.</param>
        /// <param name="useIneractiveSort">Determines if the interactive sort must be respected.</param>
        /// <param name="useThisGroup">Determines if the same level group should be used to search for item.</param>
        /// <returns>The value or null.</returns>
        public dynamic GetPreviousColumn(object dummy = null, bool useIneractiveSort = false, bool useThisGroup = true)
        {
            HeaderData previousColumnItem = ColumnItem.GetPreviousItem(useIneractiveSort, useThisGroup);
            if (previousColumnItem == null)
                return null;

            if (!CheckAggregate())
                return null;

            return GetAggregateValue(previousColumnItem.Index, RowItem.Index);
        }

        /// <summary>
        /// Gets value of previous row. This method is for internal use only.
        /// </summary>
        /// <param name="dummy">Dummy parameter.</param>
        /// <param name="useIneractiveSort">Determines if the interactive sort must be respected.</param>
        /// <param name="useThisGroup">Determines if the same level group should be used to search for item.</param>
        /// <returns>The value or null.</returns>
        public dynamic GetPreviousRow(object dummy = null, bool useIneractiveSort = false, bool useThisGroup = true)
        {
            HeaderData previousRowItem = RowItem.GetPreviousItem(useIneractiveSort, useThisGroup);
            if (previousRowItem == null)
                return null;

            if (!CheckAggregate())
                return null;

            return GetAggregateValue(ColumnItem.Index, previousRowItem.Index);
        }

        /// <summary>
        /// Gets value of next column. This method is for internal use only.
        /// </summary>
        /// <param name="dummy">Dummy parameter.</param>
        /// <param name="useIneractiveSort">Determines if the interactive sort must be respected.</param>
        /// <param name="useThisGroup">Determines if the same level group should be used to search for item.</param>
        /// <returns>The value or null.</returns>
        public dynamic GetNextColumn(object dummy = null, bool useIneractiveSort = false, bool useThisGroup = true)
        {
            HeaderData nextColumnItem = ColumnItem.GetNextItem(useIneractiveSort, useThisGroup);
            if (nextColumnItem == null)
                return null;

            if (!CheckAggregate())
                return null;

            return GetAggregateValue(nextColumnItem.Index, RowItem.Index);
        }

        /// <summary>
        /// Gets value of next row. This method is for internal use only.
        /// </summary>
        /// <param name="dummy">Dummy parameter.</param>
        /// <param name="useIneractiveSort">Determines if the interactive sort must be respected.</param>
        /// <param name="useThisGroup">Determines if the same level group should be used to search for item.</param>
        /// <returns>The value or null.</returns>
        public dynamic GetNextRow(object dummy = null, bool useIneractiveSort = false, bool useThisGroup = true)
        {
            HeaderData nextRowItem = RowItem.GetNextItem(useIneractiveSort, useThisGroup);
            if (nextRowItem == null)
                return null;

            if (!CheckAggregate())
                return null;

            return GetAggregateValue(ColumnItem.Index, nextRowItem.Index);
        }

        /// <summary>
        /// Gets column total value. This method is for internal use only.
        /// </summary>
        /// <param name="dummy">Dummy parameter.</param>
        /// <returns>The value or null.</returns>
        public dynamic GetGrandColumnTotal(object dummy = null)
        {
            if (!CheckAggregate())
                return null;

            return GetAggregateValue(ColumnItem.Index, Descriptor.RowIndex);
        }

        /// <summary>
        /// Gets row total value. This method is for internal use only.
        /// </summary>
        /// <param name="dummy">Dummy parameter.</param>
        /// <returns>The value or null.</returns>
        public dynamic GetGrandRowTotal(object dummy = null)
        {
            if (!CheckAggregate())
                return null;

            return GetAggregateValue(Descriptor.ColumnIndex, RowItem.Index); 
        }

        /// <summary>
        /// Gets grand total value. This method is for internal use only.
        /// </summary>
        /// <param name="dummy">Dummy parameter.</param>
        /// <returns>The value or null.</returns>
        public dynamic GetGrandTotal(object dummy = null)
        {
            if (!CheckAggregate())
                return null;

            return GetAggregateValue(Descriptor.ColumnIndex, Descriptor.RowIndex);
        }

        /// <summary>
        /// Gets column group total value. This method is for internal use only.
        /// </summary>
        /// <param name="dummy">Dummy parameter.</param>
        /// <returns>The value or null.</returns>
        public dynamic GetColumnTotal(object dummy = null)
        {
            if (!CheckAggregate())
                return null;

            int rowIndex = RowItem.Parent.Index;
            if (rowIndex == 0)
                rowIndex = Descriptor.RowIndex;
            return GetAggregateValue(ColumnItem.Index, rowIndex);
        }

        /// <summary>
        /// Gets row group total value. This method is for internal use only.
        /// </summary>
        /// <param name="dummy">Dummy parameter.</param>
        /// <returns>The value or null.</returns>
        public dynamic GetRowTotal(object dummy = null)
        {
            if (!CheckAggregate())
                return null;

            int columnIndex = ColumnItem.Parent.Index;
            if (columnIndex == 0)
                columnIndex = Descriptor.ColumnIndex;
            return GetAggregateValue(columnIndex, RowItem.Index);
        }

        /// <summary>
        /// Gets column group max value. This method is for internal use only.
        /// </summary>
        /// <param name="dummy">Dummy parameter.</param>
        /// <returns>The value or null.</returns>
        public dynamic GetColumnMaxValue(object dummy = null)
        {
            if (!CheckAggregate())
                return null;

            dynamic maxValue = null;
            HeaderDataList parent = RowItem.GetGroup();
            if (parent != null)
            {
                foreach (HeaderData d in parent)
                {
                    dynamic value = GetAggregateValue(ColumnItem.Index, d.Index);
                    if (maxValue == null || maxValue < value)
                        maxValue = value;
                }
            }
            
            return maxValue;
        }

        /// <summary>
        /// Gets column group min value. This method is for internal use only.
        /// </summary>
        /// <param name="dummy">Dummy parameter.</param>
        /// <returns>The value or null.</returns>
        public dynamic GetColumnMinValue(object dummy = null)
        {
            if (!CheckAggregate())
                return null;

            dynamic minValue = null;
            HeaderDataList parent = RowItem.GetGroup();
            if (parent != null)
            {
                foreach (HeaderData d in parent)
                {
                    dynamic value = GetAggregateValue(ColumnItem.Index, d.Index);
                    if (minValue == null || minValue > value)
                        minValue = value;
                }
            }

            return minValue;
        }

        /// <summary>
        /// Gets row group max value. This method is for internal use only.
        /// </summary>
        /// <param name="dummy">Dummy parameter.</param>
        /// <returns>The value or null.</returns>
        public dynamic GetRowMaxValue(object dummy = null)
        {
            if (!CheckAggregate())
                return null;

            dynamic maxValue = null;
            HeaderDataList parent = ColumnItem.GetGroup();
            if (parent != null)
            {
                foreach (HeaderData d in parent)
                {
                    dynamic value = GetAggregateValue(d.Index, RowItem.Index);
                    if (maxValue == null || maxValue < value)
                        maxValue = value;
                }
            }

            return maxValue;
        }

        /// <summary>
        /// Gets row group min value. This method is for internal use only.
        /// </summary>
        /// <param name="dummy">Dummy parameter.</param>
        /// <returns>The value or null.</returns>
        public dynamic GetRowMinValue(object dummy = null)
        {
            if (!CheckAggregate())
                return null;

            dynamic minValue = null;
            HeaderDataList parent = ColumnItem.GetGroup();
            if (parent != null)
            {
                foreach (HeaderData d in parent)
                {
                    dynamic value = GetAggregateValue(d.Index, RowItem.Index);
                    if (minValue == null || minValue > value)
                        minValue = value;
                }
            }

            return minValue;
        }

        /// <summary>
        /// Gets percent of column total. This method is for internal use only.
        /// </summary>
        /// <param name="dummy">Dummy parameter.</param>
        /// <returns>The value.</returns>
        public dynamic GetPercentOfColumnTotal(object dummy = null)
        {
            if (!CheckAggregate())
                return null;

            return Div(GetAggregateValue(ColumnItem.Index, RowItem.Index), GetColumnTotal());
        }

        /// <summary>
        /// Gets percent of row total. This method is for internal use only.
        /// </summary>
        /// <param name="dummy">Dummy parameter.</param>
        /// <returns>The value.</returns>
        public dynamic GetPercentOfRowTotal(object dummy = null)
        {
            if (!CheckAggregate())
                return null;

            return Div(GetAggregateValue(ColumnItem.Index, RowItem.Index), GetRowTotal());
        }

        /// <summary>
        /// Gets percent of grand total. This method is for internal use only.
        /// </summary>
        /// <param name="dummy">Dummy parameter.</param>
        /// <returns>The value.</returns>
        public dynamic GetPercentOfGrandTotal(object dummy = null)
        {
            if (!CheckAggregate())
                return null;

            return Div(GetAggregateValue(ColumnItem.Index, RowItem.Index), GetGrandTotal());
        }

        /// <summary>
        /// Gets percent of previous column. This method is for internal use only.
        /// </summary>
        /// <param name="dummy">Dummy parameter.</param>
        /// <param name="useIneractiveSort">Determines if the interactive sort must be respected.</param>
        /// <param name="useThisGroup">Determines if the same level group should be used to search for item.</param>
        /// <returns>The value or null.</returns>
        public dynamic GetPercentOfPreviousColumn(object dummy = null, bool useIneractiveSort = false, bool useThisGroup = true)
        {
            if (!CheckAggregate())
                return null;

            return Div(GetAggregateValue(ColumnItem.Index, RowItem.Index), GetPreviousColumn(dummy, useIneractiveSort, useThisGroup));
        }

        /// <summary>
        /// Gets percent of previous row. This method is for internal use only.
        /// </summary>
        /// <param name="dummy">Dummy parameter.</param>
        /// <param name="useIneractiveSort">Determines if the interactive sort must be respected.</param>
        /// <param name="useThisGroup">Determines if the same level group should be used to search for item.</param>
        /// <returns>The value or null.</returns>
        public dynamic GetPercentOfPreviousRow(object dummy = null, bool useIneractiveSort = false, bool useThisGroup = true)
        {
            if (!CheckAggregate())
                return null;

            return Div(GetAggregateValue(ColumnItem.Index, RowItem.Index), GetPreviousRow(dummy, useIneractiveSort, useThisGroup));
        }
    }
}