#region <<< Using Directives >>>
using System;
using System.Linq;

using Volpe.Cafe.EffectsModels;
using Volpe.Cafe.Generic;
using Volpe.Cafe.Logging;
using Volpe.Cafe.MarketData.Design;
using Volpe.Cafe.ModelSettings;
using Volpe.Cafe.Reporting;

using VS = Volpe.Cafe.VehicleStyle;
#endregion

namespace Volpe.Cafe.Model
{
    /// <summary>
    /// Provides methods to support the Dynamic VMT model.
    /// </summary>
    [Serializable]
    public class VMTModel
    {

        #region <<< Nested Types >>>

        /// <summary>
        /// Defines a static VMT model, that computes vehicle miles traveled based on a static VMT schedule.
        /// </summary>
        [Serializable]
        class StaticVMTModel : VMTModel
        {
            public StaticVMTModel(ICompliance complianceModel) : base(complianceModel, null, null, false) { }

            public override double GetNonReboundVMT(int year, int age, VS vehStyle, FuelValue fuelShare)
            {
                return this.Parameters.MilesDriven[vehStyle][age];
            }

            public override double GetVMT(int year, int age, VS vehStyle, FuelValue onRoadFE, FuelValue fuelShare)
            {
                return this.Parameters.MilesDriven[vehStyle][age] *
                    (1 + this.CalcCPMRate(year, age, vehStyle, onRoadFE, fuelShare));
            }

            public override double GetReallocatedVMT(int year, int age, VS vehStyle)
            {
                return 0;
            }
        }

        /// <summary>
        /// Defines a singleton class that creates and runs a "no-tech" compliance model based on baseline scenario.
        /// This will obtain a sales and scrappage response to baseline standards, but w/o tech application.
        /// </summary>
        [Serializable]
        class VMTNoTechAnalysis
        {
            private VMTNoTechAnalysis(ICompliance baseComplianceModel)
            {
                ICompliance compliance = baseComplianceModel.CreateNew();
                IIndustry data = baseComplianceModel.Data.CreateNew();

                // override settings for fake run
                var complSettings = baseComplianceModel.Settings;
                var settings = new ModelingSettings(
                    complSettings.IOSettings.     Clone(),
                    complSettings.RuntimeSettings.Clone())
                {
                    Technologies = complSettings.Technologies,
                    Parameters   = complSettings.Parameters,
                    Scenarios    = new Scenarios(complSettings.Scenarios.Baseline)
                };

                settings.RuntimeSettings.TechStartYear = settings.RuntimeSettings.AnalysisEndYear + 1; // effectively disables tech application
                settings.RuntimeSettings.DfsSrIterations = 2; // use minimum possible iterations for sales model
                settings.RuntimeSettings.DisableDynamicVmtModel = true; // VMT model should be disabled, otherwise infinite loop
                settings.RuntimeSettings.DisableDynamicScrappageModel = true; // need to run scrappage model separately, so we can save results
                settings.RuntimeSettings.DisableEffectsModel = true;
                settings.IOSettings.ReportingLevel = ReportingLevel.Disabled;
                settings.IOSettings.LogLevel = LogLevel.Disabled;
                settings.IOSettings.CompressModelOutputs = false;

                compliance.Start(data, settings);
                compliance.WaitForCompletion();

                this._scrappageModel = ScrappageModel.CreateDynamicScrappageModel(compliance, settings.Scenarios[0]);
                this._complianceModel = compliance;
            }

            public static VMTNoTechAnalysis CreateInstance(ICompliance baseComplianceModel, Scenario scen)
            {
                if (Instance == null || scen.IsBaseline)
                {
                    Instance = new VMTNoTechAnalysis(baseComplianceModel);
                }

                return Instance;
            }

            public VSValue<double[]> GetScrappedFleetDataByCY(int calendarYear)
            {
                return this._scrappageModel.GetScrappedFleetByCY(calendarYear);
            }

            public IIndustry GetComplianceDataByMY(int modelYear)
            {
                var baseScen = this._complianceModel.Settings.Scenarios[0];
                return this._complianceModel.GetData(baseScen);
            }

            static VMTNoTechAnalysis Instance = null;
            ICompliance _complianceModel;
            ScrappageModel _scrappageModel;
        }

