using System;
using System.Linq;

using Volpe.Cafe.MarketData;
using Volpe.Cafe.MarketData.Design;
using Volpe.Cafe.ModelSettings;
using Volpe.Cafe.TechAnalysis;

using RC = Volpe.Cafe.RegulatoryClass;

namespace Volpe.Cafe.Model
{
    /// <summary>
    /// Provides helper functions to assist the compliance model in performing regulatory and compliance calculations.
    /// </summary>
    public static class Standards
    {
        #region <<< Vehicle FE/CO-2 target calculations >>>

        /// <summary>
        /// Calculates the fuel economy target, in gallons per mile (gpm), for the specified vehicle.
        /// </summary>
        /// <param name="scen">The compliance scenario being analyzed.</param>
        /// <param name="year">The model year being analyzed.</param>
        /// <param name="vehInfo">The vehicle for which to calculate the fuel economy target.</param>
        /// <param name="round">
        ///     <c>true</c>, to round the fuel economy target before returning;
        ///     <c>false</c>, to return the fuel economy target unrounded.
        /// </param>
        /// <returns>
        /// The vehicle fuel economy target.
        /// </returns>
        public static double GetTarget(Scenario scen, int year, IVehicleInfo vehInfo, bool round = true)
        {
            var vehRC = vehInfo.RegClass;
            var scenInfo = scen[vehRC, year];
            if (scenInfo == null || vehRC == RC.None) {
                // unregulated or unsupported reg-class
                return 0;
            }

            int function = scenInfo.Function;
            double[] coefficients = scenInfo.Coefficients;

            return GetTarget(
                scen, year, vehRC,
                vehInfo.FuelShare.FuelType,
                vehInfo.Footprint,
                vehInfo.CurbWeight,
                vehInfo.GVWR,
                vehInfo.GCWR,
                vehInfo.Drive,
                vehInfo.CARBAgreement,
                function, coefficients, round);
        }

        /// <summary>
        /// Calculates the fuel economy target, in gallons per mile (gpm), for a vehicle with the specified attributes,
        /// using the given target <paramref name="function"/> and <paramref name="coefficients"/>.
        /// </summary>
        /// <param name="scen">The compliance scenario being analyzed.</param>
        /// <param name="year">The model year being analyzed.</param>
        /// <param name="vehRC">
        ///     The regulatory class of the vehicle for which to calculate the fuel economy target.
        /// </param>
        /// <param name="vehFuelType">
        ///     The fuel type of the vehicle for which to calculate the fuel economy target.
        ///     Not applicable if function type is not work-factor-based.
        /// </param>
        /// <param name="vehFP">
        ///     The footprint (in sq.ft.) of the vehicle for which to calculate the fuel economy target.
        ///     Not applicable if function type is not footprint-based.
        /// </param>
        /// <param name="vehCW">
        ///     The curb weight (in lbs.) of the vehicle for which to calculate the fuel economy target.
        ///     Not applicable if function type is not weight-based or work-factor-based.
        /// </param>
        /// <param name="vehGVWR">
        ///     The GVWR (in lbs.) of the vehicle for which to calculate the fuel economy target.
        ///     Not applicable if function type is not work-factor-based.
        /// </param>
        /// <param name="vehGCWR">
        ///     The GCWR (in lbs.) of the vehicle for which to calculate the fuel economy target.
        ///     Not applicable if function type is not work-factor-based.
        /// </param>
        /// <param name="vehDrive">
        ///     The drive type of the vehicle for which to calculate the fuel economy target.
        ///     Not applicable if function type is not work-factor-based.
        /// </param>
        /// <param name="carbMfr">
        ///     <c>true</c>, if the manufacturer subscribes to the CARB agreement; <c>false</c>, otherwise.
        /// </param>
        /// <param name="function">The target function type to use in calculating the fuel economy target.</param>
        /// <param name="coefficients">
        ///     The target function coefficients to use in calculating the fuel economy target.
        /// </param>
        /// <param name="round">
        ///     <inheritdoc cref="GetTarget(Scenario, int, IVehicleInfo, bool)" path="/param[@name='round']"/>
        /// </param>
        /// <inheritdoc cref="GetTarget(Scenario, int, IVehicleInfo, bool)" path="/returns"/>
        public static double GetTarget(
            Scenario scen, int year, RC vehRC, FuelType vehFuelType, double vehFP, double vehCW, double vehGVWR,
            double vehGCWR, DriveType vehDrive, bool carbMfr, int function, double[] coefficients, bool round)
        {
            if (round) {
                vehFP = Math.Round(vehFP, 1);
                vehCW = Math.Round(vehCW);
                vehGVWR = Math.Round(vehGVWR);
                vehGCWR = Math.Round(vehGCWR);
            }

            double A = coefficients[0];
            double B = coefficients[1];
            double C = coefficients[2];
            double D = coefficients[3];
            double E = coefficients[4];
            double F = coefficients[5];
            double G = coefficients[6];
            double H = coefficients[7];
            //double I = coefficients[8];
            //double J = coefficients[9];

            // determine the vehicle target (in gpm space)
            double target = 0.0, invA = 1.0 / A, invB = 1.0 / B;
            double exp;
            double t1, t2;

            switch (function) {
                //
                // Flat standard function
                //
                // A:  mpg
                //
                case 1:
                    target = invA;
                    break;

                //
                // Logistic area-/weight-based function
                //
                // A:  mpg ("ceiling")
                // B:  mpg ("floor")
                // C:  square feet or pounds ("midpoint")
                // D:  square feet or pounds ("width")
                //
                case 2:     // area-based
                case 3:     // weight-based
                    exp = Math.Exp((((function == 2) ? vehFP : vehCW) - C) / D);
                    target = invA + (invB - invA) * (exp / (1.0 + exp));
                    break;

                //
                // Exponential area-/weight-based function
                //
                // A:  mpg ("ceiling")
                // B:  mpg (should be > A)
                // C:  square feet or pounds (determines "height")
                //
                case 4:     // area-based
                case 5:     // weight-based
                    exp = Math.Exp(1.0 - ((function == 4) ? vehFP : vehCW) / C);
                    target = invA - exp * invB;
                    break;

                //
                // Linear area-/weight-based function
                //
                // A:  mpg ("ceiling")
                // B:  mpg ("floor")
                // C:  change in gpm / change in square feet or pounds ("slope" of the function)
                // D:  gpm ("y-intercept")
                //
                case 6:     // area-based
                case 7:     // weight-based
                    target = C * ((function == 6) ? vehFP : vehCW) + D;
                    target = (target < invA) ? invA : (target > invB) ? invB : target;
                    break;

                //
                // Linear work-factor-based function
                //
                // Coefficients for gasoline and CNG vehicles, SHEVs, and PHEVs:
                //   A:  change in gal/100-miles / change in work-factor ("slope" of the function)
                //   B:  gallons per 100-miles ("y-intercept")
                // Coefficients for diesel vehicles, BEVs, and FCVs:
                //   C:  change in gal/100-miles / change in work-factor ("slope" of the function)
                //   D:  gallons per 100-miles ("y-intercept")
                //
                case 8:
                    // use compression-ignition coefs (D, C) for diesel, BEV, and FCVs
                    // use spark-ignition coefs (A, B) for all other vehicles (including PHEVs)
                    bool isCI = vehFuelType.Contains(FuelType.Diesel)
                             || vehFuelType == FuelType.Electricity
                             || vehFuelType == FuelType.Hydrogen;
                    double vehWF = GetWorkFactor(vehRC, vehCW, vehGVWR, vehGCWR, vehDrive, round);
                    target = (isCI ? (C * vehWF + D) : (A * vehWF + B)) / 100; // divide by 100 to convert to gpm
                    break;

                //
                // Linear work-factor-based function
                //
                // A:  change in gal/100-miles / change in work-factor ("slope" of the function)
                // B:  gallons per 100-miles ("y-intercept")
                //
                case 9:
                    vehWF = GetWorkFactor(vehRC, vehCW, vehGVWR, vehGCWR, vehDrive, round);
                    target = (A * vehWF + B) / 100; // divide by 100 to convert to gpm
                    break;

                //
                // Linear work-factor-based function with an upper bound
                //
                // A:  change in gal/100-miles / change in work-factor ("slope" of the function)
                // B:  gallons per 100-miles ("y-intercept")
                // C:  gallons per 100-miles ("ceiling")
                //
                case 10:
                    vehWF = GetWorkFactor(vehRC, vehCW, vehGVWR, vehGCWR, vehDrive, round);
                    target = Math.Min(A * vehWF + B, C) / 100; // divide by 100 to convert to gpm
                    break;

                //
                // Linear CARB-conditional area-/weight-based function
                //
                // Primary function coefficients (applies to non-CARB manufacturers):
                //   A:  mpg ("ceiling")
                //   B:  mpg ("floor")
                //   C:  change in gpm / change in square feet or pounds ("slope" of the function)
                //   D:  gpm ("y-intercept")
                // Secondary function coefficients (applies to CARB manufacturers):
                //   E:  mpg ("ceiling")
                //   F:  mpg ("floor")
                //   G:  change in gpm / change in square feet or pounds ("slope" of the function)
                //   H:  gpm ("y-intercept")
                //
                case 16:     // area-based
                case 17:     // weight-based
                    double a = carbMfr ? E : A;
                    double b = carbMfr ? F : B;
                    double c = carbMfr ? G : C;
                    double d = carbMfr ? H : D;
                    invA = 1 / a;
                    invB = 1 / b;

                    target = c * ((function == 16) ? vehFP : vehCW) + d;
                    target = (target < invA) ? invA : (target > invB) ? invB : target;
                    break;

                //
                // Dual linear area-/weight-based function
                //
                // Primary function coefficients:
                //   A:  mpg ("ceiling")
                //   B:  mpg ("floor")
                //   C:  change in gpm / change in square feet or pounds ("slope" of the function)
                //   D:  gpm ("y-intercept")
                // Secondary function coefficients:
                //   E:  mpg ("ceiling")
                //   F:  mpg ("floor")
                //   G:  change in gpm / change in square feet or pounds ("slope" of the function)
                //   H:  gpm ("y-intercept")
                //
                case 206:   // area-based
                case 207:   // weight-based
                    // calculate target for the primary function
                    t1 = C * ((function == 206) ? vehFP : vehCW) + D;
                    t1 = (t1 < invA) ? invA : (t1 > invB) ? invB : t1;

                    // calculate target for the 2nd function
                    double invE = 1.0 / E, invF = 1.0 / F;
                    t2 = G * ((function == 206) ? vehFP : vehCW) + H;
                    t2 = (t2 < invE) ? invE : (t2 > invF) ? invF : t2;

                    target = Math.Min(t1, t2);
                    break;

                //
                // Dual linear work-factor-based function
                //
                // Coefficients for gasoline and CNG vehicles, SHEVs, and PHEVs:
                //   A:  change in gal/100-miles / change in work-factor ("slope" of the function)
                //   B:  gallons per 100-miles ("y-intercept")
                // Coefficients for diesel vehicles, BEVs, and FCVs:
                //   C:  change in gal/100-miles / change in work-factor ("slope" of the function)
                //   D:  gallons per 100-miles ("y-intercept")
                // Secondary function coefficients:
                //   E:  the model year whose function serves as the "floor" for this function
                //
                case 208:
                    // calculate target for the primary function
                    t1 = GetTarget(
                        scen, year, vehRC, vehFuelType, vehFP, vehCW, vehGVWR, vehGCWR, vehDrive, carbMfr,
                        8, coefficients, round);

                    // calculate target for the secondary function
                    int year2 = (int)E;
                    if (year2 < year) {
                        var scenInfo = scen[vehRC, year2];
                        int f2 = scenInfo.Function;
                        var c2 = scenInfo.Coefficients;

                        t2 = GetTarget(
                            scen, year2, vehRC, vehFuelType, vehFP, vehCW, vehGVWR, vehGCWR, vehDrive, carbMfr,
                            f2, c2, round);
                    }
                    else {
                        t2 = double.MaxValue;
                    }

                    target = Math.Min(t1, t2);
                    break;
            }

            return round ? RoundFETarget(target, vehRC) : target;
        }

