#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)

using System;
using System.Collections.Generic;
using System.Linq;

using Volpe.Cafe.MarketData;
using Volpe.Cafe.MarketData.BaselineInfo;
using Volpe.Cafe.MarketData.Design;
using Volpe.Cafe.MarketData.Parsers;
using Volpe.Cafe.ModelSettings;
using Volpe.Cafe.TechAnalysis;

using RC = Volpe.Cafe.RegulatoryClass;
using TI = Volpe.Cafe.TechnologyIndexes;

namespace Volpe.Cafe
{
    /// <summary>
    /// Provides extension methods for the market-data classes.
    /// </summary>
    public static class MarketDataExtensions
    {
        #region <<< IIndustry extensions >>>

        /// <summary>
        /// Creates a new copy of the <see cref="IIndustry"/> object using the baseline information for the
        /// manufacturers and the various <see cref="IOEMProduct"/>s associated with this instance, excluding all BEVs
        /// and FCVs from the fleet. BEV/FCV-only manufacturers will be removed from the fleet data as well.<br/>
        /// All of the compliance data produced during runtime is discarded.
        /// </summary>
        /// <returns>
        /// A new copy of the <see cref="IIndustry"/> object using only the baseline information and with all BEVs and
        /// FCVs removed from the fleet.
        /// </returns>
        public static IIndustry CreateNewWithoutBevFcv(this IIndustry data)
        {
            var bVehs = data.Vehicles.Where(item => !item.BaselineInfo.HevType.IsBevOrFcv())
                                     .Select(item => item.BaselineInfo)
                                     .ToList();

            var mfrs = new HashSet<string>(bVehs.Select(item => item.ManufacturerName).Distinct());

            var bMfrs = data.Manufacturers
                .Where(item => mfrs.Contains(item.BaselineInfo.Name))
                .Select(item => (item.BaselineInfo, item.BaselineCreditBank, item.CreditsAndAdjustments))
                .ToList();

            var bPlts = FilterMfrs<IPlatform, BaselinePlatform>(data.Platforms);
            var bEngs = FilterMfrs<IEngine, BaselineEngine>(data.Engines);
            var bTrns = FilterMfrs<ITransmission, BaselineTransmission>(data.Transmissions);

            var bPltFams = FilterMfrs<IPlatformFamily, BaselineComponentFamily>(data.PlatformFamilies);
            var bEngFams = FilterMfrs<IEngineFamily, BaselineComponentFamily>(data.EngineFamilies);
            var bTrnFams = FilterMfrs<ITransmissionFamily, BaselineComponentFamily>(data.TransmissionFamilies);

            // update leader and refresh/redesign for platforms
            // note: not necessary for engs/trns, because BEV/FCV do not list a powertrain
            bPlts = UpdatePlatformLeaderAndDesignYears();

            return new Industry(bMfrs, bVehs, bPlts, bEngs, bTrns, bPltFams, bEngFams, bTrnFams);

            // local helper function
            List<B> FilterMfrs<T, B>(IEnumerable<T> products) where T : IOEMProduct where B : IOEMProductInfo
            {
                return products.Where(item => mfrs.Contains(item.BaselineInfo.ManufacturerName))
                               .Select(item => (B)item.BaselineInfo)
                               .ToList();
            }

            List<BaselinePlatform> UpdatePlatformLeaderAndDesignYears()
            {
                HashSet<int> bVehCodes = new HashSet<int>(bVehs.ConvertAll(veh => veh.Code));
                var newBasePlatforms = new List<BaselinePlatform>();

                foreach (var basePlatform in bPlts) {
                    int oldLeaderCode = basePlatform.LeaderCode;
                    if (oldLeaderCode == -1 || bVehCodes.Contains(oldLeaderCode)) {
                        // platform didn't specify a leader -or- leader still exists
                        newBasePlatforms.Add(basePlatform);
                        continue;
                    }

                    var queryVehs = bVehs.Where(veh => veh.PlatformCode == basePlatform.Code);
                    var newLeader = LeaderSelection.SelectLeaderVehicle(queryVehs);

                    int newLeaderCode = -1;
                    var newRefreshYears = ModelYearKey.Empty;
                    var newRedesignYears = ModelYearKey.Empty;

                    if (newLeader != null) {
                        newLeaderCode = newLeader.Code;
                        newRefreshYears = newLeader.RefreshYears;
                        newRedesignYears = newLeader.RedesignYears;
                    }

                    var newValues = new Dictionary<string, object> {
                        // carry over existing platform data
                        ["Code"] = basePlatform.Code,
                        ["ManufacturerName"] = basePlatform.ManufacturerName,
                        ["Name"] = basePlatform.Name,
                        ["ComponentName"] = basePlatform.ComponentName,
                        ["FamilyName"] = basePlatform.FamilyName,
                        ["UsedTechnologies"] = basePlatform.UsedTechnologies,
                        ["AvailableTechnologies"] = basePlatform.AvailableTechnologies,

                        // platform data from new leader
                        ["LeaderCode"] = newLeaderCode,
                        ["RefreshYears"] = newRefreshYears,
                        ["RedesignYears"] = newRedesignYears
                    };

                    var newBasePlatform = new BaselinePlatform(newValues);
                    newBasePlatforms.Add(newBasePlatform);
                }

                return newBasePlatforms;
            }
        }

