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

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Threading;

using Volpe.Cafe.EffectsData;
using Volpe.Cafe.Generic;
using Volpe.Cafe.Logging;
using Volpe.Cafe.MarketData.Design;
using Volpe.Cafe.ModelSettings;
using Volpe.Cafe.Reporting;
using Volpe.Cafe.UI;
using Volpe.Cafe.Utils;
using Volpe.Cafe.VehicleSimulation;

using TI = Volpe.Cafe.TechnologyIndexes;

namespace Volpe.Cafe.Model
{
    /// <summary>
    /// Provides an abstract base class for the compliance model implementation.
    /// </summary>
    /// <remarks>
    /// The <see cref="ComplianceBase"/> class provides only the basic implementation of the <see cref="ICompliance"/>
    /// interface and can be used to simplify the implementation for extending classes.  When the
    /// <see cref="Start(IIndustry, ModelingSettings)"/> method is called, the <see cref="ComplianceBase"/> class spawns
    /// a new thread on which the modeling process will run.
    /// </remarks>
    [Serializable]
    [ModelDescription(
        "ComplianceBase", "Provides an abstract base class for the compliance model implementation.", 1.6F)]
    public abstract class ComplianceBase : ICompliance
    {
        #region <<< Events >>>

        public event PromptEventHandler Prompt;

        public event ModelingEventHandler ModelingStarted;
        public event ModelingEventHandler ModelingStopped;
        public event ModelingEventHandler ModelingCompleted;
        public event ModelingEventHandler ModelingChanged;

        public event ModelingProgressEventHandler ScenarioStarted;
        public event ModelingProgressEventHandler ScenarioCompleted;
        public event ModelingProgressEventHandler IterationStarted;
        public event ModelingProgressEventHandler IterationCompleted;
        public event ModelingProgressEventHandler ModelYearStarted;
        public event ModelingProgressEventHandler ModelYearCompleted;
        public event ModelingProgressEventHandler ManufacturerStarted;
        public event ModelingProgressEventHandler ManufacturerCompleted;

        #endregion

        #region <<< Ctors >>>

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

        /// <summary>
        /// Cleans up any resources being used by this <see cref="ComplianceBase"/> instance before it is reclaimed by
        /// the garbage collector.
        /// </summary>
        ~ComplianceBase()
        {
            // terminate the compliance process if it is still running
            this.Stop(false);

            // clear all registered events
            this.Prompt                = null;
            //
            this.ModelingStarted       = null;
            this.ModelingStopped       = null;
            this.ModelingCompleted     = null;
            this.ModelingChanged       = null;
            //
            this.ScenarioStarted       = null;
            this.ScenarioCompleted     = null;
            this.IterationStarted      = null;
            this.IterationCompleted    = null;
            this.ModelYearStarted      = null;
            this.ModelYearCompleted    = null;
            this.ManufacturerStarted   = null;
            this.ManufacturerCompleted = null;
        }

        #endregion

        #region <<< Methods >>>

        #region < Methods for raising events >

        /// <summary>
        /// Raises the <see cref="ComplianceBase.Prompt"/> event.
        /// </summary>
        /// <param name="e">The <see cref="PromptEventArgs"/> to pass to the event.</param>
        protected virtual void OnPrompt(PromptEventArgs e)
        {
            this.Prompt?.Invoke(this, e);
        }

        /// <summary>
        /// Raises the <see cref="ComplianceBase.ModelingStarted"/> event.
        /// </summary>
        protected virtual void OnModelingStarted()
        {
            this.ModelingStarted?.Invoke(this, new ModelingEventArgs(this.State));
        }
        /// <summary>
        /// Raises the <see cref="ComplianceBase.ModelingStopped"/> event.
        /// </summary>
        protected virtual void OnModelingStopped()
        {
            this.ModelingStopped?.Invoke(this, new ModelingEventArgs(this.State));
        }
        /// <summary>
        /// Raises the <see cref="ComplianceBase.ModelingCompleted"/> event.
        /// </summary>
        protected virtual void OnModelingCompleted()
        {
            this.ModelingCompleted?.Invoke(this, new ModelingEventArgs(this.State));
        }
        /// <summary>
        /// Raises the <see cref="ComplianceBase.ModelingChanged"/> event.
        /// </summary>
        protected virtual void OnModelingChanged()
        {
            this.ModelingChanged?.Invoke(this, new ModelingEventArgs(this.State));
        }

