#region << Using Directives >>
using System;
using System.Collections;
using System.Collections.Generic;
using Volpe.Cafe;
using Volpe.Cafe.Data;
using Volpe.Cafe.Settings;
using RC = Volpe.Cafe.RegulatoryClass;
using Volpe.Cafe.Generic;
#endregion
namespace Volpe.Cafe.Model
{
    public sealed class Standards
    {
        #region 
        #region 
        public static RCDouble GetIndustryStandard(Industry data, Scenario scen, ModelYear year)
        {
            RCDouble standard = new RCDouble();
            foreach (RegulatoryClass regClass in RCValue<object>.Classes)
            {
                standard[regClass] = Standards.GetIndustryStandard(data, scen, year, regClass);
            }
            return standard;
        }
        public static double GetIndustryStandard(Industry data, Scenario scen, ModelYear year, RC regClass)
        {
            List<Manufacturer> mfrs = data.Manufacturers;
            double standardSum = 0, salesSum    = 0;
            for (int i = 0, mfrCount = mfrs.Count; i < mfrCount; i++)
            {   
                Manufacturer manufacturer = mfrs[i];
                double mfrSales = Standards.GetSales   (manufacturer,       year, regClass);
                double mfrStnd  = Standards.GetStandard(manufacturer, scen, year, regClass, null, 0);
                standardSum += (mfrSales == 0 || mfrStnd == 0) ? 0 : mfrSales / mfrStnd;
                salesSum    += mfrSales;
            }
            return (standardSum == 0) ? 0 : salesSum / standardSum;
        }
        public static void CalcStandard(Manufacturer mfr, Scenario scen, ModelYear year)
        {
            foreach (RegulatoryClass regClass in RCValue<object>.Classes)
            {
                if (mfr.ModelData.Sales[regClass] != 0)
                {
                    Standards.CalcStandard(mfr, scen, year, regClass);
                }
            }
        }
        public static void CalcStandard(Manufacturer mfr, Scenario scen, ModelYear year, RC regClass)
        {
            mfr.ModelData.Standard[regClass] = Standards.GetStandard(mfr, scen, year, regClass, null, 0);
        }
        public static double GetStandard(Manufacturer mfr, Scenario scen, ModelYear year, RC regClass, TechImpact[] techImpacts,
            int techImpactCount)
        {
            if (techImpactCount > 0 && (techImpacts == null || techImpacts.Length < techImpactCount))
            {   
                throw new ArgumentOutOfRangeException("techImpacts", "techImpacts is null or less than techImpactCount.");
            }
            Manufacturer.CModelData mmd = mfr.ModelData;
            double standard = 0;
            int yrIndex = year.Index;
            int      fncType;
            double[] fncCoef;
            int numCoeff = Standards.GetCoefficients(regClass, scen, year, out fncType, out fncCoef);
            if (fncType == 0 || fncCoef == null || regClass == RC.None)
            {   
                standard = 0;
            }
            else if (fncType == 1)
            {   
                standard = fncCoef[0];
            }
            else if (fncType == 99)
            {   
                standard = mmd.DefaultStandard[yrIndex][regClass];
            }
            else
            {   
                double prevStandard = mmd.PreliminaryStandard[regClass];
                bool recalcStandard = (prevStandard == 0 || double.IsNaN(prevStandard) || techImpactCount == 0);
                if (!recalcStandard)
                {
                    if (FunctionInformation.IsAreaBasedFunction(fncType))
                    {   
                        for (int i = 0; i < techImpactCount; i++)
                        {
                            if (!techImpacts[i].Ignore &&
                                 techImpacts[i].HasImpact          [yrIndex] &&
                                 techImpacts[i].HasFootprintChanged(yrIndex)) { recalcStandard = true; break; }
                        }
                    }
                    else if (FunctionInformation.IsWeightBasedFunction(fncType))
                    {   
                        for (int i = 0; i < techImpactCount; i++)
                        {
                            if (!techImpacts[i].Ignore &&
                                 techImpacts[i].HasImpact       [yrIndex] &&
                                 techImpacts[i].HasWeightChanged(yrIndex)) { recalcStandard = true; break; }
                        }
                    }
                    else if (FunctionInformation.IsWorkFactorBasedFunction(fncType))
                    {   
                        for (int i = 0; i < techImpactCount; i++)
                        {
                            if (!techImpacts[i].Ignore &&
                                 techImpacts[i].HasImpact         [yrIndex] &&
                                (techImpacts[i].HasFuelTypeChanged(yrIndex) ||
                                 techImpacts[i].HasWeightChanged  (yrIndex) ||
                                 techImpacts[i].HasGVWRChanged    (yrIndex) ||
                                 techImpacts[i].HasGCWRChanged    (yrIndex))) { recalcStandard = true; break; }
                        }
                    }
                }
                if (recalcStandard)
                {   
                    double sales  = 0;      
                    double stdSum = 0;      
                    List<Vehicle> vehs = mfr.Vehicles;
                    for (int i = 0, vehCount = vehs.Count; i < vehCount; i++)
                    {
                        Vehicle              veh      = vehs[i];
                        Vehicle.CDescription vd       = veh.Description;
                        double               vehSales = vd.Sales[yrIndex];
                        if (vehSales != 0 && veh.RegClass == regClass)
                        {
                            double target = 0;
                            int idx = -1;
                            for (int j = 0; j < techImpactCount; j++)
                            {
                                if (!techImpacts[j].Ignore && veh == techImpacts[j].Vehicle[yrIndex]) { idx = j; break; }
                            }
                            target = Standards.GetTarget(veh, scen, year,
                                (idx == -1) ? FuelType.None : techImpacts[idx].NewFuelShare[yrIndex].FuelType,
                                (idx == -1) ? 0             : vd.Footprint,
                                (idx == -1) ? 0             : vd.CurbWeight + techImpacts[idx].DeltaCurbWeight[yrIndex],
                                (idx == -1) ? 0             : vd.GVWR       + techImpacts[idx].DeltaGVWR      [yrIndex],
                                (idx == -1) ? 0             : vd.GCWR       + techImpacts[idx].DeltaGCWR      [yrIndex],
                                (idx == -1) ? '\0'          : vd.Drive,
                                fncType, fncCoef);
                            sales  += vehSales;
                            stdSum += vehSales * target;
                        } 
                    } 
                    standard = (sales == 0) ? 0 : sales / stdSum;
                }
                else
                {   
                    standard = prevStandard;
                }
            }
            if (techImpactCount == 0)
            {   
                mmd.PreliminaryStandard[regClass] = RoundFE(standard, regClass);
            }
            if (standard < scen.ScenInfo[regClass].MinStndMpg[yrIndex]) { standard = scen.ScenInfo[regClass].MinStndMpg[yrIndex]; }
            double pctIndStnd = scen.ScenInfo[regClass].MinStndPct[yrIndex] * mmd.AverageStandard[regClass];
            if (standard < pctIndStnd) { standard = pctIndStnd; }
            return RoundFE(standard, regClass);
        }
        public static double GetTarget(Vehicle veh, Scenario scen, ModelYear year)
        {
            return Standards.GetTarget(veh, scen, year, FuelType.None, 0, 0, 0, 0, '\0', 0, null);
        }
        public static double GetTarget(Vehicle veh, Scenario scen, ModelYear year,
            FuelType fuel, double area, double weight, double gvwr, double gcwr, char drive,
            int fncType, double[] fncCoef)
        {
            if (fuel    == FuelType.None) { fuel   = veh.Description.FuelShare.FuelType; }
            if (area    <= 0            ) { area   = veh.Description.Footprint ; }
            if (weight  <= 0            ) { weight = veh.Description.CurbWeight; }
            if (gvwr    <= 0            ) { gvwr   = veh.Description.GVWR      ; }
            if (gcwr    <= 0            ) { gcwr   = veh.Description.GCWR      ; }
            if (drive   == '\0'         ) { drive  = veh.Description.Drive     ; }
            if (fncType == 0 || fncCoef == null)
            {   
                Standards.GetCoefficients(veh.RegClass, scen, year, out fncType, out fncCoef);
            }
            return Standards.GetTarget(veh, scen, year, fuel, area, weight, gvwr, gcwr, drive, fncType, fncCoef[0], fncCoef[1],
                fncCoef[2], fncCoef[3], fncCoef[4], fncCoef[5], fncCoef[6], fncCoef[7], fncCoef[8], fncCoef[9]);
        }
        public static double GetTarget(Vehicle veh, Scenario scen, ModelYear year,
            FuelType fuel, double area, double weight, double gvwr, double gcwr, char drive,
            int fncType, double A, double B, double C, double D, double E, double F, double G, double H, double I, double J)
        {
            double target = 0.0, invA = 1.0 / A, invB = 1.0 / B;
            double exp;
            double t1, t2;
            switch (fncType)
            {
                case 1:
                    target = invA;
                    break;
                case 2:     
                case 3:     
                    exp    = Math.Exp((((fncType == 2) ? area : weight) - C) / D);
                    target = invA + (invB - invA) * (exp / (1.0 + exp));
                    break;
                case 4:     
                case 5:     
                    exp    = Math.Exp(1.0 - ((fncType == 4) ? area : weight) / C);
                    target = invA - exp * invB;
                    break;
                case 6:     
                case 7:     
                    target = C * ((fncType == 6) ? area : weight) + D;                  
                    target = (target < invA) ? invA : (target > invB) ? invB : target;  
                    break;
                case 8:     
                    bool isDSL = ((fuel & FuelType.Diesel     ) == FuelType.Diesel ||
                                  (fuel & FuelType.Biodiesel20) == FuelType.Biodiesel20);
                    bool isCNG = ((fuel & FuelType.CNG        ) == FuelType.CNG);
                    double workFactor = Standards.GetWorkFactor(veh, scen, year, weight, gvwr, gcwr, drive, A, B);
                    target = ((isCNG) ? (G * workFactor + H) :                          
                              (isDSL) ? (E * workFactor + F) :                          
                                        (C * workFactor + D)                            
                             ) / 100;                                                   
                    break;
                case 206:   
                case 207:   
                    t1 = C * ((fncType == 206) ? area : weight) + D;                    
                    t1 = (t1 < invA) ? invA : (t1 > invB) ? invB : t1;                  
                    double invE = 1.0 / E, invF = 1.0 / F;
                    t2 = G * ((fncType == 206) ? area : weight) + H;                    
                    t2 = (t2 < invE) ? invE : (t2 > invF) ? invF : t2;                  
                    target = (t1 < t2) ? t1 : t2;
                    break;
                case 208:   
                    t1 = Standards.GetTarget(veh, scen, year, fuel, area, weight, gvwr, gcwr, drive, 8, A, B, C, D, E, F, G, H, I, J);
                    t2 = Standards.GetTarget(veh, scen, new ModelYear((int)I), fuel, area, weight, gvwr, gcwr, drive, 0, null);
                    target = (t1 < t2) ? t1 : t2;
                    break;
            }
            return target;
        }
        public static RC GetRegClass(Vehicle vehicle)
        {
            switch (vehicle.Description.RegulatoryIndicator)
            {
                case "PC"   : return RC.PassengerCar;
                case "LT"   : return RC.LightTruck;
                case "LT2B3": return RC.LightTruck2b3;
                default     : return RC.None;
            }
        }
        public static double GetWorkFactor(Vehicle veh, Scenario scen, ModelYear year)
        {
            int      fncType;
            double[] fncCoef;
            Standards.GetCoefficients(veh.RegClass, scen, year, out fncType, out fncCoef);
            if (FunctionInformation.IsWorkFactorBasedFunction(fncType))
            {
                double xwdCoef = fncCoef[0];
                double mltCoef = fncCoef[1];
                Vehicle.CDescription vd = veh.Description;
                return GetWorkFactor(veh, scen, year, vd.CurbWeight, vd.GVWR, vd.GCWR, vd.Drive, xwdCoef, mltCoef);
            }
            else { return 0; }
        }
        static double GetWorkFactor(Vehicle veh, Scenario scen, ModelYear year,
            double curbWeight, double gvwr, double gcwr, char drive, double xwdCoef, double mltCoef)
        {
            double xwd = (drive == '4') ? xwdCoef : 0;
            return (gvwr - curbWeight + xwd) * mltCoef + (gcwr - gvwr) * (1 - mltCoef);
        }
        public static double GetTestWeight(Vehicle veh, Scenario scen, ModelYear year)
        {
            return Standards.GetTestWeight(veh, scen, year, 0, 0, 0, 0);
        }
        public static double GetTestWeight(Vehicle veh, Scenario scen, ModelYear year,
            double weight, double gvwr, double gcwr,
            int twFunction)
        {
            if (weight <= 0) { weight = veh.Description.CurbWeight; }
            if (gvwr   <= 0) { gvwr   = veh.Description.GVWR      ; }
            if (gcwr   <= 0) { gcwr   = veh.Description.GCWR      ; }
            if (twFunction <= 0)
            {
                RegulatoryClass regClass = (veh.RegClass == RegulatoryClass.None) ? Standards.GetRegClass(veh) : veh.RegClass;
                if (regClass == RegulatoryClass.None) { return 0; }
                twFunction = scen.ScenInfo[regClass].TWFunction[year.Index];
                if (twFunction == 0)
                {
                    twFunction =
                        (regClass == RegulatoryClass.PassengerCar ) ? 1 :
                        (regClass == RegulatoryClass.LightTruck   ) ? 2 :
                        (regClass == RegulatoryClass.LightTruck2b3) ? 3 : 0;
                }
            }
            double testWeight = 0;
            switch (twFunction)
            {
                case 1:
                case 2:
                case 3:
                    double w = (twFunction == 3) ? (weight + gvwr) / 2 : weight + 300;
                    double r = (w > 5500) ? 500 : (w > 4000) ? 250 : 125;
                    testWeight = Math.Max(1000, Math.Ceiling(w / r - 0.5) * r);
                    if (twFunction == 1 && testWeight > 5500) { testWeight = 5500; }
                    break;
            }
            return testWeight;
        }
        #endregion
        #region 
        public static RCDouble GetSales(Manufacturer mfr, ModelYear year)
        {
            RCDouble sales = new RCDouble();
            foreach (RegulatoryClass regClass in RCValue<object>.Classes)
            {
                sales[regClass] = Standards.GetSales(mfr, year, regClass);
            }
            return sales;
        }
        public static double GetSales(Manufacturer mfr, ModelYear year, RC regClass)
        {
            return Standards.GetSales(mfr.Vehicles, year, regClass);
        }
        public static double GetSales(List<Vehicle> vehs, ModelYear year, RC regClass)
        {
            double sales = 0;
            int yrIndex = year.Index;
            for (int i = 0, vehCount = vehs.Count; i < vehCount; i++)
            {
                if (vehs[i].RegClass == regClass) { sales += vehs[i].Description.Sales[yrIndex]; }
            }
            return sales;
        }
        #endregion
        #region 
        public static void CalcFines(Manufacturer mfr, Scenario scen, ModelYear year, ModelingSettings settings)
        {
            foreach (RegulatoryClass regClass in RCValue<object>.Classes)
            {
                if (mfr.ModelData.Sales[regClass] != 0)
                {
                    Standards.CalcFines(mfr, scen, year, settings, regClass);
                }
            }
        }
        public static void CalcFines(Manufacturer mfr, Scenario scen, ModelYear year, ModelingSettings settings, RC regClass)
        {
            Manufacturer.CModelData mmd = mfr.ModelData;
            double credits     = mmd.Credits    [regClass];     
            double tCreditsIn  = mmd.TCreditsIn [regClass];     
            double tCreditsOut = mmd.TCreditsOut[regClass];     
            double netCredits  = credits + tCreditsIn - tCreditsOut;
            double fineRate    = scen.ScenInfo[regClass].FineRate[year.Index];  
            mmd.Fines[regClass] = (double.IsNaN(credits)) ? 0 : -fineRate * Math.Min(netCredits, 0);
        }
        public static RCDouble TestFines(Manufacturer mfr, Scenario scen, ModelYear year, ModelingSettings settings,
            RCDouble newCafe, TechImpact[] techImpacts, int techImpactCount)
        {
            RCDouble fines = new RCDouble();
            foreach (RegulatoryClass regClass in RCValue<object>.Classes)
            {
                if (mfr.ModelData.Sales[regClass] != 0)
                {
                    fines[regClass] = Standards.TestFines(mfr, scen, year, settings, regClass, newCafe[regClass], techImpacts, techImpactCount);
                }
            }
            return fines;
        }
        public static double TestFines(Manufacturer mfr, Scenario scen, ModelYear year, ModelingSettings settings, RC regClass,
            double newCafe, TechImpact[] techImpacts, int techImpactCount)
        {
            double credits = Standards.TestCredits(mfr, scen, year, settings, regClass, newCafe, techImpacts, techImpactCount);
            double fineRate = scen.ScenInfo[regClass].FineRate[year.Index];
            return -fineRate * Math.Min(credits, 0);
        }
        #endregion
        #region 
        public static void CalcCredits(Manufacturer mfr, Scenario scen, ModelYear year, ModelingSettings settings)
        {
            foreach (RegulatoryClass regClass in RCValue<object>.Classes)
            {
                if (mfr.ModelData.Sales[regClass] != 0)
                {
                    Standards.CalcCredits(mfr, scen, year, settings, regClass);
                }
            }
        }
        public static void CalcCredits(Manufacturer mfr, Scenario scen, ModelYear year, ModelingSettings settings, RC regClass)
        {
            Manufacturer.CModelData mmd = mfr.ModelData;
            double sales     = mmd.Sales   [regClass];
            double standard  = mmd.Standard[regClass];
            double cafe      = mmd.CAFE    [regClass];
            if (double.IsNaN(sales) || double.IsNaN(standard) || double.IsNaN(cafe)) { mmd.Credits[regClass] = 0; }
            else
            {   
                cafe += Standards.GetFFVCredits(mfr, scen, year, regClass);
                if (regClass == RegulatoryClass.LightTruck2b3)
                {
                    mmd.Credits[regClass] = Math.Round((100 / standard - Math.Round(100 / cafe, 2)) * sales * 100);
                }
                else
                {
                    mmd.Credits[regClass] = Math.Round((Math.Round(cafe, 1) - standard) * sales * 10);
                }
            }
        }
        public static RCDouble TestCredits(Manufacturer mfr, Scenario scen, ModelYear year, ModelingSettings settings,
            RCDouble newCafe, TechImpact[] techImpacts, int techImpactCount)
        {
            RCDouble credits = new RCDouble();
            foreach (RegulatoryClass regClass in RCValue<object>.Classes)
            {
                if (mfr.ModelData.Sales[regClass] != 0)
                {
                    credits[regClass] = Standards.TestCredits(mfr, scen, year, settings, regClass, newCafe[regClass], techImpacts, techImpactCount);
                }
            }
            return credits;
        }
        public static double TestCredits(Manufacturer mfr, Scenario scen, ModelYear year, ModelingSettings settings,
            RC regClass, double newCafe, TechImpact[] techImpacts, int techImpactCount)
        {
            Manufacturer.CModelData mmd = mfr.ModelData;
            double sales    = mmd.Sales[regClass];
            double standard = Standards.GetStandard(mfr, scen, year, regClass, techImpacts, techImpactCount);
            newCafe += Standards.GetFFVCredits(mfr, scen, year, regClass);
            if (regClass == RegulatoryClass.LightTruck2b3)
            {
                return (100 / standard - 100 / newCafe) * sales * 100;
            }
            else
            {
                return (newCafe - standard) * sales * 10;
            }
        }
        public static RCDouble TestCreditsValue(Manufacturer mfr, Scenario scen, ModelYear year, ModelingSettings settings,
            RCDouble newCafe, TechImpact[] techImpacts, int techImpactCount)
        {
            RCDouble credits = new RCDouble();
            foreach (RegulatoryClass regClass in RCValue<object>.Classes)
            {
                if (mfr.ModelData.Sales[regClass] != 0)
                {
                    credits[regClass] = Standards.TestCreditsValue(mfr, scen, year, settings, regClass, newCafe[regClass], techImpacts, techImpactCount);
                }
            }
            return credits;
        }
        public static double TestCreditsValue(Manufacturer mfr, Scenario scen, ModelYear year, ModelingSettings settings,
            RC regClass, double newCafe, TechImpact[] techImpacts, int techImpactCount)
        {
            double credits = Standards.TestCredits(mfr, scen, year, settings, regClass, newCafe, techImpacts, techImpactCount);
            double fineRate = scen.ScenInfo[regClass].FineRate[year.Index];
            return fineRate * Math.Max(credits, 0);
        }
        static double GetFFVCredits(Manufacturer mfr, Scenario scen, ModelYear year, RC regClass)
        {
            if (mfr.Description.CreditsApplyToBaseline || !scen.IsBaseline)
            {
                return mfr.Description.AvailableCredits[regClass][year.Index];
            }
            return 0;
        }
        #endregion
        #region 
        public static void CalcCAFE(Manufacturer mfr, Scenario scen, ModelYear year, ModelingSettings settings)
        {
            foreach (RegulatoryClass regClass in RCValue<object>.Classes)
            {
                if (mfr.ModelData.Sales[regClass] != 0)
                {
                    Standards.CalcCAFE(mfr, scen, year, settings, regClass);
                }
            }
        }
        public static void CalcCAFE(Manufacturer mfr, Scenario scen, ModelYear year, ModelingSettings settings, RC regClass)
        {
            double sales    = mfr.ModelData.Sales[regClass];
            double sofe     = 0;
            double sofe2Bag = 0;
            List<Vehicle> vehs = mfr.Vehicles;
            for (int i = 0, vehCount = vehs.Count; i < vehCount; i++)
            {
                Vehicle              veh      = vehs[i];
                Vehicle.CDescription vd       = veh.Description;
                double               vehSales = vd.Sales[year.Index];
                if (vehSales != 0 && veh.RegClass == regClass)
                {
                    double vehFE       = RoundFE(Standards.GetAverageFuelEconomy(veh, scen, year, settings, true, true , true ), regClass);
                    double vehFE2Bag   = RoundFE(Standards.GetAverageFuelEconomy(veh, scen, year, settings, true, false, false), regClass);
                    double vehSOFE     = vehSales / vehFE;
                    double vehSOFE2Bag = vehSales / vehFE2Bag;
                    sofe     += vehSOFE;
                    sofe2Bag += vehSOFE2Bag;
                    veh.ModelData.SalesOverFE = vehSOFE;
                } 
            } 
            Manufacturer.CModelData mmd = mfr.ModelData;
            mmd.SalesOverFE[regClass] =  sofe;
            mmd.CAFE       [regClass] = (sales == 0) ? 0 : sales / sofe;
            mmd.CAFE_2Bag  [regClass] = (sales == 0) ? 0 : sales / sofe2Bag;
        }
        public static RCDouble TestCAFE(Manufacturer mfr, Scenario scen, ModelYear year, ModelingSettings settings,
            TechImpact[] techImpacts, int techImpactCount, bool useRounding)
        {
            RCDouble cafe = new RCDouble();
            foreach (RegulatoryClass regClass in RCValue<object>.Classes)
            {
                if (mfr.ModelData.Sales[regClass] != 0)
                {
                    cafe[regClass] = Standards.TestCAFE(mfr, scen, year, settings, regClass, techImpacts, techImpactCount, useRounding);
                }
            }
            return cafe;
        }
        public static double TestCAFE(Manufacturer mfr, Scenario scen, ModelYear year, ModelingSettings settings, RC regClass,
            TechImpact[] techImpacts, int techImpactCount, bool useRounding)
        {
            if (techImpactCount > 0 && (techImpacts == null || techImpacts.Length < techImpactCount))
            {   
                throw new ArgumentOutOfRangeException("techImpacts", "techImpacts is null or less than techImpactCount.");
            }
            Manufacturer.CModelData mmd = mfr.ModelData;
            double sales = mmd.Sales      [regClass];
            double sofe  = mmd.SalesOverFE[regClass];
            for (int i = 0; i < techImpactCount; i++)
            {
                if (techImpacts[i].Ignore || !techImpacts[i].HasImpact[year.Index]) { continue; }
                Vehicle veh = techImpacts[i].Vehicle[year.Index];
                if (veh.RegClass == regClass)
                {
                    Vehicle.CDescription vd = veh.Description;
                    double vehSales = vd.Sales[year.Index];
                    if (vehSales != 0)
                    {
                        FuelValue adjFE    = Standards.GetAdjustedFuelEconomy(veh, techImpacts[i], scen, year, settings, true, true, true);
                        double    vehFE    = Standards.GetAverageFuelEconomy(adjFE, techImpacts[i].NewFuelShare[year.Index], regClass, scen, year, settings);
                        double    vehSOFE  = veh.ModelData.SalesOverFE;
                        double    sofeDiff = vehSales / ((useRounding) ? RoundFE(vehFE, regClass) : vehFE) - vehSOFE;
                        sofe += sofeDiff;
                    }
                }
            }
            return (sales == 0) ? 0 : sales / sofe;
        }
        #endregion
        #region 
        public static double GetAverageFuelEconomy(FuelValue fuelEconomy, FuelValue fuelShare, RegulatoryClass regClass,
            Scenario scen, ModelYear year, ModelingSettings settings)
        {
            if (fuelShare.IsMultiFuel)
            {   
                int mode = scen.ScenInfo[regClass].MultiFuel[year.Index];
                if (mode == 0 || (mode == 1 && fuelShare.Electricity == 0))
                {
                    return fuelEconomy.PrimaryValue;
                }
                else if ((mode == 1 || mode == 2) && fuelShare.Electricity > 0)
                {
                    double elcFS = scen.ScenInfo[regClass].MultiFuelPHEVShare[year.Index];
                    if (elcFS == 0) { elcFS = fuelShare.Electricity; }
                    return 1 / ((1 - elcFS) / fuelEconomy.PrimaryValue + elcFS / fuelEconomy.Electricity);
                }
                else if (mode == 2 && fuelShare.Ethanol85 > 0)
                {
                    double e85FS = scen.ScenInfo[regClass].MultiFuelFFVShare[year.Index];
                    if (e85FS == 0) { e85FS = fuelShare.Ethanol85; }
                    return 1 / ((1 - e85FS) / fuelEconomy.Gasoline + e85FS / fuelEconomy.Ethanol85);
                }
                else if (mode == 2 && fuelShare.Biodiesel20 > 0)
                {
                    double b20FS = scen.ScenInfo[regClass].MultiFuelFFVShare[year.Index];
                    if (b20FS == 0) { b20FS = fuelShare.Biodiesel20; }
                    return 1 / ((1 - b20FS) / fuelEconomy.Diesel + b20FS / fuelEconomy.Biodiesel20);
                }
                throw new ArgumentException("The vehicle's fuel economy and/or fuel share values are not valid; or, incorrect multi-fuel mode specified in the scenario.");
            }
            else
            {   
                return fuelEconomy.PrimaryValue;
            }
        }
        public static double GetAverageFuelEconomy(Vehicle veh, Scenario scen, ModelYear year, ModelingSettings settings,
            bool applyPEFAdjustment, bool applyACAdjustment, bool applyOffCycleCredit)
        {
            FuelValue fuelEconomy = Standards.GetAdjustedFuelEconomy(veh, scen, year, settings, applyPEFAdjustment,
                applyACAdjustment, applyOffCycleCredit);
            return Standards.GetAverageFuelEconomy(fuelEconomy, veh.Description.FuelShare, veh.RegClass, scen, year, settings);
        }
        public static FuelValue GetAdjustedFuelEconomy(Vehicle veh, Scenario scen, ModelYear year, ModelingSettings settings,
            bool applyPEFAdjustment, bool applyACAdjustment, bool applyOffCycleCredit)
        {
            return Standards.GetAdjustedFuelEconomy(veh, null, scen, year, settings, applyPEFAdjustment, applyACAdjustment,
                applyOffCycleCredit);
        }
        static FuelValue GetAdjustedFuelEconomy(Vehicle veh, TechImpact techImpact, Scenario scen, ModelYear year,
            ModelingSettings settings, bool applyPEFAdjustment, bool applyACAdjustment, bool applyOffCycleCredit)
        {
            FuelValue fuelEconomy = (techImpact == null) ? veh.Description.FuelEconomy : techImpact.NewFuelEconomy[year.Index];
            if (applyPEFAdjustment)
            {
                FuelValue ed = settings.Parameters.FuelProperties.EnergyDensity;
                fuelEconomy[FuelType.Ethanol85  ] = fuelEconomy.Ethanol85   / 0.15;
                fuelEconomy[FuelType.Electricity] = fuelEconomy.Electricity * 82.049 * (ed.Electricity / ed.Gasoline);
            }
            RegulatoryClass regClass = veh.RegClass;
            double gramsCO2 = 0;
            if (applyACAdjustment && scen.ScenInfo[regClass].IncludeAC[year.Index])
            {
                gramsCO2 += scen.ScenInfo[regClass].ACAdjustment[year.Index];
            }
            if (applyOffCycleCredit)
            {
                double cap = scen.ScenInfo[regClass].OffCycleCap[year.Index];
                double occ = veh.ModelData.OffCycleCredit;
                if (techImpact != null) { occ += techImpact.OffCycleCredit[year.Index]; }
                gramsCO2 += Math.Min(occ, cap);
            }
            if (gramsCO2 == 0) { return fuelEconomy; }
            FuelValue fuelShare = (techImpact == null) ? veh.Description.FuelShare : techImpact.NewFuelShare[year.Index];
            FuelValue fuelCarbon = Standards.GetFuelCarbonContent(settings, true);
            foreach (FuelType fuel in FTValue<object>.Classes)
            {
                if (fuelShare[fuel] > 0) { fuelEconomy[fuel] = fuelCarbon[fuel] / (fuelCarbon[fuel] / fuelEconomy[fuel] - gramsCO2); }
            }
            return fuelEconomy;
        }
        static FuelValue GetFuelCarbonContent(ModelingSettings settings, bool useGasolineForGEG)
        {
            FuelValue fuelCarbon = new FuelValue();
            FuelValue massDensity   = settings.Parameters.FuelProperties.MassDensity;
            FuelValue carbonContent = settings.Parameters.FuelProperties.CarbonContent;
            foreach (FuelType fuel in FTValue<object>.Classes)
            {
                if (useGasolineForGEG && (fuel == FuelType.Electricity || fuel == FuelType.Hydrogen || fuel == FuelType.CNG))
                {   
                    fuelCarbon[fuel] = massDensity.Gasoline * carbonContent.Gasoline * 44 / 12;
                }
                else
                {
                    fuelCarbon[fuel] = massDensity[fuel] * carbonContent[fuel] * 44 / 12;
                }
            }
            return fuelCarbon;
        }
        #endregion
        #region 
        public static double GetVehicleCO2Rating(Vehicle veh, Scenario scen, ModelYear year, ModelingSettings settings,
            bool applyPEFAdjustment, bool applyACAdjustment, bool applyOffCycleCredit)
        {
            FuelValue       fuelEconomy = Standards.GetAdjustedFuelEconomy(veh, scen, year, settings, applyPEFAdjustment,
                                                                           applyACAdjustment, applyOffCycleCredit);
            FuelValue       fuelShare   = veh.Description.FuelShare;
            RegulatoryClass regClass    = veh.RegClass;
            return Standards.GetVehicleCO2Rating(fuelEconomy, fuelShare, regClass, scen, year, settings);
        }
        public static double GetVehicleCO2Rating(FuelValue fuelEconomy, FuelValue fuelShare, RegulatoryClass regClass, Scenario scen,
            ModelYear year, ModelingSettings settings)
        {
            FuelValue fuelCarbon = Standards.GetFuelCarbonContent(settings, true);
            FuelValue fuelCO2    = new FuelValue();
            foreach (FuelType fuel in FTValue<object>.Classes)
            {
                if (fuelShare[fuel] > 0) { fuelCO2[fuel] = (fuelCarbon[fuel] / fuelEconomy[fuel]); }
            }
            if (fuelShare.IsMultiFuel)
            {   
                int mode = scen.ScenInfo[regClass].MultiFuel[year.Index];
                if (mode == 0 || (mode == 1 && fuelShare.Electricity == 0))
                {
                    return fuelCO2.PrimaryValue;
                }
                else if ((mode == 1 || mode == 2) && fuelShare.Electricity > 0)
                {
                    double elcFS = scen.ScenInfo[regClass].MultiFuelPHEVShare[year.Index];
                    if (elcFS == 0) { elcFS = fuelShare.Electricity; }
                    return (1 - elcFS) * fuelCO2.PrimaryValue + elcFS * fuelCO2.Electricity;
                }
                else if (mode == 2 && fuelShare.Ethanol85 > 0)
                {
                    double e85FS = scen.ScenInfo[regClass].MultiFuelFFVShare[year.Index];
                    if (e85FS == 0) { e85FS = fuelShare.Ethanol85; }
                    return (1 - e85FS) * fuelCO2.Gasoline + e85FS * fuelCO2.Ethanol85;
                }
                else if (mode == 2 && fuelShare.Biodiesel20 > 0)
                {
                    double b20FS = scen.ScenInfo[regClass].MultiFuelFFVShare[year.Index];
                    if (b20FS == 0) { b20FS = fuelShare.Biodiesel20; }
                    return (1 - b20FS) * fuelCO2.Diesel + b20FS * fuelCO2.Biodiesel20;
                }
                throw new ArgumentException("The vehicle's fuel economy and/or fuel share values are not valid; or, incorrect multi-fuel mode specified in the scenario.");
            }
            else
            {   
                return fuelCO2.PrimaryValue;
            }
        }
        public static double GetVehicleCO2Target(Vehicle veh, Scenario scen, ModelYear year, ModelingSettings settings)
        {
            double    feTarget   = Standards.GetTarget(veh, scen, year);
            FuelValue fuelCarbon = Standards.GetFuelCarbonContent(settings, true);
            FuelType  fuel       = veh.Description.FuelShare.FuelType;
            bool      isDSL      = ((fuel & FuelType.Diesel     ) == FuelType.Diesel ||
                                    (fuel & FuelType.Biodiesel20) == FuelType.Biodiesel20);
            bool      isCNG      = ((fuel & FuelType.CNG        ) == FuelType.CNG);
            return ((isDSL) ? fuelCarbon.Diesel   :
                    (isCNG) ? fuelCarbon.CNG      :
                              fuelCarbon.Gasoline) * feTarget;
        }
        public static RCDouble GetVehicleCO2Rating(Vehicle[] vehs, Scenario scen, ModelYear year, ModelingSettings settings)
        {
            return Standards.CalcAggregateCO2(vehs, scen, year, settings, false, true, true, true);
        }
        public static RCDouble GetVehicleCO2Target(Vehicle[] vehs, Scenario scen, ModelYear year, ModelingSettings settings)
        {
            return Standards.CalcAggregateCO2(vehs, scen, year, settings, true, true, true, true);
        }
        static RCDouble CalcAggregateCO2(Vehicle[] vehs, Scenario scen, ModelYear year, ModelingSettings settings, bool calcTarget,
            bool applyPEF, bool applyAC, bool applyOffCycle)
        {
            RCDouble salesSum = RCDouble.Zero;
            RCDouble co2Sum   = RCDouble.Zero;
            foreach (Vehicle veh in vehs)
            {
                double   co2   = (calcTarget) ? Standards.GetVehicleCO2Target(veh, scen, year, settings) :
                                                Standards.GetVehicleCO2Rating(veh, scen, year, settings, applyPEF, applyAC, applyOffCycle);
                double   sales = veh.Description.Sales[year.Index];
                RC       rc    = veh.RegClass;
                co2Sum  [rc]  += sales * co2;
                salesSum[rc]  += sales;
            }
            foreach (RegulatoryClass regClass in RCValue<object>.Classes)
            {
                if (salesSum[regClass] != 0) { co2Sum[regClass] /= salesSum[regClass]; }
            }
            return co2Sum;
        }
        public static RCDouble GetManufacturerCO2Rating(Manufacturer mfr, Scenario scen, ModelYear year, ModelingSettings settings)
        {
            return Standards.GetVehicleCO2Rating(mfr.Vehicles.ToArray(), scen, year, settings);
        }
        public static RCDouble GetManufacturerCO2Target(Manufacturer mfr, Scenario scen, ModelYear year, ModelingSettings settings)
        {
            return Standards.GetVehicleCO2Target(mfr.Vehicles.ToArray(), scen, year, settings);
        }
        public static RCDouble GetIndustryCO2Rating(Industry ind, Scenario scen, ModelYear year, ModelingSettings settings)
        {
            return Standards.GetVehicleCO2Rating(ind.Vehicles.ToArray(), scen, year, settings);
        }
        public static RCDouble GetIndustryCO2Target(Industry ind, Scenario scen, ModelYear year, ModelingSettings settings)
        {
            return Standards.GetVehicleCO2Target(ind.Vehicles.ToArray(), scen, year, settings);
        }
        #endregion
        #region 
        public static void CalcVehicleTaxCredit(Industry data, Scenario scen, ModelYear year)
        {
            List<Manufacturer> mfrs = data.Manufacturers;
            for (int i = 0, mfrCount = mfrs.Count; i < mfrCount; i++)
            {
                List<Vehicle> vehs = mfrs[i].Vehicles;
                for (int j = 0, vehCount = vehs.Count; j < vehCount; j++)
                {
                    Vehicle veh = vehs[j];
                    veh.ModelData.TaxCredit = Standards.GetVehicleTaxCredit(veh, scen, year);
                }
            }
        }
        public static double GetVehicleTaxCredit(Vehicle veh, Scenario scen, ModelYear year)
        {
            ScenarioInfo scenInfo = scen.ScenInfo[veh.RegClass];
            return (veh.HEVType == HEVType.PureElectric) ? scenInfo.EVTaxCredit  [year.Index] :
                   (veh.HEVType == HEVType.PlugInHybrid) ? scenInfo.PHEVTaxCredit[year.Index] : 0;
        }
        #endregion
        #region 
        public static double GetVehicleFuelCost(FuelValue fuelEconomy, FuelValue fuelShare, RegulatoryClass regClass,
            Scenario scen, ModelYear year, FuelValue gap, FuelValue fuelCost)
        {
            FuelType primaryFuel = fuelEconomy.PrimaryFuel;
            if (fuelShare.IsMultiFuel)
            {   
                int mode = scen.ScenInfo[regClass].MultiFuel[year.Index];
                if (mode == 0 || (mode == 1 && fuelShare.Electricity == 0))
                {
                    return fuelCost[primaryFuel] / (fuelEconomy.PrimaryValue * (1 - gap[primaryFuel]));
                }
                else if ((mode == 1 || mode == 2) && fuelShare.Electricity > 0)
                {
                    double elcFS = scen.ScenInfo[regClass].MultiFuelPHEVShare[year.Index];
                    if (elcFS == 0) { elcFS = fuelShare.Electricity; }
                    return fuelCost[primaryFuel] / (fuelEconomy[primaryFuel] * (1 - gap[primaryFuel])) * (1 - elcFS) +
                           fuelCost.Electricity  / (fuelEconomy.Electricity  * (1 - gap.Electricity )) *      elcFS;
                }
                else if (mode == 2 && fuelShare.Ethanol85 > 0)
                {
                    double e85FS = scen.ScenInfo[regClass].MultiFuelFFVShare[year.Index];
                    if (e85FS == 0) { e85FS = fuelShare.Ethanol85; }
                    return fuelCost[primaryFuel] / (fuelEconomy[primaryFuel] * (1 - gap[primaryFuel])) * (1 - e85FS) +
                           fuelCost.Ethanol85    / (fuelEconomy.Ethanol85    * (1 - gap.Ethanol85   )) *      e85FS;
                }
                else if (mode == 2 && fuelShare.Biodiesel20 > 0)
                {
                    double b20FS = scen.ScenInfo[regClass].MultiFuelFFVShare[year.Index];
                    if (b20FS == 0) { b20FS = fuelShare.Biodiesel20; }
                    return fuelCost[primaryFuel] / (fuelEconomy[primaryFuel] * (1 - gap[primaryFuel])) * (1 - b20FS) +
                           fuelCost.Biodiesel20  / (fuelEconomy.Biodiesel20  * (1 - gap.Biodiesel20 )) *      b20FS;
                }
                throw new ArgumentException("The vehicle's fuel economy and/or fuel share values are not valid; or, incorrect multi-fuel mode specified in the scenario.");
            }
            else
            {   
                return fuelCost[primaryFuel] / (fuelEconomy.PrimaryValue * (1 - gap[primaryFuel]));
            }
        }
        public static int GetCoefficients(RC regClass, Scenario scen, ModelYear year, out int fncType, out double[] fncCoeff)
        {
            fncType  = scen.ScenInfo[regClass].Function    [year.Index];
            fncCoeff = scen.ScenInfo[regClass].Coefficients[year.Index];
            return fncCoeff.Length;
        }
        #endregion
        static double RoundFE(double value, RC regClass)
        {
            return (regClass == RC.LightTruck2b3) ? 100 / Math.Round(100 / value, 2) : Math.Round(value, 1);
        }
        #endregion
    }
}

