#region <<< Using Directives >>>
using System.Collections.Generic;
using System.Linq;

using Volpe.Cafe.Generic;
using Volpe.Cafe.Model;
using Volpe.Cafe.ModelSettings;
using Volpe.Cafe.Utils;

using RC = Volpe.Cafe.RegulatoryClass;
#endregion

namespace Volpe.Cafe.IO.InputParsers
{
    /// <summary>
    /// Provides an input parser for loading and parsing Excel Scenarios files.
    /// </summary>
    sealed class XlScenariosParser : XlParser
    {

        #region /*** Nested Types ***/

        class SectionInfo
        {
            public RC     RegClass;
            public int    SectionIndex;
            public int    SectionStart;
            public int    SectionEnd;
            public int    CoeffStartRow;
            public int    CO2CoeffStartRow;
            public int [] VarIndexes;  // the variables within each section
            public bool[] VarOptional; // the min-stnd are optional for all RC's except DC; PEFs are optional for HDPUV
            public int    MinMY, MaxMY;
        }

        #endregion

        #region /*** Ctors ***/

        internal XlScenariosParser(string path) : base(path) { }

        #endregion

        #region /*** Methods ***/

        protected override void ParseInternal()
        {
            var scenList = new List<Scenario>();

            // scan each worksheet name, looking for a scenario sheet
            for (int i = 0; i < this.SheetNames.Length; i++) {
                if (this.SheetNames[i].StartsWith("scen_", true, null)) {   // found a "valid" scenario worksheet -- load it
                    scenList.Add(this.ParseScenario(i, scenList.Count));
                }
            }

            if (scenList.Count == 0) {
                this.LogError("Scenarios file has no valid compliance scenario.");
            }
            else {
                this._scens = new Scenarios(scenList);
            }
        }

        Scenario ParseScenario(int wsIndex, int scenIndex)
        {
            Scenario scenario = null;

            // activate the worksheet
            this.ActivateWorksheet(wsIndex);

            // begin parsing ...
            //
            // find and verify sections
            string[] sectionNames = new string[] { "Passenger Car", "Light Truck", "HDPUV" };
            RC    [] sectionRCs   = new RC    [] { RC.AllPassengerCar, RC.LightTruck, RC.HDPUV };
            List<SectionInfo> sections = this.FindSections(sectionNames, sectionRCs);

            bool noSectionErrors = this.VerifySections(wsIndex, sectionNames, sectionRCs, sections);

            // The Light Duty section is a special case with its own verification and parsing
            IAnnualValue<bool> reclassifyLdVehs = this.VerifyAndParseLightDuty(wsIndex, sections);

            if (!noSectionErrors || reclassifyLdVehs == null) {
                return scenario;
            }

            // get scenario description
            string scenDescr = this.GetString(0, 0);

            // parse scenario info from each section
            var scenInfo = new RCAltValue<List<ScenarioInfo>>();
            bool validValues = true;
            foreach (SectionInfo sectInfo in sections) {
                var scenInfoRC = this.ParseScenarioInfo(sectInfo);
                if (scenInfoRC.Any() == false) {
                    validValues = false;
                    break;
                }

                scenInfo[sectInfo.RegClass] = scenInfoRC;
            }

            int minMY = sections.First().MinMY;
            int maxMY = sections.First().MaxMY;

            if (validValues) {
                scenario = new Scenario(new Dictionary<string, object>
                {
                    { "Index"                      , scenIndex },
                    { "Description"                , scenDescr },
                    { "MinMY"                      , minMY },
                    { "MaxMY"                      , maxMY },
                    { "ReclassifyLightDutyVehicles", reclassifyLdVehs },
                    { "ScenInfo"                   , scenInfo },
                });
            }

            return scenario;
        }

        List<SectionInfo> FindSections(string[] sectionNames, RC[] sectionRCs)
        {
            // scan for regulatory class definitions -- each reg-class definition is within its own section
            int rows = this.Rows;
            List<SectionInfo> sections = new List<SectionInfo>();
            //
            SectionInfo sectInfo = null;
            for (int i = 1; i < rows; i++) {
                string cell = this.GetString(i, 0);
                if (StringHelper.EqualsAny(cell, sectionNames, true, true, out int sectionIndex)) {
                    if (sectInfo != null) {   // close previous section
                        sectInfo.SectionEnd = i - 1;
                        sections.Add(sectInfo);
                    }
                    // open new section
                    sectInfo = new SectionInfo {
                        RegClass = sectionRCs[sectionIndex],
                        SectionIndex = sectionIndex,
                        SectionStart = i
                    };
                }
            }
            if (sectInfo != null) {   // close last section
                sectInfo.SectionEnd = rows - 1;
                sections.Add(sectInfo);
            }
            //
            return sections;
        }

