#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)

using System;

using Volpe.Cafe.EffectsModels;
using Volpe.Cafe.Generic;
using Volpe.Cafe.Logging;
using Volpe.Cafe.MarketData.Design;
using Volpe.Cafe.ModelSettings;

namespace Volpe.Cafe.DfsSrModels.DfsModels
{
    /// <inheritdoc cref="DfsModel.PriceElasticity"/>
    [Serializable]
    public sealed class PriceElasticityDfsModel : DfsModelBase
    {
        /// <summary>
        /// Initializes a new instances of the <see cref="PriceElasticityDfsModel"/> class, using the specified
        /// industry data, modeling settings, log writer, and reference DFS model.
        /// </summary>
        /// <param name="referenceDfsModel">
        ///     The DFS model to use for obtaining the fleet shares attributed to the forecast fleet from the baseline
        ///     scenario. If not specified, this value will default to a <see cref="FixedFleetShares"/> DFS model.
        /// </param>
        /// <inheritdoc cref="DfsModelBase(IIndustry, ModelingSettings, ILogger)" path="/param"/>
        /// <inheritdoc cref="DfsModelBase(IIndustry, ModelingSettings, ILogger)" path="/exception"/>
        public PriceElasticityDfsModel(
            IIndustry data, ModelingSettings settings, ILogger logWriter, IDfsModel referenceDfsModel)
            : base(data, settings, logWriter)
        {
            this._referenceDfsModel = referenceDfsModel ?? new FixedFleetShares(data, settings, logWriter);
        }

        private static readonly string DebugLogFileName = "dfs_adj_base_share_sn{0}.txt";
        private static readonly string[] DebugLogFileHeaders = {
            "Scenario", "Iteration", "Year",
            "CarFE_Base", "CarRegCost_Base", "CarTaxCredit_Base", "CarBatteryTaxCredit_Base",
            "TruckFE_Base", "TruckRegCost_Base", "TruckTaxCredit_Base", "TruckBatteryTaxCredit_Base",
            "CarFE_Scen", "CarRegCost_Scen", "CarTaxCredit_Scen", "CarBatteryTaxCredit_Scen",
            "TruckFE_Scen", "TruckRegCost_Scen", "TruckTaxCredit_Scen", "TruckBatteryTaxCredit_Scen",
            "CarFuelSavings", "CarDeltaRegCost", "CarDeltaTaxCredit",
            "TruckFuelSavings", "TruckDeltaRegCost", "TruckDeltaTaxCredit",
            "RelativeVehValue", "DeltaUtility", "CarShareBase", "CarShareScen"
        };

        private readonly IDfsModel _referenceDfsModel;

        public override AggregationMode PreferredAggregationMode => this._referenceDfsModel.PreferredAggregationMode;
        public override int NumberOfLaggedYears => this._referenceDfsModel.NumberOfLaggedYears;

        protected override FleetTypeValue CalcNewFleetSharesInternal(Scenario scen, int iteration, int year,
            AnnualValue<ForecastFleet> baseFleet, AnnualValue<ForecastFleet> scenFleet)
        {
            return scen.IsBaseline
                ? this._referenceDfsModel.CalcNewFleetShares(scen, iteration, year, baseFleet, scenFleet)
                : this.CalcAdjustedShare(scen, iteration, year, baseFleet[year], scenFleet[year]);
        }

        private FleetTypeValue CalcAdjustedShare(
            Scenario scen, int iteration, int year, ForecastFleet baseFleet, ForecastFleet scenFleet)
        {
            var baseCarData = baseFleet.CarForecastData;
            var scenCarData = scenFleet.CarForecastData;
            var baseTrkData = baseFleet.TruckForecastData;
            var scenTrkData = scenFleet.TruckForecastData;

            CalcNetBenefits(baseCarData, scenCarData, out double carDeltaRegCost, out double carDeltaTaxCredit,
                            out double _, out double carFuelSavings, out double carNetBenefits);
            CalcNetBenefits(baseTrkData, scenTrkData, out double truckDeltaRegCost, out double truckDeltaTaxCredit,
                            out double _, out double truckFuelSavings, out double truckNetBenefits);

            double priceCoefficient = this.Settings.RuntimeSettings.PriceElasticityDfsModelCoefficient;
            double relativeVehValue = truckNetBenefits - carNetBenefits;
            double deltaUtility = Math.Log(1 / this.BaseShares[year].Car - 1);
            double newCarShare = 1 / (1 + Math.Exp(deltaUtility + priceCoefficient * relativeVehValue));

            this.WriteDebugLog(
                scen.Index, iteration, year,
                // baseline data
                baseCarData.AverageRatedFE, baseCarData.RegCost, baseCarData.TaxCredit, baseCarData.BatteryTaxCredit,
                baseTrkData.AverageRatedFE, baseTrkData.RegCost, baseTrkData.TaxCredit, baseTrkData.BatteryTaxCredit,
                // scenario data
                scenCarData.AverageRatedFE, scenCarData.RegCost, scenCarData.TaxCredit, scenCarData.BatteryTaxCredit,
                scenTrkData.AverageRatedFE, scenTrkData.RegCost, scenTrkData.TaxCredit, scenTrkData.BatteryTaxCredit,
                // deltas vs baseline
                carFuelSavings, carDeltaRegCost, carDeltaTaxCredit,
                truckFuelSavings, truckDeltaRegCost, truckDeltaTaxCredit,
                // semi-results and results
                relativeVehValue, deltaUtility, this.BaseShares[year].Car, newCarShare);

            return new FleetTypeValue(newCarShare, 1 - newCarShare, 1);

            // local helper function
            void CalcNetBenefits(ForecastFleetData baseData, ForecastFleetData scenData,
                out double deltaRegCost, out double deltaTaxCredit, out double deltaBatteryTaxCredit,
                out double fuelSavings, out double netBenefits)
            {
                deltaRegCost = scenData.RegCost - baseData.RegCost;
                deltaTaxCredit = scenData.TaxCredit - baseData.TaxCredit;
                deltaBatteryTaxCredit = scenData.BatteryTaxCredit - baseData.BatteryTaxCredit;
                fuelSavings = Effects.CalcAssumedFuelSavings(
                    year, baseData.RatedFE, baseData.FuelShare, scenData.RatedFE, scenData.FuelShare, this.Settings);
                netBenefits = deltaRegCost - deltaTaxCredit - deltaBatteryTaxCredit - fuelSavings;
            }
        }

        private void WriteDebugLog(params object[] values)
        {
            int scenIndex = (int)values[0];
            var debugLog = this.LogWriter.GetDebugLog(
                string.Format(DebugLogFileName, scenIndex),
                DebugLogFileHeaders);

            debugLog?.WriteValues(values);
        }

    }
}