        class VmtModelLogging
        {
            public static readonly string RefVmtFileName = "vmt_ref-vmt-model_sn{0}.txt";
            public static readonly string[] RefVmtHeader = {
                "CY", "FE", "CPM_lag1", "CPM", "LN_VMT_lag1", "LN_VMT",
                "LN_RDPI_lag3", "LN_RDPI_lag2", "LN_RDPI_lag1", "LN_RDPI",
                "Z_t-1", "Diff_LN_VMT", "RefVMT", "Actual VMT"
            };

            public static readonly string DeltaVmtFileName = "vmt_delta-miles_sn{0}.txt";
            public static readonly string[] DeltaVmtHeader = {
                "CY", "VehStyle", "Age", "RefVMT_CY", "AcutalVMT_CY", "TotalDeltaMiles_CY", "ActualFleet_CY",
                "ActualVMT_CY_Style", "ActualFleet_CY_Style", "R_CY_Style",
                "PartialCPMRate_CY_Style_Age", "ActualVMT_CY_Style_Age",
                "ActualFleet_CY_Style_Age", "DeltaMiles_CY_Style_Age", "DeltaMiles_x_Fleet_CY_Style_Age"
            };

            public static string GetRefVmtFileName(int scenIndex)
            {
                return string.Format(RefVmtFileName, scenIndex);
            }

            public static string GetDeltaVmtFileName(int scenIndex)
            {
                return string.Format(DeltaVmtFileName, scenIndex);
            }

        }

        #endregion

        #region <<< Ctors >>>

        /// <summary>
        /// Initializes a new instance of the <see cref="VMTModel"/>, associated with the specified compliance model,
        /// scrappage model, and scenario.
        /// </summary>
        /// <param name="complianceModel">The compliance model associated with this <see cref="VMTModel"/> instance.</param>
        /// <param name="scrappageModel">The scrappage model associated with this <see cref="VMTModel"/> instance.</param>
        /// <param name="scen">The scenario for which to obtain the final vehicle data to use for estimating VMT.</param>
        /// <param name="initDeltaMiles">true, to initialize delta miles; false, otherwise.</param>
        VMTModel(ICompliance complianceModel, ScrappageModel scrappageModel, Scenario scen, bool initDeltaMiles)
        {
            this.ComplianceModel = complianceModel;
            this.ScrappageModel  = scrappageModel;
            this.Scenario        = scen;

            this._minHstYear = complianceModel.MinHistoricYear;
            this._minYear    = complianceModel.MinYear;
            this._maxYear    = complianceModel.MaxYear;
            this._yearCount  = this._maxYear - this._minYear + 1;

            if (initDeltaMiles)
            {
                this.InitActualFleetInfo();
                this.InitNoTechAnalysis();
                this.InitReferenceMpg();
                this.InitReferenceVMT();
                this.InitDeltaMiles();
            }
        }

        #endregion

        #region <<< Methods >>>

        /// <summary>
        /// Initializes and returns a new instance of the dynamic <see cref="VMTModel"/>, associated with the specified
        /// compliance model, scrappage model, and scenario, which can be used to dynamically estimate the VMT schedule.
        /// </summary>
        /// <param name="complianceModel">The compliance model associated with this <see cref="VMTModel"/> instance.</param>
        /// <param name="scrappageModel">The scrappage model associated with this <see cref="VMTModel"/> instance.</param>
        /// <param name="scen">The scenario for which to obtain the final vehicle data to use for estimating VMT.</param>
        /// <returns>A dynamic <see cref="VMTModel"/>.</returns>
        public static VMTModel CreateDynamicVMTModel(
            ICompliance complianceModel, ScrappageModel scrappageModel, Scenario scen)
        {
            bool is2b3Only = complianceModel.Data.Vehicles.All(veh => veh.BaselineInfo.Style.Is2b3());
            return is2b3Only
                ? CreateStaticVMTModel(complianceModel)
                : new VMTModel(complianceModel, scrappageModel, scen, true);
        }
        /// <summary>
        /// Initializes and returns a new instance of a legacy VMT model, associated with the specified compliance
        /// model, which uses a static VMT schedule.
        /// </summary>
        /// <param name="complianceModel">The compliance model associated with this <see cref="VMTModel"/> instance.</param>
        /// <returns>A legacy VMT model, using a static VMT schedule.</returns>
        public static VMTModel CreateStaticVMTModel(ICompliance complianceModel)
        {
            return new StaticVMTModel(complianceModel);
        }

