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

using System;
using System.Linq;

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

namespace Volpe.Cafe.DfsSrModels.SalesEstimators
{
    /// <inheritdoc cref="SalesEstimator.NominalForecast"/>
    [Serializable]
    public sealed class NominalForecastEstimator : SalesEstimatorBase
    {
        /// <summary>
        /// Initializes a new instances of the <see cref="NominalForecastEstimator"/> class, using the specified
        /// industry data, modeling settings, and log writer.
        /// </summary>
        /// <inheritdoc path="/param"/>
        /// <inheritdoc path="/exception"/>
        public NominalForecastEstimator(IIndustry data, ModelingSettings settings, ILogger logWriter)
            : base(data, settings, logWriter)
        {
            double baseSalesLd = this.GetBaselineLightDutySales();
            double baseSalesHdpuv = this.GetBaselineHdpuvSales();
            var salesModelValues = this.Settings.Parameters.SalesModelValues;

            this._annualForecastLd = this.InitNominalForecastDynamic(
                baseSalesLd, salesModelValues.NFCoefficientsLD, DfsSrHelpers.VehStylesLightDuty);
            this._annualForecastHdpuv = this.InitNominalForecastDynamic(
                baseSalesHdpuv, salesModelValues.NFCoefficientsHDPUV, DfsSrHelpers.VehStylesHdpuv);
        }

        private IAnnualValue<double> _annualForecastLd;
        private IAnnualValue<double> _annualForecastHdpuv;

        public override double CalcNewLightDutySales(
            Scenario scen, int iteration, int year, ForecastFleet baseFleet, ForecastFleet scenFleet)
        {
            return this._annualForecastLd[year];
        }

        public override double CalcNewHdpuvSales(
            Scenario scen, int iteration, int year, ForecastFleet baseFleet, ForecastFleet scenFleet)
        {
            return this._annualForecastHdpuv[year];
        }

        private IAnnualValue<double> InitNominalForecastDynamic(
            double baselineSales, double[] nfCoefficients, VehicleStyle[] vehStyles)
        {
            var nominalForecast = new AnnualValue<double>(this.MinYear, this.MaxYear, outOfBoundsMode: -1);
            nominalForecast[this.MinYear] = baselineSales;

            var parameters = this.Settings.Parameters;
            var macroParams = parameters.EconomicValues.MacroParameters;

            double totalSalesLag1 = baselineSales / 1e6; // in millions
            double totalSalesLag2 = GetHistoricSales(this.MinYear - 1) / 1e6; // in millions
            double totalSalesLag3 = GetHistoricSales(this.MinYear - 2) / 1e6; // in millions
            double newVehPerHhLag1 = totalSalesLag1
                                   / macroParams.GetNumHouseholds(this.MinYear) * 1000; // convert to units

            for (int year = this.MinYear + 1; year <= this.MaxYear; year++) {
                int lagYear = year - 1;
                double threeYrSumPerHH = (totalSalesLag1 + totalSalesLag2 + totalSalesLag3) * 1000
                                       / macroParams.GetNumHouseholds(lagYear);
                double newVehPerHH =
                    nfCoefficients[0] +
                    nfCoefficients[1] * newVehPerHhLag1 +
                    nfCoefficients[2] * threeYrSumPerHH +
                    nfCoefficients[3] * Math.Log(macroParams.GetGdp(year)) +
                    nfCoefficients[4] * Math.Log(macroParams.GetGdp(lagYear)) +
                    nfCoefficients[5] * macroParams.GetConsumerSentiment(year) +
                    nfCoefficients[6] * macroParams.GetConsumerSentiment(lagYear);

                double newSales = newVehPerHH * macroParams.GetNumHouseholds(year) / 1000; // convert back to millions
                nominalForecast[year] = newSales * 1e6 * nfCoefficients[7];

                // shift values for the next loop iteration
                newVehPerHhLag1 = newVehPerHH;
                totalSalesLag3 = totalSalesLag2;
                totalSalesLag2 = totalSalesLag1;
                totalSalesLag1 = newSales;
            }

            return nominalForecast.AsReadOnly();

            // local helper functions
            double GetHistoricSales(int hYear)
            {
                var hfd = parameters.HistoricFleetData;
                return vehStyles.Sum(vehStyle => hfd.GetHistoricFleetData(vehStyle, hYear).InitialProduction);
            }
        }

    }
}
