#region << Using Directives >>
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using Volpe.Cafe.Data;
using Volpe.Cafe.IO;
using Volpe.Cafe.IO.Reporting.XL;
using Volpe.Cafe.Model;
using Volpe.Cafe.MonteCarlo.Data;
using Volpe.Cafe.MonteCarlo.Settings;
using Volpe.Cafe.Settings;
using Volpe.Cafe.UI;
using Volpe.Cafe.Utils;
using Volpe.Cafe.IO.Reporting.CSV;
using Volpe.Cafe.MonteCarlo.IO.Reporting;
#endregion
namespace Volpe.Cafe.MonteCarlo.Model
{
    [Serializable]
    [ModelDescription("Monte-Carlo Model", "Performs uncertainty analysis by slightly varying the initial input parameters and " +
         "re-running the standard compliance model multiple times.", 1.0F)]
    public sealed class MonteCarloModel : ComplianceBase
    {
        #region 
        class ScheduledTrial
        {
            public ScheduledTrial(int trialDataIndex, TrialInfo trial)
            {
                this.TrialDataIndex = trialDataIndex;
                this.Trial = trial;
            }
            public readonly int TrialDataIndex;
            public readonly TrialInfo Trial;
        }
        #endregion
        #region 
        public MonteCarloModel() : base() { }
        #endregion
        #region 
        #region 
        protected override void OnModelingStopped()
        {
            base.OnModelingStopped();
            this.AfterModelingStoppedOrCompleted(false);
        }
        protected override void OnModelingCompleted()
        {
            base.OnModelingCompleted();
            this.AfterModelingStoppedOrCompleted(true);
        }
        void AfterModelingStoppedOrCompleted(bool completed)
        {
            if (this._completedTrialCount > 0)
            {
                if (this._mcSettings.GenerateMCLogs)
                {
                    PromptEventArgs e = null;
                    if (!completed)
                    {
                        e = new PromptEventArgs("Generate Logs?", "The Monte-Carlo model did not complete successfully.\n\n" +
                            "Would you like to generate Monte-Carlo log files for the " + this._completedTrialCount + " completed trials?",
                            PromptOption.YesNo);
                        this.OnPrompt(e);
                    }
                    if (completed || e.PromptResult == PromptResult.Yes)
                    {   
                        this.GenerateLogFiles();
                    }
                }
            }
        }
        #endregion
        #region 
        public override ICompliance CreateNew()
        {
            return new MonteCarloModel();
        }
        protected override CsvReportGenerator CreateCsvReportGenerator()
        {
            return new CsvMonteCarloReportGenerator(this);
        }
        protected override void StartInternal()
        {
            this.Begin();
        }
        protected override void OverrideUserSettings(ModelingSettings settings)
        {
            base.OverrideUserSettings(settings);
            settings.OutputSettings.WriteLogFiles         = false;
            settings.OutputSettings.WriteExtendedLogFiles = false;
            settings.OutputSettings.GenerateXLReports     = false;
        }
        protected override string Validate()
        {
            string message = base.Validate();
            if (message != string.Empty) { return message; }
            MonteCarloSettings mcSettings = (MonteCarloSettings)this.Settings.GetRuntimeSettings(typeof(MonteCarloSettings));
            if (mcSettings == null)
            {
                message = "The modeling settings for the Monte-Carlo model were not found.";
            }
            else if (!File.Exists(mcSettings.TrialsFile))
            {
                message = "The input \"trials file\" could not be found.";
            }
            if (message != string.Empty)
            {
                message = "The following errors have occured during initialization:\n" + message + "\n\nThe model will not run.";
            }
            return message;
        }
        protected override void AbortInternal(bool abortWhenReady)
        {
            lock (this._trialsQueue) { this._trialsQueue = null; }
            if (this._currentCompliance != null)
            {
                lock (this._currentLock)
                {
                    for (int i = 0; i < this._currentCompliance.Count; i++)
                    {
                        ICompliance compliance = this._currentCompliance[i];
                        if (compliance != null && compliance.Running) { compliance.Abort(abortWhenReady); }
                    }
                }
            }
            this.WaitForWorkerThreadsToExit();
            base.AbortInternal(abortWhenReady);
        }
        #endregion
        void Begin()
        {
            this._mcSettings = (MonteCarloSettings)this.Settings.GetRuntimeSettings(typeof(MonteCarloSettings));
            if (!this.InitializeTrialData()) { return; }
            this._trialsQueue = new Queue<ScheduledTrial>();
            this._currentTrials     = new List<int>();
            this._currentCompliance = new List<ICompliance>();
            this._currentLock       = new object();
            this._completedTrialCount = 0;
            this._maxWorkers = (this._mcSettings.MaxThreads == -1) ? (int)Math.Ceiling(SystemInfo.ProcessorCount * 1.00) : this._mcSettings.MaxThreads;
            if (this._maxWorkers > 4) { this._maxWorkers = 4; }
            this._workers = new Thread[this._maxWorkers];
            for (int i = 0; i < this._workers.Length; i++)
            {
                this._workers[i] = new Thread(new ThreadStart(this.MCWorkerThread));
                this._workers[i].Name = "MCWorkerThread" + i.ToString("00");
                this._workers[i].Start();
            }
            this.RunTrialLoop();
        }
        bool InitializeTrialData()
        {
            this._trialsFile = this._mcSettings.TrialsFile;
            StreamReader sr  = new StreamReader(this._trialsFile);
            string     line  = sr.ReadLine();   
            List<TrialInfo> trials = new List<TrialInfo>();
            while ((line = sr.ReadLine()) != null)
            {
                TrialInfo trial = new TrialInfo();
                trial.FromCsvString(line);
                trials.Add(trial);
            }
            if (sr != null) { sr.Close(); }
            this._trialCount = trials.Count;
            if (this._trialCount < 1)
            {
                this.OnPrompt(new PromptEventArgs("Monte-Carlo Error", "Error parsing the Monte-Carlo Trials file.  " +
                    "Possibly the file does not contain valid Monte-Carlo trial data.", PromptOption.Ok));
                return false;
            }
            this._trialData = new TrialData[this.ScenCount][];
            for (int i = 0; i < this.ScenCount; i++)
            {
                this._trialData[i] = new TrialData[this._trialCount];
                for (int j = 0; j < this._trialCount; j++)
                {
                    this._trialData[i][j] = new TrialData(trials[j]);
                }
            }
            return true;
        }
        void RunTrialLoop()
        {
            for (int i = 0; i < this._trialCount; i++)
            {   
                TrialData trialData = this._trialData[0][i];
                if (!trialData.HasData)
                {   
                    this.ScheduleTrial(i, trialData.TrialInfo);
                }
                if (this.AbortRequested) { break; }
            } 
            int queueCount = 1;
            while (this._trialsQueue != null && queueCount > 0)
            {
                Thread.Sleep(ThreadTimeout);
                if (this.AbortRequested) { break; }
                lock (this._trialsQueue) { queueCount = this._trialsQueue.Count; }
            }
            this._trialsQueue = null;
            if (this.AbortRequested) { return; }
            int complianceCount = 1;
            while (complianceCount > 0)
            {
                Thread.Sleep(ThreadTimeout);
                if (this.AbortRequested) { break; }
                lock (this._currentLock) { complianceCount = this._currentCompliance.Count; }
            }
        }
        void ScheduleTrial(int trialDataIndex, TrialInfo trial)
        {
            if (this._trialsQueue == null) { return; }
            while (true)
            {
                bool fullQueue = false;
                lock (this._trialsQueue) { fullQueue = (this._trialsQueue.Count >= this._maxWorkers * 2); }
                if (fullQueue) { Thread.Sleep(ThreadTimeout); }     
                else { break; }
                if (this.AbortRequested) { return; }
            }
            lock (this._trialsQueue)
            {
                this._trialsQueue.Enqueue(new ScheduledTrial(trialDataIndex, trial));
            }
        }
        void MCWorkerThread()
        {
            string threadName = Thread.CurrentThread.Name;
            Console.WriteLine("entering thread " + threadName);
            while (this._trialsQueue != null)
            {
                ScheduledTrial scheduledTrial = null;
                lock (this._trialsQueue)
                {
                    if (this._trialsQueue.Count > 0) { scheduledTrial = this._trialsQueue.Dequeue(); }
                }
                if (scheduledTrial != null) { this.BeginTrial(scheduledTrial); }    
                else                        { Thread.Sleep(ThreadTimeout); }        
                if (this.AbortRequested) { break; }
            }
            Console.WriteLine("exiting thread " + threadName);
        }
        void BeginTrial(ScheduledTrial scheduledTrial)
        {
            Industry         data     = this.Data.Clone();
            ModelingSettings settings = this.Settings.Clone();
            scheduledTrial.Trial.InitializeTrial(data, settings);
            ICompliance compliance = new Compliance();
            lock (this._currentLock)
            {
                this._currentTrials    .Add(scheduledTrial.Trial.Index);
                this._currentCompliance.Add(compliance);
            }
            settings.OutputSettings.DisableLogWriter   = true;
            settings.OutputSettings.GenerateXLReports  = false;
            settings.OutputSettings.GenerateCSVReports = false;
            compliance.Prompt          += new PromptEventHandler  (this.Compliance_Prompt);
            compliance.ModelingChanged += new ModelingEventHandler(this.Compliance_ModelingChanged);
            compliance.Start(data, settings);
            while (compliance.Running) { Thread.Sleep(ThreadTimeout); }     
            string errorString = null;
            if (this.AbortRequested)
            {
                errorString = "Trial " + scheduledTrial.Trial.Index + " failed; abort requested by user.";
            }
            else if (compliance.Stopped)
            {
                errorString = "Trial " + scheduledTrial.Trial.Index + " failed; " + compliance.LastErrorMessage;
            }
            else
            {   
                try
                {
                    this.SaveTrialData(scheduledTrial.TrialDataIndex, compliance, settings);
                    Interlocked.Increment(ref this._completedTrialCount);
                }
                catch (Exception ex)
                {
                    errorString = "Trial " + scheduledTrial.Trial.Index + " failed; " + ex.Message;
                }
            }
            if (errorString != null) { this.LogWriter.WriteErrorLine(errorString); }
            lock (this._currentLock)
            {
                this._currentTrials    .Remove(scheduledTrial.Trial.Index);
                this._currentCompliance.Remove(compliance);
            }
        }
        void SaveTrialData(int trialDataIndex, ICompliance compliance, ModelingSettings settings)
        {
            LogWriter writer = this.LogWriter;
            for (int i = 0; i < this.ScenCount; i++)
            {   
                Industry[] scenData = compliance.GetData(settings.Scenarios[i]);
                EffectsData[] effectsData = new EffectsData[this.YearCount];
                for (int j = 0; j < this.YearCount; j++)
                {
                    effectsData[j] = compliance.GetEffectsData(settings.Scenarios[i], this.MinYear + j);
                }
                this._trialData[i][trialDataIndex].SaveTrialData(settings.Scenarios[i], this.MinYearIndex, this.YearCount,
                    this._trialData[0][trialDataIndex], writer, scenData, effectsData, settings);
            }
        }
        internal TrialData[] GetTrialData(Scenario scen)
        {
            return this._trialData[scen.Index];
        }
        void GenerateLogFiles()
        {
            List<Vehicle> vehs = this.Data.Vehicles;
            ASCIIEncoding ascii = new ASCIIEncoding();
            string outPath = this.Settings.OutputSettings.OutputPath + "\\MC-logs";
            if (!Directory.Exists(outPath)) { Directory.CreateDirectory(outPath); }
            for (int i = 0; i < this.ScenCount; i++)
            {
                FileMode mode = FileMode.Create; FileAccess access = FileAccess.Write; FileShare share = FileShare.Read;
                FileStream fsTrials = null, fsCosts  = null, fsFC = null;
                try
                {   
                    string csv;
                    byte[] buffer;
                    if (i == 0)
                    {   
                        fsTrials = new FileStream(outPath + "\\MC_trials.csv", mode, access, share);
                        csv      = TrialInfo.CreateCsvHeader() + "\n";
                        buffer   = ascii.GetBytes(csv);
                        fsTrials.Write(buffer, 0, buffer.Length);
                        fsCosts = new FileStream(outPath + "\\MC_tech_costs.csv", mode, access, share);
                        buffer  = ascii.GetBytes("Index," + TechnologyIndexes.TechAbbrCSV + "\n");
                        fsCosts.Write(buffer, 0, buffer.Length);
                        fsFC  = new FileStream(outPath + "\\MC_tech_fcs.csv", mode, access, share);
                        buffer = ascii.GetBytes("Index," + TechnologyIndexes.TechAbbrCSV + "\n");
                        fsFC.Write(buffer, 0, buffer.Length);
                    }
                    for (int j = 0; j < this._trialCount; j++)
                    {
                        if (this._trialData[i][j].HasData)
                        {
                            if (i == 0)
                            {   
                                csv = this._trialData[i][j].TrialInfo.ToCsvString() + "\n";
                                buffer = ascii.GetBytes(csv);
                                fsTrials.Write(buffer, 0, buffer.Length);
                                double[] costs = this.GetTechCosts(this._trialData[i][j].TrialInfo.TechCostScaleFactor, vehs);
                                csv = this._trialData[i][j].TrialInfo.Index.ToString() + ",";
                                for (int k = 0; k < TechnologyIndexes.TechnologyCount; k++)
                                {
                                    csv += (costs[k].ToString() + ",");
                                }
                                csv    = csv.Remove(csv.Length - 1, 1) + "\n";
                                buffer = ascii.GetBytes(csv);
                                fsCosts.Write(buffer, 0, buffer.Length);
                                double[] fc1 = this.GetTechFC(this._trialData[i][j].TrialInfo.TechFCScaleFactor, vehs);
                                csv = this._trialData[i][j].TrialInfo.Index.ToString() + ",";
                                for (int k = 0; k < TechnologyIndexes.TechnologyCount; k++)
                                {
                                    csv += (fc1[k].ToString() + ",");
                                }
                                csv    = csv.Remove(csv.Length - 1, 1) + "\n";
                                buffer = ascii.GetBytes(csv);
                                fsFC.Write(buffer, 0, buffer.Length);
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                }
                finally
                {   
                    if (fsTrials != null) { fsTrials.Close(); }
                    if (fsCosts  != null) { fsCosts .Close(); }
                    if (fsFC     != null) { fsFC    .Close(); }
                }
            }
        }
        double[] GetTechCosts(double[] scales, List<Vehicle> vehs)
        {
            double[] costs = new double[TechnologyIndexes.TechnologyCount];
            for (int i = 0; i < TechnologyIndexes.TechnologyCount; i++)
            {
                double ttlSales = 0;    
                double ttlCost  = 0;    
                for (int j = 0; j < vehs.Count; j++)
                {
                    Vehicle veh = vehs[j];
                    TechnologyAttributes ta = this.Settings.Technologies[i].Attributes[veh];
                    if (ta.Applicable && (ta.CostTable[this.MinYearIndex] != 0 || ta.FC != 0))
                    {
                        double[] sales = veh.Description.Sales;
                        for (int k = this.MinYearIndex; k <= this.MaxYearIndex; k++)
                        {
                            ttlSales += sales[k];
                            ttlCost  += sales[k] * ta.CostTable[k];
                        } 
                    }
                } 
                costs[i] = ttlCost / ttlSales * scales[i];  
            } 
            return costs;
        }
        double[] GetTechFC(double[] scales, List<Vehicle> vehs)
        {
            double[] fc = new double[TechnologyIndexes.TechnologyCount];
            for (int i = 0; i < TechnologyIndexes.TechnologyCount; i++)
            {
                double ttlSales = 0;    
                double ttlFC    = 0;    
                for (int j = 0; j < vehs.Count; j++)
                {
                    Vehicle veh = vehs[j];
                    TechnologyAttributes ta = this.Settings.Technologies[i].Attributes[veh];
                    if (ta.Applicable && (ta.CostTable[this.MinYearIndex] != 0 || ta.FC != 0))
                    {
                        double techClassFC = ta.FC;
                        double[] sales = veh.Description.Sales;
                        double vehTtlSales = 0;     
                        for (int k = this.MinYearIndex; k <= this.MaxYearIndex; k++)
                        {
                            ttlSales    += sales[k];
                            vehTtlSales += sales[k];
                        } 
                        ttlFC += (vehTtlSales * techClassFC);
                    }
                } 
                fc[i] = ttlFC / ttlSales * scales[i];   
            } 
            return fc;
        }
        #region 
        void Compliance_Prompt(object sender, PromptEventArgs e)
        {
            this.OnPrompt(e);
        }
        void Compliance_ModelingChanged(object sender, ModelingEventArgs e)
        {
            bool stoppedOrCompleted = (e.State == ModelingState.Completed || e.State == ModelingState.Stopped);
            if (stoppedOrCompleted)
            {   
                ((ICompliance)sender).Prompt          -= new PromptEventHandler  (this.Compliance_Prompt         );
                ((ICompliance)sender).ModelingChanged -= new ModelingEventHandler(this.Compliance_ModelingChanged);
            }
        }
        #endregion
        #region 
        void WaitForWorkerThreadsToExit()
        {
            Console.WriteLine("waiting for worker threads to exit ...");
            if (this._workers != null)
            {
                for (int i = 0; i < this._workers.Length; i++)
                {
                    if (this._workers[i] != null && this._workers[i].IsAlive)
                    {
                        Thread.Sleep(250);
                        i--;
                    }
                }
            }
            Console.WriteLine("all worker threads exited.");
        }
        #endregion
        #endregion
        #region 
        #region 
        public override IModelingProgress Progress
        {
            get
            {
                int        [] currentTrials     = null;
                ICompliance[] currentCompliance = null;
                if (this._currentCompliance != null && this._currentTrials != null)
                {
                    lock (this._currentLock)
                    {
                        currentTrials     = this._currentTrials    .ToArray();
                        currentCompliance = this._currentCompliance.ToArray();
                    }
                }
                string trialsString = "";
                if (currentTrials != null)
                {   
                    for (int i = 0, trialCount = Math.Min(currentTrials.Length, 5); i < trialCount; i++)
                    {
                        trialsString += ((i == 0) ? "" : ", ") + currentTrials[i];
                    }
                    if (currentTrials.Length > 5) { trialsString += " (+" + (currentTrials.Length - 5) + " more)"; }
                }
                trialsString = "Examing MC Trial(s): " + trialsString + ".\nNumber of trials examined: " +
                    (this._completedTrialCount) + " / " + (this._trialCount) + ".";
                double runtime  = this.Runtime / 60000D;
                double avgSpeed = (runtime  == 0 || this._completedTrialCount == 0) ? -1D :
                    (Math.Round(this._completedTrialCount / (double)runtime, 1));
                int timeRemain  = (avgSpeed <= 0) ? -1 : (int)((this._trialCount - this._completedTrialCount) / avgSpeed);
                string avgSpeedString =
                    "Avg. Speed: " + ((avgSpeed == -1) ? "<calculating ...>" : avgSpeed + " t/m") +
                    "  Approximate Time Remaining: " + ((timeRemain == -1) ? "<calculating ...>" : (timeRemain < 1) ? "<1 m" : timeRemain + " m.");
                trialsString += "\n" + avgSpeedString;
                string progressString = "";
                if (currentCompliance != null && currentTrials != null)
                {
                    for (int i = 0; i < currentCompliance.Length; i++)
                    {
                        string pStr = "refreshing";
                        IModelingProgress p = currentCompliance[i].Progress;
                        if (p != null && p.Scenario != null)
                        {
                            pStr = "Sn " + (p.Scenario.Index + 1) + "/" + this.ScenCount;
                            if (p.ModelYear != null) { pStr += ", MY " + p.ModelYear + "/" + this.MaxYear; }
                        }
                        progressString += ((i == 0) ? "" : "\n") + "Trial " + currentTrials[i].ToString() + ": " + pStr;
                    }
                }
                return new ModelingProgress(progressString, trialsString);
            }
        }
        #endregion
        #endregion
        #region 
        MonteCarloSettings _mcSettings;
        string _trialsFile;
        int    _trialCount;     
        const int ThreadTimeout = 1000;
        [NonSerialized] Queue<ScheduledTrial> _trialsQueue;         
        [NonSerialized] int                   _maxWorkers;          
        [NonSerialized] Thread[]              _workers;             
        [NonSerialized] List<int>             _currentTrials;       
        [NonSerialized] List<ICompliance>     _currentCompliance;   
        [NonSerialized] object                _currentLock;         
        TrialData[][] _trialData;                       
        int           _completedTrialCount;             
        #endregion
    }
}