        /// <summary>
        /// Calculates the CO-2 target, in grams per mile, for the specified vehicle.
        /// </summary>
        /// <param name="scen">The compliance scenario being analyzed.</param>
        /// <param name="year">The model year being analyzed.</param>
        /// <param name="vehInfo">The vehicle for which to calculate the CO-2 target.</param>
        /// <param name="vehFETarget">
        ///     The optional vehicle fuel economy target based on which to calculate the CO-2 target. Specify
        ///     <see cref="double.NaN"/> to auto-calculate. Applicable only when a non-CO-2 specific function is used.
        /// </param>
        /// <param name="round">
        ///     <c>true</c>, to round the CO-2 target before returning;
        ///     <c>false</c>, to return the CO-2 target unrounded.
        /// </param>
        /// <returns>
        /// The vehicle CO-2 target.
        /// </returns>
        public static double GetVehicleCO2Target(
            Scenario scen, int year, IVehicleInfo vehInfo, double vehFETarget = double.NaN, bool round = true)
        {
            var vehRC = vehInfo.RegClass;
            var scenInfo = scen[vehRC, year];
            if (scenInfo == null || vehRC == RC.None) {
                // unregulated or unsupported reg-class
                return 0;
            }

            int function = scenInfo.CO2Function;
            double[] coefficients = scenInfo.CO2Coefficients;

            if (TargetFunctionInformation.IsCO2OnlyFunction(function)) {
                return GetVehicleCO2Target(
                    scen, year, vehRC,
                    vehInfo.FuelType,
                    vehInfo.Footprint,
                    vehInfo.CurbWeight,
                    vehInfo.GVWR,
                    vehInfo.GCWR,
                    vehInfo.Drive,
                    vehInfo.CARBAgreement,
                    function, coefficients, round);
            }

            // using fuel economy target function
            if (double.IsNaN(vehFETarget)) {
                vehFETarget = GetTarget(
                    scen, year, vehRC,
                    vehInfo.FuelType,
                    vehInfo.Footprint,
                    vehInfo.CurbWeight,
                    vehInfo.GVWR,
                    vehInfo.GCWR,
                    vehInfo.Drive,
                    vehInfo.CARBAgreement,
                    function, coefficients, round);
            }

            double vehCO2Target = vehFETarget * scenInfo.CO2Factor + scenInfo.CO2Offset;
            return round ? Math.Round(vehCO2Target, 1) : vehCO2Target;
        }

        /// <summary>
        /// Calculates the CO-2 target, in grams per mile, for a vehicle with the specified attributes, using the given
        /// target <paramref name="function"/> and <paramref name="coefficients"/>.
        /// </summary>
        /// <param name="scen">The compliance scenario being analyzed.</param>
        /// <param name="year">The model year being analyzed.</param>
        /// <param name="vehRC">
        ///     The regulatory class of the vehicle for which to calculate the CO-2 target.
        /// </param>
        /// <param name="vehFuelType">
        ///     The fuel type of the vehicle for which to calculate the CO-2 target.
        ///     Not applicable if function type is not work-factor-based.
        /// </param>
        /// <param name="vehFP">
        ///     The footprint (in sq.ft.) of the vehicle for which to calculate the CO-2 target.
        ///     Not applicable if function type is not footprint-based.
        /// </param>
        /// <param name="vehCW">
        ///     The curb weight (in lbs.) of the vehicle for which to calculate the CO-2 target.
        ///     Not applicable if function type is not weight-based or work-factor-based.
        /// </param>
        /// <param name="vehGVWR">
        ///     The GVWR (in lbs.) of the vehicle for which to calculate the CO-2 target.
        ///     Not applicable if function type is not work-factor-based.
        /// </param>
        /// <param name="vehGCWR">
        ///     The GCWR (in lbs.) of the vehicle for which to calculate the CO-2 target.
        ///     Not applicable if function type is not work-factor-based.
        /// </param>
        /// <param name="vehDrive">
        ///     The drive type of the vehicle for which to calculate the CO-2 target.
        ///     Not applicable if function type is not work-factor-based.
        /// </param>
        /// <param name="carbMfr">
        ///     <c>true</c>, if the manufacturer subscribes to the CARB agreement; <c>false</c>, otherwise.
        /// </param>
        /// <param name="function">The target function type to use in calculating the CO-2 target.</param>
        /// <param name="coefficients">The target function coefficients to use in calculating the CO-2 target.</param>
        /// <param name="round">
        ///     <inheritdoc cref="GetVehicleCO2Target(Scenario, int, IVehicleInfo, double, bool)"
        ///      path="/param[@name='round']"/>
        /// </param>
        /// <inheritdoc cref="GetVehicleCO2Target(Scenario, int, IVehicleInfo, double, bool)" path="/returns"/>
        /// <remarks>
        /// This method is defined to calculate the CO-2 target only when using the CO-2 program specific target
        /// functions. To calculate the CO-2 target for non-CO-2 functions, the <see cref="GetTarget(Scenario, int, RC,
        /// FuelType, double, double, double, double, DriveType, bool, int, double[], bool)"/> method should be used to
        /// first obtain the fuel economy target, then convert using the applicable CO-2 factor and offset.
        /// </remarks>
        public static double GetVehicleCO2Target(
            Scenario scen, int year, RC vehRC, FuelType vehFuelType, double vehFP, double vehCW, double vehGVWR,
            double vehGCWR, DriveType vehDrive, bool carbMfr, int function, double[] coefficients, bool round)
        {
            if (round) {
                vehFP = Math.Round(vehFP, 1);
                vehCW = Math.Round(vehCW);
                vehGVWR = Math.Round(vehGVWR);
                vehGCWR = Math.Round(vehGCWR);
            }

            double A = coefficients[0];
            double B = coefficients[1];
            double C = coefficients[2];
            double D = coefficients[3];
            double E = coefficients[4];
            double F = coefficients[5];
            double G = coefficients[6];
            double H = coefficients[7];
            double I = coefficients[8];
            double J = coefficients[9];

            // determine the vehicle CO-2 target (in grams/mile)
            double target = 0;
            double attribute;

            switch (function) {
                //
                // Piecewise linear area/weight-based function
                //
                // A:  grams/mile at lower bound ("floor")
                // B:  grams/mile at upper bound ("ceiling")
                // C:  change in grams/mile over change in square feet/pounds ("slope" of the function)
                // D:  grams/mile ("y-intercept")
                // E:  footprint/curb weight lower bound
                // F:  footprint/curb weight upper bound
                //
                case 306:   // area-based
                case 307:   // weight-based
                    attribute = (function == 306) ? vehFP : vehCW;
                    target = (attribute <= E) ? A
                           : (attribute > F) ? B
                           : Math.Min(B, C * attribute + D);
                    break;

                //
                // Linear work-factor-based function
                //
                // Coefficients for gasoline and CNG vehicles, SHEVs, and PHEVs:
                //   A:  change in grams/mile / change in work-factor ("slope" of the function)
                //   B:  grams/mile ("y-intercept")
                // Coefficients for diesel vehicles, BEVs, and FCVs:
                //   C:  change in grams/mile / change in work-factor ("slope" of the function)
                //   D:  grams/mile ("y-intercept")
                //
                case 308:
                    // use compression-ignition coefs (C, D) for diesel, BEV, and FCVs
                    // use spark-ignition coefs (A, B) for all other vehicles (including PHEVs)
                    bool isCI = vehFuelType.Contains(FuelType.Diesel)
                             || vehFuelType == FuelType.Electricity
                             || vehFuelType == FuelType.Hydrogen;
                    double vehWF = GetWorkFactor(vehRC, vehCW, vehGVWR, vehGCWR, vehDrive, round);
                    target = isCI ? (C * vehWF + D) : (A * vehWF + B);
                    break;

                //
                // Linear work-factor-based function
                //
                // A:  change in grams/mile / change in work-factor ("slope" of the function)
                // B:  grams/mile ("y-intercept")
                //
                case 309:
                    vehWF = GetWorkFactor(vehRC, vehCW, vehGVWR, vehGCWR, vehDrive, round);
                    target = A * vehWF + B;
                    break;

                //
                // Linear work-factor-based function with an upper bound
                //
                // A:  change in grams/mile / change in work-factor ("slope" of the function)
                // B:  grams/mile ("y-intercept")
                // C:  grams/mile ("ceiling")
                //
                case 310:
                    vehWF = GetWorkFactor(vehRC, vehCW, vehGVWR, vehGCWR, vehDrive, round);
                    target = Math.Min(A * vehWF + B, C);
                    break;

                //
                // Piecewise linear CARB-conditional area-/weight-based function
                //
                // Primary function coefficients (applies to non-CARB manufacturers):
                //   A:  grams/mile at lower bound ("floor")
                //   B:  grams/mile at upper bound ("ceiling")
                //   C:  change in grams/mile over change in square feet/pounds ("slope" of the function)
                //   D:  grams/mile ("y-intercept")
                // Bounding function coefficients:
                //   E:  footprint/curb weight lower bound
                //   F:  footprint/curb weight upper bound
                // Secondary function coefficients (applies to CARB manufacturers):
                //   G:  grams/mile at lower bound ("floor")
                //   H:  grams/mile at upper bound ("ceiling")
                //   I:  change in grams/mile over change in square feet/pounds ("slope" of the function)
                //   J:  grams/mile ("y-intercept")
                //
                case 316:   // area-based
                case 317:   // weight-based
                    double a = carbMfr ? G : A;
                    double b = carbMfr ? H : B;
                    double c = carbMfr ? I : C;
                    double d = carbMfr ? J : D;

                    attribute = (function == 316) ? vehFP : vehCW;
                    target = (attribute <= E) ? a
                           : (attribute > F) ? b
                           : Math.Min(b, c * attribute + d);
                    break;

                //
                // Dual Piecewise linear area/weight-based function
                //
                // A:  grams/mile at lower bound ("floor")
                // B:  grams/mile at upper bound ("ceiling")
                // C:  change in grams/mile over change in square feet/pounds ("slope" of the function)
                // D:  grams/mile ("y-intercept")
                // E:  change in grams/mile over change in square feet/pounds ("slope" of the function)
                // F:  grams/mile ("y-intercept")
                // G:  footprint/curb weight lower bound
                // H:  footprint/curb weight mid bound
                // I:  footprint/curb weight upper bound
                //
                case 406:   // area-based
                case 407:   // weight-based
                    attribute = (function == 406) ? vehFP : vehCW;
                    target = (attribute <= G) ? A
                           : (attribute > I) ? B
                           : (attribute <= H) ? Math.Min(B, C * attribute + D)
                           :                    Math.Min(B, E * attribute + F);
                    break;
            }

            return round ? Math.Round(target, 1) : target;
        }

