#region << Using Directives >>
using System;
using Volpe.Cafe.Data;
using Volpe.Cafe.Settings;
using Volpe.Cafe.Utils;
using Volpe.XlLib;
using System.Collections.Generic;
using Volpe.Cafe.Generic;
#endregion
namespace Volpe.Cafe.IO.InputParsers
{
    sealed class XlParametersParser : XlParser
    {
        #region 
        internal XlParametersParser(string path, string password) : base(path, password, true) { }
        #endregion
        #region 
        protected override void ParseInternal()
        {
            string[] sheets = this.SheetNames;
            string[] names = new string[] {
                "Economic Values",          
                "Vehicle Age Data",         
                "Fuel Prices",              
                "Fuel Economy Data",        
                "Fleet Analysis Values",    
                "Historic Fleet Data",      
                "Safety Values",            
                "Credit Trading Values",    
                "DFS Model Values",         
                "Fuel Properties",          
                "Emission Costs",           
                "Upstream Emissions"};      
            string[] teNames = new string[] {
                "TE_CO" , "TE_VOC", "TE_NOX", "TE_SO2", "TE_PM" , "TE_CO2", "TE_CH4", "TE_N2O",     
                "TE_ACT", "TE_ACR", "TE_BNZ", "TE_BTD", "TE_FRM", "TE_DPM", "TE_MTBE"};             
            int[] indexes   = new int[names  .Length];
            int[] teIndexes = new int[teNames.Length];
            for (int i = 0; i < indexes  .Length; i++) { indexes  [i] = -1; }
            for (int i = 0; i < teIndexes.Length; i++) { teIndexes[i] = -1; }
            for (int i = 0; i < sheets.Length; i++)
            {
                for (int j = 0; j < names.Length; j++)
                {
                    if (indexes[j] == -1 && Interaction.StringCompare(sheets[i], names[j], true)) { indexes[j] = i; break; }
                }
                for (int j = 0; j < teNames.Length; j++)
                {
                    if (teIndexes[j] == -1 && Interaction.StringCompare(sheets[i], teNames[j], true)) { teIndexes[j] = i; break; }
                }
            }
            if (!this.VerifyIndexes("Parameters workbook", indexes  , names  )) { return; }
            double[] fuelMix;
            EconomicValues      economicValues      = this.ParseEconomicValues     (indexes[ 0]);
            VehicleAgeData      survivalRates       = this.ParseVehicleAgeData     (indexes[ 1], 1);
            VehicleAgeData      milesDriven         = this.ParseVehicleAgeData     (indexes[ 1], 8);
            FuelPrices          fuelPrices          = this.ParseFuelPrices         (indexes[ 2]);
            FuelEconomyData     fuelEconomyData     = this.ParseFuelEconomyData    (indexes[ 3]);
            FleetAnalysisValues fleetAnalysisValues = this.ParseFleetAnalysisValues(indexes[ 4]);
            HistoricFleetData   historicFleetData   = this.ParseHistoricFleetData  (indexes[ 5]);
            SafetyValues        safetyValues        = this.ParseSafetyValues       (indexes[ 6]);
            CreditTradingValues creditTradingValues = this.ParseCreditTradingValues(indexes[ 7]);
            DFSModelValues      dfsModelValues      = this.ParseDFSModelValues     (indexes[ 8]);
            FuelProperties      fuelProperties      = this.ParseFuelProperties     (indexes[ 9],     out fuelMix);
            EmissionCosts       emissionCosts       = this.ParseEmissionCosts      (indexes[10]);
            UpstreamEmissions   upstreamEmissions   = this.ParseUpstreamEmissions  (indexes[11],         fuelMix);
            TailpipeEmissions   tailpipeEmissions   = this.ParseTailpipeEmissions  (teNames, teIndexes,  fuelMix);
            this._value = new Parameters(economicValues, survivalRates, milesDriven, fuelPrices, fuelEconomyData,
                fleetAnalysisValues, historicFleetData, safetyValues, creditTradingValues, dfsModelValues, fuelProperties,
                emissionCosts, upstreamEmissions, tailpipeEmissions);
        }
        EconomicValues ParseEconomicValues(int sheetIndex)
        {
            this.ActivateWorksheet(sheetIndex);
            VCValue <double        > reboundEffect      = new VCValue<double         >();
            VCValue <double        > discRate           = new VCValue<double         >();
            VCValue <int           > drBaseYear         = new VCValue<int            >();
            VCObject<VMTGrowthRate > vmtGrowthRate      = new VCObject<VMTGrowthRate >();
            VCValue <FuelValue     > onRoadGap          = new VCValue<FuelValue      >();
            VCValue <FuelValue     > refuelTime         = new VCValue<FuelValue      >();
            VCValue <double        > refuelTank         = new VCValue<double         >();
            VCValue <double        > vehTravelTimeValue = new VCValue<double         >();
            VCObject<ExternalCosts > externalCosts      = new VCObject<ExternalCosts >();
            VCObject<OperatingCosts> operatingCosts     = new VCObject<OperatingCosts>();
            int row = 2, col = 1;
            foreach (VehicleClass vehClass in VCValue<object>.Classes)
            {
                row = 2;
                reboundEffect     [vehClass] = this.GetDouble        (++row, col);
                discRate          [vehClass] = this.GetDouble        (++row, col);
                drBaseYear        [vehClass] = this.GetInt32         (++row, col); row += 1;
                vmtGrowthRate     [vehClass] = this.GetVMTGrowthRate (++row, col); row += 4;
                onRoadGap         [vehClass] = this.GetFuelValue     (++row, col); row += FTValue<object>.Classes.Length;
                refuelTime        [vehClass] = this.GetFuelValue     (++row, col); row += FTValue<object>.Classes.Length - 1;
                refuelTank        [vehClass] = this.GetDouble        (++row, col);
                vehTravelTimeValue[vehClass] = this.GetDouble        (++row, col); row += 1;
                externalCosts     [vehClass] = this.GetExternalCosts (++row, col); row += 3;
                operatingCosts    [vehClass] = this.GetOperatingCosts(++row, col); row += 6;
                col++;
            }
            int minCY = 0, maxCY = 0;
            List<double> monopsony   = new List<double>();
            List<double> priceShock  = new List<double>();
            List<double> milSecurity = new List<double>();
            int prevYear = -1;
            row += 2;
            for (; row < this.Rows; row++)
            {
                string errHDR = "Economic Values (R:" + (row + 1).ToString("0000") + "):  ";
                if (this.GetString(row, 0) == string.Empty) { continue; }
                int year = this.GetInt32(row, 0);
                if (!ModelYear.IsValid(year)) { this.LogError(errHDR + "Calendar year is not valid."); }
                if (prevYear != -1 && prevYear + 1 != year) { this.LogError(errHDR + "Calendar years are not contiguous."); }
                prevYear = year;
                if (minCY == 0) { minCY = year; }
                else            { maxCY = year; }
                monopsony  .Add(this.GetDouble(row, 1));
                priceShock .Add(this.GetDouble(row, 2));
                milSecurity.Add(this.GetDouble(row, 3));
            }
            EconomicCosts economicCosts = new EconomicCosts(minCY, maxCY, monopsony.ToArray(), priceShock.ToArray(), milSecurity.ToArray());
            return new EconomicValues(reboundEffect, discRate, drBaseYear, vmtGrowthRate, onRoadGap, refuelTime, refuelTank,
                vehTravelTimeValue, economicCosts, externalCosts, operatingCosts);
        }
        VehicleAgeData ParseVehicleAgeData(int sheetIndex, int sCol)
        {
            this.ActivateWorksheet(sheetIndex);
            double[] cars     = new double[Interaction.CY];
            double[] vans     = new double[Interaction.CY];
            double[] suvs     = new double[Interaction.CY];
            double[] pickups  = new double[Interaction.CY];
            double[] class12a = new double[Interaction.CY];
            double[] class2b3 = new double[Interaction.CY];
            double[] class456 = new double[Interaction.CY];
            for (int i = 0; i < Interaction.CY; i++)
            {
                cars    [i] = this.GetDouble(5 + i, sCol + 0);
                vans    [i] = this.GetDouble(5 + i, sCol + 1);
                suvs    [i] = this.GetDouble(5 + i, sCol + 2);
                pickups [i] = this.GetDouble(5 + i, sCol + 3);
                class12a[i] = this.GetDouble(5 + i, sCol + 4);
                class2b3[i] = this.GetDouble(5 + i, sCol + 5);
                class456[i] = this.GetDouble(5 + i, sCol + 6);
            }
            return new VehicleAgeData(cars, vans, suvs, pickups, class12a, class2b3, class456);
        }
        FuelPrices ParseFuelPrices(int sheetIndex)
        {
            this.ActivateWorksheet(sheetIndex);
            int minCY = 0, maxCY = 0;
            FTObject<double[]> fuelTax = new FTObject<double[]>();
            FTObject<double[]> fpLow   = new FTObject<double[]>();
            FTObject<double[]> fpAvg   = new FTObject<double[]>();
            FTObject<double[]> fpHigh  = new FTObject<double[]>();
            int prevYear = -1;
            for (int row = 5; row < this.Rows; row++)
            {
                string errHDR = "Fuel Prices (R:" + (row + 1).ToString("0000") + "):  ";
                if (this.GetString(row, 0) == string.Empty) { continue; }
                int year = this.GetInt32(row, 0);
                if (year < 1900) { this.LogError(errHDR + "Calendar year is not valid."); }
                if (prevYear != -1 && prevYear + 1 != year) { this.LogError(errHDR + "Calendar years are not contiguous."); }
                prevYear = year;
                if (minCY == 0) { minCY = year; }
                else            { maxCY = year; }
            }
            int yearCount = maxCY - minCY + 1;
            for (int i = 0; i < FTValue<object>.Classes.Length; i++)
            {
                FuelType fuel = FTValue<object>.Classes[i];
                fuelTax[fuel] = new double[yearCount];
                fpLow  [fuel] = new double[yearCount];
                fpAvg  [fuel] = new double[yearCount];
                fpHigh [fuel] = new double[yearCount];
                for (int row = 5; row < this.Rows; row++)
                {
                    if (this.GetString(row, 0) == string.Empty) { continue; }
                    fpLow  [fuel][row - 5] = Math.Round(this.GetDouble(row, 1 + 4 * i), 4);
                    fpAvg  [fuel][row - 5] = Math.Round(this.GetDouble(row, 2 + 4 * i), 4);
                    fpHigh [fuel][row - 5] = Math.Round(this.GetDouble(row, 3 + 4 * i), 4);
                    fuelTax[fuel][row - 5] = Math.Round(this.GetDouble(row, 4 + 4 * i), 4);
                }
            }
            return new FuelPrices(minCY, maxCY, fuelTax, fpLow, fpAvg, fpHigh);
        }
        FuelEconomyData ParseFuelEconomyData(int sheetIndex)
        {
            this.ActivateWorksheet(sheetIndex);
            int minMY = 0, maxMY = 0;
            FTObject<VCObject<double[]>> fuelEconomy = new FTObject<VCObject<double[]>>();
            FTObject<VCObject<double[]>> fuelShare   = new FTObject<VCObject<double[]>>();
            int prevYear = -1;
            for (int row = 5; row < this.Rows; row++)
            {
                string errHDR = "Fuel Economy Data (R:" + (row + 1).ToString("0000") + "):  ";
                if (this.GetString(row, 0) == string.Empty) { continue; }
                int year = this.GetInt32(row, 0);
                if (year < 1900) { this.LogError(errHDR + "Model year is not valid."); }
                if (prevYear != -1 && prevYear + 1 != year) { this.LogError(errHDR + "Model years are not contiguous."); }
                prevYear = year;
                if (minMY == 0) { minMY = year; }
                else            { maxMY = year; }
            }
            int ftCount = FTValue<object>.Classes.Length;
            int vcCount = VCValue<object>.Classes.Length;
            int yearCount = maxMY - minMY + 1;
            for (int i = 0; i < ftCount; i++)
            {
                FuelType fuel = FTValue<object>.Classes[i];
                fuelEconomy[fuel] = new VCObject<double[]>();
                fuelShare  [fuel] = new VCObject<double[]>();
                for (int j = 0; j < vcCount; j++)
                {
                    VehicleClass vehClass = VCValue<object>.Classes[j];
                    fuelEconomy[fuel][vehClass] = new double[yearCount];
                    fuelShare  [fuel][vehClass] = new double[yearCount];
                    for (int row = 5; row < this.Rows; row++)
                    {
                        if (this.GetString(row, 0) == string.Empty) { continue; }
                        fuelEconomy[fuel][vehClass][row - 5] = this.GetDouble(row, 1           + i + j * (ftCount * 2));
                        fuelShare  [fuel][vehClass][row - 5] = this.GetDouble(row, 1 + ftCount + i + j * (ftCount * 2));
                    }
                }
            }
            return new FuelEconomyData(minMY, maxMY, fuelEconomy, fuelShare);
        }
        FleetAnalysisValues ParseFleetAnalysisValues(int sheetIndex)
        {
            this.ActivateWorksheet(sheetIndex);
            int minMY = 0, maxMY = 0;
            VCValue <double  > baseCAFEGrowthRates = new VCValue <double  >();
            VCValue <double  > scenCAFEGrowthRates = new VCValue <double  >();
            VCValue <double  > cafeStartYear       = new VCValue <double  >();
            VCObject<double[]> salesForecast       = new VCObject<double[]>();
            int prevYear = -1;
            for (int row = 8; row < this.Rows; row++)
            {
                string errHDR = "Fleet Analysis Values (R:" + (row + 1).ToString("0000") + "):  ";
                if (this.GetString(row, 0) == string.Empty) { continue; }
                int year = this.GetInt32(row, 0);
                if (year < 1900) { this.LogError(errHDR + "Model year is not valid."); }
                if (prevYear != -1 && prevYear + 1 != year) { this.LogError(errHDR + "Model years are not contiguous."); }
                prevYear = year;
                if (minMY == 0) { minMY = year; }
                else            { maxMY = year; }
            }
            int yearCount = maxMY - minMY + 1;
            int col = 1;
            foreach (VehicleClass vehClass in VCValue<object>.Classes)
            {
                baseCAFEGrowthRates[vehClass] = this.GetDouble(4, col);
                scenCAFEGrowthRates[vehClass] = this.GetDouble(5, col);
                cafeStartYear      [vehClass] = this.GetDouble(6, col);
                salesForecast      [vehClass] = new double[yearCount];
                for (int i = 0; i < yearCount; i++)
                {
                    salesForecast[vehClass][i] = this.GetDouble(8 + i, col);
                }
                col++;
            }
            return new FleetAnalysisValues(minMY, maxMY, baseCAFEGrowthRates, scenCAFEGrowthRates, cafeStartYear, salesForecast);
        }
        HistoricFleetData ParseHistoricFleetData(int sheetIndex)
        {
            this.ActivateWorksheet(sheetIndex);
            int minMY = 0, maxMY = 0;
            int prevYear = -1;
            int ageCounter = 0;
            for (int row = 3; row < this.Rows; row++)
            {
                string errHDR = "Historic Fleet Data (R:" + (row + 1).ToString("0000") + "):  ";
                if (this.GetString(row, 0) == string.Empty) { continue; }
                int year = this.GetInt32(row, 0);
                int age  = this.GetInt32(row, 1);
                if (age != (++ageCounter)) { this.LogError(errHDR + "Vehicle ages are not contiguous."); }
                if (ageCounter == 40) { ageCounter = 0; }
                if (ageCounter == 1 && prevYear != -1 && prevYear + 1 != year)
                {   
                    if (prevYear != -1 && prevYear + 1 != year) { this.LogError(errHDR + "Model years are not contiguous."); }
                }
                else if (ageCounter > 1 && prevYear != -1 && prevYear != year)
                {   
                    this.LogError(errHDR + "Model years are out of order.");
                }
                prevYear = year;
                if (minMY == 0) { minMY = year; }
                else            { maxMY = year; }
            }
            int yearCount = maxMY - minMY + 1;
            VCObject<double[][]> fleetData = new VCObject<double[][]>();
            foreach (VehicleClass vehClass in VCValue<object>.Classes)
            {
                fleetData[vehClass] = new double[yearCount][];
                for (int i = 0; i < yearCount; i++)
                {
                    fleetData[vehClass][i] = new double[Interaction.CY];
                }
            }
            int col;
            for (int i = 0; i < yearCount; i++)
            {
                for (int j = 0; j < Interaction.CY; j++)
                {
                    col = 2;
                    foreach (VehicleClass vehClass in VCValue<object>.Classes)
                    {
                        fleetData[vehClass][i][j] = this.GetDouble(3 + j + i * Interaction.CY, col++);
                    }
                }
            }
            return new HistoricFleetData(minMY, maxMY, fleetData);
        }
        SafetyValues ParseSafetyValues(int sheetIndex)
        {
            this.ActivateWorksheet(sheetIndex);
            SCValue<double> threshold   = new SCValue<double>();
            SCValue<double> smallEffect = new SCValue<double>();
            SCValue<double> smallBase   = new SCValue<double>();
            SCValue<double> smallFMVSS  = new SCValue<double>();
            SCValue<double> largeEffect = new SCValue<double>();
            SCValue<double> largeBase   = new SCValue<double>();
            SCValue<double> largeFMVSS  = new SCValue<double>();
            double fatalityCosts;
            double growthRate;
            int    baseYear;
            int row = 2;
            foreach (SafetyClass sftClass in SCValue<object>.Classes)
            {
                threshold[sftClass] = this.GetDouble(row++, 1);
            }
            row += 2;
            foreach (SafetyClass sftClass in SCValue<object>.Classes)
            {
                smallEffect[sftClass] = this.GetDouble(row, 1);
                smallBase  [sftClass] = this.GetDouble(row, 2);
                smallFMVSS [sftClass] = this.GetDouble(row, 3);
                row++;
                largeEffect[sftClass] = this.GetDouble(row, 1);
                largeBase  [sftClass] = this.GetDouble(row, 2);
                largeFMVSS [sftClass] = this.GetDouble(row, 3);
                row++;
            }
            row += 2;
            fatalityCosts = this.GetDouble(row++, 1);
            growthRate    = this.GetDouble(row++, 1);
            baseYear      = this.GetInt32 (row++, 1);
            return new SafetyValues(threshold, smallEffect, smallBase, smallFMVSS, largeEffect, largeBase, largeFMVSS,
                fatalityCosts, growthRate, baseYear);
        }
        CreditTradingValues ParseCreditTradingValues(int sheetIndex)
        {
            this.ActivateWorksheet(sheetIndex);
            bool allowTrading   = this.GetBool (3, 3);
            bool allowTransfers = this.GetBool (4, 3);
            bool allowCarryFwd  = this.GetBool (5, 3);
            int  carryFwdYears  = this.GetInt32(6, 3);
            bool allowCarryBwd  = this.GetBool (7, 3);
            int  carryBwdYears  = this.GetInt32(8, 3);
            int minCY = 0, maxCY = 0;
            RCObject<double[]> transferCaps = new RCObject<double[]>();
            RCObject<double[]> adjVMT       = new RCObject<double[]>();
            int prevYear = -1;
            for (int row = 12; row < this.Rows; row++)
            {
                string errHDR = "Credit Trading Values (R:" + (row + 1).ToString("0000") + "):  ";
                if (this.GetString(row, 0) == string.Empty) { continue; }
                int year = this.GetInt32(row, 0);
                if (!ModelYear.IsValid(year)) { this.LogError(errHDR + "Calendar year is not valid."); }
                if (prevYear != -1 && prevYear + 1 != year) { this.LogError(errHDR + "Calendar years are not contiguous."); }
                prevYear = year;
                if (minCY == 0) { minCY = year; }
                else            { maxCY = year; }
            }
            int yearCount = maxCY - minCY + 1;
            int rcCount = RCDouble.Classes.Length;
            for (int i = 0; i < rcCount; i++)
            {
                RegulatoryClass regClass = RCDouble.Classes[i];
                transferCaps[regClass] = new double[yearCount];
                adjVMT      [regClass] = new double[yearCount];
                for (int row = 12; row < this.Rows; row++)
                {
                    if (this.GetString(row, 0) == string.Empty) { continue; }
                    transferCaps[regClass][row - 12] = this.GetDouble(row, 1 + i);
                    adjVMT      [regClass][row - 12] = this.GetDouble(row, 1 + i + rcCount);
                }
            }
            return new CreditTradingValues(allowTrading, allowTransfers, allowCarryFwd, carryFwdYears, allowCarryBwd, carryBwdYears,
                minCY, transferCaps, adjVMT, CreditAdjustmentMode.Variable);
        }
        DFSModelValues ParseDFSModelValues(int sheetIndex)
        {
            this.ActivateWorksheet(sheetIndex);
            int row = 1;
            double          initialLDVShare = this.GetDouble (++row, 1);
            VCValue<double> initialFE       = this.GetVCValue(++row, 1); row += VCValue<double>.Classes.Length;
            double          epsilon         = this.GetDouble (  row, 1);
            return new DFSModelValues(initialLDVShare, initialFE, epsilon);
        }
        FuelProperties ParseFuelProperties(int sheetIndex, out double[] fuelMix)
        {
            this.ActivateWorksheet(sheetIndex);
            fuelMix = new double[3];
            fuelMix[0] = this.GetDouble(3, 1);
            fuelMix[1] = this.GetDouble(4, 1);
            fuelMix[2] = this.GetDouble(5, 1);
            FuelValue energyDensity = this.ParseFuelValue(3, 2, fuelMix);
            FuelValue massDensity   = this.ParseFuelValue(3, 3, fuelMix);
            FuelValue carbonContent = this.ParseFuelValue(3, 4, fuelMix);
            FuelValue co2Emissions  = massDensity * carbonContent * 44 / 12;
            FuelValue so2Emissions  = this.ParseFuelValue(3, 5, fuelMix);
            FuelValue fsLowImports    = new FuelValue();
            FuelValue fsReducedRef    = new FuelValue();
            FuelValue reducedDomCrude = new FuelValue();
            FuelValue reducedImpCrude = new FuelValue();
            int row = 9 + FTValue<object>.Classes.Length;
            int col = 1;
            foreach (FuelType fuel in FTValue<object>.Classes)
            {
                fsLowImports   [fuel] = this.GetDouble(row++, col);
                fsReducedRef   [fuel] = this.GetDouble(row++, col);
                reducedDomCrude[fuel] = this.GetDouble(row++, col);
                reducedImpCrude[fuel] = this.GetDouble(row++, col);
                row -= 4;
                col++;
            }
            return new FuelProperties(energyDensity, massDensity, carbonContent, co2Emissions, so2Emissions,
                                      fsLowImports, fsReducedRef, reducedDomCrude, reducedImpCrude);
        }
        EmissionCosts ParseEmissionCosts(int sheetIndex)
        {
            this.ActivateWorksheet(sheetIndex);
            double co        = this.GetDouble(3, 1);
            double voc       = this.GetDouble(4, 1);
            double nox       = this.GetDouble(5, 1);
            double pm        = this.GetDouble(6, 1);
            double so2       = this.GetDouble(7, 1);
            double ch4Scalar = this.GetDouble(8, 1);
            double n2oScalar = this.GetDouble(9, 1);
            double drLow   = this.GetDouble(12, 1);
            double drAvg   = this.GetDouble(12, 2);
            double drHigh  = this.GetDouble(12, 3);
            double drVHigh = this.GetDouble(12, 4);
            int minCY = 0, maxCY = 0;
            List<double> costsLow   = new List<double>();
            List<double> costsAvg   = new List<double>();
            List<double> costsHigh  = new List<double>();
            List<double> costsVHigh = new List<double>();
            int prevYear = -1;
            for (int row = 14; row < this.Rows; row++)
            {
                string errHDR = "Emission Costs (R:" + (row + 1).ToString("0000") + "):  ";
                if (this.GetString(row, 0) == string.Empty) { continue; }
                int year = this.GetInt32(row, 0);
                if (!ModelYear.IsValid(year)) { this.LogError(errHDR + "Calendar year is not valid."); }
                if (prevYear != -1 && prevYear + 1 != year) { this.LogError(errHDR + "Calendar years are not contiguous."); }
                prevYear = year;
                if (minCY == 0) { minCY = year; }
                else            { maxCY = year; }
                costsLow  .Add(this.GetDouble(row, 1));
                costsAvg  .Add(this.GetDouble(row, 2));
                costsHigh .Add(this.GetDouble(row, 3));
                costsVHigh.Add(this.GetDouble(row, 4));
            }
            CO2EmissionCosts co2 = new CO2EmissionCosts(minCY, maxCY, drLow, costsLow.ToArray(), drAvg, costsAvg.ToArray(),
                drHigh, costsHigh.ToArray(), drVHigh, costsVHigh.ToArray());
            return new EmissionCosts(co, voc, nox, pm, so2, ch4Scalar, n2oScalar, co2);
        }
        UpstreamEmissions ParseUpstreamEmissions(int sheetIndex, double[] fuelMix)
        {
            this.ActivateWorksheet(sheetIndex);
            int fuelCount = FTValue<object>.Classes.Length + 2;
            int row = 4;
            FuelValue co           = this.ParseFuelValue( row              , 6, fuelMix);
            FuelValue voc          = this.ParseFuelValue((row += fuelCount), 6, fuelMix);
            FuelValue nox          = this.ParseFuelValue((row += fuelCount), 6, fuelMix);
            FuelValue so2          = this.ParseFuelValue((row += fuelCount), 6, fuelMix);
            FuelValue pm           = this.ParseFuelValue((row += fuelCount), 6, fuelMix);
            FuelValue co2          = this.ParseFuelValue((row += fuelCount), 6, fuelMix);
            FuelValue acetaldehyde = this.ParseFuelValue((row += fuelCount), 6, fuelMix);
            FuelValue acrolein     = this.ParseFuelValue((row += fuelCount), 6, fuelMix);
            FuelValue benzene      = this.ParseFuelValue((row += fuelCount), 6, fuelMix);
            FuelValue butadiene    = this.ParseFuelValue((row += fuelCount), 6, fuelMix);
            FuelValue formaldehyde = this.ParseFuelValue((row += fuelCount), 6, fuelMix);
            FuelValue ch4          = this.ParseFuelValue((row += fuelCount), 6, fuelMix);
            FuelValue dpm10        = this.ParseFuelValue((row += fuelCount), 6, fuelMix);
            FuelValue mtbe         = this.ParseFuelValue((row += fuelCount), 6, fuelMix);
            FuelValue n2o          = this.ParseFuelValue((row += fuelCount), 6, fuelMix);
            return new UpstreamEmissions(co, voc, nox, so2, pm, co2, ch4, n2o,
                acetaldehyde, acrolein, benzene, butadiene, formaldehyde, dpm10, mtbe);
        }
        TailpipeEmissions ParseTailpipeEmissions(string[] sheets, int[] indexes, double[] fuelMix)
        {
            int minMY = -1, maxMY = -1;
            for (int i = 0; i < indexes.Length; i++)
            {
                if (indexes[i] != -1)
                {
                    this.ActivateWorksheet(indexes[i]);
                    for (int row = 4; row < this.Rows; row++)
                    {
                        string errHDR = sheets[i] + " (R:" + (row + 1).ToString("0000") + "):  ";
                        if (this.GetString(row, 0) == string.Empty) { continue; }
                        int year = this.GetInt32(row, 0);
                        if (year < 1900) { this.LogError(errHDR + "Model year is not valid."); }
                        minMY = (minMY == -1) ? year : Math.Min(minMY, year);
                        maxMY = (maxMY == -1) ? year : Math.Max(maxMY, year);
                    }
                }
            }
            FTObject<VCObject<double[][]>> co   = this.ParseTailpipeEmissionsHelper(sheets[ 0], indexes[ 0], fuelMix, minMY, maxMY);
            FTObject<VCObject<double[][]>> voc  = this.ParseTailpipeEmissionsHelper(sheets[ 1], indexes[ 1], fuelMix, minMY, maxMY);
            FTObject<VCObject<double[][]>> nox  = this.ParseTailpipeEmissionsHelper(sheets[ 2], indexes[ 2], fuelMix, minMY, maxMY);
            FTObject<VCObject<double[][]>> so2  = this.ParseTailpipeEmissionsHelper(sheets[ 3], indexes[ 3], fuelMix, minMY, maxMY);
            FTObject<VCObject<double[][]>> pm   = this.ParseTailpipeEmissionsHelper(sheets[ 4], indexes[ 4], fuelMix, minMY, maxMY);
            FTObject<VCObject<double[][]>> co2  = this.ParseTailpipeEmissionsHelper(sheets[ 5], indexes[ 5], fuelMix, minMY, maxMY);
            FTObject<VCObject<double[][]>> ch4  = this.ParseTailpipeEmissionsHelper(sheets[ 6], indexes[ 6], fuelMix, minMY, maxMY);
            FTObject<VCObject<double[][]>> n2o  = this.ParseTailpipeEmissionsHelper(sheets[ 7], indexes[ 7], fuelMix, minMY, maxMY);
            FTObject<VCObject<double[][]>> act  = this.ParseTailpipeEmissionsHelper(sheets[ 8], indexes[ 8], fuelMix, minMY, maxMY);
            FTObject<VCObject<double[][]>> acr  = this.ParseTailpipeEmissionsHelper(sheets[ 9], indexes[ 9], fuelMix, minMY, maxMY);
            FTObject<VCObject<double[][]>> bnz  = this.ParseTailpipeEmissionsHelper(sheets[10], indexes[10], fuelMix, minMY, maxMY);
            FTObject<VCObject<double[][]>> btd  = this.ParseTailpipeEmissionsHelper(sheets[11], indexes[11], fuelMix, minMY, maxMY);
            FTObject<VCObject<double[][]>> frm  = this.ParseTailpipeEmissionsHelper(sheets[12], indexes[12], fuelMix, minMY, maxMY);
            FTObject<VCObject<double[][]>> dpm  = this.ParseTailpipeEmissionsHelper(sheets[13], indexes[13], fuelMix, minMY, maxMY);
            FTObject<VCObject<double[][]>> mtbe = this.ParseTailpipeEmissionsHelper(sheets[14], indexes[14], fuelMix, minMY, maxMY);
            return new TailpipeEmissions(minMY, maxMY, co, voc, nox, so2, pm, co2, ch4, n2o, act, acr, bnz, btd, frm, dpm, mtbe);
        }
        FTObject<VCObject<double[][]>> ParseTailpipeEmissionsHelper(string sheetName, int sheetIndex, double[] fuelMix,
            int minMY, int maxMY)
        {
            if (sheetIndex == -1) { return null; }
            this.ActivateWorksheet(sheetIndex);
            int localMinMY = 0, localMaxMY = 0;
            int prevYear = -1;
            int ageCounter = 0;
            for (int row = 4; row < this.Rows; row++)
            {
                string errHDR = sheetName + " (R:" + (row + 1).ToString("0000") + "):  ";
                if (this.GetString(row, 0) == string.Empty) { continue; }
                int year = this.GetInt32(row, 0);
                int age  = this.GetInt32(row, 1);
                if (age != (++ageCounter)) { this.LogError(errHDR + "Vehicle ages are not contiguous."); }
                if (ageCounter == 40) { ageCounter = 0; }
                if (ageCounter == 1 && prevYear != -1 && prevYear + 1 != year)
                {   
                    if (prevYear != -1 && prevYear + 1 != year) { this.LogError(errHDR + "Model years are not contiguous."); }
                }
                else if (ageCounter > 1 && prevYear != -1 && prevYear != year)
                {   
                    this.LogError(errHDR + "Model years are out of order.");
                }
                prevYear = year;
                if (localMinMY == 0) { localMinMY = year; }
                else                 { localMaxMY = year; }
            }
            if (localMinMY != minMY || localMaxMY != maxMY)
            {
                this.LogError(sheetName + ":  Some model years are not specified.");
            }
            int yearCount = localMaxMY - localMinMY + 1;
            FTObject<VCObject<double[][]>> value = new FTObject<VCObject<double[][]>>();
            foreach (FuelType fuel in FTValue<object>.Classes)
            {
                value[fuel] = new VCObject<double[][]>();
                foreach (VehicleClass vehClass in VCValue<object>.Classes)
                {
                    value[fuel][vehClass] = new double[yearCount][];
                    for (int i = 0; i < yearCount; i++)
                    {
                        value[fuel][vehClass][i] = new double[Interaction.CY];
                    }
                }
            }
            double convGas = fuelMix[0];    
            int col;
            for (int i = 0; i < yearCount; i++)
            {
                for (int j = 0; j < Interaction.CY; j++)
                {
                    col = 2;
                    foreach (FuelType fuel in FTValue<object>.Classes)
                    {
                        if (fuel == FuelType.Gasoline)
                        {   
                            foreach (VehicleClass vehClass in VCValue<object>.Classes)
                            {
                                value[fuel][vehClass][i][j] = this.GetDouble(4 + j + i * Interaction.CY, col++) * convGas;
                            }
                            foreach (VehicleClass vehClass in VCValue<object>.Classes)
                            {
                                value[fuel][vehClass][i][j] += this.GetDouble(4 + j + i * Interaction.CY, col++) * (1 - convGas);
                            }
                        }
                        else if (fuel == FuelType.Ethanol85 || fuel == FuelType.Diesel || fuel == FuelType.Biodiesel20 ||
                                 fuel == FuelType.CNG       || fuel == FuelType.LNG    || fuel == FuelType.LPG)
                        {
                            foreach (VehicleClass vehClass in VCValue<object>.Classes)
                            {
                                value[fuel][vehClass][i][j] = this.GetDouble(4 + j + i * Interaction.CY, col++);
                            }
                        }
                        else if (fuel == FuelType.Electricity || fuel == FuelType.Hydrogen) { continue; }
                    }
                }
            }
            return value;
        }
        FuelValue ParseFuelValue(int sRow, int col, double[] fuelMix)
        {
            FuelValue value = new FuelValue();
            foreach (FuelType fuel in FTValue<object>.Classes)
            {
                if (fuel == FuelType.Gasoline)
                {
                    value[fuel] = this.GetDouble(sRow++, col) * fuelMix[0] +
                                  this.GetDouble(sRow++, col) * fuelMix[1] +
                                  this.GetDouble(sRow++, col) * fuelMix[2];
                }
                else
                {
                    value[fuel] = this.GetDouble(sRow++, col);
                }
            }
            return value;
        }
        FuelValue GetFuelValue(int sRow, int column)
        {
            FuelValue value = new FuelValue();
            foreach (FuelType fuel in FTValue<object>.Classes)
            {
                value[fuel] = this.GetDouble(sRow++, column);
            }
            return value;
        }
        VMTGrowthRate GetVMTGrowthRate(int sRow, int column)
        {
            return new VMTGrowthRate(
                this.GetInt32 (sRow++, column),
                this.GetDouble(sRow++, column),
                this.GetDouble(sRow++, column),
                this.GetDouble(sRow  , column));
        }
        ExternalCosts GetExternalCosts(int sRow, int column)
        {
            return new ExternalCosts(
                this.GetDouble(sRow++, column),
                this.GetDouble(sRow++, column),
                this.GetDouble(sRow  , column));
        }
        OperatingCosts GetOperatingCosts(int sRow, int column)
        {
            return new OperatingCosts(
                this.GetDouble(sRow++, column),
                this.GetDouble(sRow++, column),
                this.GetDouble(sRow++, column),
                this.GetDouble(sRow++, column),
                this.GetDouble(sRow  , column));
        }
        VCValue<double> GetVCValue(int sRow, int column)
        {
            VCValue<double> value = new VCValue<double>();
            foreach (VehicleClass vehClass in VCValue<double>.Classes)
            {
                value[vehClass] = this.GetDouble(sRow++, column);
            }
            return value;
        }
        #endregion
        #region 
        internal Parameters _value;
        #endregion
    }
}