        /// <summary>
        /// Obtains the "without-rebound" VMT during the specified age, for a vehicle of a given style, produced during
        /// the specified model year.
        /// </summary>
        /// <param name="year">The model year for which to calculate the non-rebound VMT.</param>
        /// <param name="age">The vehicle age for which to calculate the non-rebound VMT.</param>
        /// <param name="vehStyle">The vehicle style for which to calculate the non-rebound VMT.</param>
        /// <param name="fuelShare">The fuel share of a vehicle for which to calculate the non-rebound VMT.</param>
        /// <returns>The non-rebound VMT for the specified model year, age, and vehicle style.</returns>
        public virtual double GetNonReboundVMT(int year, int age, VS vehStyle, FuelValue fuelShare)
        {
            double milesDriven = this.Parameters.MilesDriven[vehStyle][age];
            if (vehStyle.Is2b3()) {
                return milesDriven;
            }

            double deltaMiles = this.GetDeltaMiles(year, age, vehStyle);
            double cpmRate = this.CalcPartialCPMRate(year, age, vehStyle, fuelShare);
            return milesDriven * (1 + cpmRate) + deltaMiles;
        }
        /// <summary>
        /// Obtains the "with-rebound" VMT during the specified age, for a vehicle of a given style, produced during
        /// the specified model year.
        /// </summary>
        /// <param name="year">The model year for which to calculate VMT.</param>
        /// <param name="age">The vehicle age for which to calculate VMT.</param>
        /// <param name="vehStyle">The vehicle style for which to calculate VMT.</param>
        /// <param name="onRoadFE">The on-road fuel economy rating of a vehicle for which to calculate VMT.</param>
        /// <param name="fuelShare">The fuel share of a vehicle for which to calculate VMT.</param>
        /// <returns>The "with-rebound" VMT for the specified model year, age, and vehicle style.</returns>
        public virtual double GetVMT(int year, int age, VS vehStyle, FuelValue onRoadFE, FuelValue fuelShare)
        {
            double milesDriven = this.Parameters.MilesDriven[vehStyle][age];
            double fullCPMRate = this.CalcCPMRate(year, age, vehStyle, onRoadFE, fuelShare);
            double vmt = milesDriven * (1 + fullCPMRate);
            if (vehStyle.Is2b3()) {
                return vmt;
            }

            double deltaMiles = this.GetDeltaMiles(year, age, vehStyle);
            double partCPMRate = this.CalcPartialCPMRate(year, age, vehStyle, fuelShare);
            return vmt + deltaMiles * (1 + fullCPMRate - partCPMRate);
        }
        /// <summary>
        /// Obtains the amount of VMT that was reallocated between the reference VMT schedule and the schedule computed
        /// by the VMT model, during the specified age, for a vehicle of a given style, produced during the specified
        /// model year.
        /// </summary>
        /// <param name="year">The model year for which to calculate the amount of reallocated VMT.</param>
        /// <param name="age">The vehicle age for which to calculate the amount of reallocated VMT.</param>
        /// <param name="vehStyle">The vehicle style for which to calculate the amount of reallocated VMT.</param>
        /// <returns>
        /// The amount of VMT reallocated between the reference schedule and the schedule computed by the VMT model.
        /// </returns>
        public virtual double GetReallocatedVMT(int year, int age, VS vehStyle)
        {
            return vehStyle.Is2b3()
                   ? 0
                   : this.GetDeltaMiles(year, age, vehStyle);
        }

        private double GetDeltaMiles(int year, int age, VS vehStyle)
        {
            int cyIndex = Math.Min(year + age, this._maxYear) - this._minYear;
            return this._vehDeltaMiles[vehStyle][cyIndex][age];
        }

