using System;
using System.Linq;

using Volpe.Cafe.MarketData.Design;
using Volpe.Cafe.ModelSettings;

namespace Volpe.Cafe.EffectsModels
{
    /// <summary>
    /// Provides static methods to assist the modeling system in performing common modeling effects calculations.
    /// </summary>
    public static class Effects
    {
        /// <summary>
        /// Calculates the cost-per-mile (CPM) in the specified calendar year, based on the specified on-road fuel
        /// economy and fuel share values.
        /// </summary>
        /// <param name="calendarYear">The calendar year during which to calculate the CPM.</param>
        /// <param name="onRoadFE">The on-road fuel economy to use for calculating the CPM.</param>
        /// <param name="fuelShare">The fuel share to use for calculating the CPM.</param>
        /// <param name="settings">The modeling settings used by the compliance model.</param>
        /// <returns>
        /// The calculated CPM for the specified calendar year, on-road fuel economy, and fuel share.
        /// </returns>
        public static double CalcCPM(
            int calendarYear, FuelValue onRoadFE, FuelValue fuelShare, ModelingSettings settings)
        {
            var fuelPrices = settings.Parameters.FuelPrices;
            var fuelProperties = settings.Parameters.FuelProperties;

            var validFuelTypes = FuelTypes.List.Where(fuelType => fuelShare[fuelType] > 0);
            double cpm =
                validFuelTypes.Sum(fuelType =>
                fuelPrices.GetFuelPrice(fuelType, calendarYear, fuelProperties) /
                onRoadFE[fuelType] * fuelShare[fuelType]);

            return cpm;
        }

        /// <summary>
        /// Calculates the estimated fuel savings in the specified calendar year, resulting from the specified
        /// reference and alternative on-road fuel economy and fuel share values, and based on the assumed number of
        /// miles during which an added investment in fuel improving technology is expected to pay back.
        /// </summary>
        /// <param name="calendarYear">The calendar year during which to calculate the estimated fuel savings.</param>
        /// <param name="refOnRoadFE">The on-road fuel economy of the reference or baseline case.</param>
        /// <param name="refFuelShare">The fuel share of the reference or baseline case.</param>
        /// <param name="altOnRoadFE">The on-road fuel economy of the alternative case.</param>
        /// <param name="altFuelShare">The fuel share of the alternative case.</param>
        /// <param name="settings">The modeling settings used by the compliance model.</param>
        /// <returns>The estimated fuel savings, based on the assumed payback miles.</returns>
        public static double CalcAssumedFuelSavings(
            int calendarYear, FuelValue refOnRoadFE, FuelValue refFuelShare, FuelValue altOnRoadFE,
            FuelValue altFuelShare, ModelingSettings settings)
        {
            double refCPM = CalcCPM(calendarYear, refOnRoadFE, refFuelShare, settings);
            double altCPM = CalcCPM(calendarYear, altOnRoadFE, altFuelShare, settings);
            return CalcAssumedFuelSavings(refCPM, altCPM, settings);
        }

        /// <summary>
        /// Calculates the estimated fuel savings resulting from the specified reference and alternative cost-per-mile
        /// (CPM) values, and based on the assumed number of miles during which an added investment in fuel improving
        /// technology is expected to pay back.
        /// </summary>
        /// <param name="refCPM">The cost-per-mile of the reference or baseline case.</param>
        /// <param name="altCPM">The cost-per-mile of the alternative case.</param>
        /// <param name="settings">The modeling settings used by the compliance model.</param>
        /// <returns>The estimated fuel savings, based on the assumed payback miles.</returns>
        public static double CalcAssumedFuelSavings(double refCPM, double altCPM, ModelingSettings settings)
        {
            return (refCPM - altCPM) * settings.RuntimeSettings.AssumedPaybackMiles;
        }