        /// <summary>
        /// Raises the <see cref="ComplianceBase.ScenarioStarted"/> event.
        /// </summary>
        /// <remarks>
        /// The event data will include the contents of the current <see cref="Progress"/> property.
        /// </remarks>
        protected virtual void OnScenarioStarted()
        {
            this.ScenarioStarted?.Invoke(this, new ModelingProgressEventArgs(this.Progress));
        }
        /// <summary>
        /// Raises the <see cref="ComplianceBase.ScenarioCompleted"/> event.
        /// </summary>
        /// <remarks>
        /// The event data will include the contents of the current <see cref="Progress"/> property.
        /// </remarks>
        protected virtual void OnScenarioCompleted()
        {
            this.ScenarioCompleted?.Invoke(this, new ModelingProgressEventArgs(this.Progress));
        }

        /// <summary>
        /// Raises the <see cref="ComplianceBase.IterationStarted"/> event.
        /// </summary>
        /// <remarks>
        /// The event data will include the contents of the current <see cref="Progress"/> property.
        /// </remarks>
        protected virtual void OnIterationStarted()
        {
            this.IterationStarted?.Invoke(this, new ModelingProgressEventArgs(this.Progress));
        }
        /// <summary>
        /// Raises the <see cref="ComplianceBase.IterationCompleted"/> event.
        /// </summary>
        /// <remarks>
        /// The event data will include the contents of the current <see cref="Progress"/> property.
        /// </remarks>
        protected virtual void OnIterationCompleted()
        {
            this.IterationCompleted?.Invoke(this, new ModelingProgressEventArgs(this.Progress));
        }

        /// <summary>
        /// Raises the <see cref="ComplianceBase.ModelYearStarted"/> event.
        /// </summary>
        /// <remarks>
        /// The event data will include the contents of the current <see cref="Progress"/> property.
        /// </remarks>
        protected virtual void OnModelYearStarted()
        {
            this.ModelYearStarted?.Invoke(this, new ModelingProgressEventArgs(this.Progress));
        }
        /// <summary>
        /// Raises the <see cref="ComplianceBase.ModelYearCompleted"/> event.
        /// </summary>
        /// <remarks>
        /// The event data will include the contents of the current <see cref="Progress"/> property.
        /// </remarks>
        protected virtual void OnModelYearCompleted()
        {
            this.ModelYearCompleted?.Invoke(this, new ModelingProgressEventArgs(this.Progress));
        }

        /// <summary>
        /// Raises the <see cref="ComplianceBase.ManufacturerStarted"/> event.
        /// </summary>
        /// <remarks>
        /// The event data will include the contents of the current <see cref="Progress"/> property.
        /// </remarks>
        protected virtual void OnManufacturerStarted()
        {
            this.ManufacturerStarted?.Invoke(this, new ModelingProgressEventArgs(this.Progress));
        }
        /// <summary>
        /// Raises the <see cref="ComplianceBase.ManufacturerCompleted"/> event.
        /// </summary>
        /// <remarks>
        /// The event data will include the contents of the current <see cref="Progress"/> property.
        /// </remarks>
        protected virtual void OnManufacturerCompleted()
        {
            this.ManufacturerCompleted?.Invoke(this, new ModelingProgressEventArgs(this.Progress));
        }

        #endregion

        public abstract ICompliance CreateNew();

