using System;

namespace Volpe.Cafe.EffectsData
{
    /// <summary>
    /// Provides owner and societal costs arising from the various modeling effects.
    /// </summary>
    [Serializable]
    public sealed class SocietalCosts : IAppendable
    {
        /// <summary>Gets or sets the new vehicle sales value.</summary>
        public double NewSalesSurplus { get; set; }

        //----- tech costs & value losses -----
        /// <summary>Gets or sets the total technology costs.</summary>
        public double TechCosts { get; set; }
        /// <summary>Gets or sets the additional maintenance and repair costs.</summary>
        public double MaintRepairCosts { get; set; }
        /// <summary>Gets or sets the implicit opportunity cost.</summary>
        public double OpportunityCost { get; set; }
        /// <summary>Gets or sets the commercial operator implicit opportunity cost.</summary>
        public double CommercialOppCost { get; set; }

        //----- energy costs -----
        /// <summary>
        /// Gets or sets the retail fuel expenditures, before any new vehicle technology application.
        /// </summary>
        public double InitRetailFuelCosts { get; set; }

        /// <summary>Gets or sets the fuel tax revenues.</summary>
        public double FuelTaxCosts { get; set; }
        /// <summary>Gets or sets the retail fuel expenditures.</summary>
        public double RetailFuelCosts { get; set; }
        /// <summary>Gets or sets the vehicle travel value.</summary>
        public double DriveSurplus { get; set; }
        /// <summary>Gets or sets the refueling time value.</summary>
        public double RefuelSurplus { get; set; }

        //--- costs from market externalities ---
        /// <summary>Gets or sets the petroleum market externalities from oil imports.</summary>
        public double EconomicCosts { get; set; }
        /// <summary>Gets or sets the congestion costs from additional vehicle use due to rebound effect.</summary>
        public double CongestionCosts { get; set; }
        /// <summary>Gets or sets the noise costs from additional vehicle use due to rebound effect.</summary>
        public double NoiseCosts { get; set; }

        //----- safety costs -----
        /// <summary>
        /// Gets or sets the fatality costs due to the reduction in vehicle weight, additional VMT, and changes in
        /// fleet age distribution.
        /// </summary>
        public double FatalityCostsOverall { get; set; }
        /// <summary>
        /// Gets or sets the fatality costs due to increases in rebound VMT.
        /// </summary>
        public double FatalityCostsRebound { get; set; }
        /// <summary>
        /// Gets or sets the fatality costs due to reduction in vehicle curb weight.
        /// </summary>
        public double FatalityCostsDeltaCW { get; set; }

        /// <summary>
        /// Gets or sets the non-fatal injury costs due to the reduction in vehicle weight, additional VMT, and changes
        /// in fleet age distribution.
        /// </summary>
        public double NonFatalInjuryCostsOverall { get; set; }
        /// <summary>
        /// Gets or sets the non-fatal injury costs due to increases in rebound VMT.
        /// </summary>
        public double NonFatalInjuryCostsRebound { get; set; }
        /// <summary>
        /// Gets or sets the non-fatal injury costs due to reduction in vehicle curb weight.
        /// </summary>
        public double NonFatalInjuryCostsDeltaCW { get; set; }

        /// <summary>
        /// Gets or sets the property damage crash costs due to the reduction in vehicle weight, additional VMT, and
        /// changes in fleet age distribution.
        /// </summary>
        public double PropertyDamageCostsOverall { get; set; }
        /// <summary>
        /// Gets or sets the property damage crash costs due to increases in rebound VMT.
        /// </summary>
        public double PropertyDamageCostsRebound { get; set; }
        /// <summary>
        /// Gets or sets the property damage crash costs due to reduction in vehicle curb weight.
        /// </summary>
        public double PropertyDamageCostsDeltaCW { get; set; }

