#region <<< Using Directives >>>
using System;
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 CompliancePerformanceChart : ModelChart
    {

        #region /*** Ctors ***/

        /// <summary>
        /// Initializes a new instance of the <see cref="CompliancePerformanceChart"/> class.
        /// </summary>
        public CompliancePerformanceChart() : base()
        {
            this.InitializeComponent();
            this.InitializeOther();
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="CompliancePerformanceChart"/> class, using the specified chart title.
        /// </summary>
        /// <param name="title">The title to display on the chart.</param>
        public CompliancePerformanceChart(string title) : this()
        {
            this.Title = title;
        }

        #endregion

        #region /*** Methods ***/

        //----- initialization -----
        void InitializeComponent()
        {
            this.SuspendLayout();
            // 
            // CompliancePerformanceChart
            // 
            this.CategoryAxisDiscreteValues = new string[] {
                "Mfr 1",
                "Mfr 2",
                "Mfr 3"};
            this.CategoryAxisLabel = "Manufacturers";
            this.Name = "CompliancePerformanceChart";
            this.PrimaryAxisLabel = "mpg";
            this.PrimaryAxisMaxValue = 30;
            this.PrimaryAxisMinValue = 0;
            this.PrimaryAxisNumberFormat = "0.0";
            this.SecondaryAxisLabel = "$";
            this.SecondaryAxisMaxValue = 300;
            this.SecondaryAxisMinValue = 0;
            this.SecondaryAxisNumberFormat = "0";
            this.ShowSecondaryAxis = true;
            this.Title = "Compliance Performance";
            this.ResumeLayout(false);
        }
        void InitializeOther()
        {
            this.InitializeAxis();
            this.InitializeSeries();
        }
        //
        protected override void InitializeChartInternal()
        {
            this.InitializeAxis();
            this.InitializeSeries();
        }
        void InitializeAxis()
        {
            string[] mfrNames = new string[this._mfrCount];
            for (int i = 0; i < mfrNames.Length; i++)
            {
                mfrNames[i] = this._data.Manufacturers[i].ProperName;
            }
            this.CategoryAxisDiscreteValues = mfrNames;
            this.ShowCategoryAxisLabel = false;
        }
        void InitializeSeries()
        {
            this.DataSeries.Clear();
            //
            this.AddDataSeries("Industry CAFE"       , AxisType.Primary  , ChartStyle.Line);
            this.AddDataSeries("CAFE Rating"         , AxisType.Primary  , ChartStyle.Bar);
            this.AddDataSeries("CAFE (2-cycle)"      , AxisType.Primary  , ChartStyle.Bar);
            this.AddDataSeries("Avg. Fuel Cost"      , AxisType.Secondary, ChartStyle.Bar);
            this.AddDataSeries("Avg. Emission Damage", AxisType.Secondary, ChartStyle.Bar);
        }

        //----- 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 = "Industry CO2";
                this.DataSeries[1].Name = "CO2 Rating";
                this.DataSeries[2].Visible = false;
                for (int i = 3; i < this.DataSeries.Count; i++)
                {   // shift colors for CO2 modeling
                    this.DataSeries[i].ColorIndex = i - 1;
                }
                this.PrimaryAxis.Label = "g/mi";
                primaryAxisMaxValue = 300;
            }
            else
            {
                this.DataSeries[0].Name = "Industry CAFE";
                this.DataSeries[1].Name = "CAFE Rating";
                this.DataSeries[2].Name = "CAFE (2-cycle)";
                for (int i = 3; i < this.DataSeries.Count; i++)
                {   // reset colors for CAFE modeling
                    this.DataSeries[i].ColorIndex = -1;
                }
                this.PrimaryAxis.Label = "mpg";
                primaryAxisMaxValue = 30;
            }
            if (!this._compliance.Running)
            {
                this.PrimaryAxis.MaxValue = primaryAxisMaxValue;
            }
        }
        void RefreshChartDataInternal(Scenario scen, ModelYear year, IManufacturer mfr)
        {
            var program = this._settings.RuntimeSettings.ComplianceProgram;
            int yearFilter = (this._yearFilter == -1) ? year : this._yearFilter;
            this.Title = $"{(program.IsCO2() ? "CO2" : "CAFE")} Performance - {yearFilter}" +
                (this._yearFilter == -1 ? "*" : "") + ChartFilters.RegClassSuffix[this._rcFilter];

            // if mfr is null, compliance modeling hasn't started yet (and rest of chart cannot be updated)
            if (mfr == null) { return; }

            var metrics = (program.IsCO2() ? ComplianceMetrics.CO2Values : ComplianceMetrics.CAFEValues) |
                          ComplianceMetrics.EffectsCosts;

            double[] indRating = new double[this._mfrCount];
            double[] rating1   = new double[this._mfrCount];
            double[] rating2   = new double[this._mfrCount];
            double[] fuelCost  = new double[this._mfrCount];
            double[] emDamage  = new double[this._mfrCount];

            // begin with calculating industry stats (the same ind stats will be applied to all mfrs)
            IIndustry data = this._compliance.GetData(scen);
            var indStatistics = ChartHelper.CalculateStatistics(data, this._rcFilter, yearFilter, metrics);

            // update stats for each mfr
            for (int i = 0; i < data.Manufacturers.Count; i++)
            {
                var statistics = ChartHelper.CalculateStatistics(
                    data.Manufacturers[i], this._rcFilter, yearFilter, metrics);

                indRating[i] = (program.IsCO2()) ? indStatistics.CO2Rating : indStatistics.CAFE;
                if (statistics.Sales > 0)
                {
                    if (program.IsCO2())
                    {
                        rating1[i] = statistics.CO2Rating;
                        rating2[i] = statistics.CO2Rating;
                    }
                    else
                    {
                        rating1[i] = statistics.CAFE;
                        rating2[i] = statistics.CAFE2Cycle;
                    }
                    fuelCost[i] = statistics.FuelCosts * 1e6 / statistics.Sales;
                    emDamage[i] = statistics.EmissionsDamage * 1e6 / statistics.Sales;
                }
            }

            double mpgMax = Math.Max(rating1.Max(), rating2.Max());
            double scalar = 50D;
            if (program.IsCAFE())
            {
                // for CAFE program, there may be a huge difference in CAFE ratings between one
                // or two mfrs and all others --
                // hence, further restrict the max rating to be bounded by the industry average
                mpgMax = Math.Min(mpgMax, indStatistics.CAFE * 5);
                scalar = 15D;
            }
            this.PrimaryAxisMaxValue = Math.Ceiling(mpgMax / scalar) * scalar;
            this.SecondaryAxisMaxValue = double.NaN;

            this.UpdateDataSeriesValues(0, indRating);
            this.UpdateDataSeriesValues(1, rating1);
            this.UpdateDataSeriesValues(2, rating2);
            this.UpdateDataSeriesValues(3, fuelCost);
            this.UpdateDataSeriesValues(4, emDamage);
        }

        /// <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)
        {
            int yearFilter = this._yearFilter;

            if (yearFilter == -1)
            {   // advancing from "auto" advance to fixed value
                yearFilter = (direction < 0) ? this._maxYear : this._minYear;
            }
            else
            {   // advance year filter and adjust for overflow
                yearFilter += direction;
                if (yearFilter < this._minYear || yearFilter > this._maxYear)
                {
                    yearFilter = -1;
                }
            }

            this._yearFilter = yearFilter;
        }

        #endregion

    }
}