        public string Validate(IIndustry data, ModelingSettings settings)
        {
            string message = string.Empty;

            if (data == null) {
                message += "\n    The modeling data was not specified.";
            }
            else if (data.ManufacturerCount == 0 || data.VehicleCount == 0) {
                message += "\n    The specified modeling data does not contain any valid manufacturers or vehicles.";
            }

            if (settings == null) {
                message += "\n    The modeling settings were not specified.";
            }
            else {
                string[] missingSets = new string[4];
                int missingCntr = 0;
                if (!settings.HasTechnologies) { missingSets[missingCntr++] = "Technologies"; }
                if (!settings.HasParameters) { missingSets[missingCntr++] = "Parameters"; }
                if (!settings.HasScenarios) { missingSets[missingCntr++] = "Scenarios"; }

                if (missingCntr > 0) {
                    string messageSets = "";
                    for (int i = 0; i < missingCntr - 1; i++) {
                        messageSets += missingSets[i] + ", ";
                    }

                    if (missingCntr == 1) {
                        messageSets = missingSets[0];
                    }
                    else {
                        if (missingCntr == 2) {
                            messageSets = messageSets.Remove(messageSets.Length - 2, 2) + " ";
                        }
                        messageSets += "and " + missingSets[missingCntr - 1];
                    }
                    message += $"\n    The {messageSets} are missing from the modeling settings.";
                }
            }

            if (message == string.Empty) {
                message = this.CrossValidateModelingSettings(data, settings);
            }

            if (message == string.Empty) {
                message = this.CrossValidateVehiclesAndSimulationDB(data, settings);
            }

            if (message != string.Empty) {
                message = $"The following errors have occurred during initialization:\n" +
                          $"{message}\n\nThe model will not run.";
            }
            return message;
        }
        private string CrossValidateVehiclesAndSimulationDB(IIndustry data, ModelingSettings settings)
        {
            //----------------------------------------------------------------------------------//
            // Cross validate the initial technology states (tech-keys) of vehicles with the    //
            // technology combinations defined in the vehicle simulation database.              //
            // If a vehicle is using a technology key that is not found in the database, either //
            // the vehicle was not properly defined in the market data file, or the simulation  //
            // database is missing the required record. In either case, log an error and fail   //
            // validation to prevent the model from starting compliance simulation.             //
            //----------------------------------------------------------------------------------//
            string message = string.Empty;
            int errCount = 0;
            var vehs = data.CreateNew().Vehicles;

            string vehErrors = string.Empty;

            foreach (var veh in vehs) {
                if (SimulationDatabase.HasValue(veh.TechKey)) {
                    continue;
                }

                errCount++;
                string errorStr = GetErrorString(veh.BaselineInfo.Code, TI.KeyToString(veh.TechKey, ';'));

                if (this.LogWriter != null) {
                    this.LogWriter.LogError(errorStr);
                }
                else if (errCount <= 10) {
                    vehErrors += "\n" + errorStr;
                }
            }

            if (errCount > 0) {
                message = $"    {errCount} vehicle(s) have initial technology keys not found in the " +
                          $"vehicle simulation database." + vehErrors;
            }
            if (errCount > 10) {
                message += "\n    ...";
            }

            return message;

            string GetErrorString(int vehCode, string techKey)
            {
                return $"    - Vehicle {vehCode} is using a technology combination that was not found in the " +
                    $"simulation database:  {techKey}";
            }
        }
        private string CrossValidateModelingSettings(IIndustry data, ModelingSettings settings)
        {
            var analysisStartYear = settings.RuntimeSettings.AnalysisStartYear;
            List<string> errors = new List<string>();

            int maxCyHistoricVmtData = settings.Parameters.EconomicValues.VMTModelParameters.MaxCY + 1;
            if (settings.RuntimeSettings.DynamicEconomicModeling && maxCyHistoricVmtData < analysisStartYear) {
                errors.Add(GetErrorString("\"VMT Model Parameters\"", "Parameters", "at least"));
            }

            int maxCyHistoricFleetData = settings.Parameters.HistoricFleetData.MaxMY + 1;
            if (maxCyHistoricFleetData != analysisStartYear) {
                errors.Add(GetErrorString("\"Historic Fleet Data\"", "Parameters", "exactly"));
            }

            if (settings.RuntimeSettings.AllowCreditTrading) {
                int cafeBankMaxMY = data.Manufacturers[0].CAFEBank.MaxYear + 1;
                if (cafeBankMaxMY < analysisStartYear) {
                    errors.Add(GetErrorString("\"CAFE Credit Bank\"", "Market Data", "at least"));
                }

                int co2BankMaxMY = data.Manufacturers[0].CO2Bank.MaxYear + 1;
                if (co2BankMaxMY < analysisStartYear) {
                    errors.Add(GetErrorString("\"CO-2 Credit Bank\"", "Market Data", "at least"));
                }
            }

            return errors.Count == 0 ? string.Empty : ConstructErrorMessage();

            string GetErrorString(string parameter, string inputFile, string equality)
            {
                return $"The {parameter} in the {inputFile} file must be defined up to " +
                    $"{equality} {analysisStartYear - 1}."; ;
            }

            string ConstructErrorMessage()
            {
                string message =
                    $"\nThe analysis start year (MY-{analysisStartYear}) defined in the runtime settings " +
                    $"conflicts with the following input(s):";

                for (int i = 0; i < errors.Count; i++) {
                    message += $"\n    - {errors[i]}";
                }

                return message;
            }
        }