        /// <summary>
        /// Gets or sets the value offsetting the risk of additional fatalities internalized by the driver, attributed
        /// to the additional miles driven due to rebound.
        /// </summary>
        public double FatalityRiskValue { get; set; }
        /// <summary>
        /// Gets or sets the value offsetting the risk of additional non-fatal injuries and property damage crashes
        /// internalized by the driver, attributed to the additional miles driven due to rebound.
        /// </summary>
        public double NonFatalRiskValue { get; set; }

        //----- emissions costs -----
        // criteria pollutants
        /// <summary>
        /// Gets or sets the owner and societal costs arising from damage caused by Carbon Monoxide emissions.
        /// </summary>
        public double EmissionsCostsCO { get; set; }
        /// <summary>
        /// Gets or sets the owner and societal costs arising from damage caused by Volatile Organic Compounds
        /// emissions.
        /// </summary>
        public double EmissionsCostsVOC { get; set; }

        // criteria pollutants pre-discounted at 3% or 7%
        /// <summary>
        /// Gets the owner and societal costs arising from damage caused by Nitrogen Oxides emissions.
        /// </summary>
        public ValueByDiscRate EmissionsCostsNOx { get; } = new ValueByDiscRate();
        /// <summary>
        /// Gets the owner and societal costs arising from damage caused by Sulfur Dioxide emissions.
        /// </summary>
        public ValueByDiscRate EmissionsCostsSO2 { get; } = new ValueByDiscRate();
        /// <summary>
        /// Gets the owner and societal costs arising from damage caused by Particulate Matter (diameter of ~2.5
        /// micrometers) emissions.
        /// </summary>
        public ValueByDiscRate EmissionsCostsPM { get; } = new ValueByDiscRate();

        // ghg pollutants at various estimates (low, average, high, very high)
        /// <summary>
        /// Gets the owner and societal costs arising from damage caused by Carbon Dioxide emissions.
        /// </summary>
        public ValueByEstimate EmissionsCostsCO2 { get; } = new ValueByEstimate();
        /// <summary>
        /// Gets the owner and societal costs arising from damage caused by Methane emissions.
        /// </summary>
        public ValueByEstimate EmissionsCostsCH4 { get; } = new ValueByEstimate();
        /// <summary>
        /// Gets the owner and societal costs arising from damage caused by Nitrous Oxide emissions.
        /// </summary>
        public ValueByEstimate EmissionsCostsN2O { get; } = new ValueByEstimate();

        /// <summary>
        /// Returns the owner and societal costs arising from damage caused by emissions from the specified pollutant.
        /// </summary>
        /// <param name="pollutant">The pollutant for which to obtain the emission costs.</param>
        /// <param name="discRate">
        ///     The approximate discount rate to use for retrieving pre-discounted NOx, SO2, or PM emission costs. If
        ///     the discount rate is >=5%, a cost value pre-discounted at 7% will be used; otherwise, a cost value
        ///     pre-discounted at 3% will be returned. Not applicable for all other pollutants.
        /// </param>
        /// <param name="ghgEstimates">
        ///     The low, average, high, or very high GHG estimates to use. Not applicable for non-GHG pollutants.
        /// </param>
        public double GetEmissionsCosts(Pollutant pollutant, double discRate, Estimates ghgEstimates)
        {
            switch (pollutant) {
                case Pollutant.CO : return this.EmissionsCostsCO;
                case Pollutant.VOC: return this.EmissionsCostsVOC;

                case Pollutant.NOx: return this.EmissionsCostsNOx[discRate];
                case Pollutant.SO2: return this.EmissionsCostsSO2[discRate];
                case Pollutant.PM : return this.EmissionsCostsPM [discRate];

                case Pollutant.CO2: return this.EmissionsCostsCO2[ghgEstimates];
                case Pollutant.CH4: return this.EmissionsCostsCH4[ghgEstimates];
                case Pollutant.N2O: return this.EmissionsCostsN2O[ghgEstimates];

                default:
                    throw new ArgumentOutOfRangeException(nameof(pollutant));
            }
        }