        #endregion

        #region <<< IManufacturer extensions >>>

        /// <summary>
        /// Calculates the net CAFE credit balance for the manufacturer for the specified regulatory class, using a
        /// manufacturer's latest revision or a revision from the specified model year.
        /// </summary>
        /// <param name="regClass">The regulatory class for which to obtain the net CAFE credit balance.</param>
        /// <param name="year">
        ///     The model year for which to obtain the net CAFE credit balance, or <c>null</c> to obtain net credits
        ///     using a manufacturer's latest revision.
        /// </param>
        /// <returns>The net CAFE credit balance for the manufacturer for the specified regulatory class.</returns>
        /// <remarks>
        /// Net credits (for either CAFE or CO-2) are defined as the sum of credits earned by and transferred into the
        /// manufacturer, minus the credits transferred out of the manufacturer.
        /// </remarks>
        public static double GetNetCredits(this IManufacturer mfr, RC regClass, int? year = null)
        {
            var stats = mfr.GetRevision(year).ComplianceStats;
            return stats.GetNetCredits(regClass);
        }
        /// <summary>
        /// Calculates the net CAFE credit balance for the manufacturer across all regulatory classes, using a
        /// manufacturer's latest revision or a revision from the specified model year.
        /// </summary>
        /// <returns>The net CAFE credit balance for the manufacturer across all regulatory classes.</returns>
        /// <inheritdoc cref="GetNetCredits(IManufacturer, RC, int?)" path="/param[@name='year']"/>
        /// <inheritdoc cref="GetNetCredits(IManufacturer, RC, int?)" path="/remarks"/>
        public static RCDouble GetNetCredits(this IManufacturer mfr, int? year = null)
        {
            var stats = mfr.GetRevision(year).ComplianceStats;
            return stats.GetNetCredits();
        }