        public void Start(IIndustry data, ModelingSettings settings)
        {
            if (this.Running || this.State == ModelingState.StartRequested) {
                // the model is already running -- exit
                string errString = "The compliance modeling process has already started at: " +
                    this.StartTime.ToString("M/dd/yyyy h:mm:ss tt") + " -- exiting ...";
                this.LogWriter?.LogError(errString);
                this.OnPrompt(new PromptEventArgs("Compliance Model Error", errString, PromptOption.Ok));
                return;
            }

            SystemInfo.SetDefaultCulture(); // ensures infinity symbols are printed correctly

            this.StartTime = new DateTime(0);
            this.StopTime = new DateTime(0);
            this.UpdateState(ModelingState.StartRequested);

            // validate model data and settings
            string validatedString = this.Validate(data, settings);
            if (validatedString != string.Empty) {
                var logWriter = this.CreateLogWriter(settings);
                logWriter.LogError(new string('-', 25));
                logWriter.LogError(validatedString);
                logWriter.LogError(new string('-', 25));

                this.OnPrompt(new PromptEventArgs("Compliance Model Error", validatedString, PromptOption.Ok));
                this.UpdateState(ModelingState.Stopped);

                return;
            }

            this.Data = settings.RuntimeSettings.ExcludeBevFcvFromAnalysis
                      ? data.CreateNewWithoutBevFcv()
                      : data;
            this.Settings = settings;
            this.LogWriter = this.CreateLogWriter();
            this.ReportGenerator = this.CreateReportGenerator();

            this._scen = null;
            this._iteration = null;
            this._year = null;
            this._mfr = null;
            this._effectsYear = 0;

            this.MaxYear = this.RuntimeSettings.AnalysisEndYear;
            this.MinYear = this.RuntimeSettings.AnalysisStartYear;
            this.MinHistoricYear = Math.Min(this.MinYear, this.Settings.Parameters.HistoricFleetData.MinMY);
            this.EffectsYearCount = this.MaxYear - this.MinHistoricYear + 1;
            this.YearCount = this.MaxYear - this.MinYear + 1;
            this.ScenCount = this.Settings.Scenarios.ScenarioCount;

            // initialize the model data for baseline and alternative scenario
            this._baseData = null;
            this._scenData = null;
            this._baseEffects = new AnnualValue<ModelingEffects>(this.MinHistoricYear, this.MaxYear);
            this._scenEffects = new AnnualValue<ModelingEffects>(this.MinHistoricYear, this.MaxYear);

            // start modeling in a new thread ...
            this.Start();
        }
        private void Start()
        {
            this.LogWriter.InitCfLogs(this.ScenCount, this.Data.ManufacturerCount);
            this.LogWriter.InitCreditTradingLogs(this.ScenCount);

            // start the compliance modeling process in a new thread
            this.StopTime = DateTime.MinValue;
            this.StartTime = DateTime.Now;
            this._complianceThread = new Thread(this.StartMainComplianceThread)
            {
                Name = "MainComplianceThread"
            };
            this._complianceThread.Start();
        }
        private void StartMainComplianceThread()
        {
            this.UpdateState(ModelingState.Running);
            ModelingState endState = ModelingState.Unstarted; // the end state of the model (stopped or completed)
            string modelType = "<UNNAMED-MODEL>";
            string modelDescription = "N/A";
            try
            {   // get the title from a description attribute of the current compliance model
                var mda = (ModelDescriptionAttribute)Attribute.GetCustomAttribute(
                    this.GetType(), typeof(ModelDescriptionAttribute));
                modelType = mda.Title;
                modelDescription = mda.Description;

                // start the compliance modeling process
                this.LastErrorMessage = null;
                this.LastException = null;
                this.LogWriter.LogSummary($"{modelType} started at {DateTime.Now}.");
                this.StartInternal();
                endState = (this.StopRequested) ? ModelingState.Stopped : ModelingState.Completed;
            }
            catch (Exception ex)
            {   // an exception has occured -- set the state as stopped
                this.LastErrorMessage = ex.Message;
                this.LastException = ex;
                this.LogWriter.LogError(
                    $"The {modelType} has stopped unexpectedly.\n" +
                    $"Runtime state: scen={this._scen?.Index}; iter={this._iteration} " +
                    $"year={this._year?.Year}; mfr={this._mfr?.ProperName}\n");
                this.LogWriter.LogError(ex.ToString());
                endState = ModelingState.Stopped;
            }
            finally
            {
                var summaryLog = this.LogWriter.Summary;
                if (summaryLog != null) {
                    summaryLog.WriteLine(new string('=', 50));
                    summaryLog.WriteLine("---------- User Settings ----------");
                    summaryLog.WriteLine("  Machine Name: " + SystemInfo.MachineName);
                    summaryLog.WriteLine("  OS Version:   " + SystemInfo.OSVersion);
                    summaryLog.WriteLine("  CLR Version:  " + SystemInfo.CLRVersion);
                    summaryLog.WriteLine("  User Name:    " + Environment.UserName);
                    summaryLog.WriteLine("---------- CAFE Model Info ----------");
                    summaryLog.WriteLine("  Product Version: " + ApplicationInfo.ProductVersion);
                    summaryLog.WriteLine("  Build Version:   " + ApplicationInfo.ProductBuildVersion);
                    summaryLog.WriteLine("  Build Date:      " + ApplicationInfo.ProductBuildDate);
                    summaryLog.WriteLine(new string('=', 50));
                    summaryLog.WriteLine("---------- General Settings ----------");
                    summaryLog.WriteLine("  ModelType=" + modelType);
                    summaryLog.WriteLine("  ModelDescription=" + modelDescription);
                    summaryLog.WriteLine("  UserNotes=" + this.UserNotes);
                    summaryLog.WriteLine("  UserKeywords=" + this.UserKeywords);

                    this.Settings.WriteSummaryLog(summaryLog);
                    this.Data.WriteSummaryLog(summaryLog);

                    summaryLog.WriteLine(new string('=', 50));
                    summaryLog.WriteLine($"{modelType} {endState} at {DateTime.Now} in {this.Runtime / 1000} sec.");
                }
                this.UpdateState(endState);
            }
        }