        /// <summary>
        /// Calculates and returns the total social costs with reference to the specified baseline societal costs
        /// object.
        /// </summary>
        /// <param name="baseCosts">
        ///     The baseline societal costs object, that is the reference for the total social costs.
        /// </param>
        public double GetTotalSocialCosts(SocietalCosts baseCosts)
        {
            return
                // calc "cost changes" as alternative - baseline
                (SumCostChangesComponents(this) - SumCostChangesComponents(baseCosts)) +
                // calc "savings" as baseline - alternative
                (SumSavingsComponents(baseCosts) - SumSavingsComponents(this));

            // local helper functions
            double SumCostChangesComponents(SocietalCosts value)
            {
                return value.NewSalesSurplus +
                       value.TechCosts +
                       value.MaintRepairCosts +
                       value.OpportunityCost +
                       value.CommercialOppCost +
                       value.CongestionCosts +
                       value.NoiseCosts +
                       value.FatalityCostsOverall +
                       value.NonFatalInjuryCostsOverall +
                       value.PropertyDamageCostsOverall;
            }

            double SumSavingsComponents(SocietalCosts value)
            {
                return value.FuelTaxCosts;
            }
        }

        /// <summary>
        /// Calculates and returns the total social benefits, for the specified discount rate and GHG estimates, with
        /// reference to the specified baseline societal costs object.
        /// </summary>
        /// <param name="discRate">
        ///     The approximate discount rate to use for retrieving pre-discounted NOx, SO2, or PM emission costs. If
        ///     the discount rate is >=5%, a cost value pre-discounted at 7% will be used; otherwise, a cost value
        ///     pre-discounted at 3% will be returned. Not applicable for all other pollutants.
        /// </param>
        /// <param name="ghgEstimates">
        ///     The low, average, high, or very high GHG estimates to use. Not applicable for non-GHG pollutants.
        /// </param>
        /// <param name="baseCosts">
        ///     The baseline societal costs object, that is the reference for the total social benefits.
        /// </param>
        public double GetTotalSocialBenefits(double discRate, Estimates ghgEstimates, SocietalCosts baseCosts)
        {
            return
                // calc "cost changes" as alternative - baseline
                (SumCostChangesComponents(this) - SumCostChangesComponents(baseCosts)) +
                // calc "savings" as baseline - alternative
                (SumSavingsComponents(baseCosts) - SumSavingsComponents(this));

            // local helper functions
            double SumCostChangesComponents(SocietalCosts value)
            {
                return value.DriveSurplus +
                       value.FatalityRiskValue +
                       value.NonFatalRiskValue;
            }

            double SumSavingsComponents(SocietalCosts value)
            {
                return value.RetailFuelCosts +
                       value.RefuelSurplus +
                       value.EconomicCosts +
                       value.EmissionsCostsCO +
                       value.EmissionsCostsVOC +
                       value.EmissionsCostsNOx[discRate] +
                       value.EmissionsCostsSO2[discRate] +
                       value.EmissionsCostsPM [discRate] +
                       value.EmissionsCostsCO2[ghgEstimates] +
                       value.EmissionsCostsCH4[ghgEstimates] +
                       value.EmissionsCostsN2O[ghgEstimates];
            }
        }