        /// <summary>
        /// Calculates the net CO-2 credit balance for the manufacturer for the specified regulatory class, using a
        /// manufacturer's latest revision or a revision from the specified model year.
        /// </summary>
        /// <param name="regClass">The regulatory class for which to obtain the net CO-2 credit balance.</param>
        /// <param name="year">
        ///     The model year for which to obtain the net CO-2 credit balance, or <c>null</c> to obtain net credits
        ///     using a manufacturer's latest revision.
        /// </param>
        /// <returns>The net CO-2 credit balance for the manufacturer for the specified regulatory class.</returns>
        /// <inheritdoc cref="GetNetCredits(IManufacturer, RC, int?)" path="/remarks"/>
        public static double GetNetCO2Credits(this IManufacturer mfr, RC regClass, int? year = null)
        {
            var stats = mfr.GetRevision(year).ComplianceStats;
            return stats.GetNetCO2Credits(regClass);
        }
        /// <summary>
        /// Calculates the net CO-2 credit balance for the manufacturer across all regulatory classes, using a
        /// manufacturer's latest revision or a revision from the specified model year.
        /// </summary>
        /// <returns>The net CO-2 credit balance for the manufacturer across all regulatory classes.</returns>
        /// <inheritdoc cref="GetNetCO2Credits(IManufacturer, RC, int?)" path="/param[@name='year']"/>
        /// <inheritdoc cref="GetNetCO2Credits(IManufacturer, RC, int?)" path="/remarks"/>
        public static RCAltDouble GetNetCO2Credits(this IManufacturer mfr, int? year = null)
        {
            var stats = mfr.GetRevision(year).ComplianceStats;
            return stats.GetNetCO2Credits();
        }

        /// <summary>
        /// Determines whether the manufacturer is in compliance with the specified compliance program for the given
        /// regulatory class, using a manufacturer's latest revision or a revision from the specified model year.
        /// </summary>
        /// <param name="program">The compliance program for which to check a manufacturer's compliance state.</param>
        /// <param name="regClass">The regulatory class for which to check a manufacturer's compliance state.</param>
        /// <param name="year">
        ///     The model year for which to determine if the manufacturer is in compliance, or <c>null</c> to determine
        ///     compliance using a manufacturer's latest revision.
        /// </param>
        /// <returns>
        /// <c>true</c>, if the manufacturer is in compliance with the specified compliance program for the given
        /// regulatory class; <c>false</c>, otherwise.
        /// </returns>
        /// <inheritdoc cref="IsInCompliance(IManufacturer, ComplianceProgram, int?)" path="/param"/>
        public static bool IsInCompliance(
            this IManufacturer mfr, ComplianceProgram program, RC regClass, int? year = null)
        {
            var stats = mfr.GetRevision(year).ComplianceStats;
            switch (program) {
                case ComplianceProgram.CO2:
                    return stats.GetNetCO2Credits(regClass) >= 0;
                case ComplianceProgram.CAFEAndCO2:
                    return stats.GetNetCredits(regClass) >= 0 && stats.GetNetCO2Credits(regClass) >= 0;
                case ComplianceProgram.CAFE:
                default:
                    return stats.GetNetCredits(regClass) >= 0;
            }
        }
        /// <summary>
        /// Determines whether the manufacturer is in compliance with the specified compliance program in all of the
        /// regulatory classes, using a manufacturer's latest revision or a revision from the specified model year.
        /// </summary>
        /// <returns>
        /// <c>true</c>, if the manufacturer is in compliance with the specified compliance program in all of the
        /// regulatory classes; <c>false</c>, otherwise.
        /// </returns>
        /// <inheritdoc cref="IsInCompliance(IManufacturer, ComplianceProgram, RC, int?)" path="/param[@name='program']"/>
        /// <inheritdoc cref="IsInCompliance(IManufacturer, ComplianceProgram, RC, int?)" path="/param[@name='year']"/>
        public static bool IsInCompliance(this IManufacturer mfr, ComplianceProgram program, int? year = null)
        {
            return RegClasses.List.All(rc => mfr.IsInCompliance(program, rc, year));
        }

        /// <summary>
        /// Calculates and returns the total employment hours for all vehicles produced by the manufacturer, in each of
        /// the regulatory classes.
        /// </summary>
        /// <param name="year">The model year for which to compute labor hours.</param>
        /// <param name="employmentValues">An object containing parameters for calculating employment effects.</param>
        /// <returns>
        /// The total employment hours for all vehicles produced by the manufacturer, separated by regulatory class.
        /// </returns>
        public static RCDouble CalcLaborHours(this IManufacturer mfr, int year, EmploymentValues employmentValues)
        {
            RCDouble hours = new RCDouble();
            foreach (IVehicle veh in mfr.Vehicles) {
                hours[veh.GetRevision(year).RegClass] += veh.CalcLaborHours(year, employmentValues);
            }
            return hours;
        }

