using System;
using System.Collections.Generic;

using Volpe.Cafe.MarketData.BaselineInfo;
using Volpe.Cafe.MarketData.Design;
using Volpe.Cafe.Model;
using Volpe.Cafe.ModelSettings;
using Volpe.Cafe.Utils;
using Volpe.Cafe.VehicleSimulation;

using TI = Volpe.Cafe.TechnologyIndexes;

namespace Volpe.Cafe.Reporting.Generators
{
    /// <summary>
    /// Defines an enumerable object used for generating rows of data for a vehicles diagnostic report.
    /// </summary>
    [Serializable]
    public class VehiclesDiagnosticReportDataGenerator : ReportDataGenerator
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="VehiclesDiagnosticReportDataGenerator"/> class, for the 
        /// specified scenario and reporting data.
        /// </summary>
        /// <inheritdoc cref="ReportDataGenerator(Scenario, ReportingData, ModelingSettings)" path="/param"/>
        public VehiclesDiagnosticReportDataGenerator(Scenario sen, ReportingData data, ModelingSettings settings)
            : base(sen, data, settings) { }

        private VehicleDiagnosticReportData _vehData;

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

        /// <inheritdoc/>
        protected override IEnumerator<object[]> GetYearEnumerator(int? year)
        {
            foreach (var veh in this.Data.ScenarioData.Vehicles) {
                int yearValue = year.Value;
                this._vehData = new VehicleDiagnosticReportData(yearValue, veh);
                yield return this.GetValues(yearValue);
            }
        }

        private object[] GetValues(int year)
        {
            this.ClearBuffer();

            this.AppendHeader(year, this._vehData.Vehicle);
            this.AppendRegulatoryClassificationInfo();
            this.AppendPlanningInfo(year);
            this.AppendTechKeys();
            this.AppendCurbWeightInfo();
            this.AppendFuelEconomyInfo(year);
            this.AppendFEAdjustmentFactorsInfo();
            this.AppendUnitCostsInfo(year);
            this.AppendTechUtilizationAndCosts(year);

            return this.GetBuffer();
        }

        private void AppendHeader(int year, IVehicle veh)
        {
            this.AppendBuffer(this.Scenario.Index);
            this.AppendBuffer(year.ToString());
            this.AppendBuffer(StringHelper.GetTitleCase(veh.ManufacturerName, 4));
            this.AppendBuffer(veh.Code.ToString());
        }

        private void AppendRegulatoryClassificationInfo()
        {
            this.AppendBuffer(this._vehData.VehicleRevision.VehicleClass.ToString());
            this.AppendBuffer(this._vehData.VehicleRevision.RegClass.ToString());
            this.AppendBuffer(this._vehData.VehicleRevision.TechnologyClass.ToString());
            this.AppendBuffer(this._vehData.VehicleRevision.EngineTechnologyClass.ToString());
        }

        private void AppendPlanningInfo(int year)
        {
            this.AppendBuffer(this._vehData.GetRedesignRefreshString(year, this._vehData.Vehicle));
            this.AppendBuffer(this._vehData.GetRedesignRefreshString(year, this._vehData.Platform));
            this.AppendBuffer(this._vehData.GetRedesignRefreshString(year, this._vehData.Engine));
            this.AppendBuffer(this._vehData.GetRedesignRefreshString(year, this._vehData.Transmission));
            this.AppendBuffer(this._vehData.GetComponentLeaderString());
        }

        private void AppendTechKeys()
        {
            this.AppendBuffer(TI.KeyToString(this._vehData.BaselineVehicle.TechKey, ';'));
            this.AppendBuffer(TI.KeyToString(this._vehData.VehicleRevision.TechKey, ';'));
        }

        private void AppendCurbWeightInfo()
        {
            this.AppendWeightValue(this._vehData.BaselineVehicle.CurbWeightMR0);
            this.AppendWeightValue(this._vehData.BaselineVehicle.GetGliderMR0(this.Settings));
            this.AppendWeightValue(this._vehData.BaselineVehicle.CurbWeight);
            this.AppendWeightValue(this._vehData.VehicleRevision.CurbWeight);
            this.AppendWeightValue(this._vehData.BaselineVehicle.CurbWeight
                                 - this._vehData.VehicleRevision.CurbWeight);
        }

        private void AppendFuelEconomyInfo(int year) 
        {
            AppendFuelEconomyHelper(this._vehData.BaselineVehicle);
            AppendFuelEconomyHelper(this._vehData.VehicleRevision);

            void AppendFuelEconomyHelper(IVehicleInfo vehInfo)
            {
                var vehRC = vehInfo.RegClass;
                FuelValue vehFS = vehInfo.FuelShare;
                FuelValue vehFE = vehInfo.FuelEconomy;
                double avgFE = Standards.GetAverageVehicleFERating(
                    this.Scenario, year, vehRC, vehFE, vehFS, null, adjustForCompliance: false, round: false);

                this.AppendVehRatings(vehFE, avgFE, vehFS, appendFuelInfo: true);
            }
        }

        private void AppendFEAdjustmentFactorsInfo()
        {
            var techClass = this._vehData.VehicleRevision.TechnologyClass;

            this.AppendBuffer(this._vehData.InitSimResult.GetFE1Adjustment(techClass));
            this.AppendBuffer(this._vehData.InitSimResult.GetFE2Adjustment(techClass));
            this.AppendBuffer(this._vehData.SimResult.GetFE1Adjustment(techClass));
            this.AppendBuffer(this._vehData.SimResult.GetFE2Adjustment(techClass));
        }