        /// <summary>
        /// Resets all owner and societal costs to zero.
        /// </summary>
        public void Clear()
        {
            // general
            this.NewSalesSurplus = 0;

            // tech costs & value losses
            this.TechCosts = 0;
            this.MaintRepairCosts = 0;
            this.OpportunityCost = 0;
            this.CommercialOppCost = 0;

            // energy costs
            this.InitRetailFuelCosts = 0;
            this.FuelTaxCosts = 0;
            this.RetailFuelCosts = 0;
            this.DriveSurplus = 0;
            this.RefuelSurplus = 0;

            // costs from market externalities
            this.EconomicCosts = 0;
            this.CongestionCosts = 0;
            this.NoiseCosts = 0;

            // safety costs
            this.FatalityCostsOverall = 0;
            this.FatalityCostsRebound = 0;
            this.FatalityCostsDeltaCW = 0;

            this.NonFatalInjuryCostsOverall = 0;
            this.NonFatalInjuryCostsRebound = 0;
            this.NonFatalInjuryCostsDeltaCW = 0;

            this.PropertyDamageCostsOverall = 0;
            this.PropertyDamageCostsRebound = 0;
            this.PropertyDamageCostsDeltaCW = 0;

            // "derived" safety costs
            this.FatalityRiskValue = 0;
            this.NonFatalRiskValue = 0;

            // emissions costs
            // criteria pollutants
            this.EmissionsCostsCO = 0;
            this.EmissionsCostsVOC = 0;

            // criteria pollutants pre-discounted at 3% or 7%
            this.EmissionsCostsNOx.Clear();
            this.EmissionsCostsSO2.Clear();
            this.EmissionsCostsPM.Clear();

            // ghg pollutants at various estimates (low, average, high, very high)
            this.EmissionsCostsCO2.Clear();
            this.EmissionsCostsCH4.Clear();
            this.EmissionsCostsN2O.Clear();
        }

        /// <summary>
        /// Appends all values from the specified <see cref="SocietalCosts"/> object to this instance.
        /// </summary>
        public void Append(SocietalCosts value)
        {
            // general
            this.NewSalesSurplus += value.NewSalesSurplus;

            // tech costs & value losses
            this.TechCosts += value.TechCosts;
            this.MaintRepairCosts += value.MaintRepairCosts;
            this.OpportunityCost += value.OpportunityCost;
            this.CommercialOppCost += value.CommercialOppCost;

            // energy costs
            this.InitRetailFuelCosts += value.InitRetailFuelCosts;
            this.FuelTaxCosts += value.FuelTaxCosts;
            this.RetailFuelCosts += value.RetailFuelCosts;
            this.DriveSurplus += value.DriveSurplus;
            this.RefuelSurplus += value.RefuelSurplus;

            // costs from market externalities
            this.EconomicCosts += value.EconomicCosts;
            this.CongestionCosts += value.CongestionCosts;
            this.NoiseCosts += value.NoiseCosts;

            // safety costs
            this.FatalityCostsOverall += value.FatalityCostsOverall;
            this.FatalityCostsRebound += value.FatalityCostsRebound;
            this.FatalityCostsDeltaCW += value.FatalityCostsDeltaCW;

            this.NonFatalInjuryCostsOverall += value.NonFatalInjuryCostsOverall;
            this.NonFatalInjuryCostsRebound += value.NonFatalInjuryCostsRebound;
            this.NonFatalInjuryCostsDeltaCW += value.NonFatalInjuryCostsDeltaCW;

            this.PropertyDamageCostsOverall += value.PropertyDamageCostsOverall;
            this.PropertyDamageCostsRebound += value.PropertyDamageCostsRebound;
            this.PropertyDamageCostsDeltaCW += value.PropertyDamageCostsDeltaCW;

            // "derived" safety costs
            this.FatalityRiskValue += value.FatalityRiskValue;
            this.NonFatalRiskValue += value.NonFatalRiskValue;

            // emissions costs
            // criteria pollutants
            this.EmissionsCostsCO += value.EmissionsCostsCO;
            this.EmissionsCostsVOC += value.EmissionsCostsVOC;

            // criteria pollutants pre-discounted at 3% or 7%
            this.EmissionsCostsNOx.Append(value.EmissionsCostsNOx);
            this.EmissionsCostsSO2.Append(value.EmissionsCostsSO2);
            this.EmissionsCostsPM.Append(value.EmissionsCostsPM);

            // ghg pollutants at various estimates (low, average, high, very high)
            this.EmissionsCostsCO2.Append(value.EmissionsCostsCO2);
            this.EmissionsCostsCH4.Append(value.EmissionsCostsCH4);
            this.EmissionsCostsN2O.Append(value.EmissionsCostsN2O);
        }

        //----- explicit interface implementation -----
        void IAppendable.Append(IAppendable value)
        {
            this.Append((SocietalCosts)value);
        }

    }
}