        /// <summary>
        /// Calculates the undiscounted fuel savings resulting from the specified reference and alternative
        /// cost-per-mile (CPM) values, based on the vehicle miles traveled over the specified period, and for the
        /// given style of vehicles.
        /// <br/>
        /// Note: The reference and alternative CPM values provided to this method will be used over the entire period.
        /// Therefore, the resulting fuel savings will reflect the fuel prices from the calendar year for which the CPM
        /// values were calculated.
        /// </summary>
        /// <param name="refCPM">The cost-per-mile of the reference or baseline case.</param>
        /// <param name="altCPM">The cost-per-mile of the alternative case.</param>
        /// <param name="period">
        ///     The period, specified in years, over which to accumulate the vehicle miles traveled.
        /// </param>
        /// <param name="vehStyle">The style of the vehicles for which to compute the fuel savings.</param>
        /// <param name="settings">The modeling settings used by the compliance model.</param>
        /// <returns>
        /// The undiscounted fuel savings, based on the vehicle miles traveled over the specified period.
        /// </returns>
        public static double CalcFuelSavings(
            double refCPM, double altCPM, double period, VehicleStyle vehStyle, ModelingSettings settings)
        {
            double[] milesDriven = settings.Parameters.MilesDriven[vehStyle];
            int periodYears = (int)Math.Ceiling(period);
            double milesSum = 0;

            for (int age = 0; age < periodYears; age++) {
                double partialYear = (period - age);
                double milesDrivenAge = milesDriven[age] * (partialYear < 1 ? partialYear : 1);
                milesSum += milesDrivenAge;
            }

            return milesSum * (refCPM - altCPM);
        }

        /// <summary>
        /// Calculates the discounted fuel savings resulting from the specified reference and alternative cost-per-mile
        /// (CPM) values, based on the vehicle miles traveled over the specified period, and for the given style of
        /// vehicles.
        /// <br/>
        /// Note: The reference and alternative CPM values provided to this method will be used over the entire period.
        /// Therefore, the resulting fuel savings will reflect the fuel prices from the calendar year for which the CPM
        /// values were calculated.
        /// </summary>
        /// <param name="year">The model year being evaluated.</param>
        /// <param name="refCPM">The cost-per-mile of the reference or baseline case.</param>
        /// <param name="altCPM">The cost-per-mile of the alternative case.</param>
        /// <param name="period">
        ///     The period, specified in years, over which to accumulate the vehicle miles traveled.
        /// </param>
        /// <param name="discoutRate">
        ///     The discount rate at which to discount the fuel savings in future calendar years.
        /// </param>
        /// <param name="discBaseYear">
        ///     The calendar year to use for "present year" discounting. If this value is used, social discounting is
        ///     assumed, with all costs and benefits being discounted to the year specified by this value. If this
        ///     value is 0, private (or consumer) discounting is implied, with all costs and benefits being discounted
        ///     to the model year being evaluated.
        /// </param>
        /// <param name="vehStyle">The style of the vehicles for which to compute the fuel savings.</param>
        /// <param name="settings">The modeling settings used by the compliance model.</param>
        /// <returns>
        /// The discounted fuel savings, based on the vehicle miles traveled over the specified period.
        /// </returns>
        public static double CalcFuelSavings(
            int year, double refCPM, double altCPM, double period, double discoutRate, int discBaseYear,
            VehicleStyle vehStyle, ModelingSettings settings)
        {
            double[] milesDriven = settings.Parameters.MilesDriven[vehStyle];
            int periodYears = (int)Math.Ceiling(period);
            double milesSum = 0;

            for (int age = 0; age < periodYears; age++) {
                double partialYear = (period - age);
                double milesDrivenAge = milesDriven[age] * (partialYear < 1 ? partialYear : 1);
                double dr = GetDiscRateMultiplier(year, age, discBaseYear, discoutRate);

                milesSum += (milesDrivenAge * dr);
            }

            return milesSum * (refCPM - altCPM);
        }

        /// <summary>
        /// Calculates the discounting multiplier to apply to future costs and benefits, for the specified model year
        /// and vehicle age, based on the given discount rate, and discounting to the specified base year.
        /// </summary>
        /// <param name="year">
        ///     The model year being evaluated. The sum of model year and vehicle age inform the calendar year for
        ///     which to calculate the discounting multiplier.
        /// </param>
        /// <param name="age">
        ///     The vehicle age being evaluated. The sum of model year and vehicle age inform the calendar year for
        ///     which to calculate the discounting multiplier.
        /// </param>
        /// <param name="discBaseYear">
        ///     The calendar year to use for "present year" discounting. If this value is used, social discounting is
        ///     assumed, with all costs and benefits being discounted to the year specified by this value. If this
        ///     value is 0, private (or consumer) discounting is implied, with all costs and benefits being discounted
        ///     to the model year being evaluated.
        /// </param>
        /// <param name="discoutRate">The discount rate based on which to calculate the discounting multiplier.</param>
        /// <returns>The discounting multiplier to apply to future costs or benefits.</returns>
        public static double GetDiscRateMultiplier(int year, int age, int discBaseYear, double discoutRate)
        {
            double drExp = (discBaseYear <= 0) ? age : Math.Max(0, year + age - discBaseYear);
            return Math.Pow(1 + discoutRate, -drExp);
        }