        /// <summary>
        /// Resets the "IsIgnored" states for each technology on all vehicles, platforms, engines, and transmissions,
        /// for the manufacturer.
        /// </summary>
        public static void ClearAllTechIgnoredStates(this IManufacturer mfr)
        {
            foreach (var item in mfr.Vehicles) { item.ClearTechIgnoredState(); }
            foreach (var item in mfr.Platforms) { item.ClearTechIgnoredState(); }
            foreach (var item in mfr.Engines) { item.ClearTechIgnoredState(); }
            foreach (var item in mfr.Transmissions) { item.ClearTechIgnoredState(); }
        }

        #endregion

        #region <<< IOEMProduct extensions >>>

        /// <summary>
        /// Resets the "IsIgnored" state for each technology on the product.
        /// </summary>
        public static void ClearTechIgnoredState(this IOEMProduct product)
        {
            foreach (int techIndex in product.TechnologyList) {
                product.TechUtilization[techIndex].IsIgnored = false;
            }
        }

        /// <summary>
        /// Installs the specified technology on the product during the given application year. If the technology is
        /// being installed from a multiyear solution (i.e., analysisYear != applicationYear), product revisions for
        /// all affected model years will be updated as well.
        /// </summary>
        /// <param name="scen">The compliance scenario being analyzed.</param>
        /// <param name="analysisYear">The model year being analyzed.</param>
        /// <param name="applicationYear">
        ///     The initial model year when the technology is to be installed on the product.
        /// </param>
        /// <param name="tech">The technology to install on the product.</param>
        /// <param name="isInherited">
        ///     <c>true</c>, if the technology being installed on a product should be inherited instead;
        ///     <c>false</c>, for a regular technology install.
        /// </param>
        /// <param name="settings">The modeling settings used for analysis.</param>
        public static void InstallTechnology(
            this IOEMProduct product, Scenario scen, int analysisYear, int applicationYear, TechnologyInfo tech,
            bool isInherited, ModelingSettings settings)
        {
            if (product is IVehicle veh) {
                InstallTechnology(veh, scen, analysisYear, applicationYear, tech, isInherited, settings);
            }
            else if (product is IOEMComponent c) {
                InstallTechnology(c, scen, analysisYear, applicationYear, tech, isInherited, settings);
            }
        }

        #endregion

        #region <<< IOEMComponent extensions >>>

        /// <inheritdoc cref="InstallTechnology(IOEMProduct,Scenario,int,int,TechnologyInfo,bool,ModelingSettings)"/>
        public static void InstallTechnology(
            this IOEMComponent component, Scenario scen, int analysisYear, int applicationYear, TechnologyInfo tech,
            bool isInherited, ModelingSettings settings)
        {
            var techUse = component.TechUtilization[tech.Index];
            if (isInherited) {
                techUse.SetInherited(applicationYear);
            }
            else {
                techUse.SetUsed(applicationYear);
            }

            // update tech-key for all component revisions
            for (int year = applicationYear; year <= analysisYear; year++) {
                var revision = component.GetRevision(year);
                revision.TechKey = KeyInitializer.TestTechKey(revision, tech.Index);
            }

            // refresh the technology utilization, ensuring that proper techs
            // get enabled or disabled based on technology dependencies
            TechUseInitializer.RefreshTechUtilization(
                component, scen, analysisYear, applicationYear, tech.Index, settings);
        }

        #endregion

        #region <<< IVehicle extensions >>>

