#region <<< Using Directives >>>
using System;
using System.Collections.Generic;
using System.Linq;

using Volpe.Cafe.EffectsModels;
using Volpe.Cafe.MarketData.Design;
using Volpe.Cafe.DfsSrModels;
using Volpe.Cafe.ModelSettings;
using Volpe.Cafe.Reporting;
using Volpe.Cafe.TechAnalysis;

using RC = Volpe.Cafe.RegulatoryClass;
using TA = Volpe.Cafe.Model.TechnologyApplicability;
using TI = Volpe.Cafe.TechnologyIndexes;
#endregion

namespace Volpe.Cafe.Model
{
    /// <summary>
    /// Provides the standard compliance modeling implementation of the compliance modeling process.
    /// </summary>
    /// <remarks>
    /// Runs a regular compliance model that estimates technology costs and benefits under scenarios with predefined
    /// flat or reformed CAFE standards.
    /// </remarks>
    [Serializable]
    [ModelDescription("Standard Compliance Model", "Runs a regular compliance model that estimates technology costs " +
        "and benefits under scenarios with predefined flat or reformed CAFE standards.", 1.3F)]
    public class ComplianceModel : ComplianceBase
    {

        #region <<< Ctors >>>

        /// <summary>
        /// Initializes a new instance of the <see cref="ComplianceModel"/> class.
        /// </summary>
        public ComplianceModel() : base() { }

        #endregion

        #region <<< Methods >>>

        #region < ComplianceBase Members >

        /// <summary>
        /// Creates and returns a new <see cref="ComplianceModel"/> instance.
        /// </summary>
        /// <returns>A new <see cref="ComplianceModel"/> instance.</returns>
        public override ICompliance CreateNew()
        {
            return new ComplianceModel();
        }

        /// <summary>
        /// Starts the compliance modeling process.
        /// </summary>
        protected override void StartInternal()
        {
            if (this.PreProcess())
            {   // pre-processing returns true if the model has not yet completed
                //
                // begin ...
                this.ScenarioLoop();
                this.PostProcess();
            }
        }

        #endregion

        #region < Main pre-/post-processing methods >

        /// <summary>
        /// Pre-process data and settings for modeling.
        /// </summary>
        /// <returns>true, to begin the modeling process; false, if modeling has previously completed.</returns>
        protected virtual bool PreProcess()
        {
            // the model has previously completed -- no need to run again
            if (this.State == ModelingState.Completed) { return false; }

            // reset progress variables
            this._scen = null;
            this._year = null;
            this._mfr  = null;
            this._effectsYear = -1;

            // initialize models
            this._effectsModel = new EffectsModel.EffectsModel(this);
            this._effectsModelLite = new EffectsModelLite(this); 

            if (this.RuntimeSettings.DynamicEconomicModeling)
            {
                this._dfsSrModel = new DfsSrModel(this.Data, this.Settings, this.LogWriter);
            }

            // return true to specify that the pre-processing succeeded
            return true;
        }
        /// <summary>
        /// Post-process data and settings for modeling.
        /// </summary>
        protected virtual void PostProcess()
        {
        }

        #endregion

        #region < Scenario processing >

        /// <summary>
        /// Begins the scenario loop for the compliance modeling process.
        /// </summary>
        protected virtual void ScenarioLoop()
        {
            foreach (var scen in this.Settings.Scenarios.ScenarioList)
            {
                this.RunScenario(scen);
                if (this.StopRequested) { break; }
            }
        }
        /// <summary>
        /// Begins examining the specified scenario.
        /// </summary>
        /// <param name="scen">The scenario to analyze.</param>
        protected virtual void RunScenario(Scenario scen)
        {
            this.PreProcessScenario (scen);
            this.IterationLoop      (scen);
            this.PostProcessScenario(scen);
        }
        /// <summary>
        /// Pre-processes the scenario for analysis.
        /// </summary>
        /// <param name="scen">The current scenario being analyzed.</param>
        protected virtual void PreProcessScenario(Scenario scen)
        {
            // update progress ...
            this._effectsYear = -1;
            this._mfr = null;
            this._year = null;
            this._iteration = null;
            this._scen = scen;
            this.OnScenarioStarted();

            // initialize compliance finder for this scenario
            this._lcf = new LinearComplianceFinder(this, scen);
        }
        /// <summary>
        /// Post-processes the scenario after the analysis for that scenario has completed. If the scenario did not
        /// complete, post-processing will not occur.
        /// </summary>
        /// <param name="scen">The current scenario being analyzed.</param>
        protected virtual void PostProcessScenario(Scenario scen)
        {
            if (this.StopRequested) {
                return;
            }

            this.RunIndustryEffects(scen);

            var baseScen = this.Settings.Scenarios.Baseline;
            var reportingData = new ReportingData(
                this.GetData(baseScen), this.GetEffectsData(baseScen),
                this.GetData(scen), this.GetEffectsData(scen));
            this.ReportGenerator.GenerateReports(scen, reportingData, this.Settings);

            this._lcf = null;
            this.OnScenarioCompleted();
        }

        void RunIndustryEffects(Scenario scen)
        {
            if (this.RuntimeSettings.DisableEffectsModel) return;

            bool dynamicEconomicModeling = this.RuntimeSettings.DynamicEconomicModeling;
            var scrappageModel = !dynamicEconomicModeling || this.RuntimeSettings.DisableDynamicScrappageModel
                               ? ScrappageModel.CreateStaticScrappageModel(this)
                               : ScrappageModel.CreateDynamicScrappageModel(this, scen);
            var vmtModel = !dynamicEconomicModeling || this.RuntimeSettings.DisableDynamicVmtModel
                         ? VMTModel.CreateStaticVMTModel(this)
                         : VMTModel.CreateDynamicVMTModel(this, scrappageModel, scen);

            // run the Effects Sub-Model for historic years
            var altRegClassMix = this.Data.CalcAltRegClassMix();
            for (int i = this.MinHistoricYear; i < this.MinYear && !this.StopRequested; i++)
            {
                this._effectsYear = i;

                var effectsData = this._effectsModel.CalculateHistoricEffects(
                    scen, i, altRegClassMix, vmtModel, scrappageModel);
                this.SetEffectsData(i, effectsData);
            }

            // run the Effects Sub-Model for analysis years
            for (int i = this.MinYear; i <= this.MaxYear && !this.StopRequested; i++)
            {
                this._effectsYear = i;

                var data = this.GetData(scen);
                var effectsData = this._effectsModel.CalculateEffects(
                    scen, this._effectsYear, data.Vehicles, vmtModel, scrappageModel);
                this.SetEffectsData(this._effectsYear, effectsData);
            }

            this._effectsYear = -1;
        }

