using System;
using System.Linq;

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

namespace Volpe.Cafe.Model
{
    /// <summary>
    /// Provides methods to assist the compliance modeling process in performing market simulation calculations, such
    /// as assigning the regulatory costs to vehicles and recalculating vehicle sales.
    /// </summary>
    [Serializable]
    public class MarketSimulation
    {
        /// <summary>
        /// Allocates the regulatory costs for all vehicles within the industry for the given model year.
        /// </summary>
        /// <param name="data">The modeling data used for modeling.</param>
        /// <param name="settings">The modeling settings used for modeling.</param>
        /// <param name="scen">The scenario currently being modeled.</param>
        /// <param name="year">The model year for which to calculate the regulatory costs.</param>
        /// <remarks>
        /// This method implements the cost allocation model for a single model year, by using the inputs that specify
        /// the cost allocation strategy assumed for each manufacturer.
        /// </remarks>
        public static void AllocateCosts(IIndustry data, ModelingSettings settings, Scenario scen, ModelYear year)
        {
            foreach (var mfr in data.Manufacturers) {
                AllocateCosts(mfr, settings, scen, year, RegulatoryClass.AllLightDuty);
                AllocateCosts(mfr, settings, scen, year, RegulatoryClass.HDPUV);

                // aggregate reg-cost to the manufacturer-level
                var mfrRegCosts = new RCDouble();
                foreach (IVehicle veh in mfr.Vehicles) {
                    var vehRevision = veh.GetRevision(year);
                    mfrRegCosts[vehRevision.RegClass] += vehRevision.ComplianceCosts.RegCost * vehRevision.Sales;
                }

                var mfrCostItems = mfr.GetRevision(year).ComplianceCosts.Items;
                for (int i = 0; i < mfrRegCosts.Items.Length; i++) {
                    mfrCostItems[i].RegCost = mfrRegCosts.Items[i];
                }
            }
        }

        /// <summary>
        /// Allocates the regulatory costs for all vehicles belonging to the specified manufacturer and for the
        /// specified regulatory class. The regulatory class must be either the HDPUV or the combined light-duty
        /// fleet.
        /// </summary>
        private static void AllocateCosts(
            IManufacturer mfr, ModelingSettings settings, Scenario scen, ModelYear year, RegulatoryClass regClass)
        {
            //------------------------------------------------------------------------------------
            // We use a simple cost-allocation strategy here, where we:
            //  > first, allocate technology costs on an as-incurred basis;
            //  > then, allocate any fines within the given fleet (separately for LD or HDPUV),
            //    distributed as the ratio of each vehicle's pseudo-fines to the manufacturer's
            //    total pseudo-fines.
            //------------------------------------------------------------------------------------
            var vehs = mfr.Vehicles.Where(veh => regClass.Contains(veh.GetRevision(year).RegClass)).ToList();
            if (vehs.Count == 0) {
                return;
            }

            foreach (IVehicle veh in vehs) {
                var vehCosts = veh.GetRevision(year).ComplianceCosts;
                vehCosts.RegCost = vehCosts.TotalTechCost;
            }

            bool includeFines = settings.RuntimeSettings.ComplianceProgram.HasCAFE();
            var mfrRevision = mfr.GetRevision(year);
            double mfrFines = (includeFines) ? mfrRevision.ComplianceStats.Fines[regClass] : 0;

            if (mfrFines > 0) {
                double[] vehPseudoFines = new double[vehs.Count];
                double mfrPseudoFines = 0;

                for (int j = 0; j < vehs.Count; j++) {
                    var vehRevision = vehs[j].GetRevision(year);
                    vehPseudoFines[j] = GetPseudoFine(vehRevision, mfrRevision, scen, year, settings);
                    mfrPseudoFines += vehPseudoFines[j];
                }

                for (int j = 0; j < vehs.Count; j++) {
                    var vehRevision = vehs[j].GetRevision(year);
                    double vehSales = vehRevision.Sales;
                    if (vehPseudoFines[j] != 0 && vehSales != 0) {
                        vehRevision.ComplianceCosts.RegCost +=
                            (mfrFines * (vehPseudoFines[j] / mfrPseudoFines)) / vehSales;
                    }
                }
            }
        }

        /// <summary>
        /// Calculates and returns the pseudo fine for the specified vehicle.
        /// </summary>
        private static double GetPseudoFine(
            IVehicleRevision vehRevision, IManufacturerRevision mfrRevision, Scenario scen, ModelYear year,
            ModelingSettings settings)
        {
            var    regClass = vehRevision.RegClass;
            double vehSales = vehRevision.Sales;
            double vehFE    = Standards.GetAverageVehicleFERating(scen, year, vehRevision, settings, true, false);
            double standard = mfrRevision.ComplianceStats.Standard[regClass];

            return (vehSales == 0)
                ? 0
                : Math.Max(0, scen[regClass, year].FineRate * vehSales * (standard - vehFE));
        }

    }
}