        double CalcPartialCPMRate(int year, int age, VS vehStyle, FuelValue fuelShare)
        {
            //
            // fuel economy and fuel share values are used for computing CPM ---
            // however, for estimating actual VMT (to use for calculating delta-miles),
            // we only wish to capture the fuel price component of rebound miles, and
            // thus, FE and FS need to be capped at the reference VMT year -- that is,
            // for all analysis years (greater than VMT ref year), assume that fuel
            // economy remains constant at whatever it was during the VMT ref year
            //
            // the fuel share param is optional, and if not specified, obtain it from
            // the historic fleet data  ---
            //  > the fuel share should not be specified when calculating delta-miles,
            //    since in that case, we wish to capture and aggregate the fuel price
            //    component of rebound miles w.r.t. the reference VMT year
            //  > the fuel share should be specified when calculating non-rebound VMT,
            //    since in that case, we wish to weigh CPM rate based on the fuel share
            //    of a specific vehicle for which the VMT is being computed
            //
            var hfd        = this.Parameters.HistoricFleetData;
            int vmtRefYear = this.Parameters.EconomicValues.Parameters[vehStyle].VMTBaseYear;
            int cappedYear = Math.Min(vmtRefYear, year);
            var onRoadFE   = hfd.GetHistoricFleetData(vehStyle, cappedYear).OnRoadFE;

            if (fuelShare.IsEmpty) {
                fuelShare = hfd.GetHistoricFleetData(vehStyle, year).FuelShare;
            }

            return this.CalcCPMRate(year, age, vehStyle, onRoadFE, fuelShare, Beta3);
        }
        double CalcCPMRate(
            int year, int age, VS vehStyle, FuelValue onRoadFE, FuelValue fuelShare, double? reboundEffect = null)
        {
            int    vmtRefYr = this.Parameters.EconomicValues.Parameters[vehStyle].VMTBaseYear;
            var    hstFleet = this.Parameters.HistoricFleetData.GetHistoricFleetData(vehStyle, vmtRefYr - age);
            double refCPM   = Effects.CalcCPM(vmtRefYr, hstFleet.OnRoadFE, fuelShare, this.Settings);
            double vehCPM   = Effects.CalcCPM(year + age, onRoadFE, fuelShare, this.Settings);

            // compute veh-to-ref cpm rate, including the VMT elasticity due to changes in fuel CPM
            reboundEffect = reboundEffect ?? this.Parameters.EconomicValues.Parameters[vehStyle].ReboundEffect;
            return (vehCPM / refCPM - 1) * reboundEffect.Value;
        }

        //------ initialization methods for VMT model -----
        void InitNoTechAnalysis()
        {
            this._noTechAnalysis = VMTNoTechAnalysis.CreateInstance(this.ComplianceModel, this.Scenario);
        }

        void InitReferenceMpg()
        {
            this._refMpg = new double[this._yearCount];
            var avgMpg = this.CalcAverageMpg(); // average mpg for historic and analysis model years

            // iterate each analysis year to collapse by-MY mpg into by-CY mpg
            // the individual MY mpg are weighted by on-road-fleet during each CY
            for (int calendarYear = this._minYear; calendarYear <= this._maxYear; calendarYear++)
            {
                var fleetData = this._noTechAnalysis.GetScrappedFleetDataByCY(calendarYear);
                double feSum = 0;
                double fleetSum = 0;

                foreach (VS vehStyle in VehStyles)
                {
                    for (int age = 0; age < Globals.CY; ++age)
                    {
                        int modelYear = calendarYear - age;
                        double mpg = avgMpg[vehStyle][modelYear - this._minHstYear];
                        double onRoadFleet = fleetData[vehStyle][age];

                        feSum += onRoadFleet * mpg;
                        fleetSum += onRoadFleet;
                    }
                }

                this._refMpg[calendarYear - this._minYear] = fleetSum / feSum;
            }
        }
        VSValue<double[]> CalcAverageMpg()
        {
            var mpgByYear = new VSValue<double[]>();

            foreach (VS vehStyle in VehStyles)
            {
                mpgByYear[vehStyle] = new double[this._maxYear - this._minHstYear + 1];

                for (int year = this._minHstYear; year <= this._maxYear; year++)
                {
                    double mpg = this.CalcAverageMpg(vehStyle, year);
                    mpgByYear[vehStyle][year - this._minHstYear] = mpg;
                }
            }

            return mpgByYear;
        }
        double CalcAverageMpg(VS vehStyle, int modelYear)
        {
            var economicValues = this.Parameters.EconomicValues;
            var onRoadGap = economicValues.Parameters[vehStyle].OnRoadGap;
            double mpg;

            if (modelYear < this._minYear)
            {   // historic vehicle years
                var hstData = this.Parameters.HistoricFleetData.GetHistoricFleetData(vehStyle, modelYear);
                mpg = FuelValue.SafeDivide(hstData.FuelShare, hstData.OnRoadFE).Total;
            }
            else
            {   // analysis vehicle years
                double vehSales = 0;
                FuelValue vehOnRoadFE = FuelValue.Zero;
                FuelValue vehFuelShare = FuelValue.Zero;

                var data = this._noTechAnalysis.GetComplianceDataByMY(modelYear);
                foreach (var veh in data.Vehicles)
                {
                    var vehRevision = veh.GetRevision(modelYear);
                    var vehRevStyle = vehRevision.RegClass.IsHDPUV() ? VS.Generic2b3 : vehRevision.Style.ToGenericStyle();
                    if (vehRevStyle != vehStyle) {
                        continue;
                    }

                    var fuelShare = Effects.GetModifiedVehicleFS(this.Scenario, modelYear, vehRevision);
                    var vehSalesFS = vehRevision.Sales * fuelShare;

                    vehSales += vehRevision.Sales;
                    vehOnRoadFE += FuelValue.SafeDivide(vehSalesFS, vehRevision.FuelEconomy * (1 - onRoadGap));
                    vehFuelShare += vehSalesFS;
                }

                vehOnRoadFE = FuelValue.SafeDivide(vehFuelShare, vehOnRoadFE);
                vehFuelShare /= vehSales;
                mpg = FuelValue.SafeDivide(vehFuelShare, vehOnRoadFE).Total;
            }

            return mpg;
        }