        /// <summary>
        /// Calculates and returns total employment hours associated with the production and sale of the vehicle.
        /// </summary>
        /// <param name="year">The model year for which to compute labor hours.</param>
        /// <param name="employmentValues">An object containing parameters for calculating employment effects.</param>
        /// <returns>The total employment hours for the vehicle.</returns>
        public static double CalcLaborHours(this IVehicle veh, int year, EmploymentValues employmentValues)
        {
            var vehBaseInfo = veh.BaselineInfo;
            var vehRevision = veh.GetRevision(year);

            double sales     = vehRevision.Sales;
            double techCost  = vehRevision.ComplianceCosts.TotalTechCost;
            double baseHours = (vehBaseInfo.USAssemblyHours * employmentValues.USAssemblyMultiplier +
                                vehBaseInfo.DealershipHours);
            double inovHours = (techCost / employmentValues.OEMRevenue +
                                techCost / employmentValues.SuplierRevenue / employmentValues.RPE
                               ) * vehBaseInfo.PercentUSContent * employmentValues.AnnualLaborHours;

            return (baseHours + inovHours) * sales;
        }

        /// <inheritdoc cref="InstallTechnology(IOEMProduct,Scenario,int,int,TechnologyInfo,bool,ModelingSettings)"/>
        public static void InstallTechnology(
            this IVehicle veh, Scenario scen, int analysisYear, int applicationYear, TechnologyInfo tech,
            bool isInherited, ModelingSettings settings)
        {
            var techUse = veh.TechUtilization[tech.Index];
            if (isInherited) {
                techUse.SetInherited(applicationYear);
            }
            else {
                techUse.SetUsed(applicationYear);
            }

            HEVType newHevType = TI.IsConversionToMHEV(tech.Index) ? HEVType.MildHybrid
                               : TI.IsConversionToSHEV(tech.Index) ? HEVType.StrongHybrid
                               : TI.IsConversionToPHEV(tech.Index) ? HEVType.PlugInHybrid
                               : TI.IsConversionToEV  (tech.Index) ? HEVType.BatteryElectric
                               : TI.IsConversionToFCV (tech.Index) ? HEVType.FuelCell
                               :                                     HEVType.Conventional;
            bool isConventional = (newHevType == HEVType.Conventional);
            bool isZev = newHevType.IsZev();

            var  eng = veh.Engine;
            var  trn = veh.Transmission;
            bool engRemoved = false;
            bool trnRemoved = false;

            if (newHevType.IsShevAndLater()) {
                engRemoved = veh.ClearEngine();
                if (eng != null) {
                    eng.RemoveVehicle(veh, analysisYear, applicationYear);
                    eng.Family.RemoveVehicle(veh, analysisYear, applicationYear);
                }

                trnRemoved = veh.ClearTransmission();
                if (trn != null) {
                    trn.RemoveVehicle(veh, analysisYear, applicationYear);
                    trn.Family.RemoveVehicle(veh, analysisYear, applicationYear);
                }
            }

            //------------------------------------------------------------------------------------
            // Update all vehicle revisions from the first year of application up to the analysis
            // year that triggered the current action. We need to do this because a "multiyear"
            // solution may affect multiple revisions (i.e., model years) of the vehicle.
            //------------------------------------------------------------------------------------
            for (int year = applicationYear; year <= analysisYear; year++) {
                var revision = veh.GetRevision(year);
                revision.TechKey = KeyInitializer.TestTechKey(revision, tech.Index);
                if (!isConventional) {
                    revision.HevType = newHevType;
                }
                if (isZev) {
                    revision.ZEVCredits = tech.GetZevCredits(revision, year);
                }
                if (engRemoved) {
                    revision.EngineCode = -1;
                }
                if (trnRemoved) {
                    revision.TransmissionCode = -1;
                }
            }

            // refresh the technology utilization, ensuring that proper techs
            // get enabled or disabled based on technology dependencies
            TechUseInitializer.RefreshTechUtilization(veh, scen, analysisYear, applicationYear, tech.Index, settings);
        }

        #endregion

        #region <<< IVehicleInfo extensions >>>

