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

using Volpe.Cafe.EspressoUI.Charts;
using Volpe.Cafe.MarketData;
using Volpe.Cafe.MarketData.Design;
using Volpe.Cafe.Model;
using Volpe.Cafe.ModelSettings;
#endregion

namespace Volpe.Cafe.EspressoUI.ModelCharts
{
    [Serializable]
    class VehicleScatterPlot : ModelChart
    {

        #region /*** Ctors ***/

        /// <summary>
        /// Initializes a new instance of the <see cref="VehicleScatterPlot"/> class.
        /// </summary>
        public VehicleScatterPlot() : base()
        {
            this.InitializeComponent();
            this.InitializeOther();
        }

        #endregion

        #region /*** Methods ***/

        //----- initialization -----
        void InitializeComponent()
        {
            this.SuspendLayout();
            // 
            // VehicleFuelEconomyScatterPlot
            // 
            this.CategoryAxisLabel = "Footprint (sq.ft.)";
            this.CategoryAxisNumberFormat = "0.0";
            this.CategoryAxisMaxValue = 60;
            this.CategoryAxisMinValue = 30;
            this.Name = "VehicleScatterPlot";
            this.PrimaryAxisLabel = "mpg";
            this.PrimaryAxisNumberFormat = "0.0";
            this.PrimaryAxisMaxValue = 30;
            this.PrimaryAxisMinValue = 0;
            this.Title = "All Vehicles";
            this.ResumeLayout(false);

        }
        void InitializeOther()
        {
            this.ChartStyle = ChartStyle.Default;
            this.InitializeSeries();
        }
        //
        protected override void InitializeChartInternal()
        {
            this.InitializeSeries();
        }
        void InitializeSeries()
        {
            this.DataSeries.Clear();
            this.AddDataSeries("FE Initial", AxisType.Primary, ChartStyle.ScatterPlotAlt);
            this.AddDataSeries("FE Final"  , AxisType.Primary, ChartStyle.ScatterPlotAlt);
        }