        #endregion

        #region < Iteration processing >

        /// <summary>
        /// Begins the convergence loop for the compliance modeling process.
        /// </summary>
        /// <param name="scen">The current scenario being analyzed.</param>
        protected virtual void IterationLoop(Scenario scen)
        {
            int iterations = this.RuntimeSettings.DynamicEconomicModeling ? this.RuntimeSettings.DfsSrIterations : 1;
            for (int iteration = 1; iteration <= iterations; iteration++)
            {
                this.RunIteration(scen, iteration);
                if (this.StopRequested) { break; }
            }
        }
        /// <summary>
        /// Begins examining the specified iteration of the convergence loop.
        /// </summary>
        /// <param name="scen">The current scenario being analyzed.</param>
        /// <param name="iteration">The convergence iteration to analyze.</param>
        protected virtual void RunIteration(Scenario scen, int iteration)
        {
            this.PreProcessIteration (scen, iteration);
            this.ModelYearLoop       (scen, iteration);
            this.PostProcessIteration(scen, iteration);
        }
        /// <summary>
        /// Pre-processes the iteration for analysis.
        /// </summary>
        /// <param name="scen">The current scenario being analyzed.</param>
        /// <param name="iteration">The current convergence iteration being analyzed.</param>
        protected virtual void PreProcessIteration(Scenario scen, int iteration)
        {
            // update progress ...
            this._mfr = null;
            this._year = null;
            this._iteration = iteration;

            this.InitializeModelDataForNewRun();
            this.OnIterationStarted();

            // initialize manufacturers, vehicles, and each of vehicle components for analysis
            var data = this.GetData(scen);
            data.InitializeForAnalysis(this.MinYear, this.MaxYear);
            foreach (var mfr in data.Manufacturers)
            {
                TechUseInitializer.InitTechUtilization(mfr, scen, this.Settings);
            }

            // initialize vehicle aggregators for all mfrs
            this._vehicleAggregators = new VehicleAggregator[data.ManufacturerCount];
            foreach (var mfr in data.Manufacturers) {
                this._vehicleAggregators[mfr.Index] = new VehicleAggregator(mfr.Vehicles, scen, this.Settings);
            }
        }
        /// <summary>
        /// Post-processes the iteration after the analysis for that iteration has completed. If the iteration did not
        /// complete, post-processing will not occur.
        /// </summary>
        /// <param name="scen">The current scenario being analyzed.</param>
        /// <param name="iteration">The current convergence iteration being analyzed.</param>
        protected virtual void PostProcessIteration(Scenario scen, int iteration)
        {
            // if user requested to terminate modeling, return without post-processing
            if (this.StopRequested) { return; }

            var data = this.GetData(scen);
            var mfrs = data.Manufacturers;

            foreach (var mfr in mfrs) {
                foreach (var item in mfr.Platforms) { item.CondenseRevisions(); }
                foreach (var item in mfr.Engines) { item.CondenseRevisions(); }
                foreach (var item in mfr.Transmissions) { item.CondenseRevisions(); }
            }

            if (this.RuntimeSettings.DynamicEconomicModeling) {
                this._forecastVehicles = this._dfsSrModel.ForecastVehicleSales(scen, iteration, data.Vehicles);
            }

            this.OnIterationCompleted();
        }

        #endregion

        #region < Model year processing >

