#region << Using Directives >>
using System;
using System.Collections.Generic;
using Volpe.Cafe.Data;
using Volpe.Cafe.Utils;
using Volpe.Cafe.Generic;
#endregion
namespace Volpe.Cafe.IO.InputParsers
{
    sealed class XlMarketDataParser : XlParser
    {
        #region 
        internal XlMarketDataParser(string path, string password) : base(path, password, true) { }
        #endregion
        #region 
        protected override void ParseInternal()
        {
            this._ind = null;
            List<Manufacturer> mfrs = new List<Manufacturer>(32);
            string[] sheets  = this.SheetNames;
            string[] names   = {"Manufacturers", "Vehicles", "Engines", "Transmissions"};
            int   [] indexes = {-1, -1, -1, -1};
            for (int i = 0; i < sheets.Length; i++)
            {
                for (int j = 0; j < names.Length; j++)
                {
                    if (Interaction.StringCompare(sheets[i], names[j], false)) { indexes[j] = i; break; }
                }
            }
            if (!this.VerifyIndexes("Market Data workbook", indexes, names)) { return; }
            int mfrWsMinYear, mfrWsMaxYear;         
            List<Manufacturer>   mc = this.LoadManufacturers(indexes[0], out mfrWsMinYear, out mfrWsMaxYear);
            List<Vehicle     >[] vc = this.LoadVehicles     (indexes[1], mc);
            List<Engine      >[] ec = this.LoadEngines      (indexes[2], mc);
            List<Transmission>[] tc = this.LoadTransmissions(indexes[3], mc);
            if (this.HasErrors) { return; }
            int mfrIndex = 0;
            int indMinYear = int.MaxValue, indMaxYear = -1;
            for (int i = 0; i < mc.Count; i++)
            {
                int minYear, maxYear;
                mc[i].Initialize(vc[i], ec[i], tc[i], out minYear, out maxYear);
                if (mc[i].VehicleCount == 0)
                {
                    this.LogError("Manufacturer Code " + mc[i].Description.Code + " does not contain any valid vehicles.");
                    return;
                }
                else
                {   
                    for (int j = 0, vehCount = vc[i].Count; j < vehCount; j++)
                    {
                        Vehicle veh = vc[i][j];
                        Vehicle.CDescription vd = veh.Description;
                        FuelType engFuel = (veh.Engine == null) ? FuelType.None : veh.Engine.Description.Fuel;
                        string hdr = "Vehicle (Code:" + vd.Code + ",Mfr:" + vd.Manufacturer + "):  ";
                        foreach (FuelType fuel in FTValue<object>.Classes)
                        {
                            if ((engFuel & fuel) == fuel)
                            {   
                                if (vd.FuelShare  [fuel] <= 0) { this.LogError(hdr + "Engine is listed as using " + fuel + " Fuel, but Vehicle " + fuel + " Fuel Share is not specified or not valid."  ); }
                                if (vd.FuelEconomy[fuel] <= 0) { this.LogError(hdr + "Engine is listed as using " + fuel + " Fuel, but Vehicle " + fuel + " Fuel Economy is not specified or not valid."); }
                            }
                            else if (fuel != FuelType.Electricity && fuel != FuelType.Hydrogen && (engFuel & fuel) != fuel)
                            {   
                                if (vd.FuelShare  [fuel] > 0) { this.LogError(hdr + "Vehicle specifies a " + fuel + " Fuel Share, but the Engine is not listed as using "   + fuel + " Fuel."); }
                                if (vd.FuelEconomy[fuel] > 0) { this.LogError(hdr + "Vehicle specifies a " + fuel + " Fuel Economy, but the Engine is not listed as using " + fuel + " Fuel."); }
                            }
                        }
                    }
                    if (this.HasErrors) { return; }
                    mfrs.Add(mc[i]);
                    mc[i].Index = mfrIndex++;
                    indMinYear = Math.Min(minYear, indMinYear);
                    indMaxYear = Math.Max(maxYear, indMaxYear);
                }
            }
            indMinYear = Math.Max(mfrWsMinYear, indMinYear);
            indMaxYear = Math.Min(mfrWsMaxYear, indMaxYear);
            this._ind = new Industry(mfrs, indMinYear, indMaxYear);
            this._ind.UpdateMinMaxFpCw();
        }
        List<Manufacturer> LoadManufacturers(int index, out int mfrMinYear, out int mfrMaxYear)
        {
            this.ActivateWorksheet(index);
            int rows = this.Rows;
            int cols = this.Columns;
            string[] names = {   
                                 "Manufacturer Code",                           
                                 "Manufacturer Name",                           
                                 "Cost Allocation Strategy",                    
                                 "Discount Rate",                               
                                 "Payback Period",                              
                                 "Payback Period (After Compliance)",           
                                 "Willingness to Pay CAFE Fines",               
                                 "Available Passenger Car Credits (mpg)",       
                                 "Available Light Truck Credits (mpg)",         
                                 "Available Light Truck 2b/3 Credits (mpg)",    
                                 "Credits Apply to Baseline"                    
                             };
            int[] indexes = new int[names.Length];
            for (int i = 0; i < indexes.Length; i++) { indexes[i] = -1; }
            for (int i = 0; i < cols; i++)
            {
                string column0 = this.GetString(0, i);
                string column1 = this.GetString(1, i);
                int column1_int = Interaction.GetInt32(column1);
                if      (Interaction.StringCompare(column1, names[ 0], true)) { indexes[0] = i; }
                else if (Interaction.StringCompare(column1, names[ 1], true)) { indexes[1] = i; }
                else if (Interaction.StringCompare(column1, names[ 2], true)) { indexes[2] = i; }
                else if (Interaction.StringCompare(column1, names[ 3], true)) { indexes[3] = i; }
                else if (Interaction.StringCompare(column1, names[ 4], true)) { indexes[4] = i; }
                else if (Interaction.StringCompare(column1, names[ 5], true)) { indexes[5] = i; }
                else if (Interaction.StringCompare(column0, names[ 6], true) && ModelYear.IsValid(column1_int)) { indexes[6] = i; }
                else if (Interaction.StringCompare(column0, names[ 7], true) && ModelYear.IsValid(column1_int)) { indexes[7] = i; }
                else if (Interaction.StringCompare(column0, names[ 8], true) && ModelYear.IsValid(column1_int)) { indexes[8] = i; }
                else if (Interaction.StringCompare(column0, names[ 9], true) && ModelYear.IsValid(column1_int)) { indexes[9] = i; }
                else if (Interaction.StringCompare(column0, names[10], true) ||
                         Interaction.StringCompare(column1, names[10], true)) { indexes[10] = i; }
            }
            mfrMinYear = mfrMaxYear = 0;
            if (!this.VerifyIndexes(this.SheetNames[index] + " worksheet", indexes, names)) { return null; }
            this.GetMinMaxYears(1, indexes[6], out mfrMinYear, out mfrMaxYear);
            List<Manufacturer> mc = new List<Manufacturer>(32);
            for (int i = 2; i < rows; i++)
            {
                Manufacturer.CDescription md = this.LoadManufacturer(i, indexes, mfrMinYear, mfrMaxYear);
                if (md != null) { mc.Add(new Manufacturer(md)); }
            }
            return mc;
        }
        Manufacturer.CDescription LoadManufacturer(int row, int[] indexes, int minYear, int maxYear)
        {
            int    mfrCode = this.GetInt32 (row, indexes[0]);
            string mfrName = this.GetString(row, indexes[1]);
            if (mfrCode <= 0 || mfrName == string.Empty) { return null; }
            Manufacturer.CDescription md = new Manufacturer.CDescription();
            md.Code = mfrCode;
            md.Name = mfrName;
            md.ProperName = Interaction.GetTitleCase(mfrName, 4);
            md.CostAllocationStrategy =  this.GetInt32 (row, indexes[ 2]);
            md.DiscountRate           =  this.GetDouble(row, indexes[ 3]);
            md.PaybackPeriod          =  this.GetDouble(row, indexes[ 4]);
            md.PaybackPeriod_OC       =  this.GetDouble(row, indexes[ 5]);
            md.CreditsApplyToBaseline = (this.GetChar  (row, indexes[10]) == 'Y');
            int arrLen = maxYear - ModelYear.MinYear + 1;
            md.WillingToPayFines      = new bool[arrLen];
            md.AvailableCredits       = new RCObject<double[]>(new double[arrLen], new double[arrLen], new double[arrLen]);
            int offset = minYear - ModelYear.MinYear;
            for (int i = 0; i < maxYear - minYear + 1; i++)
            {
                md.WillingToPayFines[offset + i] = this.GetString(row, indexes[6] + i).ToUpper().Equals("Y");
                for (int j = 0; j < md.AvailableCredits.Items.Length; j++)
                {
                    md.AvailableCredits.Items[j][offset + i] = this.GetDouble(row, indexes[7 + j] + i);
                }
            }
            return md;
        }
        List<Vehicle>[] LoadVehicles(int index, List<Manufacturer> mfrs)
        {
            this.ActivateWorksheet(index);
            int rows = this.Rows;
            int cols = this.Columns;
            string[] namesMain = new string[] {
                "Sales", "MSRP", "Price",           
                "Vehicle Code"                ,     
                "Manufacturer"                ,     
                "Model"                       ,     
                "Nameplate"                   ,     
                "Platform"                    ,     
                "Engine Code"                 ,     
                "Transmission Code"           ,     
                "Origin"                      ,     
                "Regulatory Class"            ,     
                "Technology Class"            ,     
                "Safety Class"                ,     
                "Market Segment"              ,     
                "SmallCar"                    ,     
                "MLCar"                       ,     
                "Truck"                       ,     
                "Van"                         ,     
                "SUV"                         ,     
                "AWD"                         ,     
                "Prestige"                    ,     
                "MM"                          ,     
                "ASC1"                        ,     
                "ASC2"                        ,     
                "Class"                       ,     
                "Style"                       ,     
                "Structure"                   ,     
                "Drive"                       ,     
                "Wheelbase"                   ,     
                "Footprint"                   ,     
                "Curb Weight"                 ,     
                "GVWR"                        ,     
                "GCWR"                        ,     
                "Seating (max)"               ,     
                "Fuel Capacity"               ,     
                "Type of Hybrid/Electric Vehicle",  
                "Electric Power"              ,     
                "Electric Range"              ,     
                "Refresh Years"               ,     
                "Redesign Years"              ,     
                "Percent of 2 DR Cars"        ,     
                "Employment Hours per Vehicle",     
                "Max GVWR/CW"                 ,     
                "Max GCWR/GVWR"               };    
            string[] namesFuel = new string[] {
                "Fuel Economy (Gasoline)"     ,     
                "Fuel Economy (Ethanol-85)"   ,     
                "Fuel Economy (Diesel)"       ,     
                "Fuel Economy (Biodiesel-20)" ,     
                "Fuel Economy (Electricity)"  ,     
                "Fuel Economy (Hydrogen)"     ,     
                "Fuel Economy (CNG)"          ,     
                "Fuel Economy (LNG)"          ,     
                "Fuel Economy (LPG)"          ,     
                "Fuel Share (Gasoline)"       ,     
                "Fuel Share (Ethanol-85)"     ,     
                "Fuel Share (Diesel)"         ,     
                "Fuel Share (Biodiesel-20)"   ,     
                "Fuel Share (Electricity)"    ,     
                "Fuel Share (Hydrogen)"       ,     
                "Fuel Share (CNG)"            ,     
                "Fuel Share (LNG)"            ,     
                "Fuel Share (LPG)"            };    
            string[] namesTech = new string[] {
                "EPS"  , "IACC1", "IACC2", "MHEV", "ISG", "SHEV1", "SHEV1_2", "SHEV2", "PHEV1", "PHEV2",
                "EV1"  , "EV2"  , "EV3"  , "EV4" , "FCV", "MR1"  , "MR2"    , "MR3"  , "MR4"  , "MR5"  ,
                "ROLL1", "ROLL2", "ROLL3", "LDB" , "SAX", "AERO1", "AERO2" };
            int[] indexesMain = new int[namesMain.Length];
            int[] indexesFuel = new int[namesFuel.Length];
            int[] indexesTech = new int[namesTech.Length];
            for (int i = 0; i < indexesMain.Length; i++) { indexesMain[i] = -1; }
            for (int i = 0; i < indexesFuel.Length; i++) { indexesFuel[i] = -1; }
            for (int i = 0; i < indexesTech.Length; i++) { indexesTech[i] = -1; }
            for (int i = 0; i < cols; i++)
            {
                string column = this.GetString(1, i);
                string smpCol = this.GetString(0, i);      
                for (int j = 0; j < namesMain.Length; j++)
                {
                    if (indexesMain[j] == -1)
                    {
                        if      (Interaction.StringCompare(smpCol, namesMain[j], true)) { indexesMain[j] = i; break; }
                        else if (Interaction.StringCompare(column, namesMain[j], true)) { indexesMain[j] = i; break; }
                    }
                }
                for (int j = 0; j < namesFuel.Length; j++)
                {
                    if (indexesFuel[j] == -1 && Interaction.StringCompare(column, namesFuel[j], true)) { indexesFuel[j] = i; break; }
                }
                for (int j = 0; j < namesTech.Length; j++)
                {
                    if (indexesTech[j] == -1 && Interaction.StringCompare(column, namesTech[j], true)) { indexesTech[j] = i; break; }
                }
            }
            if      (indexesMain[1] == -1 && indexesMain[2] != -1) { indexesMain[1] = indexesMain[2]; }
            else if (indexesMain[1] != -1 && indexesMain[2] == -1) { indexesMain[2] = indexesMain[1]; }
            if (!this.VerifyIndexes(this.SheetNames[index] + " worksheet", indexesMain, namesMain)) { return null; }
            if (!this.VerifyIndexes(this.SheetNames[index] + " worksheet", indexesFuel, namesFuel)) { return null; }
            if (!this.VerifyIndexes(this.SheetNames[index] + " worksheet", indexesTech, namesTech)) { return null; }
            int salesMinYear, salesMaxYear, msrpMinYear, msrpMaxYear, priceMinYear, priceMaxYear;
            this.GetMinMaxYears(1, indexesMain[0], out salesMinYear, out salesMaxYear);
            this.GetMinMaxYears(1, indexesMain[1], out msrpMinYear , out msrpMaxYear );
            this.GetMinMaxYears(1, indexesMain[2], out priceMinYear, out priceMaxYear);
            List<Vehicle>[] vc = new List<Vehicle>[mfrs.Count];
            for (int i = 0; i < mfrs.Count; i++)
            {
                vc[i] = new List<Vehicle>(32);
            }
            for (int i = 2; i < rows; i++)
            {   
                int mfrIndex;
                Vehicle.CDescription vd = this.LoadVehicle(i, indexesMain, indexesFuel, indexesTech,
                    salesMinYear, salesMaxYear, msrpMinYear, msrpMaxYear, priceMinYear, priceMaxYear, mfrs, out mfrIndex);
                if (vd != null && mfrIndex != -1) { vc[mfrIndex].Add(new Vehicle(vd)); }
            }
            return vc;
        }
        Vehicle.CDescription LoadVehicle(int row, int[] indexesMain, int[] indexesFuel, int[] indexesTech,
            int salesMinYear, int salesMaxYear, int msrpMinYear, int msrpMaxYear, int priceMinYear, int priceMaxYear,
            List<Manufacturer> mfrs, out int mfrIndex)
        {
            mfrIndex = -1;
            double curbWeigth = this.GetDouble(row, indexesMain[31]);   
            double gvwr       = this.GetDouble(row, indexesMain[32]);
            double gcwr       = this.GetDouble(row, indexesMain[33]);
            Vehicle.CDescription vd = new Vehicle.CDescription(curbWeigth, gvwr, gcwr);
            string vehCode = this.GetString(row, indexesMain[3]);
            string mfrName = this.GetString(row, indexesMain[4]);
            string model   = this.GetString(row, indexesMain[5]);
            if (vehCode == string.Empty && mfrName == string.Empty && model == string.Empty) { return null; }
            if (vehCode == "X") { return null; }
            vd.Code                              = Interaction.GetInt32(vehCode); 
            vd.Manufacturer                      = mfrName;                     
            vd.Model                             = model;                       
            vd.Nameplate                         = this.GetString(row, indexesMain[ 6]);
            vd.Platform                          = this.GetString(row, indexesMain[ 7]);
            vd.EngineCode                        = this.GetInt32 (row, indexesMain[ 8]);
            vd.TransmissionCode                  = this.GetInt32 (row, indexesMain[ 9]);
            vd.Origin                            = this.GetChar  (row, indexesMain[10]);
            vd.RegulatoryIndicator               = this.GetString(row, indexesMain[11]);
            vd.TechnologyClass                   = this.GetString(row, indexesMain[12]);
            vd.SafetyClass                       = this.GetString(row, indexesMain[13]);
            vd.MarketSegment                     = this.GetInt32 (row, indexesMain[14]);
            vd.SmallCar                          = this.GetInt32 (row, indexesMain[15]);
            vd.MLCar                             = this.GetInt32 (row, indexesMain[16]);
            vd.Truck                             = this.GetInt32 (row, indexesMain[17]);
            vd.Van                               = this.GetInt32 (row, indexesMain[18]);
            vd.SUV                               = this.GetInt32 (row, indexesMain[19]);
            vd.AWD                               = this.GetInt32 (row, indexesMain[20]);
            vd.Prestige                          = this.GetInt32 (row, indexesMain[21]);
            vd.MM                                = this.GetDouble(row, indexesMain[22]);
            vd.ASC1                              = this.GetDouble(row, indexesMain[23]);
            vd.ASC2                              = this.GetDouble(row, indexesMain[24]);
            vd.Class                             = this.GetString(row, indexesMain[25]);
            vd.Style                             = this.GetString(row, indexesMain[26]);
            vd.Structure                         = this.GetString(row, indexesMain[27]);
            vd.Drive                             = this.GetChar  (row, indexesMain[28]);
            vd.Wheelbase                         = this.GetDouble(row, indexesMain[29]);
            vd.Footprint                         = this.GetDouble(row, indexesMain[30]);
            vd.CurbWeight                        = curbWeigth;                  
            vd.GVWR                              = gvwr;                        
            vd.GCWR                              = gcwr;                        
            vd.Seating                           = this.GetInt32 (row, indexesMain[34]);
            vd.FuelCapacity                      = this.GetDouble(row, indexesMain[35]);
            vd.HybridType                        = this.GetString(row, indexesMain[36]);
            vd.ElectricPower                     = this.GetDouble(row, indexesMain[37]);
            vd.ElectricRange                     = this.GetDouble(row, indexesMain[38]);
            vd.RefreshYears                      = this.LoadRefreshRedesignYears(this.GetString(row, indexesMain[39]));
            vd.RedesignYears                     = this.LoadRefreshRedesignYears(this.GetString(row, indexesMain[40]));
            vd.Percent2Dr                        = this.GetDouble(row, indexesMain[41]);
            vd.EmploymentHours                   = this.GetDouble(row, indexesMain[42]);
            vd.MaxGVWRToCW                       = this.GetDouble(row, indexesMain[43]);
            vd.MaxGCWRToGVWR                     = this.GetDouble(row, indexesMain[44]);
            for (int i = 0, fuelCount = FTValue<object>.Classes.Length; i < fuelCount; i++)
            {
                FuelType fuel = FTValue<object>.Classes[i];
                vd.FuelEconomy[fuel] = this.GetDouble(row, indexesFuel[i            ]);
                vd.FuelShare  [fuel] = this.GetDouble(row, indexesFuel[i + fuelCount]);
            }
            vd.FuelEconomy2 = vd.FuelEconomy;   
            vd.BaseFE       = vd.FuelEconomy;   
            vd.Sales = this.LoadVehicleSMP(row, indexesMain[0], salesMinYear, salesMaxYear);
            vd.Msrp  = this.LoadVehicleSMP(row, indexesMain[1], msrpMinYear , msrpMaxYear );
            vd.Price = this.LoadVehicleSMP(row, indexesMain[2], priceMinYear, priceMaxYear);
            int maxYr = (int)Math.Max(Math.Max(priceMaxYear, msrpMaxYear), salesMaxYear) - ModelYear.MinYear + 1;
            if (vd.Sales.Length < maxYr) { double[] sales = new double[maxYr]; vd.Sales.CopyTo(sales, 0); vd.Sales = sales; }
            if (vd.Msrp .Length < maxYr) { double[] msrp  = new double[maxYr]; vd.Msrp .CopyTo(msrp , 0); vd.Msrp  = msrp ; }
            if (vd.Price.Length < maxYr) { double[] price = new double[maxYr]; vd.Price.CopyTo(price, 0); vd.Price = price; }
            for (int i = 0; i < maxYr; i++)
            {
                if      (vd.Msrp[i] == 0 && vd.Price[i] != 0) { vd.Msrp[i] = vd.Price[i]; }
                else if (vd.Msrp[i] != 0 && vd.Price[i] == 0) { vd.Price[i] = vd.Msrp[i]; }
            }
            const int techCount = 27;   
            this.LoadTechApplicability(row, indexesTech, 0, techCount, out vd.UsedTechnologies, out vd.AvailableTechnologies, TechnologyIndexes.EPS);
            bool isPHEV = false;
            bool isEV_FCV = (vd.FuelEconomy.PrimaryFuel == FuelType.Electricity || vd.FuelEconomy.PrimaryFuel == FuelType.Hydrogen);
            for (int i = 0; i < vd.UsedTechnologies.Length; i++)
            {
                if (TechnologyIndexes.IsConversionToPHEV(vd.UsedTechnologies[i])) { isPHEV = true; }
                if (TechnologyIndexes.IsConversionToEV  (vd.UsedTechnologies[i]) ||
                    TechnologyIndexes.IsConversionToFCV (vd.UsedTechnologies[i])) { isEV_FCV = true; }
            }
            string hdr = "Vehicle (R:" + (row + 1).ToString("0000") + "):  ";
            mfrIndex = this.GetManufacturerIndex(mfrs, vd.Manufacturer);
            if (vd.Code < 1) { this.LogError(hdr + "Vehicle Code is not valid."); }
            if (mfrIndex == -1) { this.LogError(hdr + "Manufacturer is not specified or not valid."); }
            if (vd.Model == string.Empty) { this.LogError(hdr + "Model is not specified."); }
            if (vd.Nameplate == string.Empty) { this.LogError(hdr + "Nameplate is not specified."); }
            if (vd.EngineCode < 1 && !isEV_FCV) { this.LogError(hdr + "Engine Code is not valid."); }
            if (vd.EngineCode > 0 && isEV_FCV) { this.LogError(hdr + "Engine Code cannot be specified for pure-Electric or Hydrogen-based vehicles."); }
            if (vd.TransmissionCode < 1 && !isEV_FCV && !isPHEV) { this.LogError(hdr + "Transmission Code is not valid."); }
            if (vd.TransmissionCode > 0 && isEV_FCV) { this.LogError(hdr + "Transmission Code cannot be specified for pure-Electric or Hydrogen-based vehicles."); }
            if (vd.TransmissionCode > 0 && isPHEV) { this.LogError(hdr + "Transmission Code cannot be specified for Plug-In Hybrid vehicles."); }
            if (vd.Origin != 'I' && vd.Origin != 'D') { this.LogError(hdr + "Origin is not valid."); }
            if (!vd.FuelEconomy.IsValid) { this.LogError(hdr + "Fuel Economy is not valid (the Fuel Economy value cannot be negative)."); }
            if ( vd.FuelEconomy.IsEmpty) { this.LogError(hdr + "Fuel Economy is not valid (at least one Fuel Economy value must be specified)."); }
            if (!vd.FuelShare  .IsValid) { this.LogError(hdr + "Fuel Share is not valid (the Fuel Share value cannot be negative)."); }
            if ( vd.FuelShare  .IsEmpty) { this.LogError(hdr + "Fuel Share is not valid (at least one Fuel Share value must be specified)."); }
            if ( vd.FuelShare  .Total != 1) { this.LogError(hdr + "Fuel Share is not valid (Fuel Shares for all fuel types must add up to 100%)."); }
            foreach (FuelType fuelType in FTValue<object>.Classes)
            {
                if ((vd.FuelEconomy[fuelType] == 0 && vd.FuelShare[fuelType] != 0) ||
                    (vd.FuelEconomy[fuelType] != 0 && vd.FuelShare[fuelType] == 0))
                {
                    this.LogError(hdr + fuelType + " Fuel Economy and Fuel Share do not correspond.");
                }
            }
            if (!Interaction.StringCompareAny(vd.RegulatoryIndicator, new string[] {"PC", "LT", "LT2B3"}, false)) { this.LogError(hdr + "Regulatory Indicator is not valid."); }
            if (!Interaction.StringCompareAny(vd.TechnologyClass, new string[] {"Subcompact PC", "Subcompact Perf. PC", "Compact PC",
                "Compact Perf. PC", "Midsize PC", "Midsize Perf. PC", "Large PC", "Large Perf. PC", "Minivan LT", "Small LT",
                "Midsize LT", "Large LT", "Truck 2B3", "Van 2B3"}, true)) { this.LogError(hdr + "Technology Class is not valid."); }
            if (!Interaction.StringCompareAny(vd.SafetyClass, new string[] { "PC", "LT", "CM" }, true)) { this.LogError(hdr + "Safety Class is not valid."); }
            if (vd.MarketSegment < 0 || vd.MarketSegment > 21) { this.LogError(hdr + "Market Segment is not valid."); }
            if (vd.SmallCar != 0 && vd.SmallCar != 1) { this.LogError(hdr + "SmallCar must be 0 or 1."); }
            if (vd.MLCar != 0 && vd.MLCar != 1) { this.LogError(hdr + "MLCar must be 0 or 1."); }
            if (vd.Truck != 0 && vd.Truck != 1) { this.LogError(hdr + "Truck must be 0 or 1."); }
            if (vd.Van != 0 && vd.Van != 1) { this.LogError(hdr + "Van must be 0 or 1."); }
            if (vd.SUV != 0 && vd.SUV != 1) { this.LogError(hdr + "SUV must be 0 or 1."); }
            if (vd.AWD != 0 && vd.AWD != 1) { this.LogError(hdr + "AWD must be 0 or 1."); }
            if (vd.Prestige != 0 && vd.Prestige != 1) { this.LogError(hdr + "Prestige must be 0 or 1."); }
            if (vd.Style == string.Empty) { this.LogError(hdr + "Style is not specified."); }
            else if (!Interaction.StringCompareAny(vd.Style, new string[] { "Convertible", "Coupe", "Hatchback", "Sedan", "Wagon", "Minivan", "Van", "Sport Utility", "Pickup", "Large Pickup", "Chassis Cab", "Cutaway" }, false)) { this.LogError(hdr + "Style is not valid."); }
            if (vd.Structure == string.Empty) { this.LogError(hdr + "Structure is not specified."); }
            if (!Interaction.StringCompareAny(vd.Drive.ToString(), new string[] {"F", "R", "2", "4", "A"}, false)) { this.LogError(hdr + "Drive is not specified."); }
            if (vd.RegulatoryIndicator != "LT2B3" &&
                vd.Wheelbase    < 1) { this.LogError(hdr + "Wheelbase is not valid."); }
            if (vd.RegulatoryIndicator != "LT2B3" &&
                vd.Footprint    < 1) { this.LogError(hdr + "Footprint is not valid."); }
            if (vd.CurbWeight   < 1) { this.LogError(hdr + "Curb Weight is not valid."); }
            if (vd.GVWR         < 1) { this.LogError(hdr + "GVWR is not valid."); }
            if (vd.RegulatoryIndicator == "LT2B3" &&
                vd.GCWR         < 1) { this.LogError(hdr + "GCWR is not valid."); }
            if (vd.Seating      < 1) { this.LogError(hdr + "Seating (Max) is not valid."); }
            if (vd.FuelCapacity < 1 && (vd.FuelShare.Gasoline > 0 || vd.FuelShare.Diesel > 0 || vd.FuelShare.Ethanol85 > 0)) { this.LogError(hdr + "Fuel Capacity is not valid."); }
            if (vd.FuelShare.Electricity > 0 && vd.ElectricPower < 1) { this.LogError(hdr + "Electric Power has to be specified for electric vehicles."); }
            if (vd.FuelShare.Electricity > 0 && vd.ElectricRange < 1) { this.LogError(hdr + "Electric Range has to be specified for electric vehicles."); }
            bool foundSlMsrp = false;
            for (int i = 0; i < maxYr; i++)
            {
                if (vd.Sales[i] > 0 && vd.Msrp[i] > 0) { foundSlMsrp = true; break; }
            }
            if (!foundSlMsrp) { this.LogError(hdr + "No Sales and MSRP found for the same year."); }
            return vd;
        }
        double[] LoadVehicleSMP(int row, int column, int minYear, int maxYear)
        {
            double[] values = new double[maxYear - ModelYear.MinYear + 1];
            int years = maxYear - minYear + 1;
            int offset = minYear - ModelYear.MinYear;
            for (int i = 0; i < years; i++)
            {
                values[i + offset] = this.GetDouble(row, i + column);
            }
            return values;
        }
        List<Engine>[] LoadEngines(int index, List<Manufacturer> mfrs)
        {
            this.ActivateWorksheet(index);
            int rows = this.Rows;
            int cols = this.Columns;
            string[] namesMain = new string[] {
                "Engine Code"            ,      
                "Manufacturer"           ,      
                "Family"                 ,      
                "Configuration"          ,      
                "Fuel"                   ,      
                "Engine Oil Viscosity"   ,      
                "Cycle"                  ,      
                "Fuel Delivery System"   ,      
                "Aspiration"             ,      
                "Valvetrain Design"      ,      
                "Valve Actuation/Timing" ,      
                "Valve Lift"             ,      
                "Cylinders"              ,      
                "Valves/Cylinder"        ,      
                "Deactivation"           ,      
                "Displacement"           ,      
                "Max. Horsepower"        ,      
                "Max. Torque"            ,      
                "Engine Size"            };     
            string[] namesTech = new string[] {
                "LUB1" , "EFR1"  , "LUB2_EFR2", "CCPS", "DVVLS", "DEACS" , "ICP" , "DCP"  , "DVVLD" , "CVVL" ,
                "DEACD", "SGDI"  , "DEACO"    , "VVA" , "SGDIO", "TRBDS1", "SEGR", "DWNSP", "TRBDS2", "CEGR1",
                "CEGR2", "MILLER", "LGDI"     , "CNG" , "LNG"  , "LPG"   , "DSL" , "DTURB", "EFRD"  , "DDOWN" };
            int[] indexesMain = new int[namesMain.Length];
            int[] indexesTech = new int[namesTech.Length];
            for (int i = 0; i < namesMain.Length; i++) { indexesMain[i] = -1; }
            for (int i = 0; i < namesTech.Length; i++) { indexesTech[i] = -1; }
            for (int i = 0; i < cols; i++)
            {
                string column = this.GetString(1, i);
                for (int j = 0; j < namesMain.Length; j++)
                {
                    if (indexesMain[j] == -1 && Interaction.StringCompare(column, namesMain[j], true)) { indexesMain[j] = i; break; }
                }
                for (int j = 0; j < namesTech.Length; j++)
                {
                    if (indexesTech[j] == -1 && Interaction.StringCompare(column, namesTech[j], true)) { indexesTech[j] = i; break; }
                }
            }
            if (!this.VerifyIndexes(this.SheetNames[index] + " worksheet", indexesMain, namesMain)) { return null; }
            if (!this.VerifyIndexes(this.SheetNames[index] + " worksheet", indexesTech, namesTech)) { return null; }
            List<Engine>[] ec = new List<Engine>[mfrs.Count];
            for (int i = 0; i < mfrs.Count; i++)
            {
                ec[i] = new List<Engine>(32);
            }
            for (int i = 2; i < rows; i++)
            {   
                int mfrIndex;
                Engine.CDescription ed = this.LoadEngine(i, indexesMain, indexesTech, mfrs, out mfrIndex);
                if (ed != null && mfrIndex != -1) { ec[mfrIndex].Add(new Engine(ed)); }
            }
            return ec;
        }
        Engine.CDescription LoadEngine(int row, int[] indexesMain, int[] indexesTech, List<Manufacturer> mfrs, out int mfrIndex)
        {
            mfrIndex = -1;
            Engine.CDescription ed = new Engine.CDescription();
            string engCode = this.GetString(row, indexesMain[0]);
            string mfrName = this.GetString(row, indexesMain[1]);
            if (engCode == string.Empty && mfrName == string.Empty) { return null; }
            if (engCode == "X") { return null; }
            ed.Code                 = Interaction.GetInt32(engCode);
            ed.Manufacturer         = mfrName;                      
            ed.Family               = this.GetString(row, indexesMain[ 2]);
            ed.Configuration        = this.GetString(row, indexesMain[ 3]);
            string fuel             = this.GetString(row, indexesMain[ 4]);
            ed.Fuel                 = (fuel == "G"    ) ? FuelType.Gasoline :
                                      (fuel == "G+E85") ? FuelType.Gasoline | FuelType.Ethanol85 :
                                      (fuel == "D"    ) ? FuelType.Diesel :
                                      (fuel == "D+B20") ? FuelType.Diesel | FuelType.Biodiesel20 :
                                      (fuel == "CNG"  ) ? FuelType.CNG :
                                      (fuel == "LNG"  ) ? FuelType.LNG :
                                      (fuel == "LPG"  ) ? FuelType.LPG :
                                                          FuelType.None;
            ed.EngineOilViscosity   = this.GetString(row, indexesMain[ 5]);
            ed.Cycle                = this.GetChar  (row, indexesMain[ 6]);
            ed.FuelSystem           = this.GetString(row, indexesMain[ 7]);
            ed.Aspiration           = this.GetString(row, indexesMain[ 8]);
            ed.ValvetrainDesign     = this.GetString(row, indexesMain[ 9]);
            ed.ValveActuationTiming = this.GetString(row, indexesMain[10]);
            ed.ValveLift            = this.GetString(row, indexesMain[11]);
            ed.Cylinders            = this.GetInt32 (row, indexesMain[12]);
            ed.ValvesPerCylinder    = this.GetInt32 (row, indexesMain[13]);
            ed.Deactivation         = this.GetDouble(row, indexesMain[14]); if (ed.Deactivation == 0 && this.GetString(row, indexesMain[14]) == "Y") { ed.Deactivation = 0.3; }
            ed.Displacement         = this.GetDouble(row, indexesMain[15]);
            ed.Horsepower           = this.GetDouble(row, indexesMain[16]);
            ed.Torque               = this.GetDouble(row, indexesMain[17]);
            string engSize          = this.GetString(row, indexesMain[18]);
            ed.EngineSize           = (engSize == "SMALL"  || engSize == "SD") ? EngineSize.Small  :
                                      (engSize == "MEDIUM" || engSize == "MD") ? EngineSize.Medium :
                                      (engSize == "LARGE"  || engSize == "LD") ? EngineSize.Large  :
                                                                                 EngineSize.None;
            const int techCount = 30;   
            this.LoadTechApplicability(row, indexesTech, 0, techCount, out ed.UsedTechnologies, out ed.AvailableTechnologies, TechnologyIndexes.LUB1);
            string hdr = "Engine (R:" + (row + 1).ToString("0000") + "):  ";
            mfrIndex = this.GetManufacturerIndex(mfrs, ed.Manufacturer);
            if (ed.Code < 1) { this.LogError(hdr + "Engine Code is not valid."); }
            if (mfrIndex == -1) { this.LogError(hdr + "Manufacturer is not specified or not valid."); }
            if (ed.Configuration == string.Empty) { this.LogError(hdr + "Configuration is not specified."); }
            if (ed.Fuel == FuelType.None) { this.LogError(hdr + "Engine Fuel is not specified or not valid."); }
            if (ed.EngineOilViscosity == string.Empty) { this.LogError(hdr + "Engine Oil Viscosity is not specified."); }
            if (ed.Cycle == '\0') { this.LogError(hdr + "Cycle is not specified."); }
            if (ed.FuelSystem == string.Empty) { this.LogError(hdr + "Fuel Delivery System is not specified."); }
            if (!Interaction.StringCompareAny(ed.Aspiration, new string[] { "NA", "S", "T", "T1", "T2", "T3", "T4", "T5", "T6" }, false)) { this.LogError(hdr + "Aspiration is not valid."); }
            if (ed.Configuration != "R")
            {
                if (!Interaction.StringCompareAny(ed.ValvetrainDesign, new string[] { "OHV", "SOHC", "DOHC" }, false)) { this.LogError(hdr + "Valvetrain Design is not valid."); }
                if (ed.ValveActuationTiming == string.Empty) { this.LogError(hdr + "Valve Actuation/Timing is not specified."); }
                if (ed.ValveLift == string.Empty) { this.LogError(hdr + "Valve Lift is not specified."); }
                if (ed.Cylinders < 1) { this.LogError(hdr + "Number of Cylinders is too small."); }
                if (ed.ValvesPerCylinder < 1) { this.LogError(hdr + "Number of Valves/Cylinders is too small."); }
            }
            if (ed.Deactivation < 0) { this.LogError(hdr + "Deactivation is not valid."); }
            if (ed.Displacement < 0) { this.LogError(hdr + "Displacement is not valid."); }
            if (ed.Horsepower < 0) { this.LogError(hdr + "Horsepower is not valid."); }
            if (ed.Torque < 0) { this.LogError(hdr + "Torque is not valid."); }
            if (ed.EngineSize == EngineSize.None) { this.LogError(hdr + "Engine Size is not specified or not valid."); }
            return ed;
        }
        double GetCompressionRatio(string value)
        {
            double ratio = 0;
            if (value != "")
            {
                int index = value.IndexOf(":");
                if (index != -1)
                {   
                    ratio = Interaction.GetDouble(value.Substring(0, index)) / Interaction.GetDouble(value.Substring(index + 1));
                }
                else
                {   
                    ratio = Interaction.GetDouble(value);
                }
            }
            return ratio;
        }
        List<Transmission>[] LoadTransmissions(int index, List<Manufacturer> mfrs)
        {
            this.ActivateWorksheet(index);
            int rows = this.Rows;
            int cols = this.Columns;
            string[] namesMain = new string[] {
                "Transmission Code"      ,      
                "Manufacturer"           ,      
                "Family"                 ,      
                "Type"                   ,      
                "Number of Forward Gears"};     
            string[] namesTech = new string[] {
                "6MAN", "HETRANSM", "IATC", "NAUTO", "DCT", "8SPD", "HETRANS", "SHFTOPT" };
            int[] indexesMain = new int[namesMain.Length];
            int[] indexesTech = new int[namesTech.Length];
            for (int i = 0; i < namesMain.Length; i++) { indexesMain[i] = -1; }
            for (int i = 0; i < namesTech.Length; i++) { indexesTech[i] = -1; }
            for (int i = 0; i < cols; i++)
            {
                string column = this.GetString(1, i);
                for (int j = 0; j < namesMain.Length; j++)
                {
                    if (indexesMain[j] == -1 && Interaction.StringCompare(column, namesMain[j], true)) { indexesMain[j] = i; break; }
                }
                for (int j = 0; j < namesTech.Length; j++)
                {
                    if (indexesTech[j] == -1 && Interaction.StringCompare(column, namesTech[j], true)) { indexesTech[j] = i; break; }
                }
            }
            if (!this.VerifyIndexes(this.SheetNames[index] + " worksheet", indexesMain, namesMain)) { return null; }
            if (!this.VerifyIndexes(this.SheetNames[index] + " worksheet", indexesTech, namesTech)) { return null; }
            List<Transmission>[] tc = new List<Transmission>[mfrs.Count];
            for (int i = 0; i < mfrs.Count; i++)
            {
                tc[i] = new List<Transmission>(32);
            }
            for (int i = 2; i < rows; i++)
            {   
                int mfrIndex;
                Transmission.CDescription td = this.LoadTransmission(i, indexesMain, indexesTech, mfrs, out mfrIndex);
                if (td != null && mfrIndex != -1) { tc[mfrIndex].Add(new Transmission(td)); }
            }
            return tc;
        }
        Transmission.CDescription LoadTransmission(int row, int[] indexesMain, int[] indexesTech, List<Manufacturer> mfrs, out int mfrIndex)
        {
            mfrIndex = -1;
            Transmission.CDescription td = new Transmission.CDescription();
            string trnCode = this.GetString(row, indexesMain[0]);
            string mfrName = this.GetString(row, indexesMain[1]);
            if (trnCode == string.Empty && mfrName == string.Empty) { return null; }
            if (trnCode == "X") { return null; }
            td.Code            = Interaction.GetInt32(trnCode);
            td.Manufacturer    = mfrName;                      
            td.Family          = this.GetString(row, indexesMain[ 2]);
            td.Type            = this.GetString(row, indexesMain[ 3]);
            td.NumForwardGears = this.GetInt32 (row, indexesMain[ 4]);
            const int techCount = 8;    
            this.LoadTechApplicability(row, indexesTech, 0, techCount, out td.UsedTechnologies, out td.AvailableTechnologies, TechnologyIndexes.MAN6);
            string hdr = "Transmission (R:" + (row + 1).ToString("0000") + "):  ";
            mfrIndex = this.GetManufacturerIndex(mfrs, td.Manufacturer);
            if (td.Code < 1) { this.LogError(hdr + "Transmission Code is not valid."); }
            if (mfrIndex == -1) { this.LogError(hdr + "Manufacturer is not specified or not valid."); }
            if (!Interaction.StringCompareAny(td.Type, new string[] { "A", "M", "CVT", "AMT", "DCT" }, false)) { this.LogError(hdr + "Type is not valid."); }
            if (td.Type != "CVT" && td.NumForwardGears < 3) { this.LogError(hdr + "Number of Forward Gears is too small."); }
            return td;
        }
        void LoadTechApplicability(int row, int[] indexes, int colIndex,
            int techCount, out int[] usedTechnologies, out int[] availableTechnologies, int firstTechIndex)
        {
            int[] techAp = new int[techCount];
            int techUsedCount = 0, techAvailCount = 0;
            for (int i = 0; i < techCount; i++)
            {
                techAp[i] = this.ParseTechApplicability(this.GetString(row, indexes[colIndex + i]));
                if      (techAp[i] == 1) { techUsedCount++; }
                else if (techAp[i] == 0) { techAvailCount++; }
            }
            usedTechnologies      = new int[techUsedCount];
            availableTechnologies = new int[techAvailCount];
            for (int i = 0, ut = 0, at = 0; i < techCount; i++)
            {
                if      (techAp[i] == 1) { usedTechnologies     [ut++] = firstTechIndex + i; }
                else if (techAp[i] == 0) { availableTechnologies[at++] = firstTechIndex + i; }
            }
        }
        int ParseTechApplicability(string value)
        {
            return (value == "SKIP" || value == "_SKIP") ? -1 : (value == "USED" || value == "_USED") ? 1 : 0;
        }
        int[] LoadRefreshRedesignYears(string yearList)
        {
            return this.LoadRefreshRedesignYears(yearList.Split(',', ';'));
        }
        int[] LoadRefreshRedesignYears(string[] years)
        {
            int count = years.Length;
            List<int> parsedYears = new List<int>(count);
            for (int i = 0; i < count; i++)
            {
                int year = Interaction.GetInt32(years[i]);
                if (year > 1900 && !parsedYears.Contains(year))
                {
                    parsedYears.Add(year);
                }
            }
            parsedYears.Sort();
            return parsedYears.ToArray();
        }
        int GetManufacturerIndex(List<Manufacturer> mfrs, string mfrName)
        {
            for (int i = 0; i < mfrs.Count; i++)
            {
                if (Interaction.StringCompare(mfrs[i].Description.Name, mfrName, false)) { return i; }
            }
            return -1;
        }
        #endregion
        #region 
        internal Industry _ind;
        #endregion
    }
}