        //----- chart refreshing -----
        public override void RefreshChartData(IModelingProgress progress)
        {
            try
            {
                if (!this.IsInitialized || progress == null || progress.ModelYear == null) { return; }
                this.RefreshComplianceProgram();
                this.RefreshChartDataInternal(progress.Scenario, progress.ModelYear, progress.Manufacturer);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }
        void RefreshComplianceProgram()
        {
            double primaryAxisMaxValue = this.PrimaryAxis.MaxValue;
            var program = this._settings.RuntimeSettings.ComplianceProgram;
            if (program.IsCO2())
            {
                this.DataSeries[0].Name = "CO2 Initial";
                this.DataSeries[1].Name = "CO2 Final";
                this.PrimaryAxis.Label = "g/mi";
                primaryAxisMaxValue = 300;
            }
            else
            {
                this.DataSeries[0].Name = "FE Initial";
                this.DataSeries[1].Name = "FE Final";
                this.PrimaryAxis.Label = "mpg";
                primaryAxisMaxValue = 30;
            }
            if (!this._compliance.Running)
            {
                this.PrimaryAxis.MaxValue = primaryAxisMaxValue;
            }
        }
        void RefreshChartDataInternal(Scenario scen, ModelYear year, IManufacturer mfr)
        {
            int mfrFilter = -1;
            if (this._plotMfr)
            {
                if (mfr == null) { return; }
                mfrFilter = (this._mfrFilter == -1) ? mfr.Index : this._mfrFilter;
                if (mfrFilter >= this._mfrCount) { return; }
            }

            // update title
            this.Title = ((this._plotMfr) ?
                this._data.Manufacturers[mfrFilter].ProperName + (this._mfrFilter == -1 ? "*" : "") :
                "All") + " Vehicles" + ChartFilters.RegClassSuffix[this._rcFilter];

            // calculate number of vehicles to render
            IIndustry snData = this._compliance.GetData(scen);
            if (snData == null) {
                return;
            }

            var program = this._settings.RuntimeSettings.ComplianceProgram;
            IReadOnlyList<IManufacturer> snMfrs = (this._plotMfr)
                ? new List<IManufacturer> { snData.Manufacturers[mfrFilter] }
                : snData.Manufacturers;

            // count number of vehicles to render
            int vehCount = 0;
            foreach (var snMfr in snMfrs) {
                foreach (var snVeh in snMfr.Vehicles) {
                    var regClass = snVeh.GetRevision(year).RegClass;
                    if (ChartFilters.RegClasses[this._rcFilter].Contains(regClass)) {
                        vehCount++;
                    }
                }
            }
            //
            int displayedVehCount = vehCount;
            double interlaceFactor = 1;
            if (this._interlaced && displayedVehCount > this._interlaceThreshold)
            {
                interlaceFactor = (int)(this._interlaceThreshold * 100D / displayedVehCount) / 100D;
                displayedVehCount = (int)Math.Ceiling(displayedVehCount * interlaceFactor);
            }

            // update values -- initialize array
            double[] xValues = this.DataSeries[0].XValues;
            double[] values0 = this.DataSeries[0].Values;
            double[] values1 = this.DataSeries[1].Values;
            bool reinitVals = false;
            if (xValues == null || xValues.Length != displayedVehCount)
            {   // if number of elements changed, reinitialize arrays
                xValues = new double[displayedVehCount];
                values0 = new double[displayedVehCount];
                values1 = new double[displayedVehCount];
                reinitVals = true;
            }
            if (displayedVehCount == 0)
            {   // if no vehs in current view, set default ranges
                this.PrimaryAxisMinValue  =  0;
                this.PrimaryAxisMaxValue  = 30;
                this.CategoryAxisMinValue = 30;
                this.CategoryAxisMaxValue = 60;
            }
            else
            {   // get latest FP and FE from filtered set of vehicles
                int vehIndex = 0;
                double factor = interlaceFactor, prevFactor = -1;
                for (int i = 0; i < snMfrs.Count; i++)
                {
                    IReadOnlyList<IVehicle> vehs = snMfrs[i].Vehicles;
                    foreach (var veh in vehs)
                    {
                        var vehRevision = veh.GetRevision(year);
                        var vehBaseInfo = veh.BaselineInfo;
                        var regClass = vehRevision.RegClass;

                        if (ChartFilters.RegClasses[this._rcFilter].Contains(regClass))
                        {
                            if ((int)factor != (int)prevFactor)
                            {
                                xValues[vehIndex] = veh.GetRevision(year).Footprint;
                                if (program.IsCO2())
                                {
                                    values0[vehIndex] = Standards.GetAverageVehicleCO2Rating(
                                        scen, year, vehBaseInfo, this._settings, double.NaN, false);
                                    values1[vehIndex] = Standards.GetAverageVehicleCO2Rating(
                                        scen, year, vehRevision, this._settings, double.NaN, false);
                                }
                                else
                                {
                                    values0[vehIndex] = Standards.GetAverageVehicleFERating(
                                        scen, year, vehBaseInfo, this._settings, false, false);
                                    values1[vehIndex] = Standards.GetAverageVehicleFERating(
                                        scen, year, vehRevision, this._settings, false, false);
                                }
                                vehIndex++;
                            }
                            prevFactor = factor;
                            factor += interlaceFactor;
                        }
                    } // next veh
                } // next mfr

                // set min/max ranges
                double primaryMax = Math.Max(values0.Max(), values1.Max());
                double scalar = (program.IsCO2()) ? 50D : 15D;
                this.PrimaryAxisMinValue = 0;
                this.PrimaryAxisMaxValue = Math.Ceiling(primaryMax / scalar) * scalar;
                //
                double fpMin = xValues.Min(), fpMax = xValues.Max();
                this.CategoryAxisMinValue = Math.Floor  (fpMin / 5) * 5;
                this.CategoryAxisMaxValue = Math.Ceiling(fpMax / 5) * 5;
            }
            //
            if (reinitVals)
            {
                this.DataSeries[0].Values = null; // clear values before setting x-vals to disable series check
                this.DataSeries[0].XValues = xValues;
                this.DataSeries[0].Values  = values0;
                this.DataSeries[1].Values  = values1;
            }
            this.Series.SetDirty(false);
            this.Invalidate();
        }

        /// <summary>
        /// Cycles the chart's view in the vertical direction.
        /// </summary>
        /// <param name="direction">The direction of the view change. If direction is -1, the previous view should be selected; if
        ///   direction is 1, the next view should be selected.</param>
        protected override void CycleVertical(int direction)
        {
            // find current mfr index: -1 = auto-select mfr, index = mfr-index, !plotMfr => mfrCount = industry
            int mfrFilter = (this._plotMfr) ? this._mfrFilter : this._mfrCount;
            // advance mfr filter and adjust for overflow
            mfrFilter += direction;
            if (mfrFilter == -2) { mfrFilter = this._mfrCount; }
            else if (mfrFilter > this._mfrCount) { mfrFilter = -1; }

            if (mfrFilter > this._mfrCount)
            {   // advancing past industry to auto-mfr
                this._plotMfr = true;
                this._mfrFilter = -1;
            }
            else if (mfrFilter == -2 || mfrFilter == this._mfrCount)
            {   // advancing to plotting industry
                this._plotMfr = false;
                this._mfrFilter = -1;
            }
            else
            {   // cycling through mfrs
                this._plotMfr = true;
                this._mfrFilter = mfrFilter;
            }
        }

        /// <summary>
        /// Resets the chart to its initial state by clearing the associated resources using during the most recent compliance run.
        /// </summary>
        public override void ResetChart()
        {
            this._plotMfr = true;
            base.ResetChart();
        }

        #endregion

        #region /*** Properties ***/

        /// <summary>Gets or sets whether the data rendered in the scatter plot is interlaced whenever the number of elements
        ///   exceeds a specific threshold.</summary>
        [Browsable(false)]
        public bool Interlaced { get { return this._interlaced; } set { this._interlaced = value; } }

        #endregion

        #region /*** Variables ***/

        // data filters
        bool _plotMfr = true;

        // rendering
        bool _interlaced = true;
        int _interlaceThreshold = 800;

        #endregion

    }
}