        /// <summary>
        /// Begins the model year loop for the compliance modeling process.
        /// </summary>
        /// <param name="scen">The current scenario being analyzed.</param>
        /// <param name="iteration">The current convergence iteration being analyzed.</param>
        protected virtual void ModelYearLoop(Scenario scen, int iteration)
        {
            for (int year = this.MinYear; year <= this.MaxYear; year++)
            {
                this.RunModelYear(scen, iteration, (ModelYear)year);
                if (this.StopRequested) { break; }
            }
        }
        /// <summary>
        /// Begins examining the specified model year.
        /// </summary>
        /// <param name="scen">The current scenario being analyzed.</param>
        /// <param name="iteration">The current convergence iteration being analyzed.</param>
        /// <param name="year">The model year to analyze.</param>
        protected virtual void RunModelYear(Scenario scen, int iteration, ModelYear year)
        {
            this.PreProcessModelYear (scen, iteration, year);
            this.ManufacturerLoop    (scen, iteration, year);
            this.PostProcessModelYear(scen, iteration, year, (year.Equals(this.MaxYear)));
        }
        /// <summary>
        /// Pre-processes the model year for analysis.
        /// </summary>
        /// <param name="scen">The current scenario being analyzed.</param>
        /// <param name="iteration">The current convergence iteration being analyzed.</param>
        /// <param name="year">The current model year being analyzed.</param>
        protected virtual void PreProcessModelYear(Scenario scen, int iteration, ModelYear year)
        {
            // update progress ...
            this._mfr = null;
            this._year = year;
            this.OnModelYearStarted();

            // create a new revision for the current year for each manufacturer, vehicle,
            // and each of the vehicle components
            IIndustry data = this.GetData(scen);
            foreach (var mfr in data.Manufacturers) {
                var mfrRevision = mfr.CreateRevision();
                mfrRevision.PseudoFuelCost = new PseudoFuelCost(
                    year, this.Settings.Parameters, (Generic.VSValue<double>)mfr.BaselineInfo.PaybackPeriod);

                foreach (var item in mfr.Vehicles) { item.CreateRevision(); }
                foreach (var item in mfr.Platforms) { item.CreateRevision(); }
                foreach (var item in mfr.Engines) { item.CreateRevision(); }
                foreach (var item in mfr.Transmissions) { item.CreateRevision(); }
            }

            //------------------------------------------------------------------------------------
            // For alternative scenarios, we need to copy over previous solution from the baseline
            // scenario for the same year up until "alt-start-year". Afterwards, we can use
            // revision data from the last completed year. This is needed to enable "forced
            // baseline tech" option.
            // Note that the first year is the reference point, which does not get altered in any
            // scenario.
            //------------------------------------------------------------------------------------
            void InheritBaselineData(IReadOnlyList<IOEMProduct> products, IReadOnlyList<IOEMProduct> bProducts)
            {
                for (int j = 0; j < products.Count; j++) {
                    var product = products[j];
                    var bProduct = bProducts[j];

                    int[] techList = product.TechnologyList;
                    foreach (int techIndex in techList) {
                        var techUse = product.TechUtilization[techIndex];
                        var bTechUse = bProduct.TechUtilization[techIndex];

                        if (bTechUse.YearApplied != year || techUse.IsUsed(year)) {
                            continue;
                        }

                        var tech = this.Settings.Technologies.TechnologyList[techIndex];
                        bool isInherited = (bTechUse.YearInherited == year);
                        product.InstallTechnology(scen, year, year, tech, isInherited, this.Settings);

                        bool isSuperseded = (bTechUse.YearSuperseded == year);
                        if (isSuperseded) {
                            techUse.SetSuperseded(year);
                        }
                    }

                    if (product is IVehicle veh) {
                        veh.GetRevision(year).CopyFrom(((IVehicle)bProduct).GetRevision(year));
                    }
                    else if (product is IOEMComponent c) {
                        c.GetRevision(year).CopyFrom(((IOEMComponent)bProduct).GetRevision(year));
                    }
                }
            }

            if (!scen.IsBaseline && year != this.MinYear && year < this.RuntimeSettings.AlternativeStartYear) {
                IIndustry baseData = this.GetData(this.Scenarios[0]);

                for (int i = 0; i < data.Manufacturers.Count; i++) {
                    var mfr = data.Manufacturers[i];
                    var bMfr = baseData.Manufacturers[i];

                    InheritBaselineData(mfr.Transmissions, bMfr.Transmissions);
                    InheritBaselineData(mfr.Engines, bMfr.Engines);
                    InheritBaselineData(mfr.Platforms, bMfr.Platforms);
                    InheritBaselineData(mfr.Vehicles, bMfr.Vehicles);
                }
            }

            // apply new classification to vehicle revisions for the scenario
            bool reclassifyLdVehicles = scen.ReclassifyLightDutyVehicles[year];
            foreach (var veh in data.Vehicles) {
                var vehBaseInfo = veh.BaselineInfo;
                RC regClass = vehBaseInfo.RegClass;
                RC regClass2 = vehBaseInfo.RegClass2;

                // *** NOTE: ***
                // we only want to allow vehicle reclassification if original and secondary class
                // are both light-duty ... otherwise, weird things could happen
                veh.Revisions[year].RegClass = (reclassifyLdVehicles && regClass.IsLD() && regClass2.IsLD())
                                             ? regClass2
                                             : regClass;
            }

            //------------------------------------------------------------------------------------
            // If the DFS/SR model was run during the preceding iteration, overwrite with
            // forecasted sales calculated at the end of that iteration, unless inheriting
            // sales from the baseline scenario up until "alternative-start-year".
            // For the first iteration (iter 1), since this is the reference point, the existing
            // sales volumes should be used.
            // Additionally, for all iterations, the first analysis year is assumed to represent
            // the actual vehicle sales data, and should not be overwritten.
            //------------------------------------------------------------------------------------
            if (this.RuntimeSettings.DynamicEconomicModeling
                && iteration > 1
                && year > this.MinYear
                && (scen.IsBaseline || year >= this.RuntimeSettings.AlternativeStartYear))
            {
                var vehs = data.Vehicles;
                for (int i = 0; i < vehs.Count; i++)
                {
                    vehs[i].GetRevision(year).Sales = this._forecastVehicles[i].GetForecastSales(year);
                }
            }

            // calculate and save industry average passenger car standard for each mfr
            data.MdpcStandard[year] = Standards.CalcMdpcStandard(scen, year, data);

            //------------------------------------------------------------------------------------
            // Update tech utilization and availability for each vehicle used by a manufacturer.
            // Also update sales-volumes for each tech on a mfr (needed for phase-in).
            //------------------------------------------------------------------------------------
            foreach (var mfr in data.Manufacturers)
            {
                var mfrRevision = mfr.GetRevision(year);

                foreach (var veh in mfr.Vehicles) {
                    double vehSales = veh.GetRevision(year).Sales;
                    for (int i = 0; i < TI.TechnologyCount; i++) {
                        if (veh.TechUtilization[i].IsApplied(year)) {
                            mfrRevision.TechAppliedSales[i] += vehSales;
                        }
                    }
                }

                mfr.ClearAllTechIgnoredStates();
            }

            //------------------------------------------------------------------------------------
            // init mfr compliance statistics
            // init veh FC-adj-key (for ANL DB mappings)
            // re-calculate costs for each vehicle and aggregate the costs to the mfr-level
            // this also includes AC and off-cycle costs (with learning effect applied)
            // update mfr effects data
            // update mfr phase-in caps
            //------------------------------------------------------------------------------------
            var program = this.RuntimeSettings.ComplianceProgram;
            foreach (var mfr in data.Manufacturers)
            {
                var mfrRevision = mfr.GetRevision(year);
                mfrRevision.ComplianceStats = Standards.CalcCompliance(scen, year, mfr, this.Settings);

                foreach (var veh in mfr.Vehicles)
                {
                    var vehRevision = veh.GetRevision(year);
                    RC regClass = vehRevision.RegClass;

                    // tech-related costs
                    TA.CalcLearnedCosts(vehRevision,
                                        year,
                                        this.Settings.Technologies,
                                        out double vehBatteryTechCost,
                                        out double vehNonBatteryTechCost,
                                        out double vehMRCost);

                    // off-cycle costs
                    var offCycleCosts = OffCycleTechEvaluator.CalcOffCycleCosts(veh, scen, year, this.Settings);

                    var vehCosts = vehRevision.ComplianceCosts;
                    vehCosts.ACEfficiencyCost   = offCycleCosts.AcEfficiency;
                    vehCosts.ACLeakageCost      = offCycleCosts.AcLeakage;
                    vehCosts.OffCycleCost       = offCycleCosts.OffCycle;
                    vehCosts.NonBatteryTechCost = vehNonBatteryTechCost;
                    vehCosts.BatteryTechCost    = vehBatteryTechCost;
                    vehCosts.RegCost            = 0; // updated at end of year
                    vehCosts.MaintRepairCost    = vehMRCost;

                    double vehSales = vehRevision.Sales;
                    var mfrCosts = mfrRevision.ComplianceCosts[regClass];
                    mfrCosts.ACEfficiencyCost   += vehSales * offCycleCosts.AcEfficiency;
                    mfrCosts.ACLeakageCost      += vehSales * offCycleCosts.AcLeakage;
                    mfrCosts.OffCycleCost       += vehSales * offCycleCosts.OffCycle;
                    mfrCosts.BatteryTechCost    += vehSales * vehBatteryTechCost;
                    mfrCosts.NonBatteryTechCost += vehSales * vehNonBatteryTechCost;
                    mfrCosts.MaintRepairCost    += vehSales * vehMRCost;
                } // next veh

                // run a subset of Effects model for the current mfr and year --
                // this is necessary to enable UI progress refreshes
                this.RunManufacturerEffects(year, mfr);

                // update mfr's tech-exhausted states based on the updated "tech-applied" sales volumes
                foreach (var tech in this.Settings.Technologies.TechnologyList)
                {
                    TA.CheckPhaseIn(mfrRevision, tech, year);
                }
            } // next mfr
        }
        /// <summary>
        /// Post-processes the model year after the analysis for that year has completed. If the model year did not
        /// complete, post-processing will not occur.
        /// </summary>
        /// <param name="scen">The current scenario being analyzed.</param>
        /// <param name="iteration">The current convergence iteration being analyzed.</param>
        /// <param name="year">The current model year being analyzed.</param>
        /// <param name="recurseForMultiyear">true, to recursively execute model year post-processing in all previous
        ///     years to update, for effects of multiyear; false, otherwise.</param>
        protected virtual void PostProcessModelYear(Scenario scen, int iteration, ModelYear year, bool recurseForMultiyear)
        {
            // if user requested to terminate modeling, return without post-processing
            if (this.StopRequested) {
                return;
            }

            IIndustry data = this.GetData(scen);

            // refresh mfr-level costs
            // compliance with ZEV may cause a shift in vehicle-level sales
            // this, in turn, may cause mfr costs to be invalidated
            foreach (var mfr in data.Manufacturers) {
                var mfrRevision = mfr.GetRevision(year);
                mfrRevision.ComplianceCosts.Clear(item => item.Clear());

                foreach (var veh in mfr.Vehicles) {
                    var vehRevision = veh.GetRevision(year);
                    var mfrCosts = mfrRevision.ComplianceCosts[vehRevision.RegClass];
                    var vehCosts = vehRevision.ComplianceCosts;
                    double vehSales = vehRevision.Sales;

                    mfrCosts.ACEfficiencyCost   += vehSales * vehCosts.ACEfficiencyCost;
                    mfrCosts.ACLeakageCost      += vehSales * vehCosts.ACLeakageCost;
                    mfrCosts.OffCycleCost       += vehSales * vehCosts.OffCycleCost;
                    mfrCosts.BatteryTechCost    += vehSales * vehCosts.BatteryTechCost;
                    mfrCosts.NonBatteryTechCost += vehSales * vehCosts.NonBatteryTechCost;
                    mfrCosts.MaintRepairCost    += vehSales * vehCosts.MaintRepairCost;
                }
            }

            // allocate costs
            MarketSimulation.AllocateCosts(data, this.Settings, scen, year);

            // calculate auxiliary HEV costs for all vehicles
            TA.CalcAuxiliaryHEVCosts(data, scen, year, this.Settings.Technologies);

            if (recurseForMultiyear) {
                // if in multi-year mode, need to re-run the model year post-processing, in order to get
                // updated effects of multiyear, for all previous years by recursively passing the previous
                // year to the "PostProcessModelYear"
                if (year > this.MinYear) {
                    this.PostProcessModelYear(scen, iteration, (ModelYear)(year - 1), recurseForMultiyear);
                }
            }

            // raise model-year-completed event only for the current year (instead of all "multi-years")
            if (year == this._year) {
                this.OnModelYearCompleted();
            }
        }