        /// <summary>
        /// Calculates the test weight using the engineering characteristics of the vehicle.
        /// </summary>
        /// <param name="twFunction">
        ///     The test weight function to use when calculating the vehicle's test weight. Specify 0 to determine the
        ///     appropriate function based on the vehicle's regulatory class.
        /// </param>
        /// <returns>
        /// The vehicle test weight.<br/>
        /// A value of 0 (zero) will be returned if an invalid test weight function is provided or if one could not be
        /// auto-determined based on the vehicle's regulatory class.
        /// </returns>
        /// <remarks>
        /// The following test weight functions are supported:<br/>
        /// <b>0</b>: Auto-detect (where function 1 will be used for domestic and imported passenger cars, and function
        ///           3 will be used for light trucks and HDPUVs)<br/>
        /// <b>1</b>: Default LVW-based function with a lower bound cap of 1000 lbs. and an upper bound cap of 5500
        ///           lbs.<br/>
        /// <b>2</b>: Default LVW-based function with a lower bound cap of 1000 lbs.<br/>
        /// <b>3</b>: Default ALVW-based function with a lower bound cap of 1000 lbs.<br/>
        /// </remarks>
        public static double GetTestWeight(this IVehicleInfo vehInfo, int twFunction = 0)
        {
            return GetTestWeight(
                vehInfo.RegClass,
                vehInfo.CurbWeight,
                vehInfo.GVWR,
                twFunction);
        }

        /// <summary>
        /// Calculates the test weight for a vehicle with the specified attributes.
        /// </summary>
        /// <param name="vehRC">The regulatory class of the vehicle for which to obtain the test weight.</param>
        /// <param name="vehCW">The curb weight (in lbs.) of the vehicle for which to obtain the test weight.</param>
        /// <param name="vehGVWR">The GVWR (in lbs.) of the vehicle for which to obtain the test weight.</param>
        /// <param name="twFunction">
        ///     <inheritdoc cref="GetTestWeight(IVehicleInfo, int)" path="/param[@name='twFunction']"/>
        /// </param>
        /// <inheritdoc cref="GetTestWeight(IVehicleInfo, int)" path="/returns"/>
        /// <inheritdoc cref="GetTestWeight(IVehicleInfo, int)" path="/remarks"/>
        public static double GetTestWeight(RC vehRC, double vehCW, double vehGVWR, int twFunction = 0)
        {
            if (twFunction == 0) {
                twFunction = (vehRC.IsDC() || vehRC.IsIC()) ? 1
                           : (vehRC.IsLT() || vehRC.IsHDPUV()) ? 3
                           : 0;
            }

            if (twFunction < 1 || twFunction > 3) {
                return 0;
            }

            double w = (twFunction == 3)
                     ? (vehCW + vehGVWR) / 2
                     : vehCW + 300;
            double r = (w > 5500) ? 500
                     : (w > 4000) ? 250
                     : 125;
            double testWeight = Math.Max(1000, Math.Ceiling(w / r - 0.5) * r);

            if (twFunction == 1 && testWeight > 5500) {
                testWeight = 5500;
            }

            return testWeight;
        }

        /// <summary>
        /// Calculates the work factor using the engineering characteristics of the vehicle.
        /// </summary>
        /// <returns>
        /// The vehicle work factor, if the vehicle's regulatory class assignment is <see cref="RC.HDPUV"/>;
        /// or 0 (zero), for all other regulatory classes.
        /// </returns>
        public static double GetWorkFactor(this IVehicleInfo vehInfo)
        {
            return GetWorkFactor(
                vehInfo.RegClass,
                vehInfo.CurbWeight,
                vehInfo.GVWR,
                vehInfo.GCWR,
                vehInfo.Drive,
                round: true);
        }

