#region << Using Directives >>
using System;
using System.Collections.Generic;
#endregion

namespace Volpe.Cafe.EspressoUI.Charts
{
    /// <summary>
    /// Provides aggregate statistics for all data-series contained within a chart.
    /// </summary>
    [Serializable]
    class DataSeriesStatistics
    {

        #region /*** Ctors ***/

        /// <summary>
        /// Populates the data series statistics from the specified list of series.
        /// </summary>
        /// <param name="chart">The chart for which to populate the data series statistics.</param>
        public DataSeriesStatistics(Chart chart)
        {
            List<DataSeries> dataSeries = chart.DataSeries;
            ChartStyle       chartStyle = chart.ChartStyle;
            //
            bool hasXVals = true;
            int  xDSCount = 0, y1DSCount = 0, y2DSCount = 0, yMaxCount = 0;
            double   xMin = double.MaxValue,  xMax = double.MinValue;
            double  y1Min = double.MaxValue, y1Max = double.MinValue;
            double  y2Min = double.MaxValue, y2Max = double.MinValue;
            int   lnCount = 0, barCount = 0, sbarCount1 = 0, sbarCount2 = 0, pieCount = 0;
            int visibleCount = 0;
            //
            for (int i = 0; i < dataSeries.Count; i++)
            {
                DataSeries series      = dataSeries[i];
                ChartStyle seriesStyle = (series.Style == ChartStyle.Default) ? chartStyle : series.Style;

                // if the first series has no x-values (even if it is not visible), ignore all others as well
                if (i == 0 && series.XValues == null) { hasXVals = false; }

                // ignore series that are not visible
                if (!series.Visible) { continue; }
                visibleCount++;

                if (series.XValues != null)
                {
                    xDSCount++;
                    this.GetMinMax(series.XValues, ref xMin, ref xMax);
                }
                if (series.Values != null)
                {
                    if (series.IsSecondaryAxis) { y2DSCount++; this.GetMinMax(series.Values, ref y2Min, ref y2Max); }
                    else                        { y1DSCount++; this.GetMinMax(series.Values, ref y1Min, ref y1Max); }
                    //
                    yMaxCount = Math.Max(yMaxCount, series.Values.Length);
                }
                // count number of series display styles
                switch (seriesStyle)
                {
                    case ChartStyle.Line: lnCount ++; break;
                    case ChartStyle.Bar : barCount++; break;
                    case ChartStyle.Pie : pieCount++; break;
                    case ChartStyle.StackedBar:
                        if (series.Values != null)
                        {
                            if      (series.IsPrimaryAxis  ) { sbarCount1++; }
                            else if (series.IsSecondaryAxis) { sbarCount2++; }
                        }
                        break;
                }
            }
            // update min/max for sum of stacked series
            this.CalcStackedYMax(dataSeries, ChartStyle.StackedBar, AxisType.Primary  , yMaxCount, ref y1Min, ref y1Max);
            this.CalcStackedYMax(dataSeries, ChartStyle.StackedBar, AxisType.Secondary, yMaxCount, ref y2Min, ref y2Max);
            //
            this.HasPrimaryValues   = (y1DSCount > 0);
            this.MinPrimaryValue    = (this.HasPrimaryValues  ) ? y1Min : 0;
            this.MaxPrimaryValue    = (this.HasPrimaryValues  ) ? y1Max : 1;
            this.HasSecondaryValues = (y2DSCount > 0);
            this.MinSecondaryValue  = (this.HasSecondaryValues) ? y2Min : 0;
            this.MaxSecondaryValue  = (this.HasSecondaryValues) ? y2Max : 1;
            this.MaxValuesCount     =  yMaxCount;
            //
            if (hasXVals)
            {
                this.HasCategoryValues       =  true;
                this.CategoryValuesAllSeries = (xDSCount == dataSeries.Count);
                this.MinCategoryValue        =  xMin;
                this.MaxCategoryValue        =  xMax;
            }
            else if (this.HasPrimaryValues || this.HasSecondaryValues)
            {   // if no x-values, set default min/max for category axis to range between 1 and yMaxCount
                this.MinCategoryValue = 1;
                this.MaxCategoryValue = yMaxCount;
            }
            else
            {   // if no x or y values, set min/max to 0 and 1
                this.MinCategoryValue = 0;
                this.MaxCategoryValue = 1;
            }
            //
            this.VisibleCount           = visibleCount;
            this.LineStyleCount         = lnCount;
            this.BarStyleCount          = barCount;
            this.PieStyleCount          = pieCount;
            this.StackedBarStyleY1Count = sbarCount1;
            this.StackedBarStyleY2Count = sbarCount2;
            this.MixedStyles            = (((this.HasLineStyle         ? 1 : 0) +
                                            (this.HasBarStyle          ? 1 : 0) +
                                            (this.HasPieStyle          ? 1 : 0) +
                                            (this.HasStackedBarStyleY1 ? 1 : 0) +
                                            (this.HasStackedBarStyleY2 ? 1 : 0)) > 1);
        }

        #endregion

        #region /*** Methods ***/