        #endregion

        #region <<< Vehicle FE/CO-2 rating calculations >>>

        /// <summary>
        /// Calculates the harmonically averaged fuel economy rating, in miles per gallon (mpg), for the specified
        /// vehicle.
        /// </summary>
        /// <param name="scen">The compliance scenario being analyzed.</param>
        /// <param name="year">The model year being analyzed.</param>
        /// <param name="vehInfo">The vehicle for which to calculate the average fuel economy rating.</param>
        /// <param name="settings">The modeling settings used for analysis.</param>
        /// <param name="adjustForCompliance">
        ///     <c>true</c>, to adjust the constituent fuel economy components for compliance purposes before computing
        ///     the average fuel economy rating; <c>false</c> to use an unadjusted value.<br/>
        ///     Refer to <see cref="GetAdjustedVehicleFERating(Scenario, int, IVehicleInfo, ModelingSettings)"/> for
        ///     more information on how the adjustment for compliance is performed.
        /// </param>
        /// <param name="round">
        ///     <c>true</c>, to round the average fuel economy rating before returning;
        ///     <c>false</c>, to return the fuel economy rating unrounded.
        /// </param>
        /// <returns>
        /// The average vehicle fuel economy rating.
        /// </returns>
        public static double GetAverageVehicleFERating(
            Scenario scen, int year, IVehicleInfo vehInfo, ModelingSettings settings,
            bool adjustForCompliance = true, bool round = true)
        {
            return GetAverageVehicleFERating(
                scen, year,
                vehInfo.RegClass,
                vehInfo.FuelEconomy,
                vehInfo.FuelShare,
                settings, adjustForCompliance, round);
        }

        /// <summary>
        /// Calculates the harmonically averaged fuel economy rating, in miles per gallon (mpg), for a vehicle with the
        /// specified fuel economy and fuel share values.
        /// </summary>
        /// <param name="scen">The compliance scenario being analyzed.</param>
        /// <param name="year">The model year being analyzed.</param>
        /// <param name="vehRC">
        ///     The regulatory class of the vehicle for which to calculate the average fuel economy rating.
        /// </param>
        /// <param name="vehFE">
        ///     The vehicle fuel economy value from which to calculate the average fuel economy rating.
        /// </param>
        /// <param name="vehFS">The fuel share representing the mix of fuel the vehicle operates on.</param>
        /// <param name="settings">The modeling settings used for analysis.</param>
        /// <param name="adjustForCompliance">
        ///     <inheritdoc cref="GetAverageVehicleFERating(Scenario, int, IVehicleInfo, ModelingSettings, bool, bool)"
        ///      path="/param[@name='adjustForCompliance']"/>
        /// </param>
        /// <param name="round">
        ///     <inheritdoc cref="GetAverageVehicleFERating(Scenario, int, IVehicleInfo, ModelingSettings, bool, bool)"
        ///      path="/param[@name='round']"/>
        /// </param>
        /// <inheritdoc cref="GetAverageVehicleFERating(Scenario, int, IVehicleInfo, ModelingSettings, bool, bool)"
        ///  path="/returns"/>
        public static double GetAverageVehicleFERating(
            Scenario scen, int year, RC vehRC, FuelValue vehFE, FuelValue vehFS, ModelingSettings settings,
            bool adjustForCompliance, bool round)
        {
            FuelValue adjFE = (adjustForCompliance)
                            ? GetAdjustedVehicleFERating(scen, year, vehRC, vehFE, vehFS, settings)
                            : vehFE;
            return CalcAverageVehicleRatingHelper(scen, year, vehRC, adjFE, vehFS, true, round);
        }

        /// <summary>
        /// Calculates the adjusted fuel economy rating, in miles per gallon (mpg), for the specified vehicle that is
        /// suitable to use for compliance purposes.
        /// </summary>
        /// <param name="scen">The compliance scenario being analyzed.</param>
        /// <param name="year">The model year being analyzed.</param>
        /// <param name="vehInfo">The vehicle for which to calculate the adjusted fuel economy rating.</param>
        /// <param name="settings">The modeling settings used for analysis.</param>
        /// <returns>
        /// The adjusted vehicle fuel economy rating.
        /// </returns>
        /// <remarks>
        /// The calculation for adjusted fuel economy rating varies depending on the regulatory class of the vehicle,
        /// the fuel type for which the adjustment is being performed, and the applicability of the petroleum
        /// equivalence factor (PEF) defined in the scenario inputs.<br/>
        /// <br/>
        /// For light-duty vehicles, the adjusted fuel economy rating is calculated as follows:<br/>
        /// - <b>Gasoline</b> and <b>Diesel</b> fuel types -- no adjustment is performed;<br/>
        /// - <b>E85</b>, <b>Hydrogen</b>, and <b>CNG</b> fuel types -- the fuel economy rating is divided by 0.15;<br/>
        /// - <b>Electricity</b> fuel type -- if the PEF is set as applicable in the scenario inputs, the fuel economy
        ///   rating is adjusted by the associated PEF value; otherwise, no adjustment is performed.<br/>
        /// <br/>
        /// For HDPUV vehicles, the adjusted fuel economy rating is calculated as follows:<br/>
        /// - <b>Gasoline</b>, <b>Diesel</b>, <b>E85</b>, and <b>CNG</b> fuel types -- no adjustment is performed;<br/>
        /// - <b>Hydrogen</b> fuel type -- the fuel consumption on hydrogen is assumed to be 0 (zero) in gal/100-mile
        ///   space, and is thus set as "infinity" on an mpg basis.<br/>
        /// - <b>Electricity</b> fuel type -- if the PEF is set as applicable in the scenario inputs, the fuel economy
        ///   rating is adjusted by the associated PEF value; otherwise, the fuel consumption on electricity is assumed
        ///   to be 0 (zero) in gal/100-mile space, and is thus set as "infinity" on an mpg basis.
        /// </remarks>
        public static FuelValue GetAdjustedVehicleFERating(
            Scenario scen, int year, IVehicleInfo vehInfo, ModelingSettings settings)
        {
            return GetAdjustedVehicleFERating(
                scen, year,
                vehInfo.RegClass,
                vehInfo.FuelEconomy,
                vehInfo.FuelShare,
                settings);
        }