        /// <summary>
        /// Calculates the work factor for a vehicle with the specified attributes.
        /// </summary>
        /// <param name="vehCW">The curb weight (in lbs.) of the vehicle for which to obtain the work factor.</param>
        /// <param name="vehGVWR">The GVWR (in lbs.) of the vehicle for which to obtain the work factor.</param>
        /// <param name="vehGCWR">The GCWR (in lbs.) of the vehicle for which to obtain the work factor.</param>
        /// <param name="vehDrive">The drive type of the vehicle for which to obtain the work factor.</param>
        /// <param name="round">
        ///     <c>true</c>, to round the work factor value before returning;
        ///     <c>false</c>, to return the work factor unrounded.
        /// </param>
        /// <inheritdoc cref="GetWorkFactor(IVehicleInfo)" path="/returns"/>
        public static double GetWorkFactor(
            RC vehRC, double vehCW, double vehGVWR, double vehGCWR, DriveType vehDrive, bool round)
        {
            if (vehRC != RC.HDPUV) {
                // note: we may consider expanding WF calc to all vehicles in the future
                return 0;
            }

            // offset (in lbs) for all-wheel and four-wheel drive vehicles
            const double xwdOffset = 500;

            bool isXwd = (vehDrive == DriveType.AWD || vehDrive == DriveType.x4WD);
            double xwd = isXwd ? xwdOffset : 0;
            double wf = (vehGVWR - vehCW + xwd) * 0.75 + (vehGCWR - vehGVWR) * 0.25;

            return round ? Math.Round(wf, 0) : wf;
        }

        /// <summary>
        /// Calculates the harmonically averaged vehicle fuel cost value using the specified fuel economy on-road
        /// <paramref name="gap"/> and the given pseudo-cost of fuel.
        /// </summary>
        /// <param name="gap">The on-road fuel economy gap.</param>
        /// <param name="pseudoFuelCost">
        ///     The pseudo-cost of fuel for a vehicle based on the manufacturer-specific number of payback years
        ///     attributed to a given model year.
        /// </param>
        /// <returns>
        /// The average vehicle fuel cost based on the specified gap and pseudo-cost of fuel.
        /// </returns>
        public static double GetVehicleFuelCost(this IVehicleInfo vehInfo, FuelValue gap, FuelValue pseudoFuelCost)
        {
            FuelValue vehFE = vehInfo.FuelEconomy;
            FuelValue vehFS = vehInfo.FuelShare;
            FuelType primaryFuel = vehFS.PrimaryFuel;
            double primaryFuelCost = GetPrimaryFuelCost();

            if (!vehFS.IsMultiFuel) {
                return primaryFuelCost;
            }

            double elcFS = vehFS.Electricity;
            if (elcFS > 0) {
                return primaryFuelCost * (1 - elcFS)
                     + pseudoFuelCost.Electricity / (vehFE.Electricity * (1 - gap.Electricity)) * elcFS;
            }

            double e85FS = vehFS.Ethanol85;
            if (e85FS > 0) {
                return primaryFuelCost * (1 - e85FS)
                     + pseudoFuelCost.Ethanol85 / (vehFE.Ethanol85 * (1 - gap.Ethanol85)) * e85FS;
            }

            // FE/FS mismatch
            throw new ArgumentException("The vehicle's fuel economy and/or fuel share values are not valid");

            // local helper function
            double GetPrimaryFuelCost()
            {
                return pseudoFuelCost[primaryFuel] / (vehFE[primaryFuel] * (1 - gap[primaryFuel]));
            }
        }

        /// <summary>
        /// Calculates the glider weigh, at level 0 mass reduction, using the engineering characteristics of the
        /// vehicle.
        /// </summary>
        /// <param name="settings">The modeling settings used for analysis.</param>
        /// <returns>The vehicle glider weigh, at level 0 mass reduction.</returns>
        public static double GetGliderMR0(this IVehicleInfo vehInfo, ModelingSettings settings)
        {
            double gliderShare = settings.Technologies.GliderShare[vehInfo.TechnologyClass.GetArrayIndex()];
            return vehInfo.CurbWeightMR0 * gliderShare;
        }

        /// <summary>
        /// Returns the index of the technology corresponding to the highest level of battery technology utilization
        /// for a specific vehicle.
        /// </summary>
        public static int GetMaxBatteryTech(this IVehicleInfo vehInfo)
        {
            var techKey = vehInfo.TechKey;
            for (int i = 0; i < TI.TechnologyCount; i++) {
                if (techKey.Contains(i) && TI.IsBatteryCostTech(i)) {
                    return i;
                }
            }
            return -1;
        }

        #endregion
    }
}