        #endregion

        #region < Manufacturer processing >

        /// <summary>
        /// Begins the model year loop for the compliance modeling process.
        /// </summary>
        /// <param name="scen">The current scenario being analyzed.</param>
        /// <param name="iteration">The current convergence iteration being analyzed.</param>
        /// <param name="year">The current model year being analyzed.</param>
        protected virtual void ManufacturerLoop(Scenario scen, int iteration, ModelYear year)
        {
            IIndustry data = this.GetData(scen);
            foreach (var mfr in data.Manufacturers)
            {
                this.RunManufacturer(scen, iteration, year, mfr);
                if (this.StopRequested) {
                    break;
                }
            }
        }
        /// <summary>
        /// Begins examining the specified manufacturer.
        /// </summary>
        /// <param name="scen">The current scenario being analyzed.</param>
        /// <param name="iteration">The current convergence iteration being analyzed.</param>
        /// <param name="year">The current model year being analyzed.</param>
        /// <param name="mfr">The manufacturer to analyze.</param>
        protected virtual void RunManufacturer(Scenario scen, int iteration, ModelYear year, IManufacturer mfr)
        {
            this.PreProcessManufacturer(scen, iteration, year, mfr);

            if (year.Year < this.GetConstrainedTechApplicationYear(scen))
            {   // skip tech application
                this.PostProcessManufacturer(scen, iteration, year, mfr);
                return;
            }

            var  runtimeSettings   = this.RuntimeSettings;
            var  complianceProgram = runtimeSettings.ComplianceProgram;
            bool enforceCO2        = complianceProgram.HasCO2();

            ComplianceFinding finding;

            // STEP 1) Apply all cost-eff solutions regardless of mfr's compliance
            while ((finding = this.GetComplianceFinding(scen, iteration, year, mfr, true)) != null)
            {
                if (!finding.IsEfficient) { break; }
                //
                finding.ApplyFinding();
                this.Recomply(scen, year, mfr, finding);
                if (this.StopRequested) { return; }
            }

            // STEP 2a) Apply expiring credits
            if (!mfr.IsInCompliance(complianceProgram))
            {
                this.ApplyExpiringCredits(scen, iteration, year, mfr);
            }

            // STEP 2b) Apply all credits from oldest to newest, prefer carry fwd over transfers
            if (!mfr.IsInCompliance(complianceProgram))
            {
                this.ApplyExpiringCredits(
                    scen, iteration, year, mfr, this.Settings.Parameters.CreditTradingValues.MaxExpiringYears);
            }

            // STEP 2c) For CO-2 program only, transfer credits between fleets within the same model year
            if (enforceCO2 && !mfr.IsInCompliance(ComplianceProgram.CO2))
            {   // CO-2 standards are applicable to entire fleet --
                // thus, transfer credits immediately to offset deficit between fleets
                CreditTradingCO2.CT_FleetTransfers(scen, iteration, year, mfr, RC.AllPassengerCar, this, false, 0, false);
                CreditTradingCO2.CT_FleetTransfers(scen, iteration, year, mfr, RC.LightTruck, this, false, 0, false);
                this.Recomply(scen, year, mfr, null);
            }

            // STEP 3) Apply non-cost-eff solutions until compliance is achieved
            //
            // The non-cost-eff solutions should only be considered if any of the following is true:
            // * manufacturer prefers NOT to pay fines in at least one reg-class,
            // * compliance program, at a minimum, enforces CO2 (that is, it's either CO2 or CAFEAndCO2)
            bool evalNonEffective = enforceCO2;
            if (!evalNonEffective)
            {
                foreach (RC rc in RegClasses.List)
                {
                    evalNonEffective |= !mfr.BaselineInfo.PreferFines[rc, year];
                }
            }
            if (evalNonEffective)
            {
                bool ignoredReset = false;
                //
                while (!mfr.IsInCompliance(complianceProgram))
                {
                    finding = this.GetComplianceFinding(scen, iteration, year, mfr, false);

                    if (finding == null)
                    {   // if finding is null, the model cannot generate any more compliance solutions
                        if (!ignoredReset) {
                            // try reseting "ignored" states for all vehicles/components of a mfr
                            // in case "synergies" of already applied technologies improves the
                            // efficiency of ignored technologies
                            mfr.ClearAllTechIgnoredStates();
                            ignoredReset = true;
                        }
                        else {
                            break;
                        }
                    }
                    else if (!finding.IsValid)
                    {   // the finding is not valid -- efficiency has not been calculated for some reason (SHOULD NEVER HAPPEN)
                        throw new Exception(
                            $"The compliance finding is not valid!{Environment.NewLine}{Environment.NewLine}" +
                            "This should never happen and typically indicates an internal error in the modeling system.");
                    }
                    else
                    {   // apply the finding if mfr is NOT in compliance for any of the affected RCs
                        if (finding.IsInCompliance) {
                            finding.IgnoreTechnologies();
                        }
                        else {
                            finding.ApplyFinding();
                            this.Recomply(scen, year, mfr, finding);

                            if (enforceCO2)
                            {   // CO-2 standards are applicable to entire fleet --
                                // thus, transfer credits immediately to offset deficit between fleets
                                CreditTradingCO2.CT_FleetTransfers(scen, iteration, year, mfr, RC.AllPassengerCar, this, false, 0, false);
                                CreditTradingCO2.CT_FleetTransfers(scen, iteration, year, mfr, RC.LightTruck, this, false, 0, false);
                                this.Recomply(scen, year, mfr, null);
                            }
                        }
                    }

                    if (this.StopRequested) { return; }
                } // end while
            }

            // post-process the manufacturer
            this.PostProcessManufacturer(scen, iteration, year, mfr);
        }
        /// <summary>
        /// Pre-processes the manufacturer for analysis.
        /// </summary>
        /// <param name="scen">The current scenario being analyzed.</param>
        /// <param name="iteration">The current convergence iteration being analyzed.</param>
        /// <param name="year">The current model year being analyzed.</param>
        /// <param name="mfr">The current manufacturer being analyzed.</param>
        protected virtual void PreProcessManufacturer(Scenario scen, int iteration, ModelYear year, IManufacturer mfr)
        {
            // update progress ...
            this._mfr = mfr;
            this.OnManufacturerStarted();

            // flush buffered compliance log writes from the previous model year
            this.LogWriter.FlushCfLogs();

            // set initial zev credits
            var technologies = this.Technologies.TechnologyList;
            foreach (var veh in mfr.Vehicles) {
                var vehRevision = veh.GetRevision(year);
                for (int i = TI.TechnologyCount - 1; i >= 0; i--) {
                    if (vehRevision.TechKey[i] && TI.IsConversionToZEV(i)) {
                        vehRevision.ZEVCredits = technologies[i].GetZevCredits(vehRevision, year);
                    }
                }
            }

            if (year >= this.GetConstrainedTechApplicationYear(scen)) {
                this.EvaluateForcedApplicationTechnologies(scen, iteration, year, mfr);
                this.ComplyWithZEV(scen, iteration, year, mfr); // checks compliance with CA+S177 ZEV targets
                this.InheritTechnologies(scen, iteration, year, mfr);
            }
            else {
                this.ApplyExpiringCredits(scen, iteration, year, mfr);
            }

            // run a subset of Effects model for the current mfr and year --
            // this is necessary to enable UI progress refreshes
            this.RunManufacturerEffects(year, mfr);
        }
        /// <summary>
        /// Post-processes the manufacturer after the analysis for that manufacturer has completed. If the manufacturer
        /// did not complete, post-processing will not occur.
        /// </summary>
        /// <param name="scen">The current scenario being analyzed.</param>
        /// <param name="iteration">The current convergence iteration being analyzed.</param>
        /// <param name="year">The current model year being analyzed.</param>
        /// <param name="mfr">The current manufacturer being analyzed.</param>
        protected virtual void PostProcessManufacturer(Scenario scen, int iteration, ModelYear year, IManufacturer mfr)
        {
            // if user requested to terminate modeling, return without post-processing
            if (this.StopRequested) { return; }

            if (year >= this.GetConstrainedTechApplicationYear(scen)) {
                // check for any new technologies that may be inherited
                this.InheritTechnologies(scen, iteration, year, mfr);
            }

            if (year >= this.RuntimeSettings.TechStartYear) {
                var complianceProgram = this.RuntimeSettings.ComplianceProgram;

                // if the manufacturer is still not complying, attempt to transfer all
                // available credits to reach compliance
                if (!mfr.IsInCompliance(complianceProgram)) {
                    if (complianceProgram.HasCAFE()) {
                        CreditTrading.ExamineCredits(scen, iteration, year, mfr, this);
                    }
                    if (complianceProgram.HasCO2()) {
                        CreditTradingCO2.ExamineCredits(scen, iteration, year, mfr, this);
                    }
                    this.Recomply(scen, year, mfr, null);
                }
            }

            // run a subset of Effects model for the current mfr and year --
            // this is necessary to enable UI progress refreshes
            this.RunManufacturerEffects(year, mfr);

            this.OnManufacturerCompleted();
        }