        /// <summary>
        /// Calculates the adjusted fuel economy rating, in miles per gallon (mpg), for a vehicle with the specified
        /// fuel economy and fuel share values that is suitable to use for compliance purposes.
        /// </summary>
        /// <param name="scen">The compliance scenario being analyzed.</param>
        /// <param name="year">The model year being analyzed.</param>
        /// <param name="vehRC">
        ///     The regulatory class of the vehicle for which to calculate the adjusted fuel economy rating.
        /// </param>
        /// <param name="vehFE">
        ///     The vehicle fuel economy value from which to calculate the adjusted fuel economy rating.
        /// </param>
        /// <param name="vehFS">The fuel share representing the mix of fuel the vehicle operates on.</param>
        /// <param name="settings">The modeling settings used for analysis.</param>
        /// <inheritdoc cref="GetAdjustedVehicleFERating(Scenario,int,IVehicleInfo,ModelingSettings)" path="/returns"/>
        /// <inheritdoc cref="GetAdjustedVehicleFERating(Scenario,int,IVehicleInfo,ModelingSettings)" path="/remarks"/>
        public static FuelValue GetAdjustedVehicleFERating(
            Scenario scen, int year, RC vehRC, FuelValue vehFE, FuelValue vehFS, ModelingSettings settings)
        {
            var scenInfo = scen[vehRC, year];

            return vehRC.IsHDPUV()
                ? GetAdjustedVehicleFERating2B3()
                : GetAdjustedVehicleFERatingLD();

            // local helper functions
            FuelValue GetAdjustedVehicleFERatingLD()
            {
                FuelValue adjFE = vehFE;

                if (vehFS.Electricity != 0 && scenInfo.ApplyPef) {
                    adjFE.Electricity *= GetPefEAdjustment();
                }
                adjFE.Ethanol85 /= 0.15;
                adjFE.Hydrogen /= 0.15;
                adjFE.CNG /= 0.15;

                return adjFE;
            }

            FuelValue GetAdjustedVehicleFERating2B3()
            {
                FuelValue adjFE = vehFE;

                if (vehFS.Electricity != 0) {
                    adjFE.Electricity = scenInfo.ApplyPef
                        ? adjFE.Electricity * GetPefEAdjustment()
                        : double.PositiveInfinity;
                }
                if (vehFS.Hydrogen != 0) {
                    adjFE.Hydrogen = double.PositiveInfinity;
                }

                return adjFE;
            }

            double GetPefEAdjustment()
            {
                FuelValue ed = settings.Parameters.FuelProperties.EnergyDensity;
                return scenInfo.GetPef(vehFS.FuelType) / 1000.0 * (ed.Electricity / ed.Gasoline);
            }
        }

        /// <summary>
        /// Calculates the average CO-2 rating, in grams per mile, for the specified vehicle.
        /// </summary>
        /// <param name="scen">The compliance scenario being analyzed.</param>
        /// <param name="year">The model year being analyzed.</param>
        /// <param name="vehInfo">The vehicle for which to calculate the average CO-2 rating.</param>
        /// <param name="settings">The modeling settings used for analysis.</param>
        /// <param name="vehCO2Target">
        ///     The vehicle CO-2 target to use when including the upstream emissions for calculating the CO-2 rating of
        ///     electricity and hydrogen fuel types. Use <see cref="double.NaN"/> to calculate the CO-2 target based on
        ///     the specified vehicle.
        /// </param>
        /// <param name="round">
        ///     <c>true</c>, to round the average CO-2 rating before returning;
        ///     <c>false</c>, to return the CO-2 rating unrounded.
        /// </param>
        /// <returns>
        /// The average vehicle CO-2 rating.
        /// </returns>
        public static double GetAverageVehicleCO2Rating(
            Scenario scen, int year, IVehicleInfo vehInfo, ModelingSettings settings,
            double vehCO2Target = double.NaN, bool round = true)
        {
            if (double.IsNaN(vehCO2Target)) {
                vehCO2Target = GetVehicleCO2Target(scen, year, vehInfo, double.NaN, round);
            }
            RC vehRC = vehInfo.RegClass;
            FuelValue vehCO2 = GetVehicleCO2Rating(scen, year, vehRC, vehInfo.FuelEconomy, settings, vehCO2Target);
            return GetAverageVehicleCO2Rating(scen, year, vehRC, vehCO2, vehInfo.FuelShare, round);
        }

        /// <summary>
        /// Calculates the average CO-2 rating, in grams per mile, for a vehicle with the specified CO-2 rating and
        /// fuel share values.
        /// </summary>
        /// <param name="scen">The compliance scenario being analyzed.</param>
        /// <param name="year">The model year being analyzed.</param>
        /// <param name="vehRC">
        ///     The regulatory class of the vehicle for which to calculate the average CO-2 rating.
        /// </param>
        /// <param name="vehCO2">The vehicle CO-2 value from which to calculate the average CO-2 rating.</param>
        /// <param name="vehFS">The fuel share representing the mix of fuel the vehicle operates on.</param>
        /// <param name="round">
        ///     <inheritdoc cref="GetAverageVehicleCO2Rating(Scenario, int, IVehicleInfo, ModelingSettings, double,
        ///      bool)" path="/param[@name='round']"/>
        /// </param>
        /// <inheritdoc cref="GetAverageVehicleCO2Rating(Scenario, int, IVehicleInfo, ModelingSettings, double, bool)"
        ///  path="/returns"/>
        public static double GetAverageVehicleCO2Rating(
            Scenario scen, int year, RC vehRC, FuelValue vehCO2, FuelValue vehFS, bool round)
        {
            return CalcAverageVehicleRatingHelper(scen, year, vehRC, vehCO2, vehFS, false, round);
        }

        /// <summary>
        /// Calculates the CO-2 rating, in grams per mile, for the specified vehicle.
        /// </summary>
        /// <param name="scen">The compliance scenario being analyzed.</param>
        /// <param name="year">The model year being analyzed.</param>
        /// <param name="vehInfo">The vehicle for which to calculate the CO-2 rating.</param>
        /// <param name="settings">The modeling settings used for analysis.</param>
        /// <param name="vehCO2Target">
        ///     <inheritdoc cref="GetAverageVehicleCO2Rating(Scenario, int, IVehicleInfo, ModelingSettings, double,
        ///      bool)" path="/param[@name='vehCO2Target']"/>
        /// </param>
        /// <returns>
        /// The vehicle CO-2 rating.
        /// </returns>
        public static FuelValue GetVehicleCO2Rating(
            Scenario scen, int year, IVehicleInfo vehInfo, ModelingSettings settings, double vehCO2Target = double.NaN)
        {
            if (double.IsNaN(vehCO2Target)) {
                vehCO2Target = GetVehicleCO2Target(scen, year, vehInfo);
            }
            return GetVehicleCO2Rating(scen, year, vehInfo.RegClass, vehInfo.FuelEconomy, settings, vehCO2Target);
        }

        /// <summary>
        /// Calculates the CO-2 rating, in grams per mile, for a vehicle with the specified fuel economy and CO-2
        /// target.
        /// </summary>
        /// <param name="scen">The compliance scenario being analyzed.</param>
        /// <param name="year">The model year being analyzed.</param>
        /// <param name="vehRC">The regulatory class of the vehicle for which to calculate the CO-2 rating.</param>
        /// <param name="vehFE">The vehicle fuel economy value based on which to calculate the CO-2 rating.</param>
        /// <param name="settings">The modeling settings used for analysis.</param>
        /// <param name="vehCO2Target">
        ///     The vehicle CO-2 target to use when including the upstream emissions for calculating the CO-2 rating of
        ///     electricity and hydrogen fuel types.
        /// </param>
        /// <inheritdoc cref="GetVehicleCO2Rating(Scenario,int,IVehicleInfo,ModelingSettings,double)" path="/returns"/>
        public static FuelValue GetVehicleCO2Rating(
            Scenario scen, int year, RC vehRC, FuelValue vehFE, ModelingSettings settings, double vehCO2Target)
        {
            bool includeUpstreamForElectricty = scen[vehRC, year].CO2IncludeUpstream;
            var fuelProperties = settings.Parameters.FuelProperties;
            var energyDensity = fuelProperties.EnergyDensity;
            var vehCO2 = new FuelValue();

            var validFuelTypes = FuelTypes.List.Where(fuelType => vehFE[fuelType] > 0);
            foreach (FuelType ft in validFuelTypes) {
                if (ft != FuelType.Electricity && ft != FuelType.Hydrogen) {
                    double carbonContent = fuelProperties.GetFuelCarbonContent(ft);
                    vehCO2[ft] = carbonContent / vehFE[ft];
                }
                else if (includeUpstreamForElectricty) {
                    const double gridLoss = 0.935; // to account for grid transmission losses
                    const double avgUSUP = 0.534;  // US average electricity GHG emission rate at the powerplant (g/wH)
                    const double ghgGas = 2478;    // estimated upstream GHG emissions of gasoline (g/gal)

                    double gasToElecEnergyDensity = energyDensity.Gasoline / energyDensity.Electricity * 1000;
                    double upstreamElec = (1 / vehFE[ft]) * gasToElecEnergyDensity / gridLoss * avgUSUP;
                    double upstreamGas = ghgGas / scen[vehRC, year].CO2Factor * vehCO2Target;
                    vehCO2[ft] = upstreamElec - upstreamGas;
                }
            }

            return vehCO2;
        }

