using System;
using System.Collections.Generic;
using System.Linq;
using Volpe.Cafe.MarketData.Design;
using Volpe.Cafe.ModelSettings;
using Volpe.Cafe.Utils;

using TI = Volpe.Cafe.TechnologyIndexes;

namespace Volpe.Cafe.Reporting.Generators
{
    /// <summary>
    /// Defines an enumerable object used for generating rows of data for a technology utilization report.
    /// </summary>
    [Serializable]
    public class TechUtilizationReportDataGenerator : ReportDataGenerator
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ConsumerCostsReportDataGenerator"/> class, for the specified
        /// scenario and reporting data.
        /// </summary>
        /// <inheritdoc cref="ReportDataGenerator(Scenario, ReportingData, ModelingSettings)" path="/param"/>
        public TechUtilizationReportDataGenerator(Scenario scen, ReportingData data, ModelingSettings settings)
            : base(scen, data, settings)
        {
            int mfrCount = this.Data.ScenarioData.ManufacturerCount;
            this._mfrRange = new Range(0, mfrCount - 1, true);
        }

        private const int ParamAppRate = 0;
        private const int ParamPenRate = 1;
        private const int ParamIncrAR = 2;
        private const int ParamIncrPR = 3;

        private static readonly string[] ParamTypes = { "App-Rate", "Pen-Rate", "Incr.AR", "Incr.PR" };

        private Range _mfrRange;

        private UtilizationStats _scenDataIndustry;
        private UtilizationStats _baseDataIndustry;
        private UtilizationStats _scenData;
        private UtilizationStats _baseData;

        /// <inheritdoc/>
        protected override bool WriteYearTotals => false;

        /// <inheritdoc/>
        protected override IEnumerator<object[]> GetYearEnumerator(int? year)
        {
            this._scenDataIndustry = new UtilizationStats(this.Data.ScenarioData);
            this._baseDataIndustry = new UtilizationStats(this.Data.BaselineData);

            foreach (int? mfr in this._mfrRange) {
                this.InitDataObjects(year.Value, mfr);

                for (int rc = 0; rc < RegClasses.ReportingList.Length; rc++) {
                    if (!this.Data.DataFilters.ReportRC[rc]) {
                        continue;
                    }

                    yield return this.GetValues(year, mfr, rc, ParamAppRate);
                    yield return this.GetValues(year, mfr, rc, ParamPenRate);
                    yield return this.GetValues(year, mfr, rc, ParamIncrAR);
                    yield return this.GetValues(year, mfr, rc, ParamIncrPR);
                }
            }
        }

        private void InitDataObjects(int year, int? mfr)
        {
            if (mfr == null) {
                this._scenData = this._scenDataIndustry;
            }
            else {
                this._scenData = new UtilizationStats(year, this.Data.ScenarioData.Manufacturers[mfr.Value]);
                this._scenDataIndustry.Append(this._scenData);
            }

            if (this.Scenario.IsBaseline) {
                this._baseData = this._scenData;
            }
            else if (mfr == null) {
                this._baseData = this._baseDataIndustry;
            }
            else {
                this._baseData = new UtilizationStats(year, this.Data.BaselineData.Manufacturers[mfr.Value]);
                this._baseDataIndustry.Append(this._baseData);

            }
        }

        private object[] GetValues(int? year, int? mfr, int rc, int paramType)
        {
            this.ClearBuffer();

            var regClass = RegClasses.ReportingList[rc];

            this.AppendHeader(year, mfr, rc, paramType);
            this.AppendTechUtilization(regClass, paramType);

            return this.GetBuffer();
        }

        private void AppendHeader(int? year, int? mfr, int rc, int paramType)
        {
            this.AppendBuffer(this.Scenario.Index);
            this.AppendBuffer(StringHelper.GetTitleCase(this.Scenario.Description));
            this.AppendBuffer(year == null ? "TOTAL" : year.ToString());
            this.AppendBuffer(mfr == null ? "TOTAL" : this._scenData.Manufacturer.ProperName);
            this.AppendBuffer(RegClasses.ReportingNames[rc]);
            this.AppendBuffer(ParamTypes[paramType]);
        }

