#region << Using Directives >>
using System;
using Volpe.Cafe.Data;
using Volpe.Cafe.Utils;
using Volpe.Cafe.Generic;
#endregion
namespace Volpe.Cafe.Settings
{
    #region 
    [Serializable]
    public sealed class EconomicValues
    {
        #region 
        EconomicValues() { }
        internal EconomicValues(
            VCValue<double> reboundEffect, VCValue<double> discRate, VCValue<int> drBaseYear,
            VCObject<VMTGrowthRate> vmtGrowthRate,
            VCValue<FuelValue> onRoadGap, VCValue<FuelValue> refuelTime, VCValue<double> refuelTank, VCValue<double> vehTravelTimeValue,
            EconomicCosts economicCosts, VCObject<ExternalCosts> externalCosts, VCObject<OperatingCosts> operatingCosts)
        {
            this.ReboundEffect          = reboundEffect;
            this.DiscountRate           = discRate;
            this.DRBaseYear             = drBaseYear;
            this.VMTGrowthRate          = vmtGrowthRate;
            this.OnRoadGap              = onRoadGap;
            this.RefuelTime             = refuelTime;
            this.RefuelTankVolume       = refuelTank;
            this.VehicleTravelTimeValue = vehTravelTimeValue;
            this.EconomicCosts          = economicCosts;
            this.ExternalCosts          = externalCosts;
            this.OperatingCosts         = operatingCosts;
        }
        #endregion
        #region 
        internal EconomicValues Clone()
        {
            EconomicValues value = new EconomicValues();
            value.ReboundEffect          = this.ReboundEffect         .Clone();
            value.DiscountRate           = this.DiscountRate          .Clone();
            value.DRBaseYear             = this.DRBaseYear            .Clone();
            value.VMTGrowthRate          = this.VMTGrowthRate         .Clone();
            value.OnRoadGap              = this.OnRoadGap             .Clone();
            value.RefuelTime             = this.RefuelTime            .Clone();
            value.RefuelTankVolume       = this.RefuelTankVolume      .Clone();
            value.VehicleTravelTimeValue = this.VehicleTravelTimeValue.Clone();
            value.EconomicCosts          = this.EconomicCosts         .Clone();
            value.ExternalCosts          = this.ExternalCosts         .Clone();
            value.OperatingCosts         = this.OperatingCosts        .Clone();
            return value;
        }
        internal void UpdateForMonteCarlo(double reboundEffect, double gapGas, double gapDsl)
        {
            this.ReboundEffect = new VCValue<double>();
            for (int i = 0; i < this.ReboundEffect.Items.Length; i++)
            {
                this.ReboundEffect.Items[i]                       = reboundEffect;
                this.OnRoadGap    .Items[i][FuelType.Gasoline   ] = gapGas;
                this.OnRoadGap    .Items[i][FuelType.Ethanol85  ] = gapGas;
                this.OnRoadGap    .Items[i][FuelType.Diesel     ] = gapDsl;
                this.OnRoadGap    .Items[i][FuelType.Biodiesel20] = gapDsl;
            }
        }
        #endregion
        #region 
        public VCValue<double> ReboundEffect { get; private set; }
        public VCValue<double> DiscountRate { get; private set; }
        public VCValue<int> DRBaseYear { get; private set; }
        public VCObject<VMTGrowthRate> VMTGrowthRate { get; private set; }
        public VCValue<FuelValue> OnRoadGap { get; private set; }
        public VCValue<FuelValue> RefuelTime { get; private set; }
        public VCValue<double> RefuelTankVolume { get; private set; }
        public VCValue<double> VehicleTravelTimeValue { get; private set; }
        public EconomicCosts EconomicCosts { get; private set; }
        public VCObject<ExternalCosts> ExternalCosts { get; private set; }
        public VCObject<OperatingCosts> OperatingCosts { get; private set; }
        #endregion
    }
    [Serializable]
    public sealed class VMTGrowthRate : ICloneable
    {
        #region 
        VMTGrowthRate() { }
        internal VMTGrowthRate(int baseYear, double low, double average, double high)
        {
            this.BaseYear = baseYear;
            this.Low      = low;
            this.Average  = average;
            this.High     = high;
        }
        #endregion
        #region 
        #region 
        object ICloneable.Clone()
        {
            return this.Clone();
        }
        #endregion
        internal VMTGrowthRate Clone()
        {
            VMTGrowthRate value = new VMTGrowthRate();
            value.BaseYear = this.BaseYear;
            value.Low      = this.Low;
            value.Average  = this.Average;
            value.High     = this.High;
            return value;
        }
        public double GetVMTGrowthFactor(Estimates estimates, int year, int age)
        {
            double growthRate = (estimates == Estimates.VeryHigh || estimates == Estimates.High) ? this.High :
                (estimates == Estimates.Low) ? this.Low : this.Average;
            return Math.Pow(1 + growthRate, year + age - this.BaseYear);
        }
        #endregion
        #region 
        public int BaseYear { get; private set; }
        public double Low { get; private set; }
        public double Average { get; private set; }
        public double High { get; private set; }
        #endregion
    }
    [Serializable]
    public sealed class EconomicCosts : ICloneable
    {
        #region 
        EconomicCosts() { }
        internal EconomicCosts(int minCY, int maxCY, double[] monopsony, double[] priceShock, double[] milSecurity)
        {
            this.MinCY            = minCY;
            this.MaxCY            = maxCY;
            this.Monopsony        = monopsony;
            this.PriceShock       = priceShock;
            this.MilitarySecurity = milSecurity;
        }
        #endregion
        #region 
        #region 
        object ICloneable.Clone()
        {
            return this.Clone();
        }
        #endregion
        internal EconomicCosts Clone()
        {
            EconomicCosts value = new EconomicCosts();
            value.MinCY            = this.MinCY;
            value.MaxCY            = this.MaxCY;
            value.Monopsony        = this.Monopsony       .CloneArray();
            value.PriceShock       = this.PriceShock      .CloneArray();
            value.MilitarySecurity = this.MilitarySecurity.CloneArray();
            return value;
        }
        internal void UpdateForMonteCarlo(FuelPrices oldFP, FuelPrices newFP, double priceShockAlpha)
        {
            for (int i = 0; i < this.PriceShock.Length; i++)
            {
                int year = this.MinCY + i;
                double ratio = newFP.GetFuelPrice(Estimates.Average, FuelType.Gasoline, year) /
                               oldFP.GetFuelPrice(Estimates.Average, FuelType.Gasoline, year);
                this.PriceShock[i] *= ratio * priceShockAlpha;
            }
        }
        public double GetTotalEconomicCosts(int year)
        {
            int index = Math.Max(Math.Min(year, this.MaxCY) - this.MinCY, 0);
            return (this.Monopsony[index] + this.PriceShock[index] + this.MilitarySecurity[index]);
        }
        #endregion
        #region 
        public int MinCY { get; private set; }
        public int MaxCY { get; private set; }
        public double[] Monopsony { get; private set; }
        public double[] PriceShock { get; private set; }
        public double[] MilitarySecurity { get; private set; }
        #endregion
    }
    [Serializable]
    public sealed class ExternalCosts : ICloneable
    {
        #region 
        ExternalCosts() { }
        internal ExternalCosts(double congestion, double accident, double noise)
        {
            this.Congestion = congestion;
            this.Accident   = accident;
            this.Noise      = noise;
        }
        #endregion
        #region 
        #region 
        object ICloneable.Clone()
        {
            return this.Clone();
        }
        #endregion
        internal ExternalCosts Clone()
        {
            ExternalCosts value = new ExternalCosts();
            value.Congestion = this.Congestion;
            value.Accident   = this.Accident;
            value.Noise      = this.Noise;
            return value;
        }
        #endregion
        #region 
        public double Congestion { get; private set; }
        public double Accident { get; private set; }
        public double Noise { get; private set; }
        #endregion
    }
    [Serializable]
    public sealed class OperatingCosts : ICloneable
    {
        #region 
        OperatingCosts() { }
        internal OperatingCosts(double taxesAndFees, double financing, double insurance, double relValueLoss, double resaleValue)
        {
            this.TaxesAndFees      = taxesAndFees;
            this.Financing         = financing;
            this.Insurance         = insurance;
            this.RelativeValueLoss = relValueLoss;
            this.ResaleValue       = resaleValue;
        }
        #endregion
        #region 
        #region 
        object ICloneable.Clone()
        {
            return this.Clone();
        }
        #endregion
        internal OperatingCosts Clone()
        {
            OperatingCosts value = new OperatingCosts();
            value.TaxesAndFees      = this.TaxesAndFees;
            value.Financing         = this.Financing;
            value.Insurance         = this.Insurance;
            value.RelativeValueLoss = this.RelativeValueLoss;
            value.ResaleValue       = this.ResaleValue;
            return value;
        }
        #endregion
        #region 
        public double TaxesAndFees { get; private set; }
        public double Financing { get; private set; }
        public double Insurance { get; private set; }
        public double RelativeValueLoss { get; private set; }
        public double ResaleValue { get; private set; }
        #endregion
    }
    [Serializable]
    public sealed class VehicleAgeData
    {
        #region 
        VehicleAgeData() { }
        internal VehicleAgeData(double[] cars, double[] vans, double[] suvs, double[] pickups, double[] class12a,
            double[] class2b3, double[] class456)
        {
            this.Cars     = cars;
            this.Vans     = vans;
            this.SUVs     = suvs;
            this.Pickups  = pickups;
            this.Class12a = class12a;
            this.Class2b3 = class2b3;
            this.Class456 = class456;
        }
        #endregion
        #region 
        internal VehicleAgeData Clone()
        {
            VehicleAgeData value = new VehicleAgeData();
            value.Cars     = this.Cars    .CloneArray();
            value.Vans     = this.Vans    .CloneArray();
            value.SUVs     = this.SUVs    .CloneArray();
            value.Pickups  = this.Pickups .CloneArray();
            value.Class12a = this.Class12a.CloneArray();
            value.Class2b3 = this.Class2b3.CloneArray();
            value.Class456 = this.Class456.CloneArray();
            return value;
        }
        internal void UpdateForMonteCarlo(VCValue<double> miles0, VCValue<double> milesCap)
        {
            foreach (VehicleClass vehClass in VCValue<object>.Classes)
            {
                this.UpdateVMTSchedule(miles0[vehClass], this.GetVehicleAgeData(vehClass, VehicleStyle.None), milesCap[vehClass]);
            }
            this.Vans = this.SUVs = this.Pickups = this.Class12a;
        }
        void UpdateVMTSchedule(double newMiles0, double[] miles, double milesCap)
        {
            double ratio = newMiles0 / miles[0];
            double sum   = newMiles0;
            miles[0]     = newMiles0;
            for (int i = 1; i < miles.Length; i++)
            {
                miles[i] *= ratio;
                sum += miles[i];
                if (sum >= milesCap) { Array.Clear(miles, i + 1, miles.Length - i - 1); break; }
            }
        }
        public double[] GetVehicleAgeData(Vehicle veh)
        {
            return this.GetVehicleAgeData(veh.VehicleClass, veh.VehicleStyle);
        }
        public double[] GetVehicleAgeData(VehicleClass vehClass, VehicleStyle vehStyle)
        {
            switch (vehClass)
            {
                case VehicleClass.LDV   : return this.Cars;
                case VehicleClass.LDT1  :
                case VehicleClass.LDT2a :
                case VehicleClass.LDT12a: return (vehStyle == VehicleStyle.Minivan     ) ? this.Vans    :
                                                 (vehStyle == VehicleStyle.Van         ) ? this.Vans    :
                                                 (vehStyle == VehicleStyle.SportUtility) ? this.SUVs    :
                                                 (vehStyle == VehicleStyle.Pickup      ) ? this.Pickups : this.Class12a;
                case VehicleClass.LDT2b :
                case VehicleClass.LDT3  :
                case VehicleClass.LDT2b3: return this.Class2b3;
                case VehicleClass.MDT4  :
                case VehicleClass.MDT5  :
                case VehicleClass.MDT6  :
                case VehicleClass.MDT456: return this.Class456;
                default: throw new ArgumentOutOfRangeException("vehClass", "Vehicle class is not specified or not supported.");
            }
        }
        #endregion
        #region 
        public double[] Cars { get; private set; }
        public double[] Vans { get; private set; }
        public double[] SUVs { get; private set; }
        public double[] Pickups { get; private set; }
        public double[] Class12a { get; private set; }
        public double[] Class2b3 { get; private set; }
        public double[] Class456 { get; private set; }
        #endregion
    }
    [Serializable]
    public sealed class FuelPrices
    {
        #region 
        FuelPrices() { }
        internal FuelPrices(int minCY, int maxCY,
            FTObject<double[]> fuelTax, FTObject<double[]> fpLow, FTObject<double[]> fpAvg, FTObject<double[]> fpHigh)
        {
            this.MinCY            = minCY;
            this.MaxCY            = maxCY;
            this.FuelTax          = fuelTax;
            this.FuelPriceLow     = fpLow;
            this.FuelPriceAverage = fpAvg;
            this.FuelPriceHigh    = fpHigh;
        }
        #endregion
        #region 
        internal FuelPrices Clone()
        {
            FuelPrices value = new FuelPrices();
            value.MinCY    = this.MinCY;
            value.MaxCY    = this.MaxCY;
            value.FuelTax          = this.FuelTax         .Clone();
            value.FuelPriceLow     = this.FuelPriceLow    .Clone();
            value.FuelPriceAverage = this.FuelPriceAverage.Clone();
            value.FuelPriceHigh    = this.FuelPriceHigh   .Clone();
            return value;
        }
        internal void UpdateForMonteCarlo(double fpScaleFactor, int fpStartYear)
        {
            for (int i = fpStartYear; i <= this.MaxCY; i++)
            {
                int yr = i - this.MinCY;
                foreach (FuelType fuel in FTValue<object>.Classes)
                {
                    double[] fpH = this.FuelPriceHigh   [fuel];
                    double[] fpA = this.FuelPriceAverage[fuel];
                    fpA[yr] = (1 + fpScaleFactor * (fpH[yr] / fpH[yr - 1] - 1)) * fpA[yr - 1];
                }
            }
        }
        public double GetFuelTax(FuelType fuelType, int year)
        {
            int index = Math.Max(Math.Min(year, this.MaxCY) - this.MinCY, 0);
            return this.FuelTax[fuelType][index];
        }
        public double GetFuelTax(FuelType fuelType, int year, FuelProperties fuelProperties)
        {
            return this.ConvertToGGE(this.GetFuelTax(fuelType, year), fuelType, fuelProperties);
        }
        public double GetFuelPrice(Estimates priceEstimates, FuelType fuelType, int year)
        {
            FTObject<double[]> fp = (priceEstimates == Estimates.Low ) ? this.FuelPriceLow  :
                                    (priceEstimates == Estimates.High) ? this.FuelPriceHigh : this.FuelPriceAverage;
            int index = Math.Max(Math.Min(year, this.MaxCY) - this.MinCY, 0);
            return fp[fuelType][index];
        }
        public double GetFuelPrice(Estimates priceEstimates, FuelType fuelType, int year, FuelProperties fuelProperties)
        {
            return this.ConvertToGGE(this.GetFuelPrice(priceEstimates, fuelType, year), fuelType, fuelProperties);
        }
        double ConvertToGGE(double value, FuelType fuelType, FuelProperties fuelProperties)
        {
            FuelValue ed = fuelProperties.EnergyDensity;
            return (fuelType == FuelType.Electricity) ? value * ed.Gasoline / ed.Electricity :
                   (fuelType == FuelType.Hydrogen   ) ? value * ed.Gasoline / ed.Hydrogen    :
                   (fuelType == FuelType.CNG        ) ? value * ed.Gasoline / ed.CNG         : value;
        }
        #endregion
        #region 
        public int MinCY { get; private set; }
        public int MaxCY { get; private set; }
        public FTObject<double[]> FuelTax { get; private set; }
        public FTObject<double[]> FuelPriceLow { get; private set; }
        public FTObject<double[]> FuelPriceAverage { get; private set; }
        public FTObject<double[]> FuelPriceHigh { get; private set; }
        #endregion
    }
    [Serializable]
    public sealed class FuelEconomyData
    {
        #region 
        FuelEconomyData() { }
        internal FuelEconomyData(int minMY, int maxMY, FTObject<VCObject<double[]>> fuelEconomy,
            FTObject<VCObject<double[]>> fuelShare)
        {
            this.MinMY       = minMY;
            this.MaxMY       = maxMY;
            this.FuelEconomy = fuelEconomy;
            this.FuelShare   = fuelShare;
        }
        #endregion
        #region 
        internal FuelEconomyData Clone()
        {
            FuelEconomyData value = new FuelEconomyData();
            value.MinMY       = this.MinMY;
            value.MaxMY       = this.MaxMY;
            value.FuelEconomy = this.FuelEconomy.Clone();
            value.FuelShare   = this.FuelShare  .Clone();
            return value;
        }
        public double GetFuelEconomy(FuelType fuelType, VehicleClass vehClass, int year)
        {
            int index = Math.Max(Math.Min(year, this.MaxMY) - this.MinMY, 0);
            return this.FuelEconomy[fuelType][vehClass][index];
        }
        public FuelValue GetFuelEconomy(VehicleClass vehClass, int year)
        {
            FuelValue value = new FuelValue();
            foreach (FuelType fuelType in FTValue<object>.Classes)
            {
                value[fuelType] = this.GetFuelEconomy(fuelType, vehClass, year);
            }
            return value;
        }
        public double GetFuelShare(FuelType fuelType, VehicleClass vehClass, int year)
        {
            int index = Math.Max(Math.Min(year, this.MaxMY) - this.MinMY, 0);
            return this.FuelShare[fuelType][vehClass][index];
        }
        public FuelValue GetFuelShare(VehicleClass vehClass, int year)
        {
            FuelValue value = new FuelValue();
            foreach (FuelType fuelType in FTValue<object>.Classes)
            {
                value[fuelType] = this.GetFuelShare(fuelType, vehClass, year);
            }
            return value;
        }
        #endregion
        #region 
        public int MinMY { get; private set; }
        public int MaxMY { get; private set; }
        FTObject<VCObject<double[]>> FuelEconomy { get;  set; }
        FTObject<VCObject<double[]>> FuelShare { get;  set; }
        #endregion
    }
    [Serializable]
    public sealed class FleetAnalysisValues
    {
        #region 
        FleetAnalysisValues() { }
        internal FleetAnalysisValues(int minMY, int maxMY, VCValue<double> baseCAFEGrowthRates, VCValue<double> scenCAFEGrowthRates,
            VCValue<double> cafeStartYear, VCObject<double[]> salesForecast)
        {
            this.MinMY                   = minMY;
            this.MaxMY                   = maxMY;
            this.BaselineCAFEGrowthRates = baseCAFEGrowthRates;
            this.ScenarioCAFEGrowthRates = scenCAFEGrowthRates;
            this.CAFEStartYear           = cafeStartYear;
            this.SalesForecast           = salesForecast;
        }
        #endregion
        #region 
        internal FleetAnalysisValues Clone()
        {
            FleetAnalysisValues value = new FleetAnalysisValues();
            value.MinMY                   = this.MinMY;
            value.MaxMY                   = this.MaxMY;
            value.BaselineCAFEGrowthRates = this.BaselineCAFEGrowthRates.Clone();
            value.ScenarioCAFEGrowthRates = this.ScenarioCAFEGrowthRates.Clone();
            value.CAFEStartYear           = this.CAFEStartYear          .Clone();
            value.SalesForecast           = this.SalesForecast          .Clone();
            return value;
        }
        public double GetSalesForecast(VehicleClass vehClass, int year)
        {
            int index = Math.Max(Math.Min(year, this.MaxMY) - this.MinMY, 0);
            return this.SalesForecast[vehClass][index];
        }
        #endregion
        #region 
        public int MinMY { get; private set; }
        public int MaxMY { get; private set; }
        public VCValue<double> BaselineCAFEGrowthRates { get; private set; }
        public VCValue<double> ScenarioCAFEGrowthRates { get; private set; }
        public VCValue<double> CAFEStartYear { get; private set; }
        public VCObject<double[]> SalesForecast { get; private set; }
        #endregion
    }
    [Serializable]
    public sealed class HistoricFleetData
    {
        #region 
        HistoricFleetData() { }
        internal HistoricFleetData(int minMY, int maxMY, VCObject<double[][]> fleetData)
        {
            this.MinMY     = minMY;
            this.MaxMY     = maxMY;
            this.FleetData = fleetData;
        }
        #endregion
        #region 
        internal HistoricFleetData Clone()
        {
            HistoricFleetData value = new HistoricFleetData();
            value.MinMY     = this.MinMY;
            value.MaxMY     = this.MaxMY;
            value.FleetData = this.Clone(this.FleetData);
            return value;
        }
        VCObject<double[][]> Clone(VCObject<double[][]> value)
        {
            VCObject<double[][]> newValue = new VCObject<double[][]>();
            foreach (VehicleClass vehClass in VCValue<object>.Classes)
            {
                newValue[vehClass] = value[vehClass].CloneArray();
            }
            return newValue;
        }
        public double GetFleetData(VehicleClass vehClass, int year, int age)
        {
            int index = Math.Max(Math.Min(year, this.MaxMY) - this.MinMY, 0);
            return this.FleetData[vehClass][index][age];
        }
        #endregion
        #region 
        public int MinMY { get; private set; }
        public int MaxMY { get; private set; }
        public VCObject<double[][]> FleetData { get; private set; }
        #endregion
    }
    [Serializable]
    public sealed class SafetyValues
    {
        #region 
        SafetyValues() { }
        internal SafetyValues(SCValue<double> threshold,
            SCValue<double> smallEffect, SCValue<double> smallBase, SCValue<double> smallFMVSS,
            SCValue<double> largeEffect, SCValue<double> largeBase, SCValue<double> largeFMVSS,
            double fatalityCosts, double growthRate, int baseYear)
        {
            this.Threshold       = threshold;
            this.SmallEffect     = smallEffect;
            this.SmallBase       = smallBase;
            this.SmallFMVSS      = smallFMVSS;
            this.SmallMultiplier = this.CalcMultiplier(smallEffect, smallBase, smallFMVSS);
            this.LargeEffect     = largeEffect;
            this.LargeBase       = largeBase;
            this.LargeFMVSS      = largeFMVSS;
            this.LargeMultiplier = this.CalcMultiplier(largeEffect, largeBase, largeFMVSS);
            this.FatalityCosts   = fatalityCosts;
            this.GrowthRate      = growthRate;
            this.BaseYear        = baseYear;
        }
        #endregion
        #region 
        internal SafetyValues Clone()
        {
            SafetyValues value = new SafetyValues();
            value.Threshold       = this.Threshold      .Clone();
            value.SmallEffect     = this.SmallEffect    .Clone();
            value.SmallBase       = this.SmallBase      .Clone();
            value.SmallFMVSS      = this.SmallFMVSS     .Clone();
            value.SmallMultiplier = this.SmallMultiplier.Clone();
            value.LargeEffect     = this.LargeEffect    .Clone();
            value.LargeBase       = this.LargeBase      .Clone();
            value.LargeFMVSS      = this.LargeFMVSS     .Clone();
            value.LargeMultiplier = this.LargeMultiplier.Clone();
            value.FatalityCosts   = this.FatalityCosts;
            value.GrowthRate      = this.GrowthRate;
            value.BaseYear        = this.BaseYear;
            return value;
        }
        SCValue<double> CalcMultiplier(SCValue<double> effect, SCValue<double> baseFatals, SCValue<double> fmvss)
        {
            SCValue<double> multiplier = new SCValue<double>();
            for (int i = 0; i < multiplier.Items.Length; i++)
            {
                multiplier.Items[i] = effect.Items[i] * baseFatals.Items[i] * fmvss.Items[i];
            }
            return multiplier;
        }
        public double GetEffect(SafetyClass safetyClass, double curbWeight)
        {
            return (curbWeight < this.Threshold[safetyClass]) ? this.SmallEffect[safetyClass] : this.LargeEffect[safetyClass];
        }
        public double GetBase(SafetyClass safetyClass, double curbWeight)
        {
            return (curbWeight < this.Threshold[safetyClass]) ? this.SmallBase[safetyClass] : this.LargeBase[safetyClass];
        }
        public double GetFMVSS(SafetyClass safetyClass, double curbWeight)
        {
            return (curbWeight < this.Threshold[safetyClass]) ? this.SmallFMVSS[safetyClass] : this.LargeFMVSS[safetyClass];
        }
        public double GetMultiplier(SafetyClass safetyClass, double curbWeight)
        {
            return (curbWeight < this.Threshold[safetyClass]) ? this.SmallMultiplier[safetyClass] : this.LargeMultiplier[safetyClass];
        }
        #endregion
        #region 
        public SCValue<double> Threshold { get; private set; }
        public SCValue<double> SmallEffect { get; private set; }
        public SCValue<double> SmallBase { get; private set; }
        public SCValue<double> SmallFMVSS { get; private set; }
        public SCValue<double> SmallMultiplier { get; private set; }
        public SCValue<double> LargeEffect { get; private set; }
        public SCValue<double> LargeBase { get; private set; }
        public SCValue<double> LargeFMVSS { get; private set; }
        public SCValue<double> LargeMultiplier { get; private set; }
        public double FatalityCosts { get; private set; }
        public double GrowthRate { get; private set; }
        public int BaseYear { get; private set; }
        #endregion
    }
    [Serializable]
    public sealed class CreditTradingValues
    {
        #region 
        CreditTradingValues() { }
        internal CreditTradingValues(
            bool allowTrading, bool allowTransfers, bool allowCarryFwd, int carryFwdYears, bool allowCarryBwd, int carryBwdYears,
            int minMY, RCObject<double[]> transferCaps, RCObject<double[]> adjVMT, CreditAdjustmentMode adjMode)
        {
            this.AllowMfrTrading     = allowTrading;
            this.AllowFleetTransfers = allowTransfers;
            this.AllowCarryForward   = allowCarryFwd;
            this.CarryForwardYears   = carryFwdYears;
            this.AllowCarryBackward  = allowCarryBwd;
            this.CarryBackwardYears  = carryBwdYears;
            this.MinMY               = minMY;
            this.TransferCaps        = transferCaps;
            this.AdjustmentVMT       = adjVMT;
            this.AdjustmentMode      = adjMode;
        }
        #endregion
        #region 
        internal CreditTradingValues Clone()
        {
            CreditTradingValues value = new CreditTradingValues();
            value.AllowMfrTrading     = this.AllowMfrTrading;
            value.AllowFleetTransfers = this.AllowFleetTransfers;
            value.AllowCarryForward   = this.AllowCarryForward;
            value.CarryForwardYears   = this.CarryForwardYears;
            value.AllowCarryBackward  = this.AllowCarryBackward;
            value.CarryBackwardYears  = this.CarryBackwardYears;
            value.MinMY               = this.MinMY;
            value.TransferCaps        = this.TransferCaps .Clone();
            value.AdjustmentVMT       = this.AdjustmentVMT.Clone();
            value.AdjustmentMode      = this.AdjustmentMode;
            return value;
        }
        public double GetTransferCaps(RegulatoryClass regClass, int year)
        {
            if (year < this.MinMY) { return 0; }
            int index = Math.Min(year - this.MinMY, this.TransferCaps[regClass].Length - 1);
            return this.TransferCaps[regClass][index];
        }
        public double GetAdjustmentVMT(RegulatoryClass regClass, int year)
        {
            if (year < this.MinMY) { return 0; }
            int index = Math.Min(year - this.MinMY, this.TransferCaps[regClass].Length - 1);
            return this.AdjustmentVMT[regClass][index];
        }
        #endregion
        #region 
        public bool AllowMfrTrading { get; private set; }
        public bool AllowFleetTransfers { get; private set; }
        public bool AllowCarryForward { get; private set; }
        public int CarryForwardYears { get; private set; }
        public bool AllowCarryBackward { get; private set; }
        public int CarryBackwardYears { get; private set; }
        public int MinMY { get; private set; }
        public RCObject<double[]> TransferCaps { get; private set; }
        public RCObject<double[]> AdjustmentVMT { get; private set; }
        public CreditAdjustmentMode AdjustmentMode { get; private set; }
        #endregion
    }
    [Serializable]
    public sealed class DFSModelValues
    {
        #region 
        DFSModelValues() { }
        internal DFSModelValues(double initialLDVShare, VCValue<double> initialFE, double epsilon)
        {
            this.InitialLDVShare = initialLDVShare;
            this.InitialFE       = initialFE;
            this.Epsilon         = epsilon;
        }
        #endregion
        #region 
        internal DFSModelValues Clone()
        {
            DFSModelValues value = new DFSModelValues();
            value.InitialLDVShare = this.InitialLDVShare;
            value.InitialFE       = this.InitialFE.Clone();
            value.Epsilon         = this.Epsilon;
            return value;
        }
        #endregion
        #region 
        public double InitialLDVShare { get; private set; }
        public VCValue<double> InitialFE { get; private set; }
        public double Epsilon { get; private set; }
        #endregion
    }
    [Serializable]
    public sealed class FuelProperties
    {
        #region 
        FuelProperties() { }
        internal FuelProperties(
            FuelValue energyDensity, FuelValue massDensity, FuelValue carbonContent, FuelValue co2Emissions, FuelValue so2Emissions,
            FuelValue fsLowImports, FuelValue fsReducedRef, FuelValue reducedDomCrude, FuelValue reducedImpCrude)
        {
            this.EnergyDensity                       = energyDensity;
            this.MassDensity                         = massDensity;
            this.CarbonContent                       = carbonContent;
            this.CO2Emissions                        = co2Emissions;
            this.SO2Emissions                        = so2Emissions;
            this.FuelSavingsLeadingToLowerImports    = fsLowImports;
            this.FuelSavingsLeadingToReducedRefining = fsReducedRef;
            this.ReducedRefiningFromDomesticCrude    = reducedDomCrude;
            this.ReducedRefiningFromImportedCrude    = reducedImpCrude;
        }
        #endregion
        #region 
        internal FuelProperties Clone()
        {
            FuelProperties value = new FuelProperties();
            value.EnergyDensity                       = this.EnergyDensity;
            value.MassDensity                         = this.MassDensity;
            value.CarbonContent                       = this.CarbonContent;
            value.CO2Emissions                        = this.CO2Emissions;
            value.SO2Emissions                        = this.SO2Emissions;
            value.FuelSavingsLeadingToLowerImports    = this.FuelSavingsLeadingToLowerImports;
            value.FuelSavingsLeadingToReducedRefining = this.FuelSavingsLeadingToReducedRefining;
            value.ReducedRefiningFromDomesticCrude    = this.ReducedRefiningFromDomesticCrude;
            value.ReducedRefiningFromImportedCrude    = this.ReducedRefiningFromImportedCrude;
            return value;
        }
        #endregion
        #region 
        public FuelValue EnergyDensity { get; private set; }
        public FuelValue MassDensity { get; private set; }
        public FuelValue CarbonContent { get; private set; }
        public FuelValue CO2Emissions { get; private set; }
        public FuelValue SO2Emissions { get; private set; }
        public FuelValue FuelSavingsLeadingToLowerImports { get; private set; }
        public FuelValue FuelSavingsLeadingToReducedRefining { get; private set; }
        public FuelValue ReducedRefiningFromDomesticCrude { get; private set; }
        public FuelValue ReducedRefiningFromImportedCrude { get; private set; }
        #endregion
    }
    [Serializable]
    public sealed class EmissionCosts
    {
        #region 
        EmissionCosts() { }
        internal EmissionCosts(double co, double voc, double nox, double pm, double so2, double ch4Scalar, double n2oScalar,
            CO2EmissionCosts co2)
        {
            this.CO        = co;
            this.VOC       = voc;
            this.NOX       = nox;
            this.PM        = pm;
            this.SO2       = so2;
            this.CH4Scalar = ch4Scalar;
            this.N2OScalar = n2oScalar;
            this.CO2       = co2;
        }
        #endregion
        #region 
        internal EmissionCosts Clone()
        {
            EmissionCosts value = new EmissionCosts();
            value.CO        = this.CO;
            value.VOC       = this.VOC;
            value.NOX       = this.NOX;
            value.PM        = this.PM;
            value.SO2       = this.SO2;
            value.CH4Scalar = this.CH4Scalar;
            value.N2OScalar = this.N2OScalar;
            value.CO2       = this.CO2.Clone();
            return value;
        }
        #endregion
        #region 
        public double CO { get; private set; }
        public double VOC { get; private set; }
        public double NOX { get; private set; }
        public double PM { get; private set; }
        public double SO2 { get; private set; }
        public double CH4Scalar { get; private set; }
        public double N2OScalar { get; private set; }
        public CO2EmissionCosts CO2 { get; private set; }
        #endregion
    }
    [Serializable]
    public sealed class CO2EmissionCosts
    {
        #region 
        CO2EmissionCosts() { }
        internal CO2EmissionCosts(int minCY, int maxCY,
            double discRateLow, double[] costsLow, double discRateAvg, double[] costsAvg,
            double discRateHigh, double[] costsHigh, double discRateVeryHigh, double[] costsVeryHigh)
        {
            this.MinCY = minCY;
            this.MaxCY = maxCY;
            this._discRate = new double[]   { discRateLow, discRateAvg, discRateHigh, discRateVeryHigh };
            this._costs    = new double[][] { costsLow   , costsAvg   , costsHigh   , costsVeryHigh    };
        }
        #endregion
        #region 
        internal CO2EmissionCosts Clone()
        {
            CO2EmissionCosts value = new CO2EmissionCosts();
            value.MinCY     = this.MinCY;
            value.MaxCY     = this.MaxCY;
            value._discRate = this._discRate.CloneArray();
            value._costs    = this._costs   .CloneArray();
            return value;
        }
        public double GetCO2DiscountRate(Estimates co2Estimates)
        {
            return this._discRate[(int)co2Estimates];
        }
        public double GetCO2Costs(Estimates co2Estimates, int year)
        {
            int index = Math.Max(Math.Min(year, this.MaxCY) - this.MinCY, 0);
            return this._costs[(int)co2Estimates][index];
        }
        #endregion
        #region 
        public int MinCY { get; private set; }
        public int MaxCY { get; private set; }
        #endregion
        #region 
        double[] _discRate;
        double[][] _costs;
        #endregion
    }
    [Serializable]
    public sealed class UpstreamEmissions
    {
        #region 
        UpstreamEmissions() { }
        internal UpstreamEmissions(
            FuelValue co, FuelValue voc, FuelValue nox, FuelValue so2, FuelValue pm, FuelValue co2, FuelValue ch4, FuelValue n2o,
            FuelValue acetaldehyde, FuelValue acrolein, FuelValue benzene, FuelValue butadiene, FuelValue formaldehyde,
            FuelValue dpm10, FuelValue mtbe)
        {
            this.CO           = co;                 
            this.VOC          = voc;
            this.NOx          = nox;
            this.SO2          = so2;
            this.PM           = pm;
            this.CO2          = co2;
            this.CH4          = ch4;
            this.N2O          = n2o;
            this.Acetaldehyde = acetaldehyde;       
            this.Acrolein     = acrolein;
            this.Benzene      = benzene;
            this.Butadiene    = butadiene;
            this.Formaldehyde = formaldehyde;
            this.DPM10        = dpm10;
            this.MTBE         = mtbe;
        }
        #endregion
        #region 
        internal UpstreamEmissions Clone()
        {
            UpstreamEmissions value = new UpstreamEmissions();
            value.CO           = this.CO;           
            value.VOC          = this.VOC;
            value.NOx          = this.NOx;
            value.SO2          = this.SO2;
            value.PM           = this.PM;
            value.CO2          = this.CO2;
            value.CH4          = this.CH4;
            value.N2O          = this.N2O;
            value.Acetaldehyde = this.Acetaldehyde; 
            value.Acrolein     = this.Acrolein;
            value.Benzene      = this.Benzene;
            value.Butadiene    = this.Butadiene;
            value.Formaldehyde = this.Formaldehyde;
            value.DPM10        = this.DPM10;
            value.MTBE         = this.MTBE;
            return value;
        }
        public double GetUpstreamEmissions(Pollutant pollutant, FuelType fuelType)
        {
            return this[pollutant][fuelType];
        }
        #endregion
        #region 
        public FuelValue this[Pollutant pollutant]
        {
            get
            {
                switch (pollutant)
                {
                    case Pollutant.CO          : return this.CO;
                    case Pollutant.VOC         : return this.VOC;
                    case Pollutant.NOx         : return this.NOx;
                    case Pollutant.SO2         : return this.SO2;
                    case Pollutant.PM          : return this.PM;
                    case Pollutant.CO2         : return this.CO2;
                    case Pollutant.CH4         : return this.CH4;
                    case Pollutant.N2O         : return this.N2O;
                    case Pollutant.Acetaldehyde: return this.Acetaldehyde;
                    case Pollutant.Acrolein    : return this.Acrolein;
                    case Pollutant.Benzene     : return this.Benzene;
                    case Pollutant.Butadiene   : return this.Butadiene;
                    case Pollutant.Formaldehyde: return this.Formaldehyde;
                    case Pollutant.DPM10       : return this.DPM10;
                    case Pollutant.MTBE        : return this.MTBE;
                    default:
                        throw new ArgumentOutOfRangeException("pollutant", "The specified pollutant value is not supported.");
                }
            }
        }
        public FuelValue CO { get; private set; }
        public FuelValue VOC { get; private set; }
        public FuelValue NOx { get; private set; }
        public FuelValue SO2 { get; private set; }
        public FuelValue PM { get; private set; }
        public FuelValue CO2 { get; private set; }
        public FuelValue CH4 { get; private set; }
        public FuelValue N2O { get; private set; }
        public FuelValue Acetaldehyde { get; private set; }
        public FuelValue Acrolein { get; private set; }
        public FuelValue Benzene { get; private set; }
        public FuelValue Butadiene { get; private set; }
        public FuelValue Formaldehyde { get; private set; }
        public FuelValue DPM10 { get; private set; }
        public FuelValue MTBE { get; private set; }
        #endregion
    }
    [Serializable]
    public sealed class TailpipeEmissions
    {
        #region 
        TailpipeEmissions() { }
        internal TailpipeEmissions(
            int minMY, int maxMY,
            FTObject<VCObject<double[][]>> co , FTObject<VCObject<double[][]>> voc, FTObject<VCObject<double[][]>> nox,
            FTObject<VCObject<double[][]>> so2, FTObject<VCObject<double[][]>> pm , FTObject<VCObject<double[][]>> co2,
            FTObject<VCObject<double[][]>> ch4, FTObject<VCObject<double[][]>> n2o,
            FTObject<VCObject<double[][]>> acetaldehyde, FTObject<VCObject<double[][]>> acrolein,
            FTObject<VCObject<double[][]>> benzene     , FTObject<VCObject<double[][]>> butadiene,
            FTObject<VCObject<double[][]>> formaldehyde, FTObject<VCObject<double[][]>> dpm10, FTObject<VCObject<double[][]>> mtbe)
        {
            this.MinMY        = minMY;
            this.MaxMY        = maxMY;
            this.CO           = co;
            this.VOC          = voc;
            this.NOx          = nox;
            this.SO2          = so2;
            this.PM           = pm;
            this.CO2          = co2;
            this.CH4          = ch4;
            this.N2O          = n2o;
            this.Acetaldehyde = acetaldehyde;
            this.Acrolein     = acrolein;
            this.Benzene      = benzene;
            this.Butadiene    = butadiene;
            this.Formaldehyde = formaldehyde;
            this.DPM10        = dpm10;
            this.MTBE         = mtbe;
        }
        #endregion
        #region 
        internal TailpipeEmissions Clone()
        {
            TailpipeEmissions value = new TailpipeEmissions();
            value.MinMY        = this.MinMY;
            value.MaxMY        = this.MaxMY;
            value.CO           = this.Clone(this.CO);           
            value.VOC          = this.Clone(this.VOC);
            value.NOx          = this.Clone(this.NOx);
            value.SO2          = this.Clone(this.SO2);
            value.PM           = this.Clone(this.PM);
            value.CO2          = this.Clone(this.CO2);
            value.CH4          = this.Clone(this.CH4);
            value.N2O          = this.Clone(this.N2O);
            value.Acetaldehyde = this.Clone(this.Acetaldehyde); 
            value.Acrolein     = this.Clone(this.Acrolein);
            value.Benzene      = this.Clone(this.Benzene);
            value.Butadiene    = this.Clone(this.Butadiene);
            value.Formaldehyde = this.Clone(this.Formaldehyde);
            value.DPM10        = this.Clone(this.DPM10);
            value.MTBE         = this.Clone(this.MTBE);
            return value;
        }
        FTObject<VCObject<double[][]>> Clone(FTObject<VCObject<double[][]>> value)
        {
            if (value == null) { return null; }
            FTObject<VCObject<double[][]>> newValue = new FTObject<VCObject<double[][]>>();
            foreach (FuelType fuelType in FTValue<object>.Classes)
            {
                newValue[fuelType] = new VCObject<double[][]>();
                foreach (VehicleClass vehClass in VCValue<object>.Classes)
                {
                    newValue[fuelType][vehClass] = value[fuelType][vehClass].CloneArray();
                }
            }
            return newValue;
        }
        public double GetTailpipeEmissions(Pollutant pollutant, FuelType fuelType, VehicleClass vehClass, int year, int age)
        {
            var te = this[pollutant];
            if (te == null) { return 0; }
            int index = Math.Max(Math.Min(year, this.MaxMY) - this.MinMY, 0);
            return te[fuelType][vehClass][index][age];
        }
        public bool HasEmissions(Pollutant pollutant)
        {
            return (this[pollutant] != null);
        }
        #endregion
        #region 
        public int MinMY { get; private set; }
        public int MaxMY { get; private set; }
        public FTObject<VCObject<double[][]>> this[Pollutant pollutant]
        {
            get
            {
                switch (pollutant)
                {
                    case Pollutant.CO          : return this.CO;
                    case Pollutant.VOC         : return this.VOC;
                    case Pollutant.NOx         : return this.NOx;
                    case Pollutant.SO2         : return this.SO2;
                    case Pollutant.PM          : return this.PM;
                    case Pollutant.CO2         : return this.CO2;
                    case Pollutant.CH4         : return this.CH4;
                    case Pollutant.N2O         : return this.N2O;
                    case Pollutant.Acetaldehyde: return this.Acetaldehyde;
                    case Pollutant.Acrolein    : return this.Acrolein;
                    case Pollutant.Benzene     : return this.Benzene;
                    case Pollutant.Butadiene   : return this.Butadiene;
                    case Pollutant.Formaldehyde: return this.Formaldehyde;
                    case Pollutant.DPM10       : return this.DPM10;
                    case Pollutant.MTBE        : return this.MTBE;
                    default:
                        throw new ArgumentOutOfRangeException("pollutant", "The specified pollutant value is not supported.");
                }
            }
        }
        public FTObject<VCObject<double[][]>> CO { get; private set; }
        public FTObject<VCObject<double[][]>> VOC { get; private set; }
        public FTObject<VCObject<double[][]>> NOx { get; private set; }
        public FTObject<VCObject<double[][]>> SO2 { get; private set; }
        public FTObject<VCObject<double[][]>> PM { get; private set; }
        public FTObject<VCObject<double[][]>> CO2 { get; private set; }
        public FTObject<VCObject<double[][]>> CH4 { get; private set; }
        public FTObject<VCObject<double[][]>> N2O { get; private set; }
        public FTObject<VCObject<double[][]>> Acetaldehyde { get; private set; }
        public FTObject<VCObject<double[][]>> Acrolein { get; private set; }
        public FTObject<VCObject<double[][]>> Benzene { get; private set; }
        public FTObject<VCObject<double[][]>> Butadiene { get; private set; }
        public FTObject<VCObject<double[][]>> Formaldehyde { get; private set; }
        public FTObject<VCObject<double[][]>> DPM10 { get; private set; }
        public FTObject<VCObject<double[][]>> MTBE { get; private set; }
        #endregion
    }
    #endregion
    [Serializable]
    public class Parameters : ICloneable
    {
        #region 
        Parameters() { }
        public Parameters(EconomicValues economicValues, VehicleAgeData survivalRates, VehicleAgeData milesDriven,
            FuelPrices fuelPrices, FuelEconomyData fuelEconomyData, FleetAnalysisValues fleetAnalysisValues,
            HistoricFleetData historicFleetData, SafetyValues safetyValues, CreditTradingValues creditTradingValues,
            DFSModelValues dfsModelValues, FuelProperties fuelProperties, EmissionCosts emissionCosts,
            UpstreamEmissions upstreamEmissions, TailpipeEmissions tailpipeEmissions)
        {
            this.EconomicValues      = economicValues;
            this.SurvivalRates       = survivalRates;
            this.MilesDriven         = milesDriven;
            this.FuelPrices          = fuelPrices;
            this.FuelEconomyData     = fuelEconomyData;
            this.FleetAnalysisValues = fleetAnalysisValues;
            this.HistoricFleetData   = historicFleetData;
            this.SafetyValues        = safetyValues;
            this.CreditTradingValues = creditTradingValues;
            this.DFSModelValues      = dfsModelValues;
            this.FuelProperties      = fuelProperties;
            this.EmissionCosts       = emissionCosts;
            this.UpstreamEmissions   = upstreamEmissions;
            this.TailpipeEmissions   = tailpipeEmissions;
        }
        #endregion
        #region 
        #region 
        object ICloneable.Clone()
        {
            return this.Clone();
        }
        public Parameters Clone()
        {
            Parameters value = new Parameters();
            value.EconomicValues      = this.EconomicValues     .Clone();
            value.SurvivalRates       = this.SurvivalRates      .Clone();
            value.MilesDriven         = this.MilesDriven        .Clone();
            value.FuelPrices          = this.FuelPrices         .Clone();
            value.FuelEconomyData     = this.FuelEconomyData    .Clone();
            value.FleetAnalysisValues = this.FleetAnalysisValues.Clone();
            value.HistoricFleetData   = this.HistoricFleetData  .Clone();
            value.SafetyValues        = this.SafetyValues       .Clone();
            value.CreditTradingValues = this.CreditTradingValues.Clone();
            value.DFSModelValues      = this.DFSModelValues     .Clone();
            value.FuelProperties      = this.FuelProperties     .Clone();
            value.EmissionCosts       = this.EmissionCosts      .Clone();
            value.UpstreamEmissions   = this.UpstreamEmissions  .Clone();
            value.TailpipeEmissions   = this.TailpipeEmissions  .Clone();
            return value;
        }
        #endregion
        public void UpdateForMonteCarlo(double fuelPriceScaleFactor, int fuelPriceStartYear, double priceShockAlpha,
            double reboundEffect, double onRoadGap_Gasoline, double onRoadGap_Diesel)
        {
            FuelPrices oldFP = this.FuelPrices.Clone();
            this.FuelPrices                  .UpdateForMonteCarlo(fuelPriceScaleFactor, fuelPriceStartYear);
            this.EconomicValues              .UpdateForMonteCarlo(reboundEffect, onRoadGap_Gasoline, onRoadGap_Diesel);
            this.EconomicValues.EconomicCosts.UpdateForMonteCarlo(oldFP, this.FuelPrices, priceShockAlpha);
        }
        #endregion
        #region 
        public EconomicValues EconomicValues { get; private set; }
        public VehicleAgeData SurvivalRates { get; private set; }
        public VehicleAgeData MilesDriven { get; private set; }
        public FuelPrices FuelPrices { get; private set; }
        public FuelEconomyData FuelEconomyData { get; private set; }
        public FleetAnalysisValues FleetAnalysisValues { get; private set; }
        public HistoricFleetData HistoricFleetData { get; private set; }
        public SafetyValues SafetyValues { get; private set; }
        public CreditTradingValues CreditTradingValues { get; private set; }
        public DFSModelValues DFSModelValues { get; private set; }
        public FuelProperties FuelProperties { get; private set; }
        public EmissionCosts EmissionCosts { get; private set; }
        public UpstreamEmissions UpstreamEmissions { get; private set; }
        public TailpipeEmissions TailpipeEmissions { get; private set; }
        #endregion
    }
}