        private static double CalcAverageVehicleRatingHelper(
            Scenario scen, int year, RC vehRC, FuelValue vehRating, FuelValue vehFS, bool calcAverageFE, bool round)
        {
            // begin by assuming only one fuel type is used, or multi-fuel mode setting
            // indicates use of primary fuel only
            double rating = vehRating.PrimaryValue;

            //------------------------------------------------------------------------------------
            // The multi-fuel mode is interpreted by the model as follows:
            //  0:  only primary fuel economy value is considered (primary fuel share is assumed
            //      to be 100%)
            //  1:  for FFVs (Gasoline/E85), only the gasoline fuel economy value is considered
            //      (gasoline fuel share is assumed to be 100%); for PHEVs (Gasoline/Electricity
            //      or Diesel/Electricity), both fuel economy values are considered (Gasoline or
            //      Diesel is always assumed to be the primary fuel, and the primary fuel share
            //      is assumed to be 100% minus elctricity share)
            //  2:  for FFVs and PHEVs (Gasoline/E85, Gasoline/Electricity, or Diesel/Electricity),
            //      both fuel economy values are considered (Gasoline or Diesel is always assumed
            //      to be the primary fuel, and the primary fuel share is assumed to be 100% minus
            //      the secondary fuel share)
            //------------------------------------------------------------------------------------
            if (vehFS.IsMultiFuel) {
                var scenInfo = scen[vehRC, year];
                int mode = scenInfo.MultiFuel;
                if (!calcAverageFE) {
                    // for CO-2 program, we do not support mode 0
                    mode = Math.Max(1, mode);
                }

                if ((mode == 1 || mode == 2) && vehFS.Electricity > 0) {
                    double elcFS = Math.Max(scenInfo.MultiFuelSharePhev, vehFS.Electricity);
                    rating = (calcAverageFE)
                        ? 1 / ((1 - elcFS) / vehRating.PrimaryValue + elcFS / vehRating.Electricity)
                        :      (1 - elcFS) * vehRating.PrimaryValue + elcFS * vehRating.Electricity;
                }
                else if (mode == 2 && vehFS.Ethanol85 > 0) {
                    double e85FS = Math.Max(scenInfo.MultiFuelShareFfv, vehFS.Ethanol85);
                    rating = (calcAverageFE)
                        ? 1 / ((1 - e85FS) / vehRating.Gasoline + e85FS / vehRating.Ethanol85)
                        :      (1 - e85FS) * vehRating.Gasoline + e85FS * vehRating.Ethanol85;
                }
                else if (mode != 0 && !(mode == 1 && vehFS.Ethanol85 > 0)) {
                    throw new ArgumentException(
                        $"The vehicle's {(calcAverageFE ? "fuel economy" : "CO-2")} and/or fuel share values " +
                        "are not valid; or, incorrect multi-fuel mode specified in the scenario.");
                }
            }

            return (round)
                ? (calcAverageFE) ? RoundFE(rating, vehRC) : Math.Round(rating)
                : rating;
        }

        #endregion

        #region <<< Manufacturer compliance calculations >>>

        /// <summary>
        /// Calculates the compliance statistics for the specified manufacturer.
        /// </summary>
        /// <param name="scen">The compliance scenario being analyzed.</param>
        /// <param name="year">The model year being analyzed.</param>
        /// <param name="mfr">The manufacturer for which to calculate the compliance statistics.</param>
        /// <param name="settings">The modeling settings used for analysis.</param>
        /// <returns>
        /// The manufacturer's compliance statistics.
        /// </returns>
        /// <remarks>
        /// The <see cref="CalcCompliance(Scenario, int, IManufacturer, ModelingSettings)"/> method should be called on
        /// each manufacturer before starting the evaluation of a new model year. This allows the modeling system to
        /// initialize and perform a "baseline" calculation of all compliance values for that manufacturer during the
        /// given model year.
        /// </remarks>
        public static ComplianceStatistics CalcCompliance(
            Scenario scen, int year, IManufacturer mfr, ModelingSettings settings)
        {
            //------------------------------------------------------------------------------------
            // The CalcCompliance method is called whenever a manufacturer is about to start
            // processing a new model year and the model needs to perform a first calculation of
            // all compliance values for that manufacturer.
            //
            // The CalcCompliance is executed in two steps:
            //  (1) Aggregate vehicle stats by iterating each vehicle in a manufacturer across all
            //      reg-classes;
            //  (2) Finalize by calculating manufacturer-level compliance values from the aggregated
            //      vehicle statistics:
            //      (a) First, calculate CAFE/CO2 standard and rating from the vehicle stats,
            //      (b) Then, calculate additional compliance metrics (credits, fines, etc.)
            //          based on the calculated standard and rating.
            //
            // After this method returns, the ComplianceStatistics object is initialized and
            // contains the newly updated compliance values for a manufacturer.
            //------------------------------------------------------------------------------------
            var stats = new ComplianceStatistics();
            AggregateVehicleStats(scen, year, mfr, settings, stats);
            FinalizeCompliance(scen, year, mfr, settings, stats);
            return stats;
        }

        /// <summary>
        /// Refreshes the compliance statistics for the specified manufacturer.
        /// </summary>
        /// <param name="scen">The compliance scenario being analyzed.</param>
        /// <param name="year">The model year being analyzed.</param>
        /// <param name="mfr">The manufacturer for which to refresh the compliance statistics.</param>
        /// <param name="techSolution">
        ///     A technology solution containing the impacts from application of pending technologies on one or more
        ///     vehicle models, or null, if compliance statistics is being refreshed due to the use of credits.
        /// </param>
        /// <param name="settings">The modeling settings used for analysis.</param>
        /// <remarks>
        /// The <see cref="RefreshCompliance(Scenario, int, IManufacturer, TechSolution, ModelingSettings)"/> method
        /// should be called on a manufacturer each time the model applies a new technology solution or uses credits to
        /// offset compliance shortfalls. This method will update vehicle-specific values (on an as-needed basis) and
        /// refresh all compliance values for a manufacturer.
        /// </remarks>
        public static void RefreshCompliance(
            Scenario scen, int year, IManufacturer mfr, TechSolution techSolution, ModelingSettings settings)
        {
            //------------------------------------------------------------------------------------
            // The RefreshCompliance method is called whenever a manufacturer just applied a
            // technology solution or used credits and the model needs to update (or refresh) all
            // compliance values for that manufacturer.
            //
            // The RefreshCompliance is executed in two steps:
            //  (1) Aggregate vehicle stats by iterating each test vehicle across all reg-classes
            //      (n/a, when refreshing after using credits);
            //  (2) Finalize by calculating manufacturer-level compliance values from the aggregated
            //      vehicle statistics:
            //      (a) First, calculate CAFE/CO2 standard and rating from the vehicle stats,
            //      (b) Then, calculate additional compliance metrics (credits, fines, etc.)
            //          based on the calculated standard and rating.
            //
            // After this method returns, the ComplianceStatistics object contains the updated
            // compliance values for a manufacturer.
            //------------------------------------------------------------------------------------
            var stats = mfr.GetRevision(year).ComplianceStats;
            if (techSolution != null) {
                AggregateVehicleStats(scen, year, mfr, techSolution, settings, stats);
            }
            FinalizeCompliance(scen, year, mfr, settings, stats);
        }

        /// <summary>
        /// Calculates a "test" of the compliance statistics for the specified manufacturer, based on the changes from
        /// application of pending technologies on one or more vehicle models.
        /// </summary>
        /// <param name="scen">The compliance scenario being analyzed.</param>
        /// <param name="year">The model year being analyzed.</param>
        /// <param name="mfr">The manufacturer for which to calculate a "test" of the compliance statistics.</param>
        /// <param name="techSolution">
        ///     A technology solution containing the impacts from application of pending technologies on one or more
        ///     vehicle models.
        /// </param>
        /// <param name="settings">The modeling settings used for analysis.</param>
        /// <param name="baseStats">The previously calculated manufacturer's compliance statistics.</param>
        /// <returns>
        /// The manufacturer's "test" compliance statistics, calculated based on the changes from pending technology
        /// and vehicle impacts.
        /// </returns>
        public static ComplianceStatistics TestCompliance(
            Scenario scen, int year, IManufacturer mfr, TechSolution techSolution, ModelingSettings settings,
            ComplianceStatistics baseStats)
        {
            var stats = baseStats.Clone();
            if (techSolution != null) {
                AggregateVehicleStats(scen, year, mfr, techSolution, settings, stats, false);
            }
            FinalizeCompliance(scen, year, mfr, settings, stats, false, true);
            return stats;
        }

        private static void AggregateVehicleStats(
            Scenario scen, int year, IManufacturer mfr, ModelingSettings settings, ComplianceStatistics stats)
        {
            foreach (IVehicle veh in mfr.Vehicles) {
                var vehRevision = veh.GetRevision(year);
                double vehSales = vehRevision.Sales;

                if (vehSales == 0) {
                    continue;
                }

                //--- calc vehicle-level compliance variables (FE/CO2 targets and ratings, off-cycle, etc.) ---
                double vehFETarget  = GetTarget                 (scen, year, vehRevision);
                double vehCO2Target = GetVehicleCO2Target       (scen, year, vehRevision, vehFETarget);
                double vehFE2Cycle  = GetAverageVehicleFERating (scen, year, vehRevision, settings, false);
                double vehFERating  = GetAverageVehicleFERating (scen, year, vehRevision, settings, true);
                double vehCO2Rating = GetAverageVehicleCO2Rating(scen, year, vehRevision, settings, vehCO2Target);

                //--- update mfr-level aggregation values ---
                RC vehRC = vehRevision.RegClass;
                int epaMode = mfr.BaselineInfo.EPAMultiplierMode[vehRC];
                var vehStats = stats.VehicleStats[vehRC];

                var scenInfo = scen[vehRC, year];
                double vehSalesEPA = vehSales * scenInfo.GetEpaMultiplier(vehRevision.FuelType);

                vehStats.Sales     += vehSales;
                vehStats.FETarget  += vehSales * vehFETarget;
                vehStats.FE2Cycle  += vehSales / vehFE2Cycle;
                vehStats.FERating  += vehSales / vehFERating;
                vehStats.CO2Sales  += vehSalesEPA;
                vehStats.CO2Target += (epaMode >= 2) ? vehSalesEPA * vehCO2Target : vehSales * vehCO2Target;
                vehStats.CO2Rating += (epaMode >= 1) ? vehSalesEPA * vehCO2Rating : vehSales * vehCO2Rating;

                if (vehRevision.HevType == HEVType.BatteryElectric) {
                    vehStats.BevSales += vehSales;
                    vehStats.CO2BevSales += vehSalesEPA;
                }
            }

            // prevent FE/CO2 decreases below zero
            CapVehicleStats(stats.VehicleStats);
        }