        void InitReferenceVMT()
        {
            var debugLog = this.ComplianceModel.LogWriter.GetDebugLog(
                VmtModelLogging.GetRefVmtFileName(this.Scenario.Index),
                VmtModelLogging.RefVmtHeader, persistent: false);

            this._refVMT = new double[this._yearCount];

            var economicValues = this.Parameters.EconomicValues;
            var macroParams = economicValues.MacroParameters;
            var vmtParams = economicValues.VMTModelParameters;
            int maxVmtCY = vmtParams.MaxCY;

            double lnRdpiPerCapita_Lag3 = macroParams.GetLogRdpiPerCapita(maxVmtCY - 2);
            double lnRdpiPerCapita_Lag2 = macroParams.GetLogRdpiPerCapita(maxVmtCY - 1);
            double lnRdpiPerCapita_Lag1 = macroParams.GetLogRdpiPerCapita(maxVmtCY);
            double lnVmtPerCapita_Lag1 = Math.Log(vmtParams.GetHistoricVMT(maxVmtCY)
                                                  / macroParams.GetUSPopulation(maxVmtCY));

            // for overlapping years (i.e., historic VMT years and analysis years),
            // we want to copy over historic VMT
            for (int year = this._minYear; year <= maxVmtCY; year++)
            {
                this._refVMT[year - this._minYear] = vmtParams.GetHistoricVMT(year) * 1e6;
            }

            for (int year = maxVmtCY + 1; year <= this._maxYear; year++)
            {
                // long-run model
                double gasCPM_Lag1 = this.CalcGasCPM(year - 1);
                double prevZ = lnVmtPerCapita_Lag1 -
                               (Beta1 * lnRdpiPerCapita_Lag1 +
                                Beta2 * lnRdpiPerCapita_Lag1 * lnRdpiPerCapita_Lag1 +
                                Beta3 * Math.Log(gasCPM_Lag1));

                // error correction model
                double lnRdpiPerCapita = macroParams.GetLogRdpiPerCapita(year);
                double lnVmtPerCapitaDelta =
                    Alpha +
                    Gamma1 * prevZ +
                    Gamma2 * (lnRdpiPerCapita      - lnRdpiPerCapita_Lag1) +
                    Gamma3 * (lnRdpiPerCapita_Lag1 - lnRdpiPerCapita_Lag2) +
                    Gamma4 * (lnRdpiPerCapita_Lag2 - lnRdpiPerCapita_Lag3) +
                    Gamma5 * (lnRdpiPerCapita * lnRdpiPerCapita - lnRdpiPerCapita_Lag1 * lnRdpiPerCapita_Lag1) +
                    Gamma6 * Math.Log(macroParams.GetConsumerSentiment(year));

                // final VMT calculation
                double lnVmtPerCapita = lnVmtPerCapita_Lag1 + lnVmtPerCapitaDelta;
                this._refVMT[year - this._minYear] = Math.Exp(lnVmtPerCapita) * macroParams.GetUSPopulation(year) * 1e6;

                //>>>>> debug
                debugLog?.WriteValues(
                    year, this._refMpg[year - this._minYear],
                    gasCPM_Lag1, this.CalcGasCPM(year),
                    lnVmtPerCapita_Lag1, lnVmtPerCapita,
                    lnRdpiPerCapita_Lag3, lnRdpiPerCapita_Lag2, lnRdpiPerCapita_Lag1, lnRdpiPerCapita,
                    prevZ, lnVmtPerCapitaDelta,
                    this._refVMT[year - this._minYear],
                    VehStyles.Sum(vehStyle => this._actualVMT[vehStyle][year - this._minYear]));
                //<<<<< debug

                // shift lag values for the next year
                lnRdpiPerCapita_Lag3 = lnRdpiPerCapita_Lag2;
                lnRdpiPerCapita_Lag2 = lnRdpiPerCapita_Lag1;
                lnRdpiPerCapita_Lag1 = lnRdpiPerCapita;
                lnVmtPerCapita_Lag1 = lnVmtPerCapita;
            }

            debugLog?.Close();
        }
        double CalcGasCPM(int calendarYear)
        {
            double gasFuelPrice = this.Parameters.FuelPrices.GetFuelPrice(FuelType.Gasoline, calendarYear);
            var economicValues = this.Parameters.EconomicValues;
            var vmtParams = economicValues.VMTModelParameters;

            double mpg = (calendarYear <= vmtParams.MaxCY)
                ? vmtParams.GetHistoricMPG(calendarYear)
                : this._refMpg[calendarYear - this._minYear];
            double cpm = gasFuelPrice / economicValues.Deflator2012 / mpg;

            return cpm;
        }

