#region << Using Directives >>
using System;
using System.Collections.Generic;
using System.Threading;
using Volpe.Cafe.Data;
using Volpe.Cafe.IO;
using Volpe.Cafe.IO.Reporting.CSV;
using Volpe.Cafe.IO.Reporting.XL;
using Volpe.Cafe.Settings;
using Volpe.Cafe.UI;
using Volpe.Cafe.Utils;
#endregion
namespace Volpe.Cafe.Model
{
    [Serializable]
    [ModelDescription("ComplianceBase", "Provides an abstract base class for the compliance model implementation.", 1.2F)]
    public abstract class ComplianceBase : ICompliance
    {
        #region 
        #region 
        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 ModelYearStarted;
        public event ModelingProgressEventHandler ModelYearCompleted;
        public event ModelingProgressEventHandler ManufacturerStarted;
        public event ModelingProgressEventHandler ManufacturerCompleted;
        #endregion
        #endregion
        #region 
        public ComplianceBase()
        {
            this._state = ModelingState.Unstarted;
            this._abortRequested = false;
        }
        ~ComplianceBase()
        {
            this.Abort(false);
            this.Prompt                = null;
            this.ModelingStarted       = null;
            this.ModelingStopped       = null;
            this.ModelingCompleted     = null;
            this.ModelingChanged       = null;
            this.ScenarioStarted       = null;
            this.ScenarioCompleted     = null;
            this.ModelYearStarted      = null;
            this.ModelYearCompleted    = null;
            this.ManufacturerStarted   = null;
            this.ManufacturerCompleted = null;
        }
        #endregion
        #region 
        #region 
        protected virtual void OnPrompt(PromptEventArgs e)
        {
            if (this.Prompt != null)
            {
                this.Prompt(this, e);
            }
        }
        protected virtual void OnModelingStarted()
        {
            if (this.ModelingStarted != null)
            {
                this.ModelingStarted(this, new ModelingEventArgs(this._state));
            }
        }
        protected virtual void OnModelingStopped()
        {
            if (this.ModelingStopped != null)
            {
                this.ModelingStopped(this, new ModelingEventArgs(this._state));
            }
        }
        protected virtual void OnModelingCompleted()
        {
            if (this.ModelingCompleted != null)
            {
                this.ModelingCompleted(this, new ModelingEventArgs(this._state));
            }
        }
        protected virtual void OnModelingChanged()
        {
            if (this.ModelingChanged != null)
            {
                this.ModelingChanged(this, new ModelingEventArgs(this._state));
            }
        }
        protected virtual void OnScenarioStarted()
        {
            if (this.ScenarioStarted != null)
            {
                this.ScenarioStarted(this, new ModelingProgressEventArgs(this.Progress));
            }
        }
        protected virtual void OnScenarioCompleted()
        {
            if (this.ScenarioCompleted != null)
            {
                this.ScenarioCompleted(this, new ModelingProgressEventArgs(this.Progress));
            }
        }
        protected virtual void OnModelYearStarted()
        {
            if (this.ModelYearStarted != null)
            {
                this.ModelYearStarted(this, new ModelingProgressEventArgs(this.Progress));
            }
        }
        protected virtual void OnModelYearCompleted()
        {
            if (this.ModelYearCompleted != null)
            {
                this.ModelYearCompleted(this, new ModelingProgressEventArgs(this.Progress));
            }
        }
        protected virtual void OnManufacturerStarted()
        {
            if (this.ManufacturerStarted != null)
            {
                this.ManufacturerStarted(this, new ModelingProgressEventArgs(this.Progress));
            }
        }
        protected virtual void OnManufacturerCompleted()
        {
            if (this.ManufacturerCompleted != null)
            {
                this.ManufacturerCompleted(this, new ModelingProgressEventArgs(this.Progress));
            }
        }
        #endregion
        public abstract ICompliance CreateNew();
        public void Start(Industry data, ModelingSettings settings)
        {
            if (this.Running || this._state == ModelingState.StartRequested)
            {   
                string errString = "The compliance modeling process has already started at: " +
                    this._startTime.ToString("M/dd/yyyy h:mm:ss tt") + " -- exiting ...";
                this._logWriter.WriteErrorLine(errString);
                this.OnPrompt(new PromptEventArgs("CAFE Model Error", errString, PromptOption.Ok));
                return;
            }
            this._startTime = new DateTime(0);
            this.UpdateState(ModelingState.StartRequested);     
            this.OverrideUserSettings(settings);
            if (settings.OperatingModes.MergedFleet) { data = Industry.BuildMergedFleet(data); }
            this._data      = data;
            this._settings  = settings;
            if (this._settings != null)
            {   
                if (this._settings.Scenarios != null) { this._scenCount = this._settings.Scenarios.Count; }
            }
            if (this._data != null)
            {
                this._minYear      = this._data.MinYear.Year;
                this._maxYear      = this._data.MaxYear.Year;
                this._minYearIndex = this._data.MinYear.Index;
                this._maxYearIndex = this._data.MaxYear.Index;
                this._yearCount    = this._maxYear - this._minYear + 1;
                this._vehCount     = this._data.VehicleCount;
                this._mfrCount     = this._data.ManufacturerCount;
                this._mfrVehCount  = new int   [this._mfrCount];
                this._mfrNames     = new string[this._mfrCount];
                for (int i = 0; i < this._mfrCount; i++)
                {
                    Manufacturer mfr = this._data.Manufacturers[i];
                    this._mfrVehCount[i] = mfr.VehicleCount;
                    this._mfrNames   [i] = Interaction.GetTitleCase(mfr.Description.Name, 4);
                }
                this._modelYears = new ModelYear[this._yearCount];
                for (int i = this._minYear; i <= this._maxYear; i++) { this._modelYears[i - this._minYear] = new ModelYear(i); }
                this._baseData = new Industry[this._maxYearIndex + 1];
                this._scenData = new Industry[this._maxYearIndex + 1];
                this._minEffectsYear = 1;
                this._maxEffectsYear = 0;
                if (this._settings != null)
                {
                    if (this._settings.OperatingModes.FleetAnalysis)
                    {
                        this._minEffectsYear = this._settings.Parameters.FuelEconomyData.MinMY;
                        this._maxEffectsYear = this._settings.Parameters.FuelEconomyData.MaxMY;
                    }
                    else
                    {
                        this._minEffectsYear = this._minYear;
                        this._maxEffectsYear = this._maxYear;
                    }
                }
                int effectsYears = this._maxEffectsYear - this._minEffectsYear + 1;
                this._baseEffects = new EffectsData[effectsYears];
                this._scenEffects = new EffectsData[effectsYears];
            }
            if (settings.OutputSettings.DisableLogWriter) { this._logWriter = LogWriter.Empty; }
            else { this._logWriter = this.CreateLogWriter(); }
            if (!settings.OutputSettings.GenerateXLReports) { this._xlReportGenerator = XlReportGenerator.Empty; }
            else { this._xlReportGenerator = this.CreateXlReportGenerator(); }
            if (!settings.OutputSettings.GenerateCSVReports) { this._csvReportGenerator = CsvReportGenerator.Empty; }
            else { this._csvReportGenerator = this.CreateCsvReportGenerator(); }
            string validatedString = this.Validate();
            this._valid = (validatedString == string.Empty);
            if (!this._valid)
            {   
                this._logWriter.WriteErrorLine(new string('-', 25));
                this._logWriter.WriteErrorLine(validatedString);
                this._logWriter.WriteErrorLine(new string('-', 25));
                this.OnPrompt(new PromptEventArgs("CAFE Model Error", validatedString, PromptOption.Ok));
                this.UpdateState(ModelingState.Stopped);    
                return;
            }
            this.Start();
        }
        void Start()
        {
            if (!this._logWriter.IsOpen)
            {
                this._logWriter.Open(false, this._scenCount);
            }
            this._complianceThread = new Thread(new ThreadStart(this.StartMainComplianceThread));
            this._complianceThread.Name = "MainComplianceThread";
            this._startTime = DateTime.Now;
            this._abortRequested = false;
            this.UpdateState(ModelingState.Running);                            
            this._complianceThread.Start();
        }
        void StartMainComplianceThread()
        {
            ModelingState endState = ModelingState.Unstarted;   
            string modelType = "<UNNAMED-MODEL>";
            string modelDescription = "N/A";
            try
            {   
                ModelDescriptionAttribute mda = (ModelDescriptionAttribute)Attribute.GetCustomAttribute(this.GetType(),
                    typeof(ModelDescriptionAttribute));
                modelType        = mda.Title;
                modelDescription = mda.Description;
                this._lassErrorMessage = null;
                this._logWriter.WriteSummaryLine(modelType + " started at " + DateTime.Now + ".");
                this.StartInternal();
                endState = (this._abortRequested) ? ModelingState.Stopped : ModelingState.Completed;
            }
            catch (Exception ex)
            {   
                this._lassErrorMessage = ex.Message;
                this._logWriter.WriteErrorLine(ex.ToString());
                endState = ModelingState.Stopped;
            }
            finally
            {
                this._logWriter.WriteSummaryLine(new string('=', 50));
                this._logWriter.WriteSummaryLine("---------- User Settings ----------");
                this._logWriter.WriteSummaryLine("  Machine Name: " + SystemInfo.MachineName);
                this._logWriter.WriteSummaryLine("  OS Version:   " + SystemInfo.OSVersion);
                this._logWriter.WriteSummaryLine("  CLR Version:  " + SystemInfo.CLRVersion);
                this._logWriter.WriteSummaryLine("  CAFE Version: " + ApplicationInfo.ApplicationVersion + " (" + ApplicationInfo.ApplicationTimestamp + ")");
                this._logWriter.WriteSummaryLine("  User Name:    " + System.Environment.UserName);
                this._logWriter.WriteSummaryLine(new string('=', 50));
                this._logWriter.WriteSummaryLine("---------- General Settings ----------");
                this._logWriter.WriteSummaryLine("  ModelType="        + modelType);
                this._logWriter.WriteSummaryLine("  ModelDescription=" + modelDescription);
                this._logWriter.WriteSummaryLine("  UserNotes="        + this.UserNotes);
                this._logWriter.WriteSummaryLine("  UserKeywords="     + this.UserKeywords);
                if (this._settings != null)
                {
                    this._settings.WriteSummaryLog(this._logWriter);
                }
                if (this._data != null)
                {
                    this._data.WriteSummaryLog(this._logWriter);
                }
                this._logWriter.WriteSummaryLine(new string('=', 50));
                this._logWriter.WriteSummaryLine(modelType + " " + endState + " at " + DateTime.Now + " in " +
                    (this.Runtime / 1000) + " sec.");
                this.UpdateState(endState);
            }
        }
        protected abstract void StartInternal();
        public void Abort()
        {
            this.Abort(false);
        }
        public void Abort(bool abortWhenReady)
        {
            if (this._state == ModelingState.Running || (!abortWhenReady && this._state == ModelingState.StopRequested))
            {   
                if (this._state != ModelingState.StopRequested) { this.UpdateState(ModelingState.StopRequested); }
                this._abortRequested = true;
                this.AbortInternal(abortWhenReady);
            }
        }
        protected virtual void AbortInternal(bool abortWhenReady)
        {
            if (!abortWhenReady)
            {   
                try
                {
                    if (this._complianceThread != null && this._complianceThread.IsAlive)
                    {
                        this._complianceThread.Abort();
                        this._complianceThread = null;
                    }
                }
                catch (Exception ex)
                {
                    this._logWriter.WriteErrorLine("Failed to terminate thread: " +
                        ((this._complianceThread == null) ? "null" : this._complianceThread.Name) + "\nException: " + ex.Message);
                }
                this.UpdateState(ModelingState.Stopped);
            }
        }
        protected virtual LogWriter CreateLogWriter()
        {
            return new LogWriter(
                this._settings.OutputSettings.OutputPath + "\\logs",
                this._settings.OutputSettings.WriteLogFiles,
                this._settings.OutputSettings.WriteExtendedLogFiles);
        }
        protected virtual XlReportGenerator CreateXlReportGenerator()
        {
            return XlReportGenerator.Empty;
        }
        protected virtual CsvReportGenerator CreateCsvReportGenerator()
        {
            return new CsvReportGenerator(this);
        }
        public Industry[] GetData(Scenario scen)
        {
            if (scen.IsBaseline) { return this._baseData; }
            if (this._scen == null) { return null; }
            if (this._scen.Index != scen.Index)
            {
                return null;
            }
            return this._scenData;
        }
        public Industry GetData(Scenario scen, ModelYear year)
        {
            Industry[] scenData = this.GetData(scen);
            return (scenData != null && year.Index - this._minYearIndex < this._yearCount) ? scenData[year.Index] : null;
        }
        protected void SetActiveScenarioData(ModelYear year, Industry data)
        {
            if (this._scen != null)
            {
                this._scenData[year.Index] = data;
                if (this._scen.IsBaseline) { this._baseData[year.Index] = data; }
            }
        }
        protected void ResetActiveScenarioData()
        {
            if (this._scen != null)
            {
                Array.Clear(this._scenData, 0, this._scenData.Length);
                if (this._scen.IsBaseline) { Array.Clear(this._baseData, 0, this._baseData.Length); }
            }
        }
        public EffectsData GetEffectsData(Scenario scen, int year)
        {
            EffectsData[] ed;
            if      (null       == this._scen      ) { ed = null;              } 
            else if (scen.Index == 0               ) { ed = this._baseEffects; } 
            else if (scen.Index == this._scen.Index) { ed = this._scenEffects; } 
            else                                     { ed = null;              } 
            if (ed == null || year < this._minEffectsYear || year > this._maxEffectsYear) { return null; }
            return ed[year - this._minEffectsYear];
        }
        protected void SetEffectsData(int year, EffectsData data)
        {
            if (this._scen != null)
            {
                int index = year - this._minEffectsYear;
                this._scenEffects[index] = data;
                if (this._scen.IsBaseline) { this._baseEffects[index] = data; }
            }
        }
        protected void UpdateState(ModelingState newState)
        {
            this._state = newState;
            if (this.StoppedOrCompleted)
            {   
                if (this._startTime.Ticks != 0) { this._stopTime = DateTime.Now; }
                this._logWriter.Close();            
                this._xlReportGenerator  = null;    
                this._csvReportGenerator = null;    
            }
            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();
        }
        protected virtual string Validate()
        {
            string message = string.Empty;
            if ((this.RequiredModelInputs & RequiredModelInputs.MarketData) == RequiredModelInputs.MarketData)
            {
                if (this._data == null)
                {
                    message += "\n    The modeling data was not specified.";
                }
                else if (this._data.ManufacturerCount == 0 || this._data.VehicleCount == 0)
                {
                    message += "\n    The specified modeling data does not contain any valid manufacturers or vehicles.";
                }
            }
            if (this._settings == null)
            {
                message += "\n    The modeling settings were not specified.";
            }
            else
            {
                string[] missingSets = new string[4];
                int missingCntr = 0;
                if ((this.RequiredModelInputs & RequiredModelInputs.Technologies) == RequiredModelInputs.Technologies &&
                    (this._settings.Technologies == null || this._settings.Technologies.Count == 0)) { missingSets[missingCntr++] = "Technologies"; }
                if ((this.RequiredModelInputs & RequiredModelInputs.Parameters) == RequiredModelInputs.Parameters &&
                    this._settings.Parameters == null) { missingSets[missingCntr++] = "Parameters"; }
                if ((this.RequiredModelInputs & RequiredModelInputs.Scenarios) == RequiredModelInputs.Scenarios &&
                    (this._settings.Scenarios == null || this._settings.Scenarios.Count == 0)) { 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 = "The following errors have occured during initialization:\n" + message + "\n\nThe model will not run.";
            }
            return message;
        }
        protected virtual void OverrideUserSettings(ModelingSettings settings) { }
        #endregion
        #region 
        #region 
        public bool IsValid { get { return this._valid; } }
        public virtual RequiredModelInputs RequiredModelInputs { get { return RequiredModelInputs.All; } }
        public Industry Data { get { return this._data; } }
        public Industry[] BaselineData { get { return this._baseData; } }
        public Industry[] ScenarioData { get { return this._scenData; } }
        public ModelingSettings Settings { get { return this._settings; } }
        public LogWriter LogWriter { get { return this._logWriter; } }
        public XlReportGenerator XlReportGenerator { get { return this._xlReportGenerator; } }
        public CsvReportGenerator CsvReportGenerator { get { return this._csvReportGenerator; } }
        public ModelingState State { get { return this._state; } }
        public bool Running { get { return (this._state == ModelingState.Running || this._state == ModelingState.StopRequested); } }
        public bool Stopped { get { return (this._state == ModelingState.Stopped); } }
        public bool Completed { get { return (this._state == ModelingState.Completed); } }
        public bool StoppedOrCompleted { get { return (this._state == ModelingState.Stopped || this._state == ModelingState.Completed); } }
        public abstract IModelingProgress Progress { get; }
        public DateTime StartTime { get { return this._startTime; } }
        public DateTime StopTime { get { return (this.StoppedOrCompleted) ? this._stopTime : DateTime.MinValue; } }
        public long Runtime { get { return (this._startTime.Ticks == 0) ? 0 :
            (((this.StoppedOrCompleted) ? this._stopTime.Ticks : DateTime.Now.Ticks) - this._startTime.Ticks) / 10000; } }
        public ModelYear[] ModelYears { get { return this._modelYears; } }
        #endregion
        protected bool AbortRequested { get { return this._abortRequested; } }
        public int ScenCount { get { return this._scenCount; } }
        public int YearCount { get { return this._yearCount; } }
        public int MinYear { get { return this._minYear; } }
        public int MaxYear { get { return this._maxYear; } }
        public int MinYearIndex { get { return this._minYearIndex; } }
        public int MaxYearIndex { get { return this._maxYearIndex; } }
        public int MinEffectsYear { get { return this._minEffectsYear; } }
        public int MaxEffectsYear { get { return this._maxEffectsYear; } }
        public int VehCount { get { return this._vehCount; } }
        public int MfrCount { get { return this._mfrCount; } }
        public int[] MfrVehCount { get { return this._mfrVehCount; } }
        public string[] MfrNames { get { return this._mfrNames; } }
        public string LastErrorMessage { get { return this._lassErrorMessage; } }
        public string UserNotes { get; set; }
        public string UserKeywords { get; set; }
        #endregion
        #region 
        bool               _valid;
        LogWriter          _logWriter;
        XlReportGenerator  _xlReportGenerator;
        CsvReportGenerator _csvReportGenerator;
        Industry           _data;
        ModelingSettings   _settings;
        ModelingState      _state;
        DateTime           _startTime;
        DateTime           _stopTime;
        string             _lassErrorMessage;   
        [NonSerialized] Thread _complianceThread;
        [NonSerialized] bool   _abortRequested;
        Industry [] _baseData;      
        Industry [] _scenData;      
        ModelYear[] _modelYears;    
        EffectsData[] _baseEffects; 
        EffectsData[] _scenEffects; 
        int      _scenCount;
        int      _yearCount;
        int      _vehCount;
        int      _mfrCount;
        int   [] _mfrVehCount;
        string[] _mfrNames;
        int      _minYear;
        int      _maxYear;
        int      _minYearIndex;
        int      _maxYearIndex;
        int      _minEffectsYear;
        int      _maxEffectsYear;
        protected Scenario _scen;
        protected ModelYear _year;
        protected int _effectsYear;
        protected Manufacturer _mfr;
        #endregion
    }
}