        private static void AggregateVehicleStats(
            Scenario scen, int year, IManufacturer mfr, TechSolution techSolution, ModelingSettings settings,
            ComplianceStatistics stats, bool round = true)
        {
            var testVehs = techSolution.TestVehicles;

            for (int i = 0; i < testVehs.Count; i++) {
                var newTestVeh = testVehs[i];
                var oldTestVeh = newTestVeh.PreviousState;
                double newSales = newTestVeh.Sales;
                double oldSales = oldTestVeh.Sales;

                if (oldSales == 0 && newSales == 0) {
                    continue;
                }

                //----->
                // calc vehicle-level compliance variables (FE/CO2 targets and ratings, off-cycle, etc.)
                //  - calculate for the **OLD** and **NEW** vehicle states
                //  - then, aggregate as the differences between the two
                //----->
                //--- calc vehicle-level compliance variables for the **OLD** vehicle state ---
                double oldFETarget  = GetTarget                 (scen, year, oldTestVeh, round);
                double oldCO2Target = GetVehicleCO2Target       (scen, year, oldTestVeh, oldFETarget, round);
                double oldFE2Cycle  = GetAverageVehicleFERating (scen, year, oldTestVeh, settings, false, round);
                double oldFERating  = GetAverageVehicleFERating (scen, year, oldTestVeh, settings, true, round);
                double oldCO2Rating = GetAverageVehicleCO2Rating(scen, year, oldTestVeh, settings, oldCO2Target, round);

                //--- calc vehicle-level compliance variables for the **NEW** vehicle state ---
                double newFETarget  = GetTarget                 (scen, year, newTestVeh, round);
                double newCO2Target = GetVehicleCO2Target       (scen, year, newTestVeh, newFETarget, round);
                double newFE2Cycle  = GetAverageVehicleFERating (scen, year, newTestVeh, settings, false, round);
                double newFERating  = GetAverageVehicleFERating (scen, year, newTestVeh, settings, true, round);
                double newCO2Rating = GetAverageVehicleCO2Rating(scen, year, newTestVeh, settings, newCO2Target, round);

                //--- update mfr-level aggregation values ---
                RC vehRC = newTestVeh.RegClass;
                var vehStats = stats.VehicleStats[vehRC];
                int epaMode = mfr.BaselineInfo.EPAMultiplierMode[vehRC];

                var scenInfo = scen[vehRC, year];
                double newSalesEPA = newSales * scenInfo.GetEpaMultiplier(newTestVeh.FuelType);
                double oldSalesEPA = oldSales * scenInfo.GetEpaMultiplier(oldTestVeh.FuelType);

                // note: we do not presently capture change in veh-sales from tech application
                //vehStats.Sales   += newSales - oldSales;
                vehStats.FETarget  += newSales * newFETarget - oldSales * oldFETarget;
                vehStats.FE2Cycle  += newSales / newFE2Cycle - oldSales / oldFE2Cycle;
                vehStats.FERating  += newSales / newFERating - oldSales / oldFERating;
                vehStats.CO2Sales  += newSalesEPA - oldSalesEPA;
                vehStats.CO2Target += (epaMode >= 2) ? newSalesEPA * newCO2Target - oldSalesEPA * oldCO2Target
                                                     : newSales    * newCO2Target - oldSales    * oldCO2Target;
                vehStats.CO2Rating += (epaMode >= 1) ? newSalesEPA * newCO2Rating - oldSalesEPA * oldCO2Rating
                                                     : newSales    * newCO2Rating - oldSales    * oldCO2Rating;

                if (oldTestVeh.HevType != newTestVeh.HevType) {
                    if (newTestVeh.HevType == HEVType.BatteryElectric) {
                        vehStats.BevSales += newSales;
                        vehStats.CO2BevSales += newSalesEPA;
                    }
                    else if (oldTestVeh.HevType == HEVType.BatteryElectric) {
                        vehStats.BevSales -= newSales;
                        vehStats.CO2BevSales -= newSalesEPA;
                    }
                }
            }

            // prevent FE/CO2 decreases below zero
            CapVehicleStats(stats.VehicleStats);
        }

        /// <summary>
        /// This method ensures that the FE/CO2 values accumulated across all vehicles do not go below 0 (zero).
        /// <br/><br/>
        /// Since we adjust these values for a subset of vehicles when testing compliance (for performance reasons we
        /// do not want to iterate all vehicles each time), it is possible that in certain cases the drift in numerical
        /// precision could set some values below zero.
        /// <br/><br/>
        /// We want to call this method at the end of each "AggregateVehicleStats" method above.
        /// </summary>
        private static void CapVehicleStats(RCVehicleStatistics vehStats)
        {
            foreach (var vehStat in vehStats.Items) {
                vehStat.FETarget = Math.Max(0, vehStat.FETarget);
                vehStat.FE2Cycle = Math.Max(0, vehStat.FE2Cycle);
                vehStat.FERating = Math.Max(0, vehStat.FERating);
                vehStat.CO2Target = Math.Max(0, vehStat.CO2Target);
                vehStat.CO2Rating = Math.Max(0, vehStat.CO2Rating);
            }
        }

        private static void FinalizeCompliance(
            Scenario scen, int year, IManufacturer mfr, ModelingSettings settings, ComplianceStatistics stats,
            bool round = true, bool testCreditValue = false)
        {
            // CAFE program aggregates data by: DC, IC, LT, HDPUV
            foreach (RC regClass in RegClasses.List) {
                double sales = stats.VehicleStats[regClass].Sales;

                if (sales > 0) {
                    FinalizeCAFEComplianceCalcs(scen, year, mfr, regClass, settings, stats, round, testCreditValue);
                }
            }

            // CO-2 program aggregates data by: PC, LT, HDPUV
            foreach (RC regClass in RegClasses.AltList) {
                double sales = (regClass == RC.AllPassengerCar)
                    ? stats.VehicleStats[RC.DomesticCar].Sales + stats.VehicleStats[RC.ImportedCar].Sales
                    : stats.VehicleStats[regClass].Sales;

                if (sales > 0) {
                    FinalizeCO2ComplianceCalcs(scen, year, mfr, regClass, settings, stats, round, testCreditValue);
                }
            }
        }

        private static void FinalizeCAFEComplianceCalcs(
            Scenario scen, int year, IManufacturer mfr, RC regClass, ModelingSettings settings,
            ComplianceStatistics stats, bool round, bool testCreditValue)
        {
            var scenInfo = scen[regClass, year];
            var vehStats = stats.VehicleStats[regClass];
            double mfrSales = vehStats.Sales;
            double mfrBevSales = vehStats.BevSales;

            // standard
            //  - calculate "preliminary" standard first
            //  - then (in the case of DC reg-class), adjust for "minimum" level to get "final" standard
            double standard = 0, pStandard = 0;
            if (vehStats.FETarget != 0) {
                // if vehStats.FETarget is zero, that means the supplied target function was invalid for this RC
                standard = mfrSales / vehStats.FETarget;
                pStandard = RoundFE(standard, regClass); // save prelim-stnd before adjusting

                // for DC reg-class, check if the scenario uses "alternative minimum standards"
                if (regClass.IsDC()) {
                    double mdpcStandard = mfr.Industry.MdpcStandard[year];
                    if (standard < mdpcStandard) {
                        standard = mdpcStandard;
                    }
                }
                standard = RoundFE(standard, regClass);
            }

            // CAFE ratings
            // for CAFE, calculate it twice:
            //  - CAFE 2-cycle -- does not include any credits or adjustments (used for reference)
            //  - CAFE rating -- includes all external credits and adjustments (used for compliance)
            double cafe2Cycle = mfrSales / vehStats.FE2Cycle;

            var crAdjInfo = mfr.CreditsAndAdjustments[regClass, year];
            var offCycleCredits = OffCycleTechEvaluator.CalcWeightedOffCycleCredits(
                crAdjInfo, scenInfo.OffCycleCafeCaps, scenInfo.OffCycleCafeBevCaps, mfrSales, mfrBevSales);
            double cafeRating = scenInfo.CO2Factor
                              / (scenInfo.CO2Factor / (mfrSales / vehStats.FERating + crAdjInfo.FFVCredits)
                                 - offCycleCredits.Total);
            if (round) {
                cafeRating = RoundFE(cafeRating, regClass);
            }

            // get the "assumed lifetime VMT" for credit calculations
            double vmt = GetVMT(year, regClass, settings);

            // credits k.Gal
            //  - credits in thousands of gallons for each regulatory class, based on the lifetime VMT
            //  - credits k.Gal are rounded to nearest whole number (optional)
            //  - needed for eff-cost metric
            // credits
            //  - LD credits are denominated in vehicle-mpg
            //  - HDPUV credits are in gallons, but rating and standard are in mpg -- so, convert both to gpm
            //    and multiply by the assumed lifetime VMT to obtain gallons (we're re-using creditsKGal)
            //  - for LD and HDPUV, credits are rounded to nearest whole number (optional)
            double creditsKGal = (1 / standard - 1 / cafeRating) * vmt / 1000 * mfrSales;
            double credits = regClass.IsHDPUV()
                           ? creditsKGal * 1000
                           : (cafeRating - standard) * 10 * mfrSales;
            if (round) {
                creditsKGal = Math.Round(creditsKGal);
                credits = Math.Round(credits);
            }

            // credit value and fines
            //  - if testCreditValue is true (that is testing impact of new tech), only consider credits earned
            //  - if testCreditValue is false (that is calc compliance), consider net credits (earned +/- in/out)
            double netCredits  = (testCreditValue)
                               ? credits
                               : credits + stats.CreditsIn[regClass] - stats.CreditsOut[regClass];
            double creditValue = netCredits * scenInfo.CreditValue;
            double fines = Math.Max(netCredits * -scenInfo.FineRate, 0);

            // update all CAFE compliance values
            stats.SetCAFEComplianceValues(
                regClass, pStandard, standard, cafe2Cycle, cafeRating,
                credits, creditsKGal, creditValue, fines, offCycleCredits);
        }