        /// <summary>
        /// This method is called by the <see cref="Start(IIndustry, ModelingSettings)"/> method and must be overridden
        /// by a subclass. This method always begins executing in a new thread. When implemented by a subclass, starts
        /// the actual compliance modeling process.
        /// </summary>
        protected abstract void StartInternal();

        public void WaitForCompletion()
        {
            this._complianceThread?.Join();
        }

        public void Stop()
        {
            this.Stop(false);
        }
        public void Stop(bool stopWhenReady)
        {
            if (this.State == ModelingState.Running || (!stopWhenReady && this.State == ModelingState.StopRequested)) {
                // set modeling state
                if (this.State != ModelingState.StopRequested) {
                    this.UpdateState(ModelingState.StopRequested);
                    this.ReportGenerator?.CancelReporting();
                }
                // begin stopping the compliance modeling process
                this.StopInternal(stopWhenReady);
            }
        }

        /// <summary>
        /// This method is called by the <see cref="Stop(bool)"/> method and can be implemented by a subclass.
        /// </summary>
        /// <param name="stopWhenReady">If true, specifies that the pending operations will be completed prior to
        ///     stopping the modeling process; otherwise, modeling will be terminated immediately.</param>
        /// <remarks>
        /// When implemented by a subclass, aborts all compliance threads and terminates the compliance modeling
        /// process, optionally waiting for the pending operations to complete. When overriding, sub-classes should
        /// still call "base.StopInternal(stopWhenReady)" to ensure desired behavior.
        /// </remarks>
        protected virtual void StopInternal(bool stopWhenReady)
        {
            if (!stopWhenReady) {
                try {
                    // attempt to stop the compliance thread
                    if (this._complianceThread != null && this._complianceThread.IsAlive) {
                        this._complianceThread.Abort();
                        this._complianceThread = null;
                    }
                }
                catch (Exception ex) {
                    this.LogWriter?.LogError("Failed to terminate thread: " +
                        ((this._complianceThread == null) ? "null" : this._complianceThread.Name) +
                        "\nException: " + ex.Message);
                }

                // set the modeling state as stopped
                this.UpdateState(ModelingState.Stopped);
            }
        }

        public IIndustry GetData(Scenario scen)
        {
            if (scen == null) {
                return null;
            }
            if (scen.IsBaseline) {
                return this._baseData;
            }
            if (scen.Index == this._scen?.Index) {
                return this._scenData;
            }

            // TODO: obtain from cache
            return null;
        }