        bool VerifySections(int wsIndex, string[] sectionNames, RC[] sectionRCs, List<SectionInfo> sections)
        {
            // check if at least one reg-class definition found
            bool noErrors = true;
            if (sections.Count == 0) {
                this.LogError($"{this.SheetNames[wsIndex]} worksheet: Missing regulatory class definitions " +
                              $"(maybe the scenarios file is not properly aligned).");
                noErrors = false;
            }
            else {   // look for duplicate reg-class sections
                bool[] sectionFound = new bool[sectionRCs.Length];
                foreach (SectionInfo sectInfo in sections) {
                    if (sectionFound[sectInfo.SectionIndex]) {
                        this.LogError($"{this.SheetNames[wsIndex]} worksheet (R:{sectInfo.SectionStart + 1:0000}):  " +
                                      $"\"{sectionNames[sectInfo.SectionIndex]}\" regulatory class has previously " +
                                      $"been defined (duplicate reg-class definitions are not allowed).");
                        noErrors = false;
                    }
                    else {
                        sectionFound[sectInfo.SectionIndex] = true;
                        // scan for all variable names within this section
                        sectInfo.VarIndexes = new int[VarNames.Length];
                        sectInfo.VarOptional = new bool[VarNames.Length];
                        for (int i = 0; i < VarNames.Length; i++) {
                            sectInfo.VarIndexes[i] = -1;
                            sectInfo.VarOptional[i] = false;
                        }
                        // min mpg and % are applicable to DC class only and are specified in PC section
                        if (sectInfo.RegClass != RC.AllPassengerCar) {
                            sectInfo.VarOptional[6] = true; // min (mpg)
                            sectInfo.VarOptional[7] = true; // min (%)
                        }
                        for (int i = sectInfo.SectionStart + 1; i <= sectInfo.SectionEnd; i++) {
                            string variable = this.GetString(i, 0);
                            if (string.IsNullOrEmpty(variable)) { continue; } // skip empty rows
                            if (StringHelper.EqualsAny(variable, VarNames, true, true, out int varIndex)) {
                                sectInfo.VarIndexes[varIndex] = i;
                                if (string.Compare(variable, "Function", true) == 0) {
                                    sectInfo.CoeffStartRow = i + 1;
                                }
                                else if (string.Compare(variable, "CO2 Function", true) == 0) {
                                    sectInfo.CO2CoeffStartRow = i + 1;
                                }
                            }
                            else {
                                this.LogError($"{this.SheetNames[wsIndex]} worksheet (R:{i + 1:0000}):  " +
                                              $"\"{variable}\" is not a valid variable name.");
                                noErrors = false;
                            }
                        }
                        // verify found indexes
                        if (!this.VerifyIndexes($"{this.SheetNames[wsIndex]} worksheet",
                                sectInfo.VarIndexes, VarNames, sectInfo.VarOptional)) {
                            noErrors = false;
                        }

                        // check for contiguous model years (model years should appear on the same row as section name)
                        sectInfo.MinMY = 0;
                        sectInfo.MaxMY = 0;
                        int prevYear = -1;
                        for (int i = 1; i < this.Columns; i++) {
                            string modelYear = this.GetString(sectInfo.SectionStart, i);
                            int year = modelYear.GetInt32();
                            if (!ModelYear.IsValid(year)) {
                                this.LogError($"{this.SheetNames[wsIndex]} worksheet (R:{sectInfo.SectionStart + 1:0000}):" +
                                              $"  \"{modelYear}\" is not a valid model year.");
                                noErrors = false;
                                continue;
                            }
                            if (prevYear != -1 && prevYear + 1 != year) {
                                this.LogError($"{this.SheetNames[wsIndex]} worksheet (R:{sectInfo.SectionStart + 1:0000}):" +
                                              $"  Model years {prevYear} -> {year} are not contiguous.");
                                noErrors = false;
                            }
                            prevYear = year;
                            //
                            if (sectInfo.MinMY == 0) { sectInfo.MinMY = year; }
                            else { sectInfo.MaxMY = year; }
                        }
                    }
                } // next section

                // ensure that all reg-class sections exist
                for (int i = 0; i < sectionFound.Length; i++) {
                    if (!sectionFound[i]) {
                        this.LogError($"{this.SheetNames[wsIndex]} worksheet: Missing regulatory class definition " +
                                      $"for \"{sectionNames[i]}\" class.");
                        noErrors = false;
                    }
                }

                // ensure that all min/max years match
                if (noErrors) {
                    foreach (SectionInfo sectInfo in sections.Skip(1)) {
                        if (sectInfo.MinMY != sections.First().MinMY ||
                            sectInfo.MaxMY != sections.First().MaxMY) {
                            this.LogError($"{this.SheetNames[wsIndex]} worksheet: Model years do not match between " +
                                          $"regulatory class definitions.");
                            noErrors = false;
                        }
                    }
                }
            }
            return noErrors;
        }

