using System;
using System.Collections.Generic;
using System.Linq;

using Volpe.Cafe.Logging;
using Volpe.Cafe.ModelSettings;

namespace Volpe.Cafe.DfsSrModels
{
    /// <summary>
    /// Provides support for logging DFS/SR model iteration results to a text file.
    /// </summary>
    /// <remarks>
    /// This class serves as a wrapper around the <see cref="LogFileWriter"/>, by creating multiple logs via the
    /// <see cref="ILogger.GetCustomLog"/> method.
    /// </remarks>
    [Serializable]
    public class DfsSrIterationWriter
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="DfsSrIterationWriter"/> class, using the specified modeling
        /// settings and log writer.
        /// </summary>
        /// <remarks>
        /// This constructor creates one or more log files (one per scenario), using the specified column headers:<br/>
        /// - Iteration, Year, Mfr, RegClass, CAFE, CO2, TechCost, Fines, RegCost, Sales, ForecastSales,
        ///   AverageSalesOffset, AverageFleetShareOffset
        /// </remarks>
        public DfsSrIterationWriter(ModelingSettings settings, ILogger logWriter)
        {
            this.LogWriter = logWriter;

            int scenCount = settings.Scenarios.ScenarioCount;
            for (int i = 0; i < scenCount; i++) {
                this.GetLogFile(i, LogFileHeaders);
            }
        }

        private static readonly string LogFileName = "dfssr_sn{0}.txt";
        private static readonly string[] LogFileHeaders = {
            "Iteration", "Year", "Mfr", "RegClass", "CAFE", "CO2", "TechCost", "Fines", "RegCost",
            "Sales", "ForecastSales", "AverageSalesOffset", "AverageFleetShareOffset"
        };

        private ILogger LogWriter { get; }

        /// <summary>
        /// Writes Dynamic Fleet Share and Sales Response model results to a log file for the specified scenario,
        /// iteration, and model year.
        /// </summary>
        /// <param name="scen">The scenario being evaluated by the model.</param>
        /// <param name="year">The model year being evaluated by the model.</param>
        /// <param name="iteration">The iteration being evaluated by the model.</param>
        /// <param name="forecastVehs">List of forecast vehicles.</param>
        public void LogIterationResults(Scenario scen, int iteration, int year, List<ForecastVehicle> forecastVehs)
        {
            if (forecastVehs.Count == 0) {
                return;
            }

            var indForecastSales = RCDouble.Zero;
            var indSales = RCDouble.Zero;
            var indCafeRating = RCDouble.Zero;
            var indCO2Rating = RCAltDouble.Zero;
            var indTechCost = RCDouble.Zero;
            var indFines = RCDouble.Zero;
            var indRegCost = RCDouble.Zero;

            var mfrs = forecastVehs[0].Vehicle.Industry.Manufacturers;
            var mfrForecastSales = this.CalcMfrForecastSales(year, forecastVehs, mfrs.Count);

            for (int i = 0; i < mfrs.Count; i++) {
                var mfr = mfrs[i];
                var mfrRevision = mfr.GetRevision(year);
                var mfrStats = mfrRevision.ComplianceStats;
                var mfrCosts = mfrRevision.ComplianceCosts;
                var mfrSales = mfrStats.GetSales();
                var mfrTechCost = RCDouble.Zero;
                var mfrRegCost  = RCDouble.Zero;

                for (int j = 0; j < RegClasses.List.Length; j++) {
                    mfrTechCost.Items[j] = mfrCosts.Items[j].TechCost;
                    mfrRegCost.Items[j] = mfrCosts.Items[j].RegCost;
                }

                // write per-mfr data
                this.LogIteration(scen, iteration, year, mfr.Name, mfrSales, mfrForecastSales[i],
                                  mfrStats.CAFERating, mfrStats.CO2Rating, mfrTechCost, mfrStats.Fines, mfrRegCost);

                // aggregate mfr-data to industry
                foreach (var rc in RegClasses.List) {
                    indCafeRating[rc] += (mfrSales[rc] == 0) ? 0 : mfrSales[rc] / mfrStats.CAFERating[rc];
                }

                indForecastSales += mfrForecastSales[i];
                indSales += mfrSales;
                indCO2Rating += mfrStats.CO2Rating * (RCAltDouble)mfrSales;
                indTechCost += mfrTechCost;
                indFines += mfrStats.Fines;
                indRegCost += mfrRegCost;
            }

            foreach (var regClass in RegClasses.List) {
                double indSalesRC = indSales[regClass];
                indCafeRating[regClass] = (indSalesRC == 0) ? 0 : indSalesRC / indCafeRating[regClass];
            }
            foreach (var regClass in RegClasses.AltList) {
                double indSalesRC = indSales[regClass];
                indCO2Rating[regClass] = (indSalesRC == 0) ? 0 : indCO2Rating[regClass] / indSalesRC;
            }

            // write industry data
            this.LogIteration(scen, iteration, year, "TOTAL", indSales, indForecastSales,
                              indCafeRating, indCO2Rating, indTechCost, indFines, indRegCost);
        }

        private void LogIteration(
            Scenario scen, int iteration, int year, string mfrName, RCDouble sales, RCDouble forecastSales,
            RCDouble cafeRating, RCAltDouble co2Rating, RCDouble techCost, RCDouble fines, RCDouble regCost)
        {
            var log = this.GetLogFile(scen.Index);

            for (int rc = 0; rc < RegClasses.ReportingList.Length; rc++) {
                var regClass = RegClasses.ReportingList[rc];
                double salesRC = sales[regClass];
                if (salesRC <= 0) {
                    continue;
                }

                double frcSalesRC = forecastSales[regClass];
                double salesOffset = (frcSalesRC - salesRC) / salesRC;
                double fleetOffset = ((frcSalesRC / forecastSales.Total) / (salesRC / sales.Total)) - 1;
                double cafeRatingRC = cafeRating.CalcHarmonicMean(regClass, sales);
                double co2RatingRC = (regClass.IsDC() || regClass.IsIC())
                                   ? 0
                                   : co2Rating.CalcArithmeticMean(regClass, (RCAltDouble)sales);

                log.WriteValues(
                    // header cols
                    iteration, year, mfrName, RegClasses.ReportingNames[rc],
                    // data
                    cafeRatingRC.ToString("0.##"),
                    co2RatingRC.ToString("0"),
                    techCost[regClass].ToString("0.##"),
                    fines[regClass].ToString("0.##"),
                    regCost[regClass].ToString("0.##"),
                    salesRC.ToString(),
                    frcSalesRC.ToString(),
                    salesOffset.ToString("0.########"),
                    fleetOffset.ToString("0.########"));
            }
        }

        private RCDouble[] CalcMfrForecastSales(int year, List<ForecastVehicle> forecastVehs, int mfrCount)
        {
            var mfrForecastSales = Enumerable.Range(0, mfrCount).Select(_ => RCDouble.Zero).ToArray();
            foreach (var forecastVeh in forecastVehs) {
                var forecastVehData = forecastVeh.ForecastData[year];
                mfrForecastSales[forecastVeh.MfrIndex][forecastVehData.RegClass] += forecastVehData.Sales;
            }

            return mfrForecastSales;
        }

        private LogFileWriter GetLogFile(int scenIndex, string[] headers = null)
        {
            return this.LogWriter.GetCustomLog(string.Format(LogFileName, scenIndex), headers);
        }

    }
}