        public IAnnualValue<ModelingEffects> GetEffectsData(Scenario scen)
        {
            if (scen == null) {
                return null;
            }
            if (scen.IsBaseline) {
                return this._baseEffects;
            }
            if (scen.Index == this._scen?.Index) {
                return this._scenEffects;
            }

            // TODO: obtain from cache
            return null;
        }

        /// <summary>
        /// Creates a new <see cref="ILogger"/> instance to use for saving the log files generated during compliance
        /// modeling.
        /// </summary>
        /// <returns>
        /// A new <see cref="ILogger"/> instance to use for saving the log files generated during compliance modeling.
        /// An <see cref="EmptyLogger"/> will be returned if logging should be disabled.
        /// </returns>
        protected virtual ILogger CreateLogWriter()
        {
            return this.CreateLogWriter(this.Settings);
        }

        private ILogger CreateLogWriter(ModelingSettings settings)
        {
            var ioSettings = settings?.IOSettings;
            return (ioSettings == null)
                ? LoggerFactory.GetEmptyLogger()
                : LoggerFactory.CreateLogger(ioSettings.LogLevel, ioSettings.LogPath, ioSettings.DebugLogPath);
        }

        /// <summary>
        /// Creates a new <see cref="IReporter"/> instance to use for generating modeling reports during compliance
        /// modeling.
        /// </summary>
        /// <returns>
        /// A new <see cref="IReporter"/> instance to use for generating modeling reports during compliance modeling.
        /// An <see cref="EmptyReporter"/> will be returned if reporting should be disabled.
        /// </returns>
        protected virtual IReporter CreateReportGenerator()
        {
            return this.CreateReportGenerator(this.Settings);
        }

        private IReporter CreateReportGenerator(ModelingSettings settings)
        {
            var ioSettings = settings?.IOSettings;
            return (ioSettings == null)
                ? ReporterFactory.GetEmptyReporter()
                : ReporterFactory.CreateReporter(ioSettings.ReportingLevel, $"{ioSettings.OutputPath}\\reports-csv");
        }

        /// <summary>
        /// Initializes the "working" compliance modeling data object for the current scenario/iteration.
        /// </summary>
        protected void InitializeModelDataForNewRun()
        {
            if (this._scen == null) {
                return;
            }

            this._scenData = this.Data.CreateNew();
            if (this._scen.IsBaseline) {
                this._baseData = this._scenData;
            }
        }

        /// <summary>
        /// Sets the effects modeling data for the current scenario and the specified model year.
        /// </summary>
        /// <param name="year">The model year for which to set the effects modeling data.</param>
        /// <param name="data">The effects data to set.</param>
        protected void SetEffectsData(int year, ModelingEffects data)
        {
            if (this._scen == null) {
                return;
            }

            this._scenEffects[year] = data;
            if (this._scen.IsBaseline) {
                this._baseEffects[year] = data;
            }
        }

        /// <summary>
        /// Updates the modeling <see cref="State"/> to the specified value and raises the necessary events.
        /// </summary>
        /// <param name="newState">The new state of the modeling process.</param>
        protected void UpdateState(ModelingState newState)
        {
            bool isStopped = (newState == ModelingState.Stopped);
            bool isCompleted = (newState == ModelingState.Completed);

            if (isStopped || isCompleted) {
                if (this.StartTime.Ticks != 0) {
                    this.StopTime = DateTime.Now;
                }
                this.LogWriter?.Close();
                this.ReportGenerator?.Close();
            }

            if (isCompleted && this.IOSettings.ReportingLevel > ReportingLevel.Disabled
                            && this.IOSettings.CompressModelOutputs) {
                try {
                    this.CompressingOutputs = true;
                    this.ModelingChanged?.Invoke(this, new ModelingEventArgs(ModelingState.CompressOutputs));

                    string zipFile = this.IOSettings.OutputZipFile;
                    Directory.CreateDirectory(Path.GetDirectoryName(zipFile));
                    if (File.Exists(zipFile)) {
                        File.Delete(zipFile);
                    }

                    ZipFile.CreateFromDirectory(this.IOSettings.OutputPath, zipFile, CompressionLevel.Optimal, true);
                }
                finally {
                    this.CompressingOutputs = false;
                }
            }

            // set new state and raise events
            this.State = newState;

            if (this.State == ModelingState.Running) {
                this.OnModelingStarted();
            }
            else if (this.State == ModelingState.Stopped) {
                this.OnModelingStopped();
            }
            else if (this.State == ModelingState.Completed) {
                this.OnModelingCompleted();
            }

            this.OnModelingChanged();
        }

