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 TI = Volpe.Cafe.TechnologyIndexes;

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

        private VehiclesReportData _vehData;

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

        /// <inheritdoc/>
        protected override IEnumerator<object[]> GetYearEnumerator(int? year)
        {
            var mfrs = this.Data.ScenarioData.Manufacturers;

            foreach (IManufacturer mfr in mfrs) {
                foreach (IVehicle veh in mfr.Vehicles) {
                    int yearValue = year.Value;
                    this._vehData = new VehiclesReportData(yearValue, veh);
                    yield return this.GetValues(yearValue);
                }
            }
        }

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

            this.AppendHeader(year);
            this.AppendVehicleInfo();
            this.AppendPowertrainInfo();
            this.AppendFuelEconomyInfo(year);
            this.AppendCO2RatingInfo(year);
            this.AppendRegulatoryClassificationInfo();
            this.AppendPlanningInfo(year);
            this.AppendVehAttributes();
            this.AppendTargetsAndZev(year);
            this.AppendSalesAndCosts(year);
            this.AppendTechUtilization();

            return this.GetBuffer();
        }

        private void AppendHeader(int year)
        {
            this.AppendBuffer(this.Scenario.Index);
            this.AppendBuffer(StringHelper.GetTitleCase(this.Scenario.Description));
            this.AppendBuffer(year);
        }

        private void AppendVehicleInfo()
        {
            this.AppendBuffer(StringHelper.GetTitleCase(this._vehData.Vehicle.ManufacturerName, 4));
            this.AppendBuffer(this._vehData.Vehicle.Code);
            this.AppendBuffer(StringHelper.GetTitleCase(this._vehData.BaselineVehicle.Brand));
            this.AppendBuffer(StringHelper.GetTitleCase(this._vehData.BaselineVehicle.Model));
            this.AppendBuffer(StringHelper.GetTitleCase(this._vehData.BaselineVehicle.Nameplate));
            this.AppendBuffer(this._vehData.BaselineVehicle.PlatformCode);
            this.AppendBuffer(this._vehData.Vehicle.Platform.ComponentName);
            this.AppendBuffer(this._vehData.GetPlatformRevisionString());
        }

        private void AppendPowertrainInfo()
        {
            var veh = this._vehData.Vehicle;
            var baselineVeh = this._vehData.BaselineVehicle;
            var vehRevision = this._vehData.VehicleRevision;

            //----- general info -----
            this.AppendBuffer(vehRevision.HevType.ToString(getAbbr: true));
            this.AppendBuffer(baselineVeh.VehiclePower);
            this.AppendBuffer(baselineVeh.VehiclePower);

            //----- baseline engine -----
            var pEng = this._vehData.Vehicle.Manufacturer.FindEngineByCode(this._vehData.BaselineVehicle.EngineCode);
            if (pEng != null) {
                this.AppendBuffer(pEng.BaselineInfo.Code);
                this.AppendBuffer(pEng.BaselineInfo.Fuel.ToAcronymString(true));
                this.AppendBuffer(pEng.BaselineInfo.ToString());
            }
            else {
                this.AppendEmpty(3);
            }

            //----- revision engine -----
            var engRevisionString = this._vehData.GetEngineRevisionString();
            if (engRevisionString != null) {
                this.AppendBuffer(engRevisionString);
                this.AppendBuffer(vehRevision.GetVehicleEngineFuel(acronym: true));
                this.AppendBuffer(vehRevision.GetVehicleEngineType(acronym: true));
            }
            else {
                this.AppendEmpty(3);
            }

            //----- baseline transmission -----
            var pTrn = veh.Manufacturer.FindTransmissionByCode(baselineVeh.TransmissionCode);
            if (pTrn != null) {
                this.AppendBuffer(pTrn.BaselineInfo.Code);
                this.AppendBuffer(pTrn.BaselineInfo.ToString());
            }
            else {
                this.AppendEmpty(2);
            }

            //----- revision transmission -----
            var trnRevisionString = this._vehData.GetTransmissionRevisionString();
            if (trnRevisionString != null) {
                this.AppendBuffer(trnRevisionString);
                this.AppendBuffer(vehRevision.GetVehicleTransmissionType());
            }
            else {
                this.AppendEmpty(2);
            }
        }

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

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

                this.AppendVehRatings(vehFE, avgFE, vehFS, appendFuelInfo: !writeAdjustedFE);

                if (writeAdjustedFE) {
                    FuelValue adjFE = Standards.GetAdjustedVehicleFERating(scen, year, vehInfo, this.Settings);
                    double avgAdjFE = Standards.GetAverageVehicleFERating(
                        scen, year, vehRC, adjFE, vehFS, null, adjustForCompliance: false, round: true);

                    this.AppendVehRatings(adjFE, avgAdjFE, vehFS, appendFuelInfo: true);
                }
            }
        }

        private void AppendCO2RatingInfo(int year)
        {
            AppendCO2RatingHelper(this._vehData.BaselineVehicle);
            AppendCO2RatingHelper(this._vehData.VehicleRevision);

            void AppendCO2RatingHelper(IVehicleInfo vehInfo)
            {
                var scen = this.Scenario;
                var vehRC = vehInfo.RegClass;
                FuelValue vehFS = vehInfo.FuelShare;
                FuelValue vehCO2 = Standards.GetVehicleCO2Rating(scen, year, vehInfo, this.Settings);
                double avgCO2 = Standards.GetAverageVehicleCO2Rating(scen, year, vehRC, vehCO2, vehFS, round: true);

                this.AppendVehRatings(vehCO2, avgCO2, vehFS, appendFuelInfo: false);
            }
        }

        private void AppendRegulatoryClassificationInfo()
        {
            this.AppendBuffer(this._vehData.VehicleRevision.Style.ToString());
            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.ToFriendlyString());
            this.AppendBuffer(this._vehData.VehicleRevision.SafetyClass.ToString());
        }

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

        private void AppendVehAttributes()
        {
            var baselineVeh = this._vehData.BaselineVehicle;
            var vehRevision = this._vehData.VehicleRevision;

            this.AppendWeightValue(baselineVeh.CurbWeightMR0);
            this.AppendWeightValue(vehRevision.GetGliderMR0(this.Settings));
            this.AppendWeightValue(baselineVeh.CurbWeight);
            this.AppendWeightValue(vehRevision.CurbWeight);
            this.AppendWeightValue(baselineVeh.GetTestWeight());
            this.AppendWeightValue(vehRevision.GetTestWeight());
            this.AppendWeightValue(baselineVeh.GVWR);
            this.AppendWeightValue(vehRevision.GVWR);
            this.AppendWeightValue(baselineVeh.GCWR);
            this.AppendWeightValue(vehRevision.GCWR);
            this.AppendFootprintValue(vehRevision.Footprint);
            this.AppendWeightValue(vehRevision.GetWorkFactor());
        }

        private void AppendTargetsAndZev(int year)
        {
            var vehRevision = this._vehData.VehicleRevision;

            this.AppendBuffer(Standards.GetTarget(this.Scenario, year, vehRevision));
            this.AppendBuffer(Standards.GetVehicleCO2Target(this.Scenario, year, vehRevision));
            this.AppendVehRating(vehRevision.ZEVCredits);
        }

        private void AppendSalesAndCosts(int year)
        {
            this.AppendBuffer(this._vehData.BaselineVehicle.Sales);

            var vehRevision = this._vehData.VehicleRevision;
            if (vehRevision.Sales == 0) {
                // all values are zero if no sales exist for the vehicle
                this.AppendBuffer("0", 25);
                return;
            }

            // sales & msrp
            this.AppendBuffer(vehRevision.Sales);
            this.AppendCostValue(vehRevision.Msrp);
            this.AppendCostValue(vehRevision.Msrp + vehRevision.ComplianceCosts.RegCost);

            // employment
            var laborHours = this._vehData.Vehicle.CalcLaborHours(year, this.Settings.Parameters.EmploymentValues);
            this.AppendCostValue(laborHours / vehRevision.Sales);

            // unit costs
            var auxHevCosts = vehRevision.AuxiliaryHevCosts;
            var initAuxHevCosts = vehRevision.InitAuxiliaryHevCosts;

            this.AppendCostValue(vehRevision.ComplianceCosts.TotalAcOcCost);
            this.AppendCostValue(vehRevision.ComplianceCosts.BatteryTechCost);
            this.AppendCostValue(vehRevision.ComplianceCosts.NonBatteryTechCost);
            this.AppendCostValue(vehRevision.ComplianceCosts.TotalTechCost);
            this.AppendCostValue(vehRevision.ComplianceCosts.RegCost);
            this.AppendCostValue(vehRevision.ComplianceCosts.MaintRepairCost);
            this.AppendCostValue(auxHevCosts.TaxCredit);
            this.AppendCostValue(auxHevCosts.BatteryTaxCredit);
            this.AppendCostValue(auxHevCosts.HevCost - initAuxHevCosts.HevCost);
            this.AppendCostValue(auxHevCosts.TaxCredit - initAuxHevCosts.TaxCredit);
            this.AppendCostValue(auxHevCosts.BatteryTaxCredit - initAuxHevCosts.BatteryTaxCredit);
            this.AppendCostValue(auxHevCosts.ConsumerWTP - initAuxHevCosts.ConsumerWTP);
            this.AppendCostValue(auxHevCosts.TechBurden - initAuxHevCosts.TechBurden);
            this.AppendCostValue(vehRevision.InitOwnershipCosts.TaxesAndFees);
            this.AppendCostValue(vehRevision.OwnershipCosts.TaxesAndFees);
            this.AppendCostValue(vehRevision.InitOwnershipCosts.FinancingCosts);
            this.AppendCostValue(vehRevision.OwnershipCosts.FinancingCosts);
            this.AppendCostValue(vehRevision.InitOwnershipCosts.InsuranceCosts);
            this.AppendCostValue(vehRevision.OwnershipCosts.InsuranceCosts);
            this.AppendCostValue(vehRevision.Payback);
            this.AppendCostValue(vehRevision.PaybackTCO);
        }

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

        [Serializable]
        private sealed class VehiclesReportData
        {
            public VehiclesReportData(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.VehicleRevision.PlatformCode);
                this.Engine = mfr.FindEngineByCode(this.VehicleRevision.EngineCode);
                this.Transmission = mfr.FindTransmissionByCode(this.VehicleRevision.TransmissionCode);

                this.ModelYear = year;
                this.MinYear = mfr.Industry.MinYear;
            }

            public int ModelYear { get; }
            public int MinYear { get; }

            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 string GetPlatformRevisionString()
            {
                return this.GetComponentRevisionString(this.Vehicle.GetPlatformRevision);
            }

            public string GetEngineRevisionString()
            {
                return this.GetComponentRevisionString(this.Vehicle.GetEngineRevision);
            }

            public string GetTransmissionRevisionString()
            {
                return this.GetComponentRevisionString(this.Vehicle.GetTransmissionRevision);
            }

            private string GetComponentRevisionString(Func<int, bool, IOEMComponentRevision> getComponentRevision)
            {
                bool closestMatch = false;
                var revision = getComponentRevision(this.ModelYear, closestMatch);
                if (revision == null) {
                    closestMatch = true;
                    revision = getComponentRevision(this.ModelYear, closestMatch);
                }

                return (revision == null)
                    ? null
                    : this.RevisionToString(revision, closestMatch);
            }

            private string RevisionToString(IOEMComponentRevision revision, bool isClosestMatch)
            {
                string revisionString =
                    (revision.RevisionYear == this.MinYear) ? "baseline" : "v." + revision.RevisionYear;
                if (isClosestMatch) {
                    revisionString += "-R1";
                }

                return revisionString;
            }

        }

    }
}