        /// <summary>
        /// Returns a value to assign to <see cref="Scenario.ReclassifyLightDutyVehicles"/>, or null if verification 
        /// fails.
        /// </summary>
        IAnnualValue<bool> VerifyAndParseLightDuty(int wsIndex, List<SectionInfo> sections)
        {
            bool noErrors = true;

            // Find row
            int sectionRow = 0;
            for (int i = 1; i < this.Rows; i++) {
                if (StringHelper.Equals(this.GetString(i, 0), LdSectionName)) {
                    // Duplicate found
                    if (sectionRow != 0) {
                        this.LogError($"{this.SheetNames[wsIndex]} worksheet (R:{i + 1:0000}):  " +
                                      $"{LdSectionName} regulatory class has previously " +
                                      $"been defined (duplicate reg-class definitions are not allowed).");
                        noErrors = false;
                    }
                    sectionRow = i;
                }
            }
            // Missing Light Duty
            if (sectionRow == 0) {
                this.LogError($"{this.SheetNames[wsIndex]} worksheet: Missing {LdSectionName} section definition.");
                return null;
            }
            // Not first section
            if (sections.Any(section => section.SectionStart < sectionRow)) {
                this.LogError($"{this.SheetNames[wsIndex]} worksheet: {LdSectionName} section must be defined first.");
            }

            // Check MYs
            int minMY = 0;
            int maxMY = 0;
            int prevYear = -1;
            for (int i = 1; i < this.Columns; i++) {
                string modelYear = this.GetString(sectionRow, i);
                int year = modelYear.GetInt32();
                if (!ModelYear.IsValid(year)) {
                    this.LogError($"{this.SheetNames[wsIndex]} worksheet (R:{sectionRow + 1:0000}):" +
                                  $"  {modelYear} is not a valid model year.");
                    noErrors = false;
                    continue;
                }
                if (prevYear != -1 && prevYear + 1 != year) {
                    this.LogError($"{this.SheetNames[wsIndex]} worksheet (R:{sectionRow + 1:0000}):" +
                                  $"  Model years {prevYear} -> {year} are not contiguous.");
                    noErrors = false;
                }
                prevYear = year;
                //
                if (minMY == 0) { minMY = year; }
                else { maxMY = year; }
            }

            // Cross-validate MYs with other sections
            if (minMY != sections.First().MinMY ||
                maxMY != sections.First().MaxMY) {
                this.LogError($"{this.SheetNames[wsIndex]} worksheet: Model years do not match between " +
                              $"Light Duty and regulatory class definitions.");
                noErrors = false;
            }

            // If no errors create and return annual value for variable
            if (!noErrors) { return null; }

            List<bool> annualValues = new List<bool>();
            for (int i = 1; i < this.Columns; i++) {
                annualValues.Add(this.GetBool(sectionRow + 1, i));
            }
            return new ReadOnlyAnnualValue<bool>(minMY, maxMY, annualValues, outOfBoundsMode: 1);
        }
        List<ScenarioInfo> ParseScenarioInfo(SectionInfo info)
        {
            int years  = info.MaxMY - info.MinMY + 1;
            int offset = info.MinMY - ModelYear.MinYear;
            int arrLen = info.MaxMY - ModelYear.MinYear + 1;

            // init arrays
            int   [] function            = new int   [arrLen];
            double[] co2Factor           = new double[arrLen];
            double[] co2Offset           = new double[arrLen];
            bool  [] co2IncludeUpstream  = new bool  [arrLen];
            double[] epaMultiplier1      = new double[arrLen];
            double[] epaMultiplier2      = new double[arrLen];
            double[] minStndMpg          = new double[arrLen];
            double[] minStndPct          = new double[arrLen];
            bool  [] stdSettingYear      = new bool  [arrLen];
            double[] fineRate            = new double[arrLen];
            double[] creditValue         = new double[arrLen];
            double[] co2CreditValue      = new double[arrLen];
            int   [] multiFuel           = new int   [arrLen];
            bool  [] nonElectricPhevs    = new bool  [arrLen];
            double[] multiFuelShareFfv   = new double[arrLen];
            double[] multiFuelSharePhev  = new double[arrLen];
            double[] cafeACEfficiencyCap = new double[arrLen];
            double[] cafeOffCycleCap     = new double[arrLen];
            double[] co2ACEfficiencyCap  = new double[arrLen];
            double[] co2ACLeakageCap     = new double[arrLen];
            double[] co2OffCycleCap      = new double[arrLen];
            double[] acEfficiencyCosts   = new double[arrLen];
            double[] acLeakageCosts      = new double[arrLen];
            double[] offCycleCosts       = new double[arrLen];
            double[] taxCreditShev       = new double[arrLen];
            double[] taxCreditPhev       = new double[arrLen];
            double[] taxCreditBev        = new double[arrLen];
            double[] taxCreditFcv        = new double[arrLen];
            double[] payloadReturn       = new double[arrLen];
            double[] towingReturn        = new double[arrLen];
            int   [] co2function         = new int   [arrLen];
            double[] pefBev              = new double[arrLen];
            double[] pefPhev             = new double[arrLen];
            double[] taxCreditScale      = new double[arrLen];
            bool  [] applyTaxCredits     = new bool  [arrLen];

            double[] batteryTaxCreditShev   = new double[arrLen];
            double[] batteryTaxCreditPhev   = new double[arrLen];
            double[] batteryTaxCreditBev    = new double[arrLen];
            double[] batteryTaxCreditFcv    = new double[arrLen];
            double[] batteryTaxCreditScale  = new double[arrLen];
            bool  [] applyBatteryTaxCredits = new bool  [arrLen];
            bool  [] applyPef               = new bool  [arrLen];

            double[] cafeACEfficiencyBevCap = new double[arrLen];
            double[] cafeOffCycleBevCap     = new double[arrLen];
            double[] co2ACEfficiencyBevCap  = new double[arrLen];
            double[] co2ACLeakageBevCap     = new double[arrLen];
            double[] co2OffCycleBevCap      = new double[arrLen];

            double?[][] coefficients    = new double?[arrLen][];
            double?[][] co2coefficients = new double?[arrLen][];
            for (int i = 0; i < arrLen; i++) {
                coefficients[i] = new double?[CoefCount];
                co2coefficients[i] = new double?[CoefCount];
            }

            // load data
            for (int i = 0; i < years; i++) {
                int index = i + offset;
                int varNamesIndex = 0;
                int y = i + 1;
                //
                function[index] = this.GetInt32(info, varNamesIndex++, y);
                co2Factor[index] = this.GetDouble(info, varNamesIndex++, y, DefaultCO2FactorValue);
                co2Offset[index] = this.GetDouble(info, varNamesIndex++, y);
                co2IncludeUpstream[index] = this.GetBool(info, varNamesIndex++, y);
                epaMultiplier1[index] = this.GetDouble(info, varNamesIndex++, y, MinEPAMultiplier);
                epaMultiplier2[index] = this.GetDouble(info, varNamesIndex++, y, MinEPAMultiplier);
                minStndMpg[index] = this.GetDouble(info, varNamesIndex++, y);
                minStndPct[index] = this.GetDouble(info, varNamesIndex++, y, MinMinPct);
                stdSettingYear[index] = this.GetBool(info, varNamesIndex++, y);
                fineRate[index] = this.GetDouble(info, varNamesIndex++, y);
                creditValue[index] = this.GetDouble(info, varNamesIndex++, y);
                co2CreditValue[index] = this.GetDouble(info, varNamesIndex++, y);
                multiFuel[index] = this.GetInt32(info, varNamesIndex++, y, 1);
                nonElectricPhevs[index] = this.GetBool(info, varNamesIndex++, y);
                multiFuelShareFfv[index] = this.GetDouble(info, varNamesIndex++, y, MinShare);
                multiFuelSharePhev[index] = this.GetDouble(info, varNamesIndex++, y, MinShare);
                cafeACEfficiencyCap[index] = this.GetDouble(info, varNamesIndex++, y);
                cafeOffCycleCap[index] = this.GetDouble(info, varNamesIndex++, y);
                co2ACEfficiencyCap[index] = this.GetDouble(info, varNamesIndex++, y);
                co2ACLeakageCap[index] = this.GetDouble(info, varNamesIndex++, y);
                co2OffCycleCap[index] = this.GetDouble(info, varNamesIndex++, y);
                acEfficiencyCosts[index] = this.GetDouble(info, varNamesIndex++, y);
                acLeakageCosts[index] = this.GetDouble(info, varNamesIndex++, y);
                offCycleCosts[index] = this.GetDouble(info, varNamesIndex++, y);
                taxCreditShev[index] = this.GetDouble(info, varNamesIndex++, y);
                taxCreditPhev[index] = this.GetDouble(info, varNamesIndex++, y);
                taxCreditBev[index] = this.GetDouble(info, varNamesIndex++, y);
                taxCreditFcv[index] = this.GetDouble(info, varNamesIndex++, y);
                payloadReturn[index] = this.GetDouble(info, varNamesIndex++, y, MinReturn);
                towingReturn[index] = this.GetDouble(info, varNamesIndex++, y, MinReturn);
                co2function[index] = this.GetInt32(info, varNamesIndex++, y);
                pefBev[index] = this.GetDouble(info, varNamesIndex++, y);
                pefPhev[index] = this.GetDouble(info, varNamesIndex++, y);
                taxCreditScale[index] = this.GetDouble(info, varNamesIndex++, y);
                applyTaxCredits[index] = this.GetBool(info, varNamesIndex++, y);

                batteryTaxCreditShev[index] = this.GetDouble(info, varNamesIndex++, y);
                batteryTaxCreditPhev[index] = this.GetDouble(info, varNamesIndex++, y);
                batteryTaxCreditBev[index] = this.GetDouble(info, varNamesIndex++, y);
                batteryTaxCreditFcv[index] = this.GetDouble(info, varNamesIndex++, y);
                batteryTaxCreditScale[index] = this.GetDouble(info, varNamesIndex++, y);
                applyBatteryTaxCredits[index] = this.GetBool(info, varNamesIndex++, y);
                applyPef[index] = this.GetBool(info, varNamesIndex++, y);

                cafeACEfficiencyBevCap[index] = this.GetDouble(info, varNamesIndex++, y);
                cafeOffCycleBevCap[index] = this.GetDouble(info, varNamesIndex++, y);
                co2ACEfficiencyBevCap[index] = this.GetDouble(info, varNamesIndex++, y);
                co2ACLeakageBevCap[index] = this.GetDouble(info, varNamesIndex++, y);
                co2OffCycleBevCap[index] = this.GetDouble(info, varNamesIndex++, y);

                for (int j = 0; j < CoefCount; j++) {
                    coefficients[index][j] = this.GetDoubleNullable(info.CoeffStartRow + j, y);
                    co2coefficients[index][j] = this.GetDoubleNullable(info.CO2CoeffStartRow + j, y);
                }
            }

            // validate values
            bool validValues = true;
            int namesIndex = 0;
            validValues &= this.ValidateFunctions(offset, function, coefficients, namesIndex++);
            validValues &= this.ValidateCO2factors(co2Factor, offset, namesIndex++);
            ++namesIndex; // for co2Offset
            ++namesIndex; // for co2IncludeUpstream
            validValues &= this.ValidateEPAMultipliers(epaMultiplier1, offset, namesIndex++);
            validValues &= this.ValidateEPAMultipliers(epaMultiplier2, offset, namesIndex++);
            validValues &= this.ValidateGeneralField(minStndMpg, offset, namesIndex++);
            validValues &= this.ValidateMinStndPct(minStndPct, offset, namesIndex++);
            ++namesIndex; // standard-setting year
            validValues &= this.ValidateGeneralField(fineRate, offset, namesIndex++);
            validValues &= this.ValidateGeneralField(creditValue, offset, namesIndex++);
            validValues &= this.ValidateGeneralField(co2CreditValue, offset, namesIndex++);
            validValues &= this.ValidateMultiFuel(multiFuel, offset, namesIndex++);
            namesIndex++; // non-electric Phevs
            validValues &= this.ValidateShare(multiFuelShareFfv, offset, namesIndex++);
            validValues &= this.ValidateShare(multiFuelSharePhev, offset, namesIndex++);
            validValues &= this.ValidateGeneralField(cafeACEfficiencyCap, offset, namesIndex++);
            validValues &= this.ValidateGeneralField(cafeOffCycleCap, offset, namesIndex++);
            validValues &= this.ValidateGeneralField(co2ACEfficiencyCap, offset, namesIndex++);
            validValues &= this.ValidateGeneralField(co2ACLeakageCap, offset, namesIndex++);
            validValues &= this.ValidateGeneralField(co2OffCycleCap, offset, namesIndex++);
            validValues &= this.ValidateGeneralField(acEfficiencyCosts, offset, namesIndex++);
            validValues &= this.ValidateGeneralField(acLeakageCosts, offset, namesIndex++);
            validValues &= this.ValidateGeneralField(offCycleCosts, offset, namesIndex++);
            validValues &= this.ValidateGeneralField(taxCreditShev, offset, namesIndex++);
            validValues &= this.ValidateGeneralField(taxCreditPhev, offset, namesIndex++);
            validValues &= this.ValidateGeneralField(taxCreditBev, offset, namesIndex++);
            validValues &= this.ValidateGeneralField(taxCreditFcv, offset, namesIndex++);
            validValues &= this.ValidateReturn(payloadReturn, offset, namesIndex++);
            validValues &= this.ValidateReturn(towingReturn, offset, namesIndex++);
            validValues &= this.ValidateFunctions(offset, co2function, co2coefficients, namesIndex++);
            if (info.RegClass != RC.HDPUV) {
                validValues &= this.ValidateGeneralField(pefBev, offset, namesIndex++, 1);
                validValues &= this.ValidateGeneralField(pefPhev, offset, namesIndex++, 1);
            }
            else {
                namesIndex += 2;
            }
            validValues &= this.ValidateGeneralField(taxCreditScale, offset, namesIndex++);
            namesIndex++; // for applyTaxCredit
            validValues &= this.ValidateGeneralField(batteryTaxCreditShev, offset, namesIndex++);
            validValues &= this.ValidateGeneralField(batteryTaxCreditPhev, offset, namesIndex++);
            validValues &= this.ValidateGeneralField(batteryTaxCreditBev, offset, namesIndex++);
            validValues &= this.ValidateGeneralField(batteryTaxCreditFcv, offset, namesIndex++);
            validValues &= this.ValidateGeneralField(batteryTaxCreditScale, offset, namesIndex++);
            namesIndex++; // for applyBatteryTaxCredit
            namesIndex++; // for applyPEV
            validValues &= this.ValidateGeneralField(cafeACEfficiencyBevCap, offset, namesIndex++);
            validValues &= this.ValidateGeneralField(cafeOffCycleBevCap, offset, namesIndex++);
            validValues &= this.ValidateGeneralField(co2ACEfficiencyBevCap, offset, namesIndex++);
            validValues &= this.ValidateGeneralField(co2ACLeakageBevCap, offset, namesIndex++);
            validValues &= this.ValidateGeneralField(co2OffCycleBevCap, offset, namesIndex++);

            var scenInfoRC = new List<ScenarioInfo>();
            if (validValues) {
                for (int i = 0; i < years; i++) {
                    int index = i + offset;
                    var scenInfoMY = new ScenarioInfo(new Dictionary<string, object>
                    {
                        // informational values for diagnostic/debugging purposes
                        { "RegClass" , info.RegClass },
                        { "ModelYear", info.MinMY + i },
                        
                        // standard function and coefficients
                        { "Function"    , function[index] },
                        { "Coefficients", ToDoubleArray(coefficients[index]) },

                        // CO2 function and coefficients
                        { "CO2Function"    , co2function[index] },
                        { "CO2Coefficients", ToDoubleArray(co2coefficients[index]) },
                        
                        // standard adjustments and multipliers
                        { "CO2Factor"         , co2Factor[index] },
                        { "CO2Offset"         , co2Offset[index] },
                        { "CO2IncludeUpstream", co2IncludeUpstream[index] },
                        { "EpaMultiplier1"    , epaMultiplier1[index] },
                        { "EpaMultiplier2"    , epaMultiplier2[index] },
                        
                        // minimum values for domestic car standard
                        { "MinStndMpg", minStndMpg[index] },
                        { "MinStndPct", minStndPct[index] },

                        { "StandardSettingYear", stdSettingYear[index] },

                        // fine rate and credit values
                        { "FineRate"      , fineRate[index] },
                        { "CreditValue"   , creditValue[index] },
                        { "CO2CreditValue", co2CreditValue[index]},

                        // mutli-fuel applicability
                        { "MultiFuel"                  , multiFuel[index] },
                        { "NonElectricPhevsInEffects"  , nonElectricPhevs[index] },
                        { "MultiFuelShareFfv"          , multiFuelShareFfv[index] },
                        { "MultiFuelSharePhev"         , multiFuelSharePhev[index] },

                        // PEFs
                        { "ApplyPef", applyPef[index] },
                        { "PefBev" , pefBev[index] },
                        { "PefPhev", pefPhev[index] },

                        // CAFE credit caps
                        { "OffCycleCafeCaps", new OffCycleValues(
                                cafeACEfficiencyCap[index], 0, cafeOffCycleCap[index], 0) },
                        { "OffCycleCafeBevCaps", new OffCycleValues(
                                cafeACEfficiencyBevCap[index], 0, cafeOffCycleBevCap[index], 0) },

                        // CO-2 credit caps
                        { "OffCycleCO2Caps", new OffCycleValues(
                                co2ACEfficiencyCap[index], co2ACLeakageCap[index], co2OffCycleCap[index], 0) },
                        { "OffCycleCO2BevCaps", new OffCycleValues(
                                co2ACEfficiencyBevCap[index], co2ACLeakageBevCap[index], co2OffCycleBevCap[index], 0) },

                        // costs ($)
                        { "OffCycleCosts", new OffCycleValues(
                            acEfficiencyCosts[index], acLeakageCosts[index], offCycleCosts[index], 0) },

                        // tax credits ($)
                        { "TaxCreditShev", taxCreditShev[index] },
                        { "TaxCreditPhev", taxCreditPhev[index] },
                        { "TaxCreditBev" , taxCreditBev[index] },
                        { "TaxCreditFcv" , taxCreditFcv[index] },

                        { "TaxCreditScale" , taxCreditScale[index] },
                        { "ApplyTaxCredits", applyTaxCredits[index] },
                        
                        // battery tax credits ($)
                        { "BatteryTaxCreditShev", batteryTaxCreditShev[index] },
                        { "BatteryTaxCreditPhev", batteryTaxCreditPhev[index] },
                        { "BatteryTaxCreditBev" , batteryTaxCreditBev[index] },
                        { "BatteryTaxCreditFcv" , batteryTaxCreditFcv[index] },

                        { "BatteryTaxCreditScale" , batteryTaxCreditScale[index] },
                        { "ApplyBatteryTaxCredits", applyBatteryTaxCredits[index] },

                        // test weight calculations
                        { "PayloadReturn", payloadReturn[index] },
                        { "TowingReturn" , towingReturn[index] },
                    });

                    scenInfoRC.Add(scenInfoMY);
                }
            }

            return scenInfoRC;
        }