        #endregion

        #region <<< Properties >>>

        public string UserNotes { get; set; }
        public string UserKeywords { get; set; }

        public IIndustry Data { get; private set; }
        public ModelingSettings Settings { get; private set; }

        /// <summary>Gets the runtime settings used by the compliance model.</summary>
        protected RuntimeSettings RuntimeSettings => this.Settings.RuntimeSettings;
        /// <summary>Gets the I/O settings used by the compliance model.</summary>
        protected IOSettings IOSettings => this.Settings.IOSettings;

        /// <summary>
        /// Gets the technologies defined within the <see cref="ModelingSettings"/> object used by the compliance model.
        /// </summary>
        protected Technologies Technologies => this.Settings.Technologies;
        /// <summary>
        /// Gets the parameters defined within the <see cref="ModelingSettings"/> object used by the compliance model.
        /// </summary>
        protected Parameters Parameters => this.Settings.Parameters;
        /// <summary>
        /// Gets the scenarios defined within the <see cref="ModelingSettings"/> object used by the compliance model.
        /// </summary>
        protected Scenarios Scenarios => this.Settings.Scenarios;

        public ILogger LogWriter { get; private set; }
        public IReporter ReportGenerator { get; private set; }

        public ModelingState State { get; private set; }

        public bool StartRequested => (this.State == ModelingState.StartRequested);
        public bool Running => (this.State == ModelingState.Running || this.State == ModelingState.StopRequested);
        public bool StopRequested => (this.State == ModelingState.StopRequested);
        public bool Stopped => (this.State == ModelingState.Stopped);
        public bool Completed => (this.State == ModelingState.Completed);
        public bool StoppedOrCompleted => (this.Stopped || this.Completed);

        /// <summary>Gets whether that model outputs are currently being compressed.</summary>
        public bool CompressingOutputs { get; private set; }

        public abstract IModelingProgress Progress { get; }

        public DateTime StartTime { get; private set; }
        public DateTime StopTime { get; private set; }

        public long Runtime
        {
            get {
                long startTicks = this.StartTime.Ticks;
                long stopTicks = (this.StoppedOrCompleted) ? this.StopTime.Ticks : DateTime.Now.Ticks;
                return (startTicks == 0) ? 0 : (stopTicks - startTicks) / 10000;
            }
        }

        /// <summary>Gets the total number of scenarios available for modeling.</summary>
        public int ScenCount { get; private set; }
        /// <summary>Gets the total number of model years available for modeling.</summary>
        public int YearCount { get; private set; }
        /// <summary>Gets the total number of years available for effects modeling.</summary>
        public int EffectsYearCount { get; private set; }

        public int MinHistoricYear { get; private set; }
        public int MinYear { get; private set; }
        public int MaxYear { get; private set; }

        public string LastErrorMessage { get; private set; }
        public Exception LastException { get; private set; }

        #endregion

        #region <<< Variables >>>

        //----- progress variables -----
        /// <summary>
        /// Specifies the scenario currently being analyzed, or null, if the scenario loop hasn't started yet.
        /// </summary>
        protected Scenario _scen;
        /// <summary>
        /// Specifies the iteration currently being analyzed, or null, if the iteration loop hasn't started yet.
        /// </summary>
        protected int? _iteration;
        /// <summary>
        /// Specifies the model year currently being analyzed, or null, if the model year loop hasn't started yet.
        /// </summary>
        protected ModelYear _year;
        /// <summary>
        /// Specifies the manufacturer currently being analyzed, or null, if the manufacturer loop hasn't started yet.
        /// </summary>
        protected IManufacturer _mfr;
        /// <summary>
        /// Specifies the effects year currently being analyzed, or -1, if effects processing hasn't started yet.
        /// </summary>
        protected int _effectsYear;

        //----- threading support variables -----
        [NonSerialized]
        private Thread _complianceThread;

        //----- model year data variables -----
        private IIndustry _baseData;
        private IIndustry _scenData;
        private AnnualValue<ModelingEffects> _baseEffects;
        private AnnualValue<ModelingEffects> _scenEffects;

        #endregion
    }
}
