#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member

using System;

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.Experimental"/>
    [Serializable]
    public class ExperimentalDfsModel : DfsModelBase
    {
        /// <summary>
        /// Initializes a new instances of the <see cref="ExperimentalDfsModel"/> class, using the specified industry
        /// data, modeling settings, and log writer.
        /// </summary>
        /// <inheritdoc path="/param"/>
        /// <inheritdoc path="/exception"/>
        public ExperimentalDfsModel(IIndustry data, ModelingSettings settings, ILogger logWriter)
            : base(data, settings, logWriter)
        {
        }

        private static readonly string DebugLogFileName = "dfs_sn{0}.txt";
        private static readonly string[] DebugLogFileHeaders = {
            "Scenario", "Iteration", "Year",
            "FE_Diff", "Price", "RdpiPerCapita", "HpCwDiff", "HpDiff", "CwDiff", "Recession",
            "Share", "ExpShare", "CarShare", "BoundCarShare", "FinalCarShare"
        };

        public override AggregationMode PreferredAggregationMode => AggregationMode.ByRegulatoryClass;
        public override int NumberOfLaggedYears => 0;

        protected override FleetTypeValue CalcNewFleetSharesInternal(Scenario scen, int iteration, int year,
            AnnualValue<ForecastFleet> baseFleet, AnnualValue<ForecastFleet> scenFleet)
        {
            var fleet = scenFleet[year];
            var carData = fleet.CarForecastData;
            var truckData = fleet.TruckForecastData;

            var parameters = this.Settings.Parameters;
            var dfsCoefs = parameters.DfsModelValues.DfsCoefficients;

            double feDiff = carData.AverageRatedFE - truckData.AverageRatedFE;
            double price = parameters.FuelPrices.GetFuelPrice(FuelType.Gasoline, year);
            double rdpiPerCapita = parameters.EconomicValues.MacroParameters.GetRdpiPerCapita(year);
            double hpCwDiff = carData.VehiclePower / (carData.CurbWeight / 1000)
                            - truckData.VehiclePower / (truckData.CurbWeight / 1000);
            double hpDiff = carData.VehiclePower - truckData.VehiclePower;
            double cwDiff = (carData.CurbWeight - truckData.CurbWeight) / 1000;
            int recession = (year == 2009 || year == 2010) ? 1 : 0;
            int trendStartYear = (int)dfsCoefs[8];

            double share = dfsCoefs[0]
                         + dfsCoefs[1] * feDiff
                         + dfsCoefs[2] * price
                         + dfsCoefs[3] * rdpiPerCapita
                         + dfsCoefs[4] * hpCwDiff
                         + dfsCoefs[5] * hpDiff
                         + dfsCoefs[6] * cwDiff
                         + dfsCoefs[7] * recession
                         + dfsCoefs[9] * (year - trendStartYear + 1);

            double expShare = Math.Exp(share);
            double carShare = expShare / (1 + expShare);

            // if car share falls too much, we may need to apply an optional bounding function
            double boundCarShare = this.CalcBoundedCarShare(year);
            double finalCarShare = Math.Max(carShare, boundCarShare);
            double finalTruckShare = 1 - finalCarShare;

            this.WriteDebugLog(
                scen.Index, iteration, year,
                feDiff, price, rdpiPerCapita, hpCwDiff, hpDiff, cwDiff, recession,
                share, expShare, carShare, boundCarShare, finalCarShare);

            return new FleetTypeValue(finalCarShare, finalTruckShare, 1);
        }

        private double CalcBoundedCarShare(int year)
        {
            var boundCoefs = this.Settings.Parameters.DfsModelValues.BoundingCoefficients;
            int boundStartYear = (int)boundCoefs[0];
            double intercept = boundCoefs[1];
            double slope = boundCoefs[2];

            return (year < boundStartYear) ? 0 : slope * year + intercept;
        }

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

            debugLog?.WriteValues(values);
        }

    }
}