        private static void FinalizeCO2ComplianceCalcs(
            Scenario scen, int year, IManufacturer mfr, RC regClass, ModelingSettings settings,
            ComplianceStatistics stats, bool round, bool testCreditValue)
        {
            int epaMode = mfr.BaselineInfo.EPAMultiplierMode[regClass];
            var scenInfo = scen[regClass, year];
            var vehStats = stats.VehicleStats[regClass];

            double mfrSales = vehStats.Sales;
            double mfrBevSales = vehStats.BevSales;
            double co2MfrSales = vehStats.CO2Sales;
            double co2MfrBevSales = vehStats.CO2BevSales;

            // standard
            double co2Standard = 0;
            if (vehStats.CO2Target != 0) {
                // if vehStats.CO2Target is zero, that means the supplied target function was invalid for this RC
                co2Standard = Math.Round(vehStats.CO2Target / (epaMode >= 2 ? co2MfrSales : mfrSales));
            }

            // CO2 rating
            var crAdjInfo = mfr.CreditsAndAdjustments[regClass, year];
            var offCycleCredits = OffCycleTechEvaluator.CalcWeightedOffCycleCredits(
                crAdjInfo, scenInfo.OffCycleCO2Caps, scenInfo.OffCycleCO2BevCaps,
                (epaMode >= 1) ? co2MfrSales : mfrSales,
                (epaMode >= 1) ? co2MfrBevSales : mfrBevSales);
            double co2Rating = vehStats.CO2Rating / (epaMode >= 1 ? co2MfrSales : mfrSales)
                             - offCycleCredits.Total;
            if (round) {
                co2Rating = Math.Round(co2Rating);
            }

            // get the "assumed lifetime VMT" for credit calculations
            double vmt = GetVMT(year, regClass, settings);

            // credits
            //  - CO2 credits are denominated in metric-tons, but the standard and rating are in grams/mile
            //  - hence, multiply by the assumed lifetime VMT, then divide by 1e6 to obtain metric-tons
            //  - credits are rounded to nearest whole number (optional)
            double co2CrSales = (epaMode == 3) ? co2MfrSales : mfrSales;
            double co2Credits = (co2Standard - co2Rating) * vmt / 1e6 * co2CrSales;
            if (round) {
                co2Credits = Math.Round(co2Credits);
            }

            // credit value
            //  - if testCreditValue is true (that is testing impact of new tech), only consider credits earned
            //  - if testCreditValue is false (that is calc compliance), consider net credits (earned +/- in/out)
            double co2NetCredits  = (testCreditValue)
                                  ? co2Credits
                                  : co2Credits + stats.CO2CreditsIn[regClass] - stats.CO2CreditsOut[regClass];
            double co2CreditValue = co2NetCredits * scenInfo.CO2CreditValue;

            // update all CO2 compliance values
            stats.SetCO2ComplianceValues(
                regClass, co2Standard, co2Rating, co2Credits, co2CreditValue, offCycleCredits);
        }

        /// <summary>
        /// Determines whether the technology solution adversely affects the compliance state of a manufacturer for the
        /// given compliance program and between the specific range of model years.
        /// </summary>
        /// <param name="scen">The compliance scenario being analyzed.</param>
        /// <param name="startYear">The first model year to test.</param>
        /// <param name="endYear">The last model year to test.</param>
        /// <param name="mfr">
        ///     The manufacturer for which to determine the impact of a technology solution on compliance.
        /// </param>
        /// <param name="techSolutions">
        ///     An array of technology solutions containing the impacts from application of pending technologies on one
        ///     or more vehicle models, during one or more model years.
        /// </param>
        /// <param name="settings">The modeling settings used for analysis.</param>
        /// <param name="program">
        ///     The compliance program for which to determine the impact of a technology solution on compliance.
        /// </param>
        /// <returns>
        /// <c>true</c>, if the compliance state of a manufacturer may be adversely affected by application of a
        /// technology solution in at least one of the specified model years; <c>false</c>, if the compliance state of
        /// a manufacturer will not be adversely affected for all model years.
        /// </returns>
        public static bool HasComplianceDecreased(
            Scenario scen, int startYear, int endYear, IManufacturer mfr, TechSolution[] techSolutions,
            ModelingSettings settings, ComplianceProgram program)
        {
            RC[] regClasses = { RC.AllPassengerCar, RC.DomesticCar, RC.ImportedCar, RC.LightTruck, RC.HDPUV };

            bool isCAFE = program.HasCAFE();
            bool isCO2  = program.HasCO2();
            var  mfrVehStats = new RCVehicleStatistics();

            for (int i = startYear; i <= endYear; i++) {
                var techSolution = techSolutions[i - startYear];
                var testVehs     = techSolution.TestVehicles;
                var year         = techSolution.ModelYear;
                var mfrRevision  = mfr.GetRevision(year);
                var mfrStats     = mfrRevision.ComplianceStats;
                mfrStats.VehicleStats.CopyTo(mfrVehStats);

                //--- aggregate affected vehicles ---
                for (int j = 0; j < testVehs.Count; j++) {
                    // get general vehicle data
                    var newTestVeh = testVehs[j];
                    var oldTestVeh = newTestVeh.PreviousState;
                    RC  vehRC      = newTestVeh.RegClass;

                    // get ref to vehicle stats for the current reg-class
                    var vehStats = mfrVehStats[vehRC];

                    double oldSales = oldTestVeh.Sales;
                    double newSales = newTestVeh.Sales;
                    bool isHevTypeChanged = (oldTestVeh.HevType != newTestVeh.HevType);

                    // calculate CAFE-related values
                    if (isCAFE) {
                        double oldFERating = GetAverageVehicleFERating(scen, year, oldTestVeh, settings);
                        double newFERating = GetAverageVehicleFERating(scen, year, newTestVeh, settings);

                        vehStats.FERating += newSales / newFERating - oldSales / oldFERating;

                        if (isHevTypeChanged) {
                            if (newTestVeh.HevType == HEVType.BatteryElectric) {
                                vehStats.BevSales += newSales;
                            }
                            else if (oldTestVeh.HevType == HEVType.BatteryElectric) {
                                vehStats.BevSales -= newSales;
                            }
                        }
                    }

                    // calculate CO2-related values
                    if (isCO2) {
                        var    scenInfo     = scen[vehRC, year];
                        double oldCO2Rating = GetAverageVehicleCO2Rating(scen, year, oldTestVeh, settings);
                        double newCO2Rating = GetAverageVehicleCO2Rating(scen, year, newTestVeh, settings);
                        double oldSalesEPA  = oldSales * scenInfo.GetEpaMultiplier(oldTestVeh.FuelType);
                        double newSalesEPA  = newSales * scenInfo.GetEpaMultiplier(newTestVeh.FuelType);

                        vehStats.CO2Sales  += newSalesEPA - oldSalesEPA;
                        vehStats.CO2Rating += newSalesEPA * newCO2Rating - oldSalesEPA * oldCO2Rating;

                        if (isHevTypeChanged) {
                            if (newTestVeh.HevType == HEVType.BatteryElectric) {
                                vehStats.CO2BevSales += newSalesEPA;
                            }
                            else if (oldTestVeh.HevType == HEVType.BatteryElectric) {
                                vehStats.CO2BevSales -= newSalesEPA;
                            }
                        }
                    }
                } // next j (veh)

                //--- calc and check if CAFE or CO2 ratings decreased ---
                // scan all reg-classes applicable to CAFE and CO2 (PC,DC,IC,LT,HDPUV)
                foreach (RC regClass in regClasses) {
                    double mfrSales = (regClass == RC.AllPassengerCar)
                        ? mfrVehStats[RC.DomesticCar].Sales + mfrVehStats[RC.ImportedCar].Sales
                        : mfrVehStats[regClass].Sales;

                    // only consider the current reg-class if mfr produces vehicles for this reg-class
                    if (mfrSales > 0) {
                        var crAdjInfo = mfr.CreditsAndAdjustments[regClass, year];
                        var vehStats = mfrVehStats[regClass];
                        var scenInfo = scen[regClass, year];

                        // calculate CAFE-related values
                        if (isCAFE && (regClass != RC.AllPassengerCar)) {
                            var offCycleCredits = OffCycleTechEvaluator.CalcWeightedOffCycleCredits(
                                crAdjInfo, scenInfo.OffCycleCafeCaps, scenInfo.OffCycleCafeBevCaps,
                                mfrSales, vehStats.BevSales);
                            double ffvCredits = crAdjInfo.FFVCredits;
                            double cafeRating = scenInfo.CO2Factor
                                              / (scenInfo.CO2Factor / (mfrSales / vehStats.FERating + ffvCredits)
                                                 - offCycleCredits.Total);

                            if (Math.Round(cafeRating, 1) < Math.Round(mfrStats.CAFERating[regClass], 1)) {
                                return true;
                            }
                        }

                        // calculate CO2-related values
                        if (isCO2 && (regClass != RC.DomesticCar) && (regClass != RC.ImportedCar)) {
                            var offCycleCredits = OffCycleTechEvaluator.CalcWeightedOffCycleCredits(
                                crAdjInfo, scenInfo.OffCycleCO2Caps, scenInfo.OffCycleCO2BevCaps,
                                vehStats.CO2Sales, vehStats.CO2BevSales);
                            double co2Rating = vehStats.CO2Rating / vehStats.CO2Sales - offCycleCredits.Total;

                            if (Math.Round(mfrStats.CO2Rating[regClass]) < Math.Round(co2Rating)) {
                                return true;
                            }
                        }
                    }
                } // next regClass
            } // next i (model year)

            // all passed
            return false;
        }