        static double[] ToDoubleArray(double?[] coeff)
        {
            double[] array = new double[coeff.Length];
            for (int i = 0; i < coeff.Length; ++i) {
                array[i] = coeff[i] ?? 0;
            }

            return array;
        }

        bool ValidateFunctions(int offset, int[] function, double?[][] coefficients, int nameIndex)
        {
            bool valid = function.Length == coefficients.Length;
            if (valid) {
                for (int i = offset; i < function.Length; ++i) {
                    if (TargetFunctionInformation.GetCoefficientCount(function[i]) != coefficients[i].Count(c => c.HasValue)) {
                        valid = false;
                        break;
                    }
                }
            }

            if (!valid) {
                this.LogError($"Scenario input file contains invalid {VarNames[nameIndex]} values.");
            }

            return valid;
        }
        bool ValidateCO2factors(double[] co2Factor, int offset, int nameIndex)
        {
            bool result = !co2Factor.Skip(offset).Except(AllowedCO2FactorValues).Any();
            if (result == false) {
                this.LogError($"Scenario input file contains invalid {VarNames[nameIndex]} values.");
            }

            return result;
        }
        bool ValidateEPAMultipliers(double[] epaMultiplier, int offset, int nameIndex)
        {
            bool result = epaMultiplier.Skip(offset).All(m => m >= MinEPAMultiplier && m <= MaxEPAMultiplier);
            if (result == false) {
                this.LogError($"Scenario input file contains invalid {VarNames[nameIndex]} values.");
            }

            return result;
        }
        bool ValidateMinStndPct(double[] minStndPct, int offset, int nameIndex)
        {
            bool result = minStndPct.Skip(offset).All(pct => pct >= MinMinPct && pct <= MaxMinPct);
            if (result == false) {
                this.LogError($"Scenario input file contains invalid {VarNames[nameIndex]} values.");
            }

            return result;
        }
        bool ValidateMultiFuel(int[] multiFuel, int offset, int nameIndex)
        {
            bool result = !multiFuel.Skip(offset).Except(AllowedMultiFuelValues).Any();
            if (result == false) {
                this.LogError($"Scenario input file contains invalid {VarNames[nameIndex]} values.");
            }

            return result;
        }
        bool ValidateShare(double[] share, int offset, int nameIndex)
        {
            bool result = share.Skip(offset).All(m => m >= MinShare && m <= MaxShare);
            if (result == false) {
                this.LogError($"Scenario input file contains invalid {VarNames[nameIndex]} values.");
            }

            return result;
        }
        bool ValidateReturn(double[] values, int offset, int nameIndex)
        {
            bool result = values.Skip(offset).All(m => m >= MinReturn && m <= MaxReturn);
            if (result == false) {
                this.LogError($"Scenario input file contains invalid {VarNames[nameIndex]} values.");
            }

            return result;
        }
        bool ValidateGeneralField(double[] fieldValues, int offset, int nameIndex, double minValue = 0)
        {
            bool valid = fieldValues.Skip(offset).All(value => value >= minValue);
            if (!valid) {
                this.LogError($"Scenario input file contains invalid {VarNames[nameIndex]} values.");
            }

            return valid;
        }