        private void AppendUnitCostsInfo(int year)
        {
            var vehBaseline = this._vehData.BaselineVehicle;
            var vehRevision = this._vehData.VehicleRevision;

            this.AppendCostValue(vehRevision.ComplianceCosts.TotalTechCost);
            this.AppendCostValue(this._vehData.InitSimResult.GetBatteryCost(vehRevision.TechnologyClass));
            this.AppendCostValue(this._vehData.SimResult.GetBatteryCost(vehRevision.TechnologyClass));
            this.AppendRounded(GetBatteryLearningRate(vehBaseline));
            this.AppendRounded(GetBatteryLearningRate(vehRevision));

            double GetBatteryLearningRate(IVehicleInfo vehInfo)
            {
                var techList = this.Settings.Technologies.TechnologyList;
                var maxTech = vehInfo.GetMaxBatteryTech();

                return maxTech == -1 ? 0 : techList[maxTech].GetBatteryLearningRate(vehInfo, year);
            }
        }

        private void AppendTechUtilizationAndCosts(int year)
        {
            var techList = this.Settings.Technologies.TechnologyList;
            var vehRevision = this._vehData.VehicleRevision;

            for (int i = 0; i < TI.TechnologyCount; i++) {
                this.AppendBuffer(this._vehData.GetTechUsedString(i, year));
            }
            for (int i = 0; i < TI.TechnologyCount; i++) {
                this.AppendCostValue(techList[i].GetVehicleCost(vehRevision, year));
            }
            for (int i = 0; i < TI.TechnologyCount; i++) {
                this.AppendCostValue(techList[i].GetEngineCost(vehRevision, year));
            }
        }

        [Serializable]
        private sealed class VehicleDiagnosticReportData
        {
            public VehicleDiagnosticReportData(int year, IVehicle veh) 
            {
                this.Vehicle = veh;
                this.VehicleRevision = this.Vehicle.GetRevision(year);
                this.BaselineVehicle = this.Vehicle.BaselineInfo;

                var mfr = this.Vehicle.Manufacturer;
                this.Platform = mfr.FindPlatformByCode(this.BaselineVehicle.PlatformCode);
                this.Engine = mfr.FindEngineByCode(this.BaselineVehicle.EngineCode);
                this.Transmission = mfr.FindTransmissionByCode(this.BaselineVehicle.TransmissionCode);

                this.SimResult = SimulationDatabase.GetValue(this.VehicleRevision.TechKey);
                this.InitSimResult = SimulationDatabase.GetValue(this.Vehicle.BaselineInfo.TechKey);
            }

            public IVehicle Vehicle { get; private set; }
            public IVehicleRevision VehicleRevision { get; private set; }
            public BaselineVehicle BaselineVehicle { get; private set; }

            public IPlatform Platform { get; private set; }
            public IEngine Engine { get; private set; }
            public ITransmission Transmission { get; private set; }

            public ISimulationResult SimResult { get; private set; }
            public ISimulationResult InitSimResult { get; private set; }

            public string GetRedesignRefreshString(int year, IOEMProduct product)
            {
                if (product == null) {
                    return string.Empty;
                }

                // note: for veh-diag report, we want to combine redesign & refresh states when both
                return (product.IsAtRedesign(year) ? "R" : string.Empty)
                     + (product.IsAtRefresh(year) ? "F" : string.Empty);
            }

            public string GetComponentLeaderString()
            {
                int? pltLeader = this.Platform?.BaselineInfo.LeaderCode;
                int? engLeader = this.Engine?.BaselineInfo.LeaderCode;
                int? trnLeader = this.Transmission?.BaselineInfo.LeaderCode;

                return ((pltLeader.HasValue && pltLeader.Value == this.Vehicle.Code) ? "P" : string.Empty) +
                       ((engLeader.HasValue && engLeader.Value == this.Vehicle.Code) ? "E" : string.Empty) +
                       ((trnLeader.HasValue && trnLeader.Value == this.Vehicle.Code) ? "T" : string.Empty);
            }

            public string GetTechUsedString(int techIndex, int year)
            {
                var techUtilization = this.Vehicle.TechUtilization[techIndex];
                var mfrRevision = this.Vehicle.Manufacturer.GetRevision(year);

                string techUsedStr =
                    (techUtilization.IsUsedInBaseline()) ? "U" :
                    (techUtilization.IsInherited(year)) ? "I" :
                    (techUtilization.IsUsed(year)) ? "A" :
                    (techUtilization.IsDisabled || !techUtilization.IsAvailable(year)) ? "X" :
                    (mfrRevision.TechExhausted[techIndex]) ? "P" : string.Empty;

                if (techUtilization.IsSuperseded(year)) {
                    techUsedStr += "S";
                }

                // TODO: below block is special diagnostic hack for when techs were
                // "incorrectly" set as used in the input fleet ...
                // this will allow us to track and compare to previous model version ...
                // this is also likely to be removed in the future
                bool isSetAsUsedInInput = this.Vehicle.BaselineInfo.UsedTechnologiesFull[techIndex];
                bool isShevpsAndLater = this.Vehicle.BaselineInfo.HevType.IsShevAndLater();
                if (techUsedStr == "X" && isSetAsUsedInInput && !isShevpsAndLater) {
                    techUsedStr = "US";
                }

                return techUsedStr;
            }

        }

    }
}