        /// <summary>
        /// Returns the "rated" fuel economy value for the specified vehicle, in the specified year, from the
        /// effects modeling perspective.
        /// </summary>
        /// <param name="scen">The compliance scenario being evaluated.</param>
        /// <param name="year">The model year being evaluated.</param>
        /// <param name="vehInfo">The vehicle for which to compute the "rated" fuel economy value.</param>
        /// <remarks>
        /// The fuel economy value returned by this method is not the same as the fuel economy rating used for
        /// compliance purposes. This value excludes: all adjustments for improvements in air conditioning, off-cycle
        /// credits, and petroleum equivalence factor. Additionally, based on the scenario settings, this value may
        /// remove the fuel economy on electricity component for PHEVs.
        /// </remarks>
        /// <returns>
        /// The "rated" fuel economy value for the specified vehicle, from the effects modeling perspective.
        /// </returns>
        public static FuelValue CalcRatedVehicleFE(Scenario scen, int year, IVehicleInfo vehInfo)
        {
            var scenInfo = scen[vehInfo.RegClass, year];

            if (vehInfo.HevType.IsPhevOrErev() && scenInfo.NonElectricPhevsInEffects) {
                FuelValue fuelEconomy = FuelValue.Zero;
                fuelEconomy[vehInfo.FuelShare.PrimaryFuel] = vehInfo.FuelEconomy.PrimaryValue;
                return fuelEconomy;
            }
            else {
                return vehInfo.FuelEconomy;
            }
        }

        /// <summary>
        /// Converts the specified rated fuel economy value to a corresponding on-road fuel economy, by applying an
        /// on-road gap based on the specified vehicle style.
        /// </summary>
        /// <param name="ratedFE">The rated fuel economy value to convert.</param>
        /// <param name="vehStyle">The vehicle style to use for obtaining the on-road gap value.</param>
        /// <param name="settings">The modeling settings used by the compliance model.</param>
        /// <returns>The on-road fuel economy value corresponding to the specified rated fuel economy.</returns>
        public static FuelValue ConvertToOnRoadFE(FuelValue ratedFE, VehicleStyle vehStyle, ModelingSettings settings)
        {
            return ratedFE * (FuelValue.One - settings.Parameters.EconomicValues.Parameters[vehStyle].OnRoadGap);
        }

        /// <summary>
        /// Converts the specified on-road fuel economy value to a corresponding rated fuel economy, by applying an
        /// on-road gap based on the specified vehicle style.
        /// </summary>
        /// <param name="onRoadFE">The on-road fuel economy value to convert.</param>
        /// <param name="vehStyle">The vehicle style to use for obtaining the on-road gap value.</param>
        /// <param name="settings">The modeling settings used by the compliance model.</param>
        /// <returns>The rated fuel economy value corresponding to the specified on-road fuel economy.</returns>
        public static FuelValue ConvertToRatedFE(FuelValue onRoadFE, VehicleStyle vehStyle, ModelingSettings settings)
        {
            return onRoadFE / (FuelValue.One - settings.Parameters.EconomicValues.Parameters[vehStyle].OnRoadGap);
        }

        /// <summary>
        /// Returns the modified fuel share of a vehicle to be used for effects calculations. Applicable to PHEVs when
        /// the <see cref="ScenarioInfo.NonElectricPhevsInEffects"/> setting is <c>true</c>; otherwise, an original
        /// vehicle fuel share will be returned.
        /// </summary>
        /// <param name="scen">The compliance scenario being evaluated.</param>
        /// <param name="year">The model year being evaluated.</param>
        /// <param name="vehInfo">The vehicle for which to obtain a modified fuel share.</param>
        /// <returns>
        /// The vehicle's fuel share, optionally modified to remove electricity component for PHEVs if required.
        /// </returns>
        public static FuelValue GetModifiedVehicleFS(Scenario scen, int year, IVehicleInfo vehInfo)
        {
            var scenInfo = scen[vehInfo.RegClass, year];

            if (vehInfo.HevType.IsPhevOrErev() && scenInfo.NonElectricPhevsInEffects) {
                var fuelShare = new FuelValue();
                fuelShare[vehInfo.FuelShare.PrimaryFuel] = 1;
                return fuelShare;
            }
            else {
                return vehInfo.FuelShare;
            }
        }

    }
}