        /// <summary>
        /// Refreshes the compliance statistics for the manufacturer, optionally using the test vehicles affected by
        /// the most recent application of technologies, as defined by the specified compliance finding.
        /// </summary>
        /// <param name="scen">The current scenario being analyzed.</param>
        /// <param name="year">The current model year being analyzed.</param>
        /// <param name="mfr">The current manufacturer being analyzed.</param>
        /// <param name="finding">The compliance finding containing the test vehicles affected by the most recent
        ///     application of technologies; or, null, if not applicable. If null is specified, only the aggregate
        ///     manufacturer level values will be refreshed, without iterating any vehicle models.</param>
        protected void Recomply(Scenario scen, ModelYear year, IManufacturer mfr, ComplianceFinding finding = null)
        {
            var techSolution = finding?.TechSolutions[year - finding.StartYear];
            Standards.RefreshCompliance(scen, year, mfr, techSolution, this.Settings);

            // if a multiyear solution, recomply for preceding years as well
            if (finding != null && finding.IsMultiYear) {
                var techSolutions = finding.TechSolutions;

                for (int i = 0; i < finding.YearCount - 1; i++) {
                    var prevYear = techSolutions[i].ModelYear;
                    Standards.RefreshCompliance(scen, prevYear, mfr, techSolutions[i], this.Settings);
                }
            }
        }