        /// <summary>
        /// Returns whether the specified axis type has values.
        /// </summary>
        public bool HasAxisValues(AxisType axisType)
        {
            return (axisType == AxisType.Category ) ? this.HasCategoryValues  :
                   (axisType == AxisType.Primary  ) ? this.HasPrimaryValues   :
                   (axisType == AxisType.Secondary) ? this.HasSecondaryValues : false;
        }
        /// <summary>
        /// Returns the minimum value for the specified axis type, defined accross all data series.
        /// </summary>
        public double GetMinValue(AxisType axisType)
        {
            return (axisType == AxisType.Category ) ? this.MinCategoryValue  :
                   (axisType == AxisType.Primary  ) ? this.MinPrimaryValue   :
                   (axisType == AxisType.Secondary) ? this.MinSecondaryValue : 0D;
        }
        /// <summary>
        /// Returns the maximum value for the specified axis type, defined accross all data series.
        /// </summary>
        public double GetMaxValue(AxisType axisType)
        {
            return (axisType == AxisType.Category ) ? this.MaxCategoryValue  :
                   (axisType == AxisType.Primary  ) ? this.MaxPrimaryValue   :
                   (axisType == AxisType.Secondary) ? this.MaxSecondaryValue : 1D;
        }

        //----- ctor support methods -----
        void CalcStackedYMax(List<DataSeries> dataSeries, ChartStyle style, AxisType axis, int valuesCount,
            ref double yMin, ref double yMax)
        {
            for (int i = 0; i < valuesCount; i++)
            {
                double minSum = 0, maxSum = 0;
                foreach (DataSeries series in dataSeries)
                {
                    if (series.Visible && series.Style == style && series.Axis == axis && series.Values != null && i < series.Values.Length)
                    {
                        if (series.Values[i] < 0) { minSum += series.Values[i]; }
                        else                      { maxSum += series.Values[i]; }
                    }
                }
                yMin = Math.Min(yMin, minSum);
                yMax = Math.Max(yMax, maxSum);
            }
        }
        void GetMinMax(double[] arr, ref double min, ref double max)
        {
            for (int i = 0; i < arr.Length; i++)
            {
                double d = arr[i];
                min = Math.Min(min, d);
                max = Math.Max(max, d);
            }
        }

        #endregion

        #region /*** Properties ***/

        /// <summary>Gets whether the chart's data series has category values defined.</summary>
        public bool HasCategoryValues { get; protected set; }
        /// <summary>Gets whether the chart's data series has category values defined for all series.</summary>
        public bool CategoryValuesAllSeries { get; protected set; }
        /// <summary>Gets the minimum category value defined accross all data series.</summary>
        public double MinCategoryValue { get; protected set; }
        /// <summary>Gets the maximum category value defined accross all data series.</summary>
        public double MaxCategoryValue { get; protected set; }

        /// <summary>Gets whether the chart's data series has primary axis values defined.</summary>
        public bool HasPrimaryValues { get; protected set; }
        /// <summary>Gets the minimum primary axis value defined accross all data series.</summary>
        public double MinPrimaryValue { get; protected set; }
        /// <summary>Gets the maximum primary axis value defined accross all data series.</summary>
        public double MaxPrimaryValue { get; protected set; }
        /// <summary>Gets whether the chart's data series has secondary axis values defined.</summary>
        public bool HasSecondaryValues { get; protected set; }
        /// <summary>Gets the minimum secondary axis value defined accross all data series.</summary>
        public double MinSecondaryValue { get; protected set; }
        /// <summary>Gets the maximum secondary axis value defined accross all data series.</summary>
        public double MaxSecondaryValue { get; protected set; }
        /// <summary>Gets the maximum count of axis values accross all data series.</summary>
        public int MaxValuesCount { get; protected set; }

        /// <summary>Gets the number of visible data series.</summary>
        public int VisibleCount { get; protected set; }

        //----- number of types of each chart style -----
        /// <summary>Gets the number of line-chart style data series.</summary>
        public int LineStyleCount { get; protected set; }
        /// <summary>Gets the number of bar-chart style data series.</summary>
        public int BarStyleCount { get; protected set; }
        /// <summary>Gets the number of pie-chart style data series.</summary>
        public int PieStyleCount { get; protected set; }
        /// <summary>Gets the number of stacked-bar-chart style data series on the primary axis.</summary>
        public int StackedBarStyleY1Count { get; protected set; }
        /// <summary>Gets the number of stacked-bar-chart style data series on the secondary axis.</summary>
        public int StackedBarStyleY2Count { get; protected set; }
        //
        /// <summary>Gets whether any of the chart's data series use line-chart style.</summary>
        public bool HasLineStyle { get { return this.LineStyleCount > 0; } }
        /// <summary>Gets whether any of the chart's data series use bar-chart style.</summary>
        public bool HasBarStyle { get { return this.BarStyleCount > 0; } }
        /// <summary>Gets whether any of the chart's data series use pie-chart style.</summary>
        public bool HasPieStyle { get { return this.PieStyleCount > 0; } }
        /// <summary>Gets whether any of the chart's data series use stacked-bar-chart style on the primary axis.</summary>
        public bool HasStackedBarStyleY1 { get { return this.StackedBarStyleY1Count > 0; } }
        /// <summary>Gets whether any of the chart's data series use stacked-bar-chart style on the secondary axis.</summary>
        public bool HasStackedBarStyleY2 { get { return this.StackedBarStyleY2Count > 0; } }
        /// <summary>Gets whether the chart's data series have different display styles.</summary>
        public bool MixedStyles { get; protected set; }

        #endregion

    }
}