        void InitActualFleetInfo()
        {
            this._actualFuelShare  = new VSValue<FuelValue[]>();
            this._actualFleet      = new VSValue<double[]>();
            this._actualVMT        = new VSValue<double[]>();
            this._actualFleetByAge = new VSValue<double[][]>();
            this._actualVMTByAge   = new VSValue<double[][]>();

            foreach (VS vehStyle in VehStyles)
            {
                this._actualFuelShare [vehStyle] = new FuelValue[this._yearCount];
                this._actualFleet     [vehStyle] = new double   [this._yearCount];
                this._actualVMT       [vehStyle] = new double   [this._yearCount];
                this._actualFleetByAge[vehStyle] = new double   [this._yearCount][];
                this._actualVMTByAge  [vehStyle] = new double   [this._yearCount][];

                for (int yrIndex = 0; yrIndex < this._yearCount; yrIndex++)
                {
                    this._actualFleetByAge[vehStyle][yrIndex] = new double[Globals.CY];
                    this._actualVMTByAge  [vehStyle][yrIndex] = new double[Globals.CY];
                }
            }

            // historic vehicle years
            for (int year = this._minHstYear; year < this._minYear; year++)
            {
                var fleetData = this.ScrappageModel.GetScrappedFleet(year);
                foreach (VS vehStyle in VehStyles)
                {
                    this.AggregateFleetAndVMT(year, vehStyle, 0, FuelValue.Zero, null, fleetData[vehStyle]);
                }
            }

            // analysis vehicle years
            for (int year = this._minYear; year <= this._maxYear; year++)
            {
                int yrIndex  = year - this._minYear;
                var data     = this.ComplianceModel.GetData(this.Scenario);
                var survRate = this.ScrappageModel.GetSurvivalRates(year);
                var sales    = new VSValue<double>();

                foreach (var veh in data.Vehicles)
                {
                    var vehRevision = veh.GetRevision(year);
                    var vehStyle = vehRevision.RegClass.IsHDPUV() ? VS.Generic2b3 : vehRevision.Style;
                    if (vehStyle.Is2b3()) {
                        continue;
                    }

                    var fuelShare = Effects.GetModifiedVehicleFS(this.Scenario, year, vehRevision);

                    this.AggregateFleetAndVMT(
                        year, vehStyle, vehRevision.Sales, fuelShare, survRate[vehStyle], null);

                    // for analysis vehicles, also aggregate average fuel-share by MY
                    sales[vehStyle] += vehRevision.Sales;
                    this._actualFuelShare[vehStyle][yrIndex] += vehRevision.Sales * fuelShare;
                }

                foreach (VS vehStyle in VehStyles)
                {
                    this._actualFuelShare[vehStyle][yrIndex] /= sales[vehStyle];
                }
            }
        }
        void AggregateFleetAndVMT(int year, VS vehStyle, double vehSales, FuelValue vehFS, double[] survRate,
            double[] fleetData)
        {
            double[] milesDrv = this.Parameters.MilesDriven[vehStyle];

            for (int age = 0; age < Globals.CY; age++)
            {
                int cyIndex = year + age - this._minYear;
                if (0 <= cyIndex && cyIndex < this._yearCount)
                {
                    // calculation of fleet and VMT should be constrained such that calendar year
                    // is between min and max analysis years
                    double cpmRate = this.CalcPartialCPMRate(year, age, vehStyle, vehFS);
                    double fleet   = (survRate == null) ? fleetData[age] : vehSales * survRate[age];
                    double vmt     = milesDrv[age] * (1 + cpmRate);

                    this._actualFleet     [vehStyle][cyIndex]      += fleet;
                    this._actualVMT       [vehStyle][cyIndex]      += fleet * vmt;
                    this._actualFleetByAge[vehStyle][cyIndex][age] += fleet;
                    this._actualVMTByAge  [vehStyle][cyIndex][age] += fleet * vmt;
                }
            }
        }

