using System;
using Volpe.Cafe.ModelSettings;
using Volpe.Cafe.Generic;

namespace Volpe.Cafe
{
    /// <summary>
    /// Provides the pseudo-cost of fuel for a vehicle based on a specific number of payback years.
    /// </summary>
    /// <remarks>
    /// The pseudo fuel cost value incorporates the annual miles driven by a typical vehicle over a given range of
    /// payback years, along with the fuel price over the same range of calendar years. However, it excludes the fuel
    /// economy rating of specific vehicles. The actual fuel cost for a given vehicle model is then obtained by
    /// combining the pseudo-cost of fuel computed here with the fuel economy of that vehicle.
    /// </remarks>
    [Serializable]
    public sealed class PseudoFuelCost
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="PseudoFuelCost"/> class, calculating the pseudo-cost of fuel
        /// for a set of representative vehicles, based on a specific number of payback years.
        /// </summary>
        /// <param name="year">The model year for which to calculate the pseudo-cost of fuel.</param>
        /// <param name="parameters">The modeling parameters to use for calculating the pseudo fuel cost.</param>
        /// <param name="paybackPeriod">
        ///     The manufacturer-specific payback period to use for calculating the pseudo fuel cost.
        /// </param>
        public PseudoFuelCost(ModelYear year, Parameters parameters, VSValue<double> paybackPeriod)
        {
            this.ModelYear = year;
            this.PaybackPeriod = paybackPeriod;
            this.Values = new VSValue<FuelValue>();

            this.CalculatePseudoFuelCostByStyle(year, parameters, paybackPeriod, VehicleStyle.GenericCar);
            this.CalculatePseudoFuelCostByStyle(year, parameters, paybackPeriod, VehicleStyle.GenericVanSUV);
            this.CalculatePseudoFuelCostByStyle(year, parameters, paybackPeriod, VehicleStyle.GenericPickup);
            this.CalculatePseudoFuelCostByStyle(year, parameters, paybackPeriod, VehicleStyle.Generic2b3);
        }


        /// <summary>
        /// Gets the pseudo-cost of fuel for a vehicle for the specified <see cref="VehicleStyle"/>.
        /// </summary>
        public FuelValue this[VehicleStyle vehStyle]
        {
            get => this.Values[vehStyle];
            private set => this.Values[vehStyle] = value;
        }

        /// <summary>Gets the model year associated with this instance.</summary>
        public int ModelYear { get; }

        /// <summary>Gets the payback periods associated with this instance.</summary>
        public VSValue<double> PaybackPeriod { get; }

        /// <summary>Gets the pseudo-cost of fuel values for various vehicle styles.</summary>
        private VSValue<FuelValue> Values { get; }


        /// <summary>
        /// Returns the string representation of this <see cref="PseudoFuelCost"/> instance.
        /// </summary>
        /// <returns>The string representation of the <see cref="PseudoFuelCost"/> instance.</returns>
        public override string ToString()
        {
            return $"{{ModelYear={this.ModelYear}, Values={this.Values}}}";
        }

        private void CalculatePseudoFuelCostByStyle(
            int year, Parameters parameters, VSValue<double> paybackPeriod, VehicleStyle vehStyle)
        {
            double[] milesDriven = parameters.MilesDriven[vehStyle];

            // compute energy density ratios required to convert from $/kwh and $/scf to $/GGE-gallon
            FuelValue energyDensity = parameters.FuelProperties.EnergyDensity;
            double elcRatio = energyDensity.Gasoline / energyDensity.Electricity;
            double hgnRatio = energyDensity.Gasoline / energyDensity.Hydrogen;
            double cngRatio = energyDensity.Gasoline / energyDensity.CNG;

            // calculate pseudo-cost of fuel for whole years, also adding partial year (if any)
            // pseudo-cost is computed based on what a **typical vehicle** is expected to drive at
            // each specific age
            var    fuelCost = new FuelValue();
            double pbFull   = paybackPeriod[vehStyle];
            int    pbYears  = (int)Math.Floor(pbFull);  // number of whole yers
            double pbFract  = pbFull - pbYears;         // fraction of a year

            for (int age = 0; age <= pbYears; age++) {
                // early exit if no partial year
                if (age == pbYears && pbFract == 0) {
                    break;
                }

                foreach (FuelType fuel in FuelTypes.List) {
                    double fuelCostAge = milesDriven[age] * parameters.FuelPrices.GetFuelPrice(fuel, year);

                    fuelCost[fuel] = (fuel == FuelType.Electricity) ? fuelCostAge * elcRatio
                                   : (fuel == FuelType.Hydrogen   ) ? fuelCostAge * hgnRatio
                                   : (fuel == FuelType.CNG        ) ? fuelCostAge * cngRatio
                                   :                                  fuelCostAge;
                }

                // add in fuel cost to the running sum --
                // for partial years, account for fraction of fuel cost only
                this[vehStyle] += (age == pbYears) ? fuelCost * pbFract : fuelCost;
            }
        }

    }
}