        #endregion

        #region <<< Industry compliance calculations >>>

        /// <summary>
        /// Calculates the minimum domestic passenger car (MDPC) standard, in miles per gallon (mpg), during the
        /// specified model year, based on all passenger car vehicles contained within the provided industry
        /// <paramref name="data"/>.
        /// </summary>
        /// <param name="scen">The compliance scenario being analyzed.</param>
        /// <param name="year">The model year being analyzed.</param>
        /// <param name="data">
        ///     The industry data for which to calculate the minimum domestic passenger car (MDPC) standard.
        /// </param>
        /// <returns>
        /// The minimum domestic passenger car (MDPC) standard for the industry during the specified model year.
        /// </returns>
        public static double CalcMdpcStandard(Scenario scen, int year, IIndustry data)
        {
            double avgPCStandard = CalcAveragePCStandard(scen, year, data);

            var scenInfo = scen[RC.DomesticCar, year];
            double minStndMpg = scenInfo.MinStndMpg;
            double minStndPct = scenInfo.MinStndPct * avgPCStandard;
            double mdpcStandard = (minStndMpg != 0 && minStndPct != 0)
                                ? Math.Max(minStndMpg, minStndPct)
                                : (minStndMpg != 0) ? minStndMpg : minStndPct;

            return mdpcStandard;
        }

        private static double CalcAveragePCStandard(Scenario scen, int year, IIndustry data)
        {
            double salesSum = 0;
            double targetSum = 0;

            foreach (var veh in data.Vehicles) {
                var vehRevision = veh.GetRevision(year);
                if (!vehRevision.RegClass.IsPC()) {
                    continue;
                }

                double vehSales = vehRevision.Sales;
                salesSum += vehSales;
                targetSum += vehSales * GetTarget(scen, year, vehRevision);
            }

            double avgPCStandard = (targetSum == 0) ? 0 : salesSum / targetSum;
            return avgPCStandard;
        }

        #endregion

        #region <<< ZEV compliance calculations >>>

        /// <summary>
        /// Calculates the ZEV compliance information -- specifically, the ZEV target and credits -- for the entire
        /// industry.
        /// </summary>
        /// <param name="scen">The compliance scenario being analyzed.</param>
        /// <param name="year">The model year being analyzed.</param>
        /// <param name="data">The industry object for which to calculate the ZEV compliance information.</param>
        /// <param name="regClass">The regulatory class for which to calculate the ZEV target.</param>
        /// <param name="settings">The modeling settings used for analysis.</param>
        /// <returns>
        /// The industry-wide ZEV compliance information.
        /// </returns>
        /// <inheritdoc cref="CalcZevCompliance(Scenario, int, IManufacturer, RC, ModelingSettings)" path="/remarks"/>
        public static ZevCompliance CalcZevCompliance(
            Scenario scen, int year, IIndustry data, RC regClass, ModelingSettings settings)
        {
            double zevTarget = 0;
            double zevCredits = 0;
            foreach (var mfr in data.Manufacturers) {
                CalcZevCompliance(year, mfr, regClass, settings, out double target, out double credits);
                zevTarget += target;
                zevCredits += credits;
            }

            return new ZevCompliance {
                Target = zevTarget,
                Credits = zevCredits
            };
        }

        /// <summary>
        /// Calculates the ZEV compliance information -- specifically, the ZEV target and credits -- for the specified
        /// manufacturer.
        /// </summary>
        /// <param name="scen">The compliance scenario being analyzed.</param>
        /// <param name="year">The model year being analyzed.</param>
        /// <param name="mfr">The manufacturer for which to calculate the ZEV compliance information.</param>
        /// <param name="regClass">The regulatory class for which to calculate the ZEV target.</param>
        /// <param name="settings">The modeling settings used for analysis.</param>
        /// <returns>
        /// The manufacturer's ZEV compliance information.
        /// </returns>
        /// <remarks>
        /// The ZEV compliance information may only be computed for the entire light-duty fleet, for the HDPUV fleet,
        /// or as a cumulative measure of both light-duty and HDPUV fleets. Use: <see cref="RC.AllLightDuty"/>,
        /// <see cref="RC.HDPUV"/>, or <see cref="RC.All"/> to obtain the desired values. For all other regulatory
        /// classes, a <see cref="ZevCompliance"/> object with zero values will be returned.
        /// </remarks>
        public static ZevCompliance CalcZevCompliance(
            Scenario scen, int year, IManufacturer mfr, RC regClass, ModelingSettings settings)
        {
            CalcZevCompliance(year, mfr, regClass, settings, out double zevTarget, out double zevCredits);

            return new ZevCompliance {
                Target = zevTarget,
                Credits = zevCredits
            };
        }

        private static void CalcZevCompliance(
            int year, IManufacturer mfr, RC regClass, ModelingSettings settings,
            out double zevTarget, out double zevCredits)
        {
            if (regClass == RC.AllLightDuty || regClass == RC.HDPUV) {
                CalcZevHelper(year, mfr, regClass, settings, out zevTarget, out zevCredits);
            }
            else if (regClass == RC.All) {
                CalcZevHelper(year, mfr, RC.AllLightDuty, settings, out double ldTarget, out double ldCredits);
                CalcZevHelper(year, mfr, RC.HDPUV, settings, out double hdpuvTarget, out double hdpuvCredits);

                zevTarget = ldTarget + hdpuvTarget;
                zevCredits = ldCredits + hdpuvCredits;
            }
            else {
                zevTarget = zevCredits = 0;
            }
        }

        private static void CalcZevHelper(
            int year, IManufacturer mfr, RC regClass, ModelingSettings settings,
            out double zevTarget, out double zevCredits)
        {
            var zevParams = settings.Parameters.ZEVCreditValues;
            var mfrRevision = mfr.GetRevision(year);
            var mfrBaseInfo = mfr.BaselineInfo;

            double mfrSales = mfrRevision.ComplianceStats.GetSales(regClass);
            double zevSales = mfrSales * mfrBaseInfo.GetZevSalesShare(regClass, year);

            // target
            double zevReqPct = zevParams.GetZevRequirement(year, regClass);
            zevTarget = zevSales * zevReqPct;

            // credits
            double phevCap = (!regClass.IsHDPUV() && year <= 2025 && mfrBaseInfo.IgnoreZevPhevCap)
                           ? int.MaxValue
                           : zevSales * zevParams.GetPhevCap(year, regClass);
            double phevCredits = 0;
            double evCredits = 0;

            // aggregate phev/bev/fcev credits
            foreach (IVehicle veh in mfr.Vehicles) {
                var vehRevision = veh.GetRevision(year);
                if (!regClass.Contains(vehRevision.RegClass)) {
                    continue;
                }

                double vehZevCredits = vehRevision.ZEVCredits * vehRevision.Sales;

                if (vehRevision.HevType == HEVType.PlugInHybrid) {
                    phevCredits += vehZevCredits;
                }
                else if (vehRevision.HevType == HEVType.FuelCell
                      || vehRevision.HevType == HEVType.BatteryElectric) {
                    evCredits += vehZevCredits;
                }
            }

            double zevCreditShare = mfrBaseInfo.GetZevCreditShare(regClass);
            zevCredits = Math.Min(phevCap, phevCredits * zevCreditShare)
                       + evCredits * zevCreditShare;
        }

        #endregion

        #region <<< Misc helper methods >>>

        /// <summary>
        /// Rounds the fuel economy value, based on the specified regulatory class assignment of a vehicle.
        /// NOTE: A zero check is not performed; if value is zero, unexpected results may be returned.
        /// </summary>
        internal static double RoundFE(double value, RC vehRC)
        {
            return vehRC.IsHDPUV()
                ? 100 / Math.Round(100 / value, 3)  // value is in mpg, but rounding is on a gal/100-mi basis
                : Math.Round(value, 1);
        }

        /// <summary>
        /// Rounds the fuel economy target, based on the specified regulatory class assignment of a vehicle.
        /// NOTE: A zero check is not performed; if value is zero, unexpected results may be returned.
        /// </summary>
        internal static double RoundFETarget(double value, RC vehRC)
        {
            return vehRC.IsHDPUV()
                ? Math.Round(value * 100, 3) / 100  // value is in gpm, but rounding is on a gal/100-mi basis
                : 1 / Math.Round(1 / value, 2);     // value is in gpm, but rounding is on mi/gal (mpg) basis
        }

        private static double GetVMT(int year, RC regClass, ModelingSettings settings)
        {
            // credit trading values define VMT by CAFE reg-clas (that is: DC, IC, LT)
            // for CO-2 ratings, DC/IC split does not exist --
            // hence, PC class maps to DC when grabbing VMT
            if (regClass == RC.AllPassengerCar) {
                regClass = RC.DomesticCar;
            }
            return settings.Parameters.CreditTradingValues.GetAdjustmentVMT(regClass, year);
        }

        /// <inheritdoc cref="MarketDataExtensions.GetWorkFactor(RC, double, double, double, DriveType, bool)"/>
        private static double GetWorkFactor(
            RC vehRC, double vehCW, double vehGVWR, double vehGCWR, DriveType vehDrive, bool round)
        {
            return MarketDataExtensions.GetWorkFactor(vehRC, vehCW, vehGVWR, vehGCWR, vehDrive, round);
        }

        #endregion
    }
}