        private void AppendTechUtilization(RegulatoryClass regClass, int paramType)
        {
            bool incremental = (paramType == ParamIncrAR || paramType == ParamIncrPR);
            double[] scenUtilizationRate = this._scenData.GetUtilizationRate(paramType, regClass);

            if (incremental) {
                double[] baseUtilizationRate = this._baseData.GetUtilizationRate(paramType, regClass);
                for (int i = 0; i < TI.TechnologyCount; i++) {
                    scenUtilizationRate[i] -= baseUtilizationRate[i];
                }
            }

            for (int i = 0; i < TI.TechnologyCount; i++) {
                this.AppendRounded(scenUtilizationRate[i]);
            }
        }

        [Serializable]
        private sealed class UtilizationStats
        {
            public UtilizationStats(int year, IManufacturer mfr)
            {
                this.Industry = mfr.Industry;
                this.Manufacturer = mfr;

                this.CalcTechUtilization(year, mfr);
            }

            public UtilizationStats(IIndustry industry)
            {
                this.Industry = industry;
                this.Manufacturer = null;

                this.TotalSales = new RCDouble();
                this.AppliedSales = new RCDouble[TI.TechnologyCount];
                this.UsedSales = new RCDouble[TI.TechnologyCount];

                for (int i = 0; i < TI.TechnologyCount; i++) {
                    this.AppliedSales[i] = new RCDouble();
                    this.UsedSales[i] = new RCDouble();
                }
            }

            public IIndustry Industry { get; }
            public IManufacturer Manufacturer { get; }

            public RCDouble[] AppliedSales { get; private set; }
            public RCDouble[] UsedSales { get; private set; }
            public RCDouble TotalSales { get; private set; }

            public void Append(UtilizationStats mfrStats)
            {
                this.TotalSales.Append(mfrStats.TotalSales);
                for (int i = 0; i < TI.TechnologyCount; i++) {
                    this.AppliedSales[i].Append(mfrStats.AppliedSales[i]);
                    this.UsedSales[i].Append(mfrStats.UsedSales[i]);
                }
            }

            public double[] GetUtilizationRate(int paramType, RegulatoryClass regClass)
            {
                double totalSales = this.TotalSales[regClass];
                if (totalSales == 0) {
                    return new double[TI.TechnologyCount];
                }

                bool isAppRate = (paramType == ParamAppRate || paramType == ParamIncrAR);
                var utilizationSales = isAppRate ? this.AppliedSales : this.UsedSales;
                var utilizationRates = utilizationSales.Select(item => item[regClass] / totalSales).ToArray();
                return utilizationRates;
            }

            private void CalcTechUtilization(int year, IManufacturer mfr)
            {
                this.TotalSales = mfr.GetRevision(year).ComplianceStats.GetSales();
                this.AppliedSales = new RCDouble[TI.TechnologyCount];
                this.UsedSales = new RCDouble[TI.TechnologyCount];

                for (int i = 0; i < TI.TechnologyCount; i++) {
                    var appiedSales = new RCDouble();
                    var usedSales = new RCDouble();

                    foreach (var veh in mfr.Vehicles) {
                        var vehTechUse = veh.TechUtilization[i];
                        if (!vehTechUse.IsUsed(year) || vehTechUse.IsSuperseded(year)) {
                            continue;
                        }

                        var vehRevision = veh.GetRevision(year);
                        var regClass = vehRevision.RegClass;
                        double vehSales = vehRevision.Sales;

                        if (vehTechUse.IsApplied(year)) {
                            appiedSales[regClass] += vehSales;
                        }
                        usedSales[regClass] += vehSales;
                    }

                    this.AppliedSales[i] = appiedSales;
                    this.UsedSales[i] = usedSales;
                }
            }

        }

    }
}