        void InitDeltaMiles()
        {
            var debugLog = this.ComplianceModel.LogWriter.GetDebugLog(
                VmtModelLogging.GetDeltaVmtFileName(this.Scenario.Index),
                VmtModelLogging.DeltaVmtHeader, persistent: false);

            //----- calc delta miles by veh-style, CY, and age -----
            this._vehDeltaMiles = new VSValue<double[][]>();
            foreach (var vehStyle in VehStyles)
            {
                this._vehDeltaMiles[vehStyle] = new double[this._yearCount][];
                for (int cyIndex = 0; cyIndex < this._yearCount; cyIndex++)
                {
                    this._vehDeltaMiles[vehStyle][cyIndex] = new double[Globals.CY];
                }
            }

            for (int cyIndex = 0; cyIndex < this._yearCount; cyIndex++)
            {
                double totalActualFleetCY = VehStyles.Sum(vehStyle => this._actualFleet[vehStyle][cyIndex]);
                double totalActualVMTCY = VehStyles.Sum(vehStyle => this._actualVMT[vehStyle][cyIndex]);
                double totalDeltaMilesCY = this._refVMT[cyIndex] - totalActualVMTCY;

                double r = (this._refVMT[cyIndex] - totalActualVMTCY) / totalActualVMTCY;

                foreach (VS vehStyle in VehStyles)
                {
                    double[] vehDeltaMiles = this._vehDeltaMiles[vehStyle][cyIndex];
                    double[] actualVMTByAge = this._actualVMTByAge[vehStyle][cyIndex];
                    double[] actualFleetByAge = this._actualFleetByAge[vehStyle][cyIndex];

                    for (int age = 0; age < Globals.CY; age++)
                    {
                        vehDeltaMiles[age] = r * actualVMTByAge[age] / actualFleetByAge[age];

                        //>>>>> debug
                        if (debugLog != null) {
                            WriteDeltaMilesLogLine(cyIndex, age, vehStyle, r, totalActualVMTCY, totalActualFleetCY);
                        }
                        //<<<<< debug
                    }
                }
            }

            debugLog?.Close();

            // debug helper method
            void WriteDeltaMilesLogLine(
                int cyIndex, int age, VS vehStyle,
                double r, double totalActualVMTCY, double totalActualFleetCY)
            {
                int yrIndex = cyIndex - age;
                FuelValue fuelShare = (yrIndex >= 0) ? this._actualFuelShare[vehStyle][yrIndex] : FuelValue.Zero;
                double cpmRate = this.CalcPartialCPMRate(this._minYear + yrIndex, age, vehStyle, fuelShare);

                debugLog.WriteValues(
                    this._minYear + cyIndex, vehStyle, age,
                    this._refVMT[cyIndex], totalActualVMTCY, this._refVMT[cyIndex] - totalActualVMTCY,
                    totalActualFleetCY,
                    this._actualVMT[vehStyle][cyIndex], this._actualFleet[vehStyle][cyIndex], r,
                    1 + cpmRate, this._actualVMTByAge[vehStyle][cyIndex][age],
                    this._actualFleetByAge[vehStyle][cyIndex][age], this._vehDeltaMiles[vehStyle][cyIndex][age],
                    this._actualFleetByAge[vehStyle][cyIndex][age] * this._vehDeltaMiles[vehStyle][cyIndex][age]);
            }
        }