        void ApplyExpiringCredits(Scenario scen, int iteration, ModelYear year, IManufacturer mfr, int maxExpiringYears = 0)
        {
            var complianceProgram = this.RuntimeSettings.ComplianceProgram;
            int expiringYrIndex = year.Index + maxExpiringYears;
            //
            if (complianceProgram.HasCAFE())
            {
                CreditTrading.CT_CarryForward  (scen, iteration, year, mfr, this, true, expiringYrIndex);
                CreditTrading.CT_FleetTransfers(scen, iteration, year, mfr, this, true, expiringYrIndex);
            }
            if (complianceProgram.HasCO2())
            {
                CreditTradingCO2.CT_CarryForward  (scen, iteration, year, mfr, this, true, expiringYrIndex);
                CreditTradingCO2.CT_FleetTransfers(scen, iteration, year, mfr, this, true, expiringYrIndex);
            }
            //
            this.Recomply(scen, year, mfr, null);
        }
        void RunManufacturerEffects(ModelYear year, IManufacturer mfr)
        {
            if (this.RuntimeSettings.DisableEffectsModel) {
                return;
            }

            var aggregateVehicles = this._vehicleAggregators[mfr.Index].Aggregate(year);
            var effectsData = this._effectsModelLite.CalculateEffects(this._scen, year, aggregateVehicles);

            mfr.Tag[$"EffectsData_{year}"] = effectsData;
        }

