#region << Using Directives >>
using System;
using System.Collections.Generic;
using Volpe.Cafe.Data;
using Volpe.Cafe.Generic;
using Volpe.Cafe.Settings;
using Volpe.Cafe.Utils;
#endregion
namespace Volpe.Cafe.Model
{
    [Serializable]
    public class EffectsModel
    {
        #region 
        public EffectsModel(ModelingSettings settings, int minYear, int maxYear, int maxEffectsYear)
        {
            this._settings   = settings;
            this._opModes    = settings.OperatingModes;
            this._parameters = settings.Parameters;
            this._minYear    = minYear;
            this._maxYear    = maxYear;
            this._maxEffYear = maxEffectsYear;
            this._baselineAvgFE    = new List<VCValue<double>>();
            this._switchGrowthYear = new VCValue<int>(3000, 3000, 3000, 3000);
        }
        #endregion
        #region 
        public void CalculateHistoricEffects(Scenario scen, int year, VCValue<bool> representedVehClasses, out EffectsData ed)
        {
            VCValue<double> discRate = this._parameters.EconomicValues.DiscountRate;
            ed = new EffectsData(year, true, discRate);
            foreach (VehicleClass vehClass in VCValue<object>.Classes)
            {
                if (representedVehClasses[vehClass])
                {
                    this.CalculateHistoricEffects(scen, year, vehClass, ed);
                }
            }
            this.FinalizeAggregateEffects(scen, year, ed, true);
        }
        public void CalculateEffects(Scenario scen, int year, List<Vehicle> baseVehs, List<Vehicle> vehs, out EffectsData ed)
        {
            this._totalVehSales = new VCValue<double>();
            if (year > this._maxYear)
            {
                for (int i = 0, vehCount = vehs.Count; i < vehCount; i++)
                {
                    this._totalVehSales[vehs[i].VehicleClass] += vehs[i].Description.Sales[ModelYear.ToIndex(this._maxYear)];
                }
            }
            VCValue<double> discRate = this._parameters.EconomicValues.DiscountRate;
            ed = new EffectsData(year, true, discRate);
            for (int i = 0, vehCount = vehs.Count; i < vehCount; i++)
            {
                this.CalculateVehicleEffects(scen, year, baseVehs[i], vehs[i], ed);
            }
            this.FinalizeAggregateEffects(scen, year, ed, false);
        }
        void FinalizeAggregateEffects(Scenario scen, int year, EffectsData ed, bool isHistoric)
        {
            OwnerAndSocietalCosts uCosts = ed.UndiscountedCosts;
            OwnerAndSocietalCosts dCosts = ed.DiscountedCosts;
            uCosts.TotalConsumerCosts.AppendValues(uCosts.RetailFuelCosts, uCosts.DriveSurplus, uCosts.RefuelSurplus);
            uCosts.TotalSocialCosts  .AppendValues(uCosts.PreTaxFuelCosts, uCosts.DriveSurplus, uCosts.RefuelSurplus,
                uCosts.EconomicCosts, uCosts.CongestionCosts, uCosts.AccidentCosts, uCosts.NoiseCosts, uCosts.FatalityCosts,
                uCosts.EmissionsCostsCO, uCosts.EmissionsCostsVOC, uCosts.EmissionsCostsNOx, uCosts.EmissionsCostsSO2,
                uCosts.EmissionsCostsPM, uCosts.EmissionsCostsCO2, uCosts.EmissionsCostsCH4, uCosts.EmissionsCostsN2O);
            dCosts.TotalConsumerCosts.AppendValues(dCosts.RetailFuelCosts, dCosts.DriveSurplus, dCosts.RefuelSurplus);
            dCosts.TotalSocialCosts  .AppendValues(dCosts.PreTaxFuelCosts, dCosts.DriveSurplus, dCosts.RefuelSurplus,
                dCosts.EconomicCosts, dCosts.CongestionCosts, dCosts.AccidentCosts, dCosts.NoiseCosts, dCosts.FatalityCosts,
                dCosts.EmissionsCostsCO, dCosts.EmissionsCostsVOC, dCosts.EmissionsCostsNOx, dCosts.EmissionsCostsSO2,
                dCosts.EmissionsCostsPM, dCosts.EmissionsCostsCO2, dCosts.EmissionsCostsCH4, dCosts.EmissionsCostsN2O);
            VCValue<double> avgFE = new VCValue<double>();
            foreach (VehicleClass vehClass in VCValue<object>.Classes)
            {
                if (!isHistoric)
                {
                    FuelValue sales = ed.GeneralEffects.Sales.Value1[vehClass];
                    ed.GeneralEffects.CurbWeight.Value1[vehClass] /= sales;
                    ed.GeneralEffects.Footprint .Value1[vehClass] /= sales;
                    ed.GeneralEffects.WorkFactor.Value1[vehClass] /= sales;
                }
                FuelValue vmt = ed.GeneralEffects.VMT.Value1[vehClass];
                ed.GeneralEffects.RatedFuelEconomy .Value1[vehClass] = vmt / ed.EnergyEffects .RatedGallons.Value1[vehClass];
                ed.GeneralEffects.OnRoadFuelEconomy.Value1[vehClass] = vmt / ed.EnergyEffects .Gallons     .Value1[vehClass];
                ed.GeneralEffects.FuelShare        .Value1[vehClass] = vmt / ed.GeneralEffects.VMT         .Value1[vehClass].Total;
                avgFE[vehClass] = vmt.Total / ed.EnergyEffects.RatedGallons.Value1[vehClass].Total;
            }
            foreach (RegulatoryClass regClass in RCValue<object>.Classes)
            {
                if (!isHistoric)
                {
                    FuelValue sales = ed.GeneralEffects.Sales.Value2[regClass];
                    ed.GeneralEffects.CurbWeight.Value2[regClass] /= sales;
                    ed.GeneralEffects.Footprint .Value2[regClass] /= sales;
                    ed.GeneralEffects.WorkFactor.Value2[regClass] /= sales;
                }
                FuelValue vmt = ed.GeneralEffects.VMT.Value2[regClass];
                ed.GeneralEffects.RatedFuelEconomy .Value2[regClass] = vmt / ed.EnergyEffects .RatedGallons.Value2[regClass];
                ed.GeneralEffects.OnRoadFuelEconomy.Value2[regClass] = vmt / ed.EnergyEffects .Gallons     .Value2[regClass];
                ed.GeneralEffects.FuelShare        .Value2[regClass] = vmt / ed.GeneralEffects.VMT         .Value2[regClass].Total;
            }
            bool overcomply = this._opModes.Overcomply && (this._opModes.OvercomplyInBaseline || !scen.IsBaseline);
            if (year >= this._maxYear && this._opModes.FleetAnalysis && overcomply)
            {
                if (scen.IsBaseline)
                {   
                    this._baselineAvgFE.Add(avgFE);
                }
                else if (year < this._maxEffYear)
                {   
                    VCValue<double> baseFE = this._baselineAvgFE[year - this._maxYear + 1];
                    foreach (VehicleClass vehClass in VCValue<object>.Classes)
                    {   
                        double scenFEGrowth = this._parameters.FleetAnalysisValues.ScenarioCAFEGrowthRates[vehClass];
                        double projectedFE  = avgFE[vehClass] * (1 + scenFEGrowth);
                        if (projectedFE < baseFE[vehClass])
                        {
                            this._switchGrowthYear[vehClass] = Math.Min(this._switchGrowthYear[vehClass], year);
                        }
                    }
                }
            } 
        }
        void CalculateHistoricEffects(Scenario scen, int year, VehicleClass vehClass, EffectsData ed)
        {
            EconomicValues      economicValues      = this._parameters.EconomicValues;
            EmissionCosts       emissionCosts       = this._parameters.EmissionCosts;
            FuelPrices          fuelPrices          = this._parameters.FuelPrices;
            FuelProperties      fuelProperties      = this._parameters.FuelProperties;
            FuelEconomyData     fuelEconomyData     = this._parameters.FuelEconomyData;
            FleetAnalysisValues fleetAnalysisValues = this._parameters.FleetAnalysisValues;
            HistoricFleetData   historicFleetData   = this._parameters.HistoricFleetData;
            SafetyValues        safetyValues        = this._parameters.SafetyValues;
            TailpipeEmissions   tailpipeEmissions   = this._parameters.TailpipeEmissions;
            UpstreamEmissions   upstreamEmissions   = this._parameters.UpstreamEmissions;
            double              consumerBenefits    = this._opModes   .ConsumerBenefitsScale;
            Estimates           priceEstimates      = this._opModes   .FuelPriceEstimates;
            Estimates           co2Estimates        = this._opModes   .CO2Estimates;
            bool                overcomply          = this._opModes   .Overcomply &&
                                                     (this._opModes   .OvercomplyInBaseline || !scen.IsBaseline);
            FuelValue scenFE   = this.GetOnRoadFE(scen, this._settings, fuelEconomyData.GetFuelEconomy(vehClass, year), vehClass);
            FuelValue scenFS   = fuelEconomyData.GetFuelShare  (vehClass, year);
            FuelValue gap      = economicValues.OnRoadGap[vehClass];
            double[]  milesDrv = this._parameters.MilesDriven.GetVehicleAgeData(vehClass, VehicleStyle.None);
            for (int age = 0; age < Interaction.CY; age++)
            {
                double bCPM, cpm, bCPMRate, cpmRate;
                this.CalculateCPM(vehClass, scenFE, scenFS, scenFE, scenFS, year, age, out bCPM, out cpm, out bCPMRate, out cpmRate);
                double vmtGrowth = economicValues.VMTGrowthRate[vehClass].GetVMTGrowthFactor(priceEstimates, year, age);
                double drExp = (economicValues.DRBaseYear[vehClass] <= 0) ? age : Math.Max(0, year + age - economicValues.DRBaseYear[vehClass]);
                double socDiscRate = Math.Pow(1 + economicValues.DiscountRate[vehClass]             , -drExp);
                double co2DiscRate = Math.Pow(1 + emissionCosts.CO2.GetCO2DiscountRate(co2Estimates), -drExp);
                foreach (FuelType fuelType in FTValue<object>.Classes)
                {
                    if (scenFS[fuelType] == 0) { continue; }
                    bool isGEGFuel = (fuelType == FuelType.Electricity || fuelType == FuelType.Hydrogen || fuelType == FuelType.CNG);
                    double fleet = scenFS[fuelType] * historicFleetData.GetFleetData(vehClass, year, age);
                    double vmt   = fleet * milesDrv[age] * vmtGrowth / 1000 * (1 + cpmRate);
                    double rGal = vmt / (scenFE[fuelType] / (1 - gap[fuelType]));
                    double gal  = vmt /  scenFE[fuelType];
                    double btu, energyUse;
                    if (isGEGFuel)
                    {
                        btu       = gal * fuelProperties.EnergyDensity.Gasoline;
                        energyUse = btu / fuelProperties.EnergyDensity[fuelType];
                    }
                    else
                    {
                        btu       = gal * fuelProperties.EnergyDensity[fuelType];
                        energyUse = gal;
                    }
                    double[] upstream = new double[Pollutants.Length], tailpipe = new double[Pollutants.Length];
                    for (int i = 0; i < Pollutants.Length; i++)
                    {
                        Pollutant pollutant = Pollutants[i];
                        bool isSO2 = (pollutant == Pollutant.SO2) && !tailpipeEmissions.HasEmissions(pollutant);
                        bool isCO2 = (pollutant == Pollutant.CO2);
                        upstream[i] = upstreamEmissions.GetUpstreamEmissions(pollutant, fuelType) * btu / 1e9;
                        tailpipe[i] =
                            (isCO2) ? (fuelProperties.CO2Emissions[fuelType] * energyUse) / 1e3 :
                            (isSO2) ? (fuelProperties.SO2Emissions[fuelType] * energyUse) / 1e3 :
                                      (tailpipeEmissions.GetTailpipeEmissions(pollutant, fuelType, vehClass, year, age) * vmt) / 1e3;
                    }
                    double fatalities = 0;  
                    double retailFuelCosts  = gal * fuelPrices.GetFuelPrice(priceEstimates, fuelType, year + age, fuelProperties) * consumerBenefits;
                    double fuelTaxCosts     = gal * fuelPrices.GetFuelTax  (                fuelType, year + age, fuelProperties) * consumerBenefits;
                    double preTaxFuelCosts  = retailFuelCosts - fuelTaxCosts;
                    double driveSurplus     = 0;    
                    double refuelSurplus    = 0;
                    double economicCosts    = gal *
                                              (fuelProperties.FuelSavingsLeadingToLowerImports   [fuelType] +
                                               fuelProperties.FuelSavingsLeadingToReducedRefining[fuelType] *
                                               fuelProperties.ReducedRefiningFromImportedCrude   [fuelType]) *
                                              economicValues.EconomicCosts.GetTotalEconomicCosts(year + age);
                    double congestionCosts  = vmt * economicValues.ExternalCosts[vehClass].Congestion;
                    double accidentCosts    = vmt * economicValues.ExternalCosts[vehClass].Accident  ;
                    double noiseCosts       = vmt * economicValues.ExternalCosts[vehClass].Noise     ;
                    double fatalityCosts    = fatalities * safetyValues.FatalityCosts / 1000; 
                    double emCostsCO        = (upstream[0] + tailpipe[0]) * emissionCosts.CO  / 1000; 
                    double emCostsVOC       = (upstream[1] + tailpipe[1]) * emissionCosts.VOC / 1000;
                    double emCostsNOx       = (upstream[2] + tailpipe[2]) * emissionCosts.NOX / 1000;
                    double emCostsSO2       = (upstream[3] + tailpipe[3]) * emissionCosts.SO2 / 1000;
                    double emCostsPM        = (upstream[4] + tailpipe[4]) * emissionCosts.PM  / 1000;
                    double rawCO2Cost       = emissionCosts.CO2.GetCO2Costs(co2Estimates, year + age);
                    double emCostsCO2       = (upstream[5] + tailpipe[5]) * rawCO2Cost / 1000;
                    double emCostsCH4       = (upstream[6] + tailpipe[6]) * rawCO2Cost / 1000 * emissionCosts.CH4Scalar;
                    double emCostsN2O       = (upstream[7] + tailpipe[7]) * rawCO2Cost / 1000 * emissionCosts.N2OScalar;
                    double discPreTaxFuelCosts  = socDiscRate * preTaxFuelCosts;
                    double discFuelTaxCosts     = socDiscRate * fuelTaxCosts;
                    double discRetailFuelCosts  = socDiscRate * retailFuelCosts;
                    double discDriveSurplus     = socDiscRate * driveSurplus;
                    double discRefuelSurplus    = socDiscRate * refuelSurplus;
                    double discEconomicCosts    = socDiscRate * economicCosts;
                    double discCongestionCosts  = socDiscRate * congestionCosts;
                    double discAccidentCosts    = socDiscRate * accidentCosts;
                    double discNoiseCosts       = socDiscRate * noiseCosts;
                    double discFatalityCosts    = socDiscRate * fatalityCosts;
                    double discEmCostsCO        = socDiscRate * emCostsCO;
                    double discEmCostsVOC       = socDiscRate * emCostsVOC;
                    double discEmCostsNOx       = socDiscRate * emCostsNOx;
                    double discEmCostsSO2       = socDiscRate * emCostsSO2;
                    double discEmCostsPM        = socDiscRate * emCostsPM;
                    double discEmCostsCO2       = co2DiscRate * emCostsCO2;
                    double discEmCostsCH4       = socDiscRate * emCostsCH4;
                    double discEmCostsN2O       = socDiscRate * emCostsN2O;
                    ed.GeneralEffects.Fleet       .AppendValue(fleet     , vehClass, fuelType, age);
                    ed.GeneralEffects.VMT         .AppendValue(vmt       , vehClass, fuelType, age);
                    ed.EnergyEffects .BTU         .AppendValue(btu       , vehClass, fuelType, age);
                    ed.EnergyEffects .Gallons     .AppendValue(gal       , vehClass, fuelType, age);
                    ed.EnergyEffects .RatedGallons.AppendValue(rGal      , vehClass, fuelType, age);
                    ed.EnergyEffects .NativeUnits .AppendValue(energyUse , vehClass, fuelType, age);
                    ed.SafetyEffects .Fatalities  .AppendValue(fatalities, vehClass, fuelType, age);
                    for (int i = 0; i < Pollutants.Length; i++)
                    {
                        ed.EmissionEffects.GetUpstreamEmissions(Pollutants[i]).AppendValue(upstream[i], vehClass, fuelType, age);
                        ed.EmissionEffects.GetTailpipeEmissions(Pollutants[i]).AppendValue(tailpipe[i], vehClass, fuelType, age);
                    }
                    ed.UndiscountedCosts.PreTaxFuelCosts  .AppendValue(preTaxFuelCosts    , vehClass, fuelType, age);
                    ed.UndiscountedCosts.FuelTaxCosts     .AppendValue(fuelTaxCosts       , vehClass, fuelType, age);
                    ed.UndiscountedCosts.RetailFuelCosts  .AppendValue(retailFuelCosts    , vehClass, fuelType, age);
                    ed.UndiscountedCosts.DriveSurplus     .AppendValue(driveSurplus       , vehClass, fuelType, age);
                    ed.UndiscountedCosts.RefuelSurplus    .AppendValue(refuelSurplus      , vehClass, fuelType, age);
                    ed.UndiscountedCosts.EconomicCosts    .AppendValue(economicCosts      , vehClass, fuelType, age);
                    ed.UndiscountedCosts.CongestionCosts  .AppendValue(congestionCosts    , vehClass, fuelType, age);
                    ed.UndiscountedCosts.AccidentCosts    .AppendValue(accidentCosts      , vehClass, fuelType, age);
                    ed.UndiscountedCosts.NoiseCosts       .AppendValue(noiseCosts         , vehClass, fuelType, age);
                    ed.UndiscountedCosts.FatalityCosts    .AppendValue(fatalityCosts      , vehClass, fuelType, age);
                    ed.UndiscountedCosts.EmissionsCostsCO .AppendValue(emCostsCO          , vehClass, fuelType, age);
                    ed.UndiscountedCosts.EmissionsCostsVOC.AppendValue(emCostsVOC         , vehClass, fuelType, age);
                    ed.UndiscountedCosts.EmissionsCostsNOx.AppendValue(emCostsNOx         , vehClass, fuelType, age);
                    ed.UndiscountedCosts.EmissionsCostsSO2.AppendValue(emCostsSO2         , vehClass, fuelType, age);
                    ed.UndiscountedCosts.EmissionsCostsPM .AppendValue(emCostsPM          , vehClass, fuelType, age);
                    ed.UndiscountedCosts.EmissionsCostsCO2.AppendValue(emCostsCO2         , vehClass, fuelType, age);
                    ed.UndiscountedCosts.EmissionsCostsCH4.AppendValue(emCostsCH4         , vehClass, fuelType, age);
                    ed.UndiscountedCosts.EmissionsCostsN2O.AppendValue(emCostsN2O         , vehClass, fuelType, age);
                    ed.DiscountedCosts  .PreTaxFuelCosts  .AppendValue(discPreTaxFuelCosts, vehClass, fuelType, age);
                    ed.DiscountedCosts  .FuelTaxCosts     .AppendValue(discFuelTaxCosts   , vehClass, fuelType, age);
                    ed.DiscountedCosts  .RetailFuelCosts  .AppendValue(discRetailFuelCosts, vehClass, fuelType, age);
                    ed.DiscountedCosts  .DriveSurplus     .AppendValue(discDriveSurplus   , vehClass, fuelType, age);
                    ed.DiscountedCosts  .RefuelSurplus    .AppendValue(discRefuelSurplus  , vehClass, fuelType, age);
                    ed.DiscountedCosts  .EconomicCosts    .AppendValue(discEconomicCosts  , vehClass, fuelType, age);
                    ed.DiscountedCosts  .CongestionCosts  .AppendValue(discCongestionCosts, vehClass, fuelType, age);
                    ed.DiscountedCosts  .AccidentCosts    .AppendValue(discAccidentCosts  , vehClass, fuelType, age);
                    ed.DiscountedCosts  .NoiseCosts       .AppendValue(discNoiseCosts     , vehClass, fuelType, age);
                    ed.DiscountedCosts  .FatalityCosts    .AppendValue(discFatalityCosts  , vehClass, fuelType, age);
                    ed.DiscountedCosts  .EmissionsCostsCO .AppendValue(discEmCostsCO      , vehClass, fuelType, age);
                    ed.DiscountedCosts  .EmissionsCostsVOC.AppendValue(discEmCostsVOC     , vehClass, fuelType, age);
                    ed.DiscountedCosts  .EmissionsCostsNOx.AppendValue(discEmCostsNOx     , vehClass, fuelType, age);
                    ed.DiscountedCosts  .EmissionsCostsSO2.AppendValue(discEmCostsSO2     , vehClass, fuelType, age);
                    ed.DiscountedCosts  .EmissionsCostsPM .AppendValue(discEmCostsPM      , vehClass, fuelType, age);
                    ed.DiscountedCosts  .EmissionsCostsCO2.AppendValue(discEmCostsCO2     , vehClass, fuelType, age);
                    ed.DiscountedCosts  .EmissionsCostsCH4.AppendValue(discEmCostsCH4     , vehClass, fuelType, age);
                    ed.DiscountedCosts  .EmissionsCostsN2O.AppendValue(discEmCostsN2O     , vehClass, fuelType, age);
                } 
            } 
        }
        void CalculateVehicleEffects(Scenario scen, int year, Vehicle baseVeh, Vehicle veh, EffectsData ed)
        {
            ModelYear modelYear = new ModelYear(Math.Min(year, this._maxYear));
            double    sales     = veh.Description.Sales[modelYear.Index];
            if (sales == 0) { return; }
            EconomicValues      economicValues      = this._parameters.EconomicValues;
            EmissionCosts       emissionCosts       = this._parameters.EmissionCosts;
            FuelPrices          fuelPrices          = this._parameters.FuelPrices;
            FuelProperties      fuelProperties      = this._parameters.FuelProperties;
            FleetAnalysisValues fleetAnalysisValues = this._parameters.FleetAnalysisValues;
            SafetyValues        safetyValues        = this._parameters.SafetyValues;
            TailpipeEmissions   tailpipeEmissions   = this._parameters.TailpipeEmissions;
            UpstreamEmissions   upstreamEmissions   = this._parameters.UpstreamEmissions;
            double              consumerBenefits    = this._opModes   .ConsumerBenefitsScale;
            Estimates           priceEstimates      = this._opModes   .FuelPriceEstimates;
            Estimates           co2Estimates        = this._opModes   .CO2Estimates;
            bool                overcomply          = this._opModes   .Overcomply &&
                                                     (this._opModes   .OvercomplyInBaseline || !scen.IsBaseline);
            RegulatoryClass regClass     = veh.RegClass;
            VehicleClass    vehClass     = veh.VehicleClass;
            VehicleStyle    vehStyle     = veh.VehicleStyle;
            SafetyClass     sftClass     = veh.SafetyClass;
            double          baseWeight   = veh.Description.BaseWeight;
            double          curbWeight   = veh.Description.CurbWeight;
            double          footprint    = veh.Description.Footprint;
            double          workFactor   = Standards.GetWorkFactor(veh, scen, modelYear);
            FuelValue       baseFE       = this.GetVehicleFE(baseVeh, scen, modelYear);
            FuelValue       baseFS       = baseVeh.Description.FuelShare;
            FuelValue       vehFE        = this.GetVehicleFE(veh, scen, modelYear);
            FuelValue       vehFS        = veh.Description.FuelShare;
            double          fuelCapacity = veh.Description.FuelCapacity;
            FuelValue       gap          = economicValues.OnRoadGap[vehClass];
            double[]        survRate     = this._parameters.SurvivalRates.GetVehicleAgeData(vehClass, vehStyle);
            double[]        milesDrv     = this._parameters.MilesDriven  .GetVehicleAgeData(vehClass, vehStyle);
            double          baseFEGrowth = fleetAnalysisValues.BaselineCAFEGrowthRates[vehClass];
            double          scenFEGrowth = fleetAnalysisValues.ScenarioCAFEGrowthRates[vehClass];
            if (year > this._maxYear)
            {
                sales *= fleetAnalysisValues.GetSalesForecast(vehClass, year) / this._totalVehSales[vehClass];
                if (overcomply)
                {
                    baseFE *= Math.Pow(1 + baseFEGrowth, year - this._maxYear);
                    if (scen.IsBaseline) { vehFE = baseFE; }
                    else
                    {
                        int switchYear = this._switchGrowthYear[vehClass];
                        vehFE *=
                            Math.Pow(1 + scenFEGrowth, Math.Min(switchYear, year) - this._maxYear) *
                            Math.Pow(1 + baseFEGrowth, Math.Max(switchYear, year) - switchYear);
                    }
                }
            }
            for (int age = 0; age < Interaction.CY; age++)
            {
                double bCPM, cpm, bCPMRate, cpmRate;
                this.CalculateCPM(vehClass, baseFE, baseFS, vehFE, vehFS, year, age, out bCPM, out cpm, out bCPMRate, out cpmRate);
                double vmtGrowth = economicValues.VMTGrowthRate[vehClass].GetVMTGrowthFactor(priceEstimates, year, age);
                double drExp = (economicValues.DRBaseYear[vehClass] <= 0) ? age : Math.Max(0, year + age - economicValues.DRBaseYear[vehClass]);
                double socDiscRate = Math.Pow(1 + economicValues.DiscountRate[vehClass]             , -drExp);
                double co2DiscRate = Math.Pow(1 + emissionCosts.CO2.GetCO2DiscountRate(co2Estimates), -drExp);
                foreach (FuelType fuelType in FTValue<object>.Classes)
                {
                    bool isGEGFuel = (fuelType == FuelType.Electricity || fuelType == FuelType.Hydrogen || fuelType == FuelType.CNG);
                    double bVMT = 0, bGal = 0;
                    if (baseFS[fuelType] != 0)
                    {   
                        bVMT = baseFS[fuelType] * sales * survRate[age] * milesDrv[age] * vmtGrowth / 1000 * (1 + bCPMRate);
                        bGal = bVMT / baseFE[fuelType];
                    }
                    double fleet = 0, vmt = 0, rGal = 0, gal = 0, btu = 0, energyUse = 0, fatalities = 0;
                    double[] upstream = new double[Pollutants.Length], tailpipe = new double[Pollutants.Length];
                    if (vehFS[fuelType] != 0)
                    {
                        fleet = vehFS[fuelType] * sales * survRate[age];
                        vmt   = vehFS[fuelType] * sales * survRate[age] * milesDrv[age] * vmtGrowth / 1000 * (1 + cpmRate);
                        rGal = vmt / (vehFE[fuelType] / (1 - gap[fuelType]));
                        gal  = vmt /  vehFE[fuelType];
                        if (isGEGFuel)
                        {
                            btu       = gal * fuelProperties.EnergyDensity.Gasoline;
                            energyUse = btu / fuelProperties.EnergyDensity[fuelType];
                        }
                        else
                        {
                            btu       = gal * fuelProperties.EnergyDensity[fuelType];
                            energyUse = gal;
                        }
                        for (int i = 0; i < Pollutants.Length; i++)
                        {
                            Pollutant pollutant = Pollutants[i];
                            bool isSO2 = (pollutant == Pollutant.SO2) && !tailpipeEmissions.HasEmissions(pollutant);
                            bool isCO2 = (pollutant == Pollutant.CO2);
                            upstream[i] = upstreamEmissions.GetUpstreamEmissions(pollutant, fuelType) * btu / 1e9;
                            tailpipe[i] =
                                (isCO2) ? (fuelProperties.CO2Emissions[fuelType] * energyUse) / 1e3 :
                                (isSO2) ? (fuelProperties.SO2Emissions[fuelType] * energyUse) / 1e3 :
                                          (tailpipeEmissions.GetTailpipeEmissions(pollutant, fuelType, vehClass, year, age) * vmt) / 1e3;
                        }
                        fatalities = vmt / 1e6 * safetyValues.GetBase(sftClass, curbWeight) * safetyValues.GetFMVSS(sftClass, curbWeight);
                        if (baseWeight != curbWeight)
                        {
                            double wrAbove = Math.Max(0, baseWeight - Math.Max(curbWeight, safetyValues.Threshold[sftClass]));
                            double wrBelow = Math.Max(0, Math.Min(baseWeight, safetyValues.Threshold[sftClass]) - curbWeight);
                            fatalities += vmt / 1e6 / 100 * (wrAbove * safetyValues.LargeMultiplier[sftClass] +
                                                             wrBelow * safetyValues.SmallMultiplier[sftClass]);
                        }
                        ed.GeneralEffects.Fleet       .AppendValue(fleet     , vehClass, regClass, fuelType, age);
                        ed.GeneralEffects.VMT         .AppendValue(vmt       , vehClass, regClass, fuelType, age);
                        ed.EnergyEffects .BTU         .AppendValue(btu       , vehClass, regClass, fuelType, age);
                        ed.EnergyEffects .Gallons     .AppendValue(gal       , vehClass, regClass, fuelType, age);
                        ed.EnergyEffects .RatedGallons.AppendValue(rGal      , vehClass, regClass, fuelType, age);
                        ed.EnergyEffects .NativeUnits .AppendValue(energyUse , vehClass, regClass, fuelType, age);
                        ed.SafetyEffects .Fatalities  .AppendValue(fatalities, vehClass, regClass, fuelType, age);
                        for (int i = 0; i < Pollutants.Length; i++)
                        {
                            ed.EmissionEffects.GetUpstreamEmissions(Pollutants[i]).AppendValue(upstream[i], vehClass, regClass, fuelType, age);
                            ed.EmissionEffects.GetTailpipeEmissions(Pollutants[i]).AppendValue(tailpipe[i], vehClass, regClass, fuelType, age);
                        }
                    } 
                    if (vehFS[fuelType] != 0)
                    {
                        double retailFuelCosts  = gal * fuelPrices.GetFuelPrice(priceEstimates, fuelType, year + age, fuelProperties) * consumerBenefits;
                        double fuelTaxCosts     = gal * fuelPrices.GetFuelTax  (                fuelType, year + age, fuelProperties) * consumerBenefits;
                        double preTaxFuelCosts  = retailFuelCosts - fuelTaxCosts;
                        double tankRefueled     = fuelCapacity * economicValues.RefuelTankVolume[vehClass];
                        double refuelSurplus    = (isGEGFuel) ? 0 :
                                                  (gal / tankRefueled) *
                                                  (economicValues.RefuelTime[vehClass][fuelType] / 60 + tankRefueled / 7.5 / 60) *
                                                  (economicValues.VehicleTravelTimeValue[vehClass]);
                        double economicCosts    = gal *
                                                  (fuelProperties.FuelSavingsLeadingToLowerImports   [fuelType] +
                                                   fuelProperties.FuelSavingsLeadingToReducedRefining[fuelType] *
                                                   fuelProperties.ReducedRefiningFromImportedCrude   [fuelType]) *
                                                  economicValues.EconomicCosts.GetTotalEconomicCosts(year + age);
                        double congestionCosts  = vmt * economicValues.ExternalCosts[vehClass].Congestion;
                        double accidentCosts    = vmt * economicValues.ExternalCosts[vehClass].Accident  ;
                        double noiseCosts       = vmt * economicValues.ExternalCosts[vehClass].Noise     ;
                        double fatalityCosts    = fatalities *
                                                  safetyValues.FatalityCosts / 1000 *                     
                                                  Math.Pow(1 + safetyValues.GrowthRate, year + age - safetyValues.BaseYear);
                        double emCostsCO        = (upstream[0] + tailpipe[0]) * emissionCosts.CO  / 1000; 
                        double emCostsVOC       = (upstream[1] + tailpipe[1]) * emissionCosts.VOC / 1000;
                        double emCostsNOx       = (upstream[2] + tailpipe[2]) * emissionCosts.NOX / 1000;
                        double emCostsSO2       = (upstream[3] + tailpipe[3]) * emissionCosts.SO2 / 1000;
                        double emCostsPM        = (upstream[4] + tailpipe[4]) * emissionCosts.PM  / 1000;
                        double rawCO2Cost       = emissionCosts.CO2.GetCO2Costs(co2Estimates, year + age);
                        double emCostsCO2       = (upstream[5] + tailpipe[5]) * rawCO2Cost / 1000;
                        double emCostsCH4       = (upstream[6] + tailpipe[6]) * rawCO2Cost / 1000 * emissionCosts.CH4Scalar;
                        double emCostsN2O       = (upstream[7] + tailpipe[7]) * rawCO2Cost / 1000 * emissionCosts.N2OScalar;
                        double discPreTaxFuelCosts  = socDiscRate * preTaxFuelCosts;
                        double discFuelTaxCosts     = socDiscRate * fuelTaxCosts;
                        double discRetailFuelCosts  = socDiscRate * retailFuelCosts;
                        double discRefuelSurplus    = socDiscRate * refuelSurplus;
                        double discEconomicCosts    = socDiscRate * economicCosts;
                        double discCongestionCosts  = socDiscRate * congestionCosts;
                        double discAccidentCosts    = socDiscRate * accidentCosts;
                        double discNoiseCosts       = socDiscRate * noiseCosts;
                        double discFatalityCosts    = socDiscRate * fatalityCosts;
                        double discEmCostsCO        = socDiscRate * emCostsCO;
                        double discEmCostsVOC       = socDiscRate * emCostsVOC;
                        double discEmCostsNOx       = socDiscRate * emCostsNOx;
                        double discEmCostsSO2       = socDiscRate * emCostsSO2;
                        double discEmCostsPM        = socDiscRate * emCostsPM;
                        double discEmCostsCO2       = co2DiscRate * emCostsCO2;
                        double discEmCostsCH4       = socDiscRate * emCostsCH4;
                        double discEmCostsN2O       = socDiscRate * emCostsN2O;
                        ed.UndiscountedCosts.PreTaxFuelCosts  .AppendValue(preTaxFuelCosts    , vehClass, regClass, fuelType, age);
                        ed.UndiscountedCosts.FuelTaxCosts     .AppendValue(fuelTaxCosts       , vehClass, regClass, fuelType, age);
                        ed.UndiscountedCosts.RetailFuelCosts  .AppendValue(retailFuelCosts    , vehClass, regClass, fuelType, age);
                        ed.UndiscountedCosts.RefuelSurplus    .AppendValue(refuelSurplus      , vehClass, regClass, fuelType, age);
                        ed.UndiscountedCosts.EconomicCosts    .AppendValue(economicCosts      , vehClass, regClass, fuelType, age);
                        ed.UndiscountedCosts.CongestionCosts  .AppendValue(congestionCosts    , vehClass, regClass, fuelType, age);
                        ed.UndiscountedCosts.AccidentCosts    .AppendValue(accidentCosts      , vehClass, regClass, fuelType, age);
                        ed.UndiscountedCosts.NoiseCosts       .AppendValue(noiseCosts         , vehClass, regClass, fuelType, age);
                        ed.UndiscountedCosts.FatalityCosts    .AppendValue(fatalityCosts      , vehClass, regClass, fuelType, age);
                        ed.UndiscountedCosts.EmissionsCostsCO .AppendValue(emCostsCO          , vehClass, regClass, fuelType, age);
                        ed.UndiscountedCosts.EmissionsCostsVOC.AppendValue(emCostsVOC         , vehClass, regClass, fuelType, age);
                        ed.UndiscountedCosts.EmissionsCostsNOx.AppendValue(emCostsNOx         , vehClass, regClass, fuelType, age);
                        ed.UndiscountedCosts.EmissionsCostsSO2.AppendValue(emCostsSO2         , vehClass, regClass, fuelType, age);
                        ed.UndiscountedCosts.EmissionsCostsPM .AppendValue(emCostsPM          , vehClass, regClass, fuelType, age);
                        ed.UndiscountedCosts.EmissionsCostsCO2.AppendValue(emCostsCO2         , vehClass, regClass, fuelType, age);
                        ed.UndiscountedCosts.EmissionsCostsCH4.AppendValue(emCostsCH4         , vehClass, regClass, fuelType, age);
                        ed.UndiscountedCosts.EmissionsCostsN2O.AppendValue(emCostsN2O         , vehClass, regClass, fuelType, age);
                        ed.DiscountedCosts  .PreTaxFuelCosts  .AppendValue(discPreTaxFuelCosts, vehClass, regClass, fuelType, age);
                        ed.DiscountedCosts  .FuelTaxCosts     .AppendValue(discFuelTaxCosts   , vehClass, regClass, fuelType, age);
                        ed.DiscountedCosts  .RetailFuelCosts  .AppendValue(discRetailFuelCosts, vehClass, regClass, fuelType, age);
                        ed.DiscountedCosts  .RefuelSurplus    .AppendValue(discRefuelSurplus  , vehClass, regClass, fuelType, age);
                        ed.DiscountedCosts  .EconomicCosts    .AppendValue(discEconomicCosts  , vehClass, regClass, fuelType, age);
                        ed.DiscountedCosts  .CongestionCosts  .AppendValue(discCongestionCosts, vehClass, regClass, fuelType, age);
                        ed.DiscountedCosts  .AccidentCosts    .AppendValue(discAccidentCosts  , vehClass, regClass, fuelType, age);
                        ed.DiscountedCosts  .NoiseCosts       .AppendValue(discNoiseCosts     , vehClass, regClass, fuelType, age);
                        ed.DiscountedCosts  .FatalityCosts    .AppendValue(discFatalityCosts  , vehClass, regClass, fuelType, age);
                        ed.DiscountedCosts  .EmissionsCostsCO .AppendValue(discEmCostsCO      , vehClass, regClass, fuelType, age);
                        ed.DiscountedCosts  .EmissionsCostsVOC.AppendValue(discEmCostsVOC     , vehClass, regClass, fuelType, age);
                        ed.DiscountedCosts  .EmissionsCostsNOx.AppendValue(discEmCostsNOx     , vehClass, regClass, fuelType, age);
                        ed.DiscountedCosts  .EmissionsCostsSO2.AppendValue(discEmCostsSO2     , vehClass, regClass, fuelType, age);
                        ed.DiscountedCosts  .EmissionsCostsPM .AppendValue(discEmCostsPM      , vehClass, regClass, fuelType, age);
                        ed.DiscountedCosts  .EmissionsCostsCO2.AppendValue(discEmCostsCO2     , vehClass, regClass, fuelType, age);
                        ed.DiscountedCosts  .EmissionsCostsCH4.AppendValue(discEmCostsCH4     , vehClass, regClass, fuelType, age);
                        ed.DiscountedCosts  .EmissionsCostsN2O.AppendValue(discEmCostsN2O     , vehClass, regClass, fuelType, age);
                    } 
                    if (baseFS[fuelType] != 0 || vehFS[fuelType] != 0)
                    {   
                        double driveSurplus     = (bVMT - vmt) * (bCPM + cpm) / 2;
                        double discDriveSurplus = socDiscRate * driveSurplus;
                        ed.UndiscountedCosts.DriveSurplus.AppendValue(driveSurplus    , vehClass, regClass, fuelType, age);
                        ed.DiscountedCosts  .DriveSurplus.AppendValue(discDriveSurplus, vehClass, regClass, fuelType, age);
                    }
                } 
            } 
            foreach (FuelType fuelType in FTValue<object>.Classes)
            {
                if (vehFS[fuelType] != 0)
                {
                    double sl = vehFS[fuelType] * sales;
                    double cw = sl * curbWeight;
                    double fp = sl * footprint;
                    double wf = sl * workFactor;
                    ed.GeneralEffects.Sales     .AppendValue(sl, vehClass, regClass, fuelType, 0);
                    ed.GeneralEffects.CurbWeight.AppendValue(cw, vehClass, regClass, fuelType, 0);
                    ed.GeneralEffects.Footprint .AppendValue(fp, vehClass, regClass, fuelType, 0);
                    ed.GeneralEffects.WorkFactor.AppendValue(wf, vehClass, regClass, fuelType, 0);
                }
            }
        }
        void CalculateCPM(VehicleClass vehClass, FuelValue bFE, FuelValue bFS, FuelValue sFE, FuelValue sFS,
            int year, int age, out double bCPM, out double sCPM, out double bCPMRate, out double sCPMRate)
        {
            EconomicValues  economicValues  = this._parameters.EconomicValues;
            FuelEconomyData fuelEconomyData = this._parameters.FuelEconomyData;
            FuelPrices      fuelPrices      = this._parameters.FuelPrices;
            FuelProperties  fuelProperties  = this._parameters.FuelProperties;
            Estimates       priceEstimates  = this._opModes   .FuelPriceEstimates;
            double hCPM = 0;
            bCPM = 0;
            sCPM = 0;
            FuelValue gap = economicValues.OnRoadGap[vehClass];
            foreach (FuelType fuel in FTValue<object>.Classes)
            {
                int    bYear = economicValues.VMTGrowthRate[vehClass].BaseYear;
                int    hYear = bYear - age;
                double hFS   = fuelEconomyData.GetFuelShare(fuel, vehClass, hYear);
                if (hFS > 0)
                {
                    double hFE = fuelEconomyData.GetFuelEconomy(fuel, vehClass, hYear);
                    double hFP = fuelPrices.GetFuelPrice(priceEstimates, fuel, bYear, fuelProperties);
                    hCPM += (hFP / (hFE * (1 - gap[fuel])) * hFS);
                }
                double fp = fuelPrices.GetFuelPrice(priceEstimates, fuel, year + age, fuelProperties);
                if (bFS[fuel] > 0) { bCPM += (fp / bFE[fuel] * bFS[fuel]); }
                if (sFS[fuel] > 0) { sCPM += (fp / sFE[fuel] * sFS[fuel]); }
            } 
            double reboundEffect = economicValues.ReboundEffect[vehClass];
            bCPMRate = (bCPM / hCPM - 1) * reboundEffect;
            sCPMRate = (sCPM / hCPM - 1) * reboundEffect;
        }
        FuelValue GetVehicleFE(Vehicle veh, Scenario scen, ModelYear year)
        {
            FuelValue ratedFE = Standards.GetAdjustedFuelEconomy(veh, scen, year, this._settings, false, true, false);
            return this.GetOnRoadFE(scen, this._settings, ratedFE, veh.VehicleClass);
        }
        FuelValue GetOnRoadFE(Scenario scen, ModelingSettings settings, FuelValue ratedFE, VehicleClass vehClass)
        {
            return ratedFE * (FuelValue.One - settings.Parameters.EconomicValues.OnRoadGap[vehClass]);
        }
        #endregion
        #region 
        static readonly Pollutant[] Pollutants = new Pollutant[] {
            Pollutant.CO,
            Pollutant.VOC,
            Pollutant.NOx,
            Pollutant.SO2,
            Pollutant.PM,
            Pollutant.CO2,
            Pollutant.CH4,
            Pollutant.N2O,
            Pollutant.Acetaldehyde,
            Pollutant.Acrolein,
            Pollutant.Benzene,
            Pollutant.Butadiene,
            Pollutant.Formaldehyde,
            Pollutant.DPM10,
            Pollutant.MTBE };
        ModelingSettings _settings;
        OperatingModes   _opModes;
        Parameters       _parameters;
        int              _minYear;
        int              _maxYear;
        int              _maxEffYear;
        VCValue<double>  _totalVehSales;
        List<VCValue<double>> _baselineAvgFE;
        VCValue<int>          _switchGrowthYear; 
        #endregion
    }
}