        #endregion

        #region <<< Properties >>>

        /// <summary>Gets the compliance model associated with this <see cref="VMTModel"/> instance.</summary>
        protected ICompliance ComplianceModel { get; }
        /// <summary>Gets the scrappage model associated with this <see cref="VMTModel"/> instance.</summary>
        protected ScrappageModel ScrappageModel { get; }
        /// <summary>Gets the scenario associated with this <see cref="VMTModel"/> instance.</summary>
        protected Scenario Scenario { get; }

        /// <summary>Gets the modeling settings used by the <see cref="ComplianceModel"/>.</summary>
        protected ModelingSettings Settings => this.ComplianceModel.Settings;
        /// <summary>Gets the parameters used by the <see cref="ComplianceModel"/>.</summary>
        protected Parameters Parameters => this.Settings.Parameters;

        #endregion

        #region <<< Variables >>>

        //----- const/static variables -----
        // long-run model coefficients
        const double Beta1 =  3.5672140;
        const double Beta2 = -0.4346586;
        const double Beta3 = -0.0850293;

        // error correction model coefficients
        const double Alpha  =  0.3992312;
        const double Gamma1 = -0.3584071;
        const double Gamma2 =  3.6607830;
        const double Gamma3 = -0.3096410;
        const double Gamma4 = -0.2494935;
        const double Gamma5 = -0.5465123;
        const double Gamma6 =  0.0458378;

        /// <summary>Specifies the <see cref="VS"/>s supported by the VMT Model. This field is read-only.</summary>
        static readonly VS[] VehStyles = { VS.GenericCar,
                                           VS.GenericVanSUV,
                                           VS.GenericPickup };

        //----- runtime variables -----
        int _minHstYear;
        int _minYear;
        int _maxYear;
        int _yearCount;

        /// <summary>
        /// Defines a "no-tech" compliance model based on baseline scenario.
        /// This obtains a sales and scrappage response to baseline standards, but w/o tech application.
        /// </summary>
        VMTNoTechAnalysis _noTechAnalysis;

        /// <summary>
        /// Pre-calculated reference mpg, weighted by on-road-fleet, between "min" and "max" analysis years to use for
        /// calculating reference VMT.
        /// Indexed by: CY.
        /// </summary>
        double[] _refMpg;
        /// <summary>
        /// Total reference VMT based on the initial fleet. This value serves as the "total" non-rebound VMT that
        /// should stay constant across alternatives.
        /// Indexed by: CY.
        /// </summary>
        double[] _refVMT;

        /// <summary>
        /// Pre-calculated actual fleet for calendar years between "min" and "max" analysis years.
        /// Indexed by: veh-style, CY.
        /// </summary>
        VSValue<double[]> _actualFleet;
        /// <summary>
        /// Pre-calculated actual VMT for calendar years between "min" and "max" analysis years.
        /// Indexed by: veh-style, CY.
        /// </summary>
        VSValue<double[]> _actualVMT;

        /// <summary>
        /// Pre-calculated actual fleet for calendar years between "min" and "max" analysis years.
        /// Indexed by: veh-style, CY, age.
        /// </summary>
        VSValue<double[][]> _actualFleetByAge;
        /// <summary>
        /// Pre-calculated actual VMT for calendar years between "min" and "max" analysis years.
        /// Indexed by: veh-style, CY, age.
        /// </summary>
        VSValue<double[][]> _actualVMTByAge;

        /// <summary>
        /// Pre-calculated actual average fuel shares for model years between "min" and "max" analysis years.
        /// Indexed by: veh-style, MY, fuel-type.
        /// </summary>
        VSValue<FuelValue[]> _actualFuelShare;

        /// <summary>
        /// Calculated delta miles between reference VMT and expected actual VMT to apply to each vehicle.
        /// Indexed by: veh-style, CY, age.
        /// </summary>
        VSValue<double[][]> _vehDeltaMiles;

        #endregion

    }
}