        void ComplyWithZEV(Scenario scen, int iteration, ModelYear year, IManufacturer mfr)
        {
            // upgrade any ZEV candidate vehicle which hasn't been upgraded yet
            var zevCandidateVehs = mfr.Vehicles.Where(
                veh => veh.BaselineInfo.IsZevCandidate && veh.BaselineInfo.ZevApplicationYear <= year);

            foreach (IVehicle veh in zevCandidateVehs) {
                this.ConvertToZEV(scen, iteration, year, mfr, veh);
            }

            if (!this.RuntimeSettings.ExcludeBevFcvFromAnalysis) {
                // shift sales for any paired ZEV vehicles
                this.ShiftSalesForZEV(scen, year, mfr, RC.HDPUV);
                this.ShiftSalesForZEV(scen, year, mfr, RC.AllLightDuty);
            }

            // shifting sales at the vehicle level requires the mfr compliance stats to be
            // recalculated in their entirety
            // we cannot simply "refersh" compliance because sales-weighted vehicle-level targets
            // and ratings need to be recomputed
            var mfrRevision = mfr.GetRevision(year);
            mfrRevision.ComplianceStats = Standards.CalcCompliance(scen, year, mfr, this.Settings);
        }
        void ShiftSalesForZEV(Scenario scen, ModelYear year, IManufacturer mfr, RC regClass)
        {
            var zevCandidateVehs = mfr.Vehicles.Where(
                veh => veh.BaselineInfo.IsZevCandidate
                    && veh.ZevReferenceVehicle != null
                    && regClass.Contains(veh.GetRevision(year).RegClass));

            // for credit potential calc, we only wish to consider BEV/FCV candidates
            double creditPotential = 0;
            double mfrZevCreditShare = mfr.BaselineInfo.GetZevCreditShare(regClass);
            var techList = this.Technologies.TechnologyList;

            foreach (var veh in zevCandidateVehs) {
                int newZevTech = veh.BaselineInfo.ZEVCandidate.Value;
                if (!TI.IsConversionToBevOrFcv(newZevTech)) {
                    continue;
                }

                var zevRefVeh = veh.ZevReferenceVehicle;
                var zevRefRevision = zevRefVeh.GetRevision(year);
                double zevCredits = techList[newZevTech].GetZevCredits(zevRefRevision, year);
                int prevZevTech = TA.GetMaxBatteryTech(zevRefRevision.TechKey);
                if (prevZevTech != -1) {
                    zevCredits -= techList[prevZevTech].GetZevCredits(zevRefRevision, year);
                }

                creditPotential += zevRefRevision.Sales * mfrZevCreditShare * zevCredits;
            }

            if (creditPotential == 0) {
                return;
            }

            // shift sales -- consider all candidates (BEV/FCV as well as PHEV)
            var zevCompliance = Standards.CalcZevCompliance(scen, year, mfr, regClass, this.Settings);
            double pctSalesToShift = Math.Min(1, zevCompliance.Shortfall / creditPotential);

            foreach (var veh in zevCandidateVehs) {
                var vehRevision = veh.GetRevision(year);
                var zevRefRevision = veh.ZevReferenceVehicle.GetRevision(year);

                double vehSalesToShift =
                    Math.Min(zevRefRevision.Sales, Math.Ceiling(zevRefRevision.Sales * pctSalesToShift));
                vehRevision.Sales += vehSalesToShift;
                zevRefRevision.Sales -= vehSalesToShift;
            }
        }
        void ConvertToZEV(Scenario scen, int iteration, ModelYear year, IManufacturer mfr, IVehicle veh)
        {
            int zevTech = veh.BaselineInfo.ZEVCandidate.Value;
            HEVType zevHevType = TI.IsConversionToPHEV(zevTech) ? HEVType.PlugInHybrid
                               : TI.IsConversionToEV(zevTech) ? HEVType.BatteryElectric
                               : TI.IsConversionToFCV(zevTech) ? HEVType.FuelCell
                               : HEVType.Conventional;

            // we don't want to upgrade a vehicle if it was already upgraded to desired ZEV state
            // or if it is at the last state
            // BEV/FCV are "last state" and cannot be upgraded further (even to more advanced BEV/FCV)
            var vehHevType = veh.GetRevision(year).HevType;
            if (vehHevType == zevHevType || vehHevType.IsBevOrFcv()) {
                return;
            }

            // if ZEV tech is not compatible with veh, return without modifying the vehicle
            // (for example: veh has manual transmission or non-gas fuel -- either veh wasn't
            //  properly classified as ZEV candidate in the input file -or- model applied an
            //  incompatible tech before it had a chance to convert the vehicle to ZEV)
            var zevTechUse = veh.TechUtilization[zevTech];
            if (!zevTechUse.IsAvailable(year)) {
                return;
            }

            // initialize CF and examine finding (using forced application)
            var techList = this.Technologies.TechnologyList;
            var cf = new ComplianceFinding(this);
            cf.ExamineForcedApplication(scen, iteration, year, mfr, techList[zevTech], veh);
            if (cf.IsValid)
            {
                cf.ApplyFinding();
                this.Recomply(scen, year, mfr, cf);
            }
        }

        void InheritTechnologies(Scenario scen, int iteration, ModelYear year, IManufacturer mfr)
        {
            this.InheritComponentTechnologies(scen, iteration, year, mfr);
        }

        void InheritComponentTechnologies(Scenario scen, int iteration, ModelYear year, IManufacturer mfr)
        {
            // for each engine, transmission, and platform, check and inherit missing technologies
            // based on the follower vehicle's TechEnabled state:
            //  1) engines and transmissions inherit during a follower's refresh or redesign
            //  2) platforms inherit during a follower's redesign only
            foreach (TechnologyInfo tech in this.Settings.Technologies.TechnologyList) {
                if (TI.IsEngineLevel(tech.Index)) {
                    foreach (IEngine value in mfr.Engines) {
                        this.InheritComponentTechnology(scen, iteration, year, value, tech);
                    }
                }
                else if (TI.IsTransmissionLevel(tech.Index)) {
                    foreach (ITransmission value in mfr.Transmissions) {
                        this.InheritComponentTechnology(scen, iteration, year, value, tech);
                    }
                }
                else if (TI.IsPlatformLevel(tech.Index)) {
                    foreach (IPlatform value in mfr.Platforms) {
                        this.InheritComponentTechnology(scen, iteration, year, value, tech);
                    }
                }
            }
        }
        void InheritComponentTechnology(
            Scenario scen, int iteration, ModelYear year, IOEMComponent component, TechnologyInfo tech)
        {
            //------------------------------------------------------------------------------------
            // This function propagates the specified technology from the specified component down
            // to all vehicles.
            //------------------------------------------------------------------------------------
            if (component.Vehicles.Count == 0 || !component.TechUtilization[tech.Index].IsUsed(year)) {
                return;
            }

            var mfr = component.Manufacturer;
            var cf = new ComplianceFinding(this);

            foreach (IVehicle veh in component.Vehicles) {
                if (veh.TechUtilization[tech.Index].IsAvailable(year)) {
                    cf.ExamineTechInheriting(scen, iteration, year, mfr, tech, veh);
                    if (cf.IsValid) {
                        cf.ApplyFinding();
                        this.Recomply(scen, year, mfr, cf);
                    }
                }
            }
        }

        void EvaluateForcedApplicationTechnologies(Scenario scen, int iteration, ModelYear year, IManufacturer mfr)
        {
            foreach (TechnologyInfo tech in this.Settings.Technologies.TechnologyList) {
                if (!TI.IsForcedApplication(tech.Index)) {
                    continue;
                }

                var cf = new ComplianceFinding(this);
                foreach (var veh in mfr.Vehicles) {
                    cf.ExamineForcedApplication(scen, iteration, year, mfr, tech, veh);
                    if (cf.IsValid) {
                        cf.ApplyFinding();
                        this.Recomply(scen, year, mfr, cf);
                    }
                }
            }
        }

        int GetConstrainedTechApplicationYear(Scenario scen)
        {
            int year = this.RuntimeSettings.TechStartYear;
            if (scen.IsBaseline == false)
            {
                year = Math.Max(year, this.RuntimeSettings.AlternativeStartYear);
            }
            return year;
        }

        #endregion