        int GetInt32(SectionInfo info, int varIndex, int y, int defaultValue = 0)
        {
            return (info.VarOptional[varIndex]) ? 0 : this.GetInt32(info.VarIndexes[varIndex], y, defaultValue);
        }
        double GetDouble(SectionInfo info, int varIndex, int y, double defaultValue = 0)
        {
            return (info.VarOptional[varIndex]) ? 0D : this.GetDouble(info.VarIndexes[varIndex], y, defaultValue);
        }
        bool GetBool(SectionInfo info, int varIndex, int y)
        {
            return !info.VarOptional[varIndex] && this.GetBool(info.VarIndexes[varIndex], y);
        }

        #endregion

        #region /*** Variables ***/

        private const string LdSectionName = "Light Duty";
        private readonly List<string> AllSectionNames = new List<string> {
            LdSectionName, "Passenger Car", "Light Truck", "HDPUV" };

        private const int CoefCount = 10;   // max number of supported coefs
        private static readonly string[] VarNames = new string[] {
            "Function",                     //  0
            "CO2 Factor",                   //  1
            "CO2 Offset",                   //  2
            "CO2 Include Upstream",         //  3
            "EPA Multiplier 1",             //  4
            "EPA Multiplier 2",             //  5
            "Min (mpg)",                    //  6 (passenger car only)
            "Min (%)",                      //  7 (passenger car only)
            "Standard Setting Year",        //  8
            "Fine Rate",                    //  9
            "Credit Value",                 // 10
            "CO2 Credit Value",             // 11
            "Multi-Fuel",                   // 12
            "Non-Electric PHEVs",           // 13
            "FFV Share",                    // 14
            "PHEV Share",                   // 15
            "CAFE - AC Efficiency Cap",     // 16
            "CAFE - Off-Cycle Cap",         // 17
            "CO2 - AC Efficiency Cap",      // 18
            "CO2 - AC Leakage Cap",         // 19
            "CO2 - Off-Cycle Cap",          // 20
            "AC Efficiency Costs",          // 21
            "AC Leakage Costs",             // 22
            "Off-Cycle Costs",              // 23
            "SHEV Tax Credit",              // 24
            "PHEV Tax Credit",              // 25
            "BEV Tax Credit",               // 26
            "FCV Tax Credit",               // 27
            "Payload Return",               // 28
            "Towing Return",                // 29
            "CO2 Function",                 // 30 
            "PEF 1",                        // 31 
            "PEF 2",                        // 32
            "Tax Credit Scale",             // 33
            "Apply Tax Credits",            // 34
            "SHEV Battery Tax Credit",      // 35
            "PHEV Battery Tax Credit",      // 36
            "BEV Battery Tax Credit",       // 37
            "FCV Battery Tax Credit",       // 38
            "Battery Tax Credit Scale",     // 39
            "Apply Battery Tax Credits",    // 40
            "Apply PEF",                    // 41
            "CAFE - AC Efficiency BEV Cap", // 42
            "CAFE - Off-Cycle BEV Cap",     // 43
            "CO2 - AC Efficiency BEV Cap",  // 44
            "CO2 - AC Leakage BEV Cap",     // 45
            "CO2 - Off-Cycle BEV Cap",      // 46
            //
            "A", "B", "C", "D", "E", "F", "G", "H", "I", "J" }; // 47+

        private const double DefaultCO2FactorValue = 8887;
        private static readonly List<double> AllowedCO2FactorValues = new List<double>
        {
            DefaultCO2FactorValue
        };
        private const int DefaultMultiFuelValue = 1;
        private static readonly List<int> AllowedMultiFuelValues = new List<int>
        {
            0, DefaultMultiFuelValue, 2
        };
        private const double MinEPAMultiplier   = 1;
        private const double MaxEPAMultiplier   = 10;
        private const double MinMinPct          = 0;
        private const double MaxMinPct          = 1;
        private const double MinShare           = 0;
        private const double MaxShare           = 1;
        private const double MinReturn          = 0;
        private const double MaxReturn          = 1;

        internal Scenarios _scens;

        #endregion

    }
}