        #region < Compliance finding processing >

        /// <summary>
        /// Searches for and returns the next most efficient compliance finding, or null, if no more findings can be
        /// generated given the runtime settings and specified parameters.
        /// </summary>
        /// <param name="scen">The current scenario being analyzed.</param>
        /// <param name="iteration">The current convergence iteration being analyzed.</param>
        /// <param name="year">The current model year being analyzed.</param>
        /// <param name="mfr">The current manufacturer being analyzed.</param>
        /// <param name="effOnly">Specifies whether only efficient solutions should be returned.</param>
        /// <returns>The most efficient compliance finding for a manufacturer for the scenario and model year being
        ///     analyzed, or null, if no such finding exists. If a finding is not efficient and the manufacturer is not
        ///     willing to pay fines, the return value will be null.</returns>
        protected virtual ComplianceFinding GetComplianceFinding(Scenario scen, int iteration, ModelYear year,
            IManufacturer mfr, bool effOnly)
        {
            // get the compliance program being evaluated
            ComplianceProgram program = this.RuntimeSettings.ComplianceProgram;

            // generate new compliance finding
            ComplianceFinding finding = this._lcf.GetNextFinding(iteration, year, mfr);

            //----------------------------------------------------------------------------------//
            // Check the compliance finding to determine if it can be returned to the           //
            // manufacturer for application or further examination, taking into consideration   //
            // the compliance program being evaluated, the manufacturer's compliance state with //
            // the given compliance program, and the details associated with the generated      //
            // compliance finding.                                                              //
            //                                                                                  //
            // * if the finding is null, return null to the manufacturer indicating that no new //
            //   solutions could be generated                                                   //
            // * if a finding was generated, but is not valid, throw an exception, since this   //
            //   is a result of some internal model error (this trap is for debugging only)     //
            // * if a finding is valid and efficient (cost-effective), return it for application//
            // * if a finding is NOT efficient AND the model is operating in "efficient-only"   //
            //   mode, return null to the manufacturer indicating that no new solutions could   //
            //   be generated with the current settings                                         //
            // * if a finding is NOT efficient, but the model is NOT operating in               //
            //   "efficient-only" mode, consider the manufacturer's compliance state with the   //
            //   compliance program(s) being evaluated as follows:                              //
            //   > if only CO2 program is being evaluated, return the finding for application   //
            //   > if only CAFE program is being evaluated, determine if the manufacturer       //
            //     prefers to pay fines in the affected reg-classes associated with the         //
            //     generated finding:                                                           //
            //     - if manufacturer prefers fines in all affected reg-classes, return null     //
            //     - if manufacturer does not prefer fines in at least one affected reg-class,  //
            //       return the finding for application                                         //
            //   > if CAFE and CO2 programs are being evaluated simultaneously, determine the   //
            //     manufacturer's compliance state with each program AND addtionally for CAFE,  //
            //     determine fine preference:                                                   //
            //     - if the manufacturer is NOT in compliance with the CO2 program, return the  //
            //       finding for application                                                    //
            //     - if the manufacturer is NOT in compliance with the CAFE program, evaluate   //
            //       fine preference and return state of the finding per above ("only CAFE      //
            //       program")                                                                  //
            //----------------------------------------------------------------------------------//

            if (finding == null) {
                return finding;
            }
            else if (!finding.IsValid) {
                // the finding is not valid -- efficiency has not been calculated for some reason (SHOULD NEVER HAPPEN)
                throw new Exception("The compliance finding is not valid!\r\n\r\n" +
                    "This should never happen and typically indicates an internal error in the modeling system.");
            }
            else if (finding.IsEfficient) {
                return finding;
            }
            else if (!effOnly) {
                switch (program) {
                    case ComplianceProgram.CO2:
                        return finding;

                    case ComplianceProgram.CAFEAndCO2:
                        bool co2InCompliance = mfr.IsInCompliance(ComplianceProgram.CO2);
                        bool cafeInCompliance = mfr.IsInCompliance(ComplianceProgram.CAFE) || finding.PreferCafeFines;
                        return (co2InCompliance && cafeInCompliance) ? null : finding;

                    case ComplianceProgram.CAFE:
                        return (finding.PreferCafeFines) ? null : finding;
                }
            }

            return null;
        }

        #endregion

        #endregion

        #region <<< Properties >>>

        /// <summary>Gets the current modeling progress, or null if the compliance model is not running.</summary>
        public override IModelingProgress Progress
        {
            get {
                string additionalInfo = string.Empty;
                if (this._effectsYear != -1) {
                    additionalInfo += $"Running effects analysis for model year: {this._effectsYear}";
                }
                if (this.ReportGenerator != null && this.ReportGenerator.IsRunning) {
                    if (additionalInfo != string.Empty) {
                        additionalInfo += Environment.NewLine;
                    }
                    additionalInfo += this.ReportGenerator.Status;
                }
                if (this.CompressingOutputs) {
                    additionalInfo += "Compressing model outputs ... this may take a while.";
                }
                return new ModelingProgress(this._scen, this._iteration, this._year, this._mfr, additionalInfo);
            }
        }

        #endregion

        #region <<< Variables >>>

        /// <summary>Provides a compliance finder to use for generating compliance findings for scenarios.</summary>
        [NonSerialized]
        private LinearComplianceFinder _lcf;

        /// <summary>Provides the effects sub-model for the emissions calculations.</summary>
        [NonSerialized]
        private EffectsModel.EffectsModel _effectsModel;

        /// <summary>Provides the effects sub-model for subset of effects calculations for GUI updates.</summary>
        [NonSerialized]
        private EffectsModelLite _effectsModelLite;

        /// <summary>Provides methods of generating aggregate vehicles for each manufacturer's fleet.</summary>
        [NonSerialized]
        private VehicleAggregator[] _vehicleAggregators;

        /// <summary>Provides the dynamic fleet share and sales response model object.</summary>
        [NonSerialized]
        private DfsSrModel _dfsSrModel;

        /// <summary>Provides the collection of forecast vehicles for DFS/SR model calculations.</summary>
        [NonSerialized]
        private List<ForecastVehicle> _forecastVehicles;

        #endregion

    }
}
