using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Timers;
using System.IO;
using iSTP_Embedded;
using iSTP_DataProcessing;
using iSTP_Tools;
using ClimaChamber;
using iSTP_DebugConsole;
using RateTableGUI;
using PressureGenerator;

namespace iSTP_Core
{
    /// <summary>
    /// Core controller.
    /// </summary>
    public class TestController : IDisposable
    {
        private iSTP_MainWnd _View; ///<summary> Main application GUI </summary>
        private TestProcedure _Model = new TestProcedure(); ///<summary> Holds information about test form *.csv </summary>

        private iSTP_ConsoleWnd TestLogWindow; ///<summary> Test logger </summary>
        private DebugConsole DebugConsole; ///<summary> Debug logger </summary>
        private PressureGeneratorController Pressure;

        private EmbeddedController Embedded; ///<summary> Control&Communication with platform </summary>
        private DataProcessing Processing; ///<summary> Control data processing </summary>
        private ClimaController ClimaChamber; ///<summary> Control climatic chamber </summary>
        private RateTableController RateTable; ///<summary> Control rotary table </summary>
        private PowerSupplyController PowerSupply; ///<summary> Control power supply </summary>

        private DeviceStatus iSTPEmbeddedStatus = DeviceStatus.NotConnected; ///<summary> Platform current status </summary>
        private DeviceStatus ClimaChamberStatus = DeviceStatus.NotConnected; ///<summary> Climatic chamber current status </summary>
        private DeviceStatus RateTableStatus = DeviceStatus.NotConnected; ///<summary> Rotary table current status </summary>
        private DeviceStatus PressureGeneratorStatus = DeviceStatus.NotConnected; ///<summary> Pressure generator current status </summary>

        private System.Timers.Timer TestTimer; ///<summary> 1 sec timer </summary>
        private int NextTestEventTime; ///<summary> Holds info about next event time </summary>
        private DateTime NextTestEventScheduledTime; ///<summary> Holds info about next event time </summary>
        private DateTime LastEnviroUpdate; ///<summary> Holds info about last peripheries update time </summary>
        private DateTime LastClimaCommunicationTime; ///<summary> Last time when controller communicate with climatic chamber </summary>
        private DateTime LastRateTableCommunicationTime; ///<summary> Last time  when controller communicate with rotary table </summary>
        private int TestIndex; ///<summary> Number of row in test csv</summary>
        private bool SRHighAnnounced = false; ///<summary> Warning: Platform cannot sends data witch current sample rate </summary>
        private bool RxTimedOut = false; ///<summary> Warning: Platform has not sent data at least 3 sec </summary>

        private TestProcedure.CmdSet LastCommand; ///<summary> Last command </summary>
        private TestProcedure.CmdSet CurrentCommand; ///<summary> Current command </summary>
        Thread CurrentTestStepThread; ///<summary> Holds thread for executing commands </summary>
        private Object TestStepLocker;
        private Object TestLogLocker;
        private StreamWriter TestLogFile;

        private HealthData EnviClass; /// <summary> Health data class </summary>

        public TestProcedure GetCurrentModel { get { return _Model; } }

        /// <summary>
        /// Types of devices
        /// </summary>
        public enum Device
        {
            iSTPembeddedPlatform,
            ClimaChamber,
            PressureGen,
            RateTable
        }

        /// <summary>
        /// Possible device status
        /// </summary>
        public enum DeviceStatus
        {
            NotConnected,
            Required,
            Connected
        }

        /// <summary>
        /// Type of message
        /// </summary>
        public enum LogMsgType
        {
            Normal,
            Warning,
            Error
        }

        /// <summary>
        /// Allocate memory, show logger and debug window
        /// </summary>
        /// <param name="view">main GUI</param>
        public TestController(iSTP_MainWnd view)
        {
            this._View = view;
            this._View.setController(this);
            this._Model.setController(this);
            this.TestLogWindow = _View.GetConsole();
            this.DebugConsole = _View.GetDebugConsole();
            this.PowerSupply = new PowerSupplyController();
            Thread.CurrentThread.Name = "Main iSTP thread";
            TestStepLocker = new Object();
            TestLogLocker = new Object();
        }

        /// <summary>
        /// True if a test is currentlly being executed.
        /// </summary>
        public bool TestRunning  { get; set; }

        /// <summary>
        /// Show power supply control
        /// </summary>
        public void ShowPowerSupplyControls()
        {
            PowerSupply.ShowControl();
        }

        /// <summary>
        /// This method shows a configurable message box.
        /// </summary>
        /// <param name="Text">Message text</param>
        /// <param name="Caption">Caption of dialog window</param>
        /// <param name="Buttons">Buttons type</param>
        /// <param name="Icon">Icon type</param>
        public void MessageHandler(String Text = "Error", String Caption = "Message", MessageBoxButtons Buttons = MessageBoxButtons.OK, MessageBoxIcon Icon = MessageBoxIcon.Error)
        {
            _View.Message(Text, Caption, Buttons, Icon);
        }

        /// <summary>
        /// This method is invoked by the View. It loads a CSV test procedure and updates View to show
        /// which devices need to be configured and the duration of the test.
        /// </summary>
        /// <param name="File">Full path to the test procedure CSV.</param>
        public bool LoadTestProcedure(String File) 
        {
            Boolean result = _Model.ParseTestCsv(File);
            if(result == true)
            {
                _View.setTotalTestTime(_Model.TotalTime);
                _View.setRemainingTestTime(_Model.TotalTime);

                if (this.iSTPEmbeddedStatus == DeviceStatus.NotConnected)
                {
                    this.iSTPEmbeddedStatus = DeviceStatus.Required;
                    _View.UpdateDeviceStatus(Device.iSTPembeddedPlatform, this.iSTPEmbeddedStatus);
                }
                if (this.ClimaChamberStatus == DeviceStatus.NotConnected)
                {
                    if (_Model.isClimaRequired == true)
                        this.ClimaChamberStatus = DeviceStatus.Required;
                    _View.UpdateDeviceStatus(Device.ClimaChamber, this.ClimaChamberStatus);
                }
                if (this.PressureGeneratorStatus == DeviceStatus.NotConnected)
                {
                    if (_Model.isPressureGeneratorRequired == true) 
                        this.PressureGeneratorStatus = DeviceStatus.Required;
                    _View.UpdateDeviceStatus(Device.PressureGen, this.PressureGeneratorStatus);
                }
                if (this.RateTableStatus == DeviceStatus.NotConnected)
                {
                    if (_Model.isRateTableRequired) 
                        this.RateTableStatus = DeviceStatus.Required;
                    _View.UpdateDeviceStatus(Device.RateTable, this.RateTableStatus);
                }
            }
            
            _View.EnableTestStart(this.EnvironmentReadyForTest);
            
            return (result);
        }

        /// <summary>
        /// This method is invoked by the View. It configures the iSTP embedded platform. It makes use
        /// of the iSTP_Embedded component to manage the configuration process.
        /// </summary>
        public void ConfigureEmbedded()
        {
            if (this.Embedded == null)
            {
                this.Embedded = new iSTP_Embedded.EmbeddedController();
            }
            
            Embedded.ResetRxStats();
            this.Embedded.Configure(this.TestRunning);
            if (Embedded.isConfigured)
            {
                this.iSTPEmbeddedStatus = DeviceStatus.Connected;
            }
            else
            {
                this.iSTPEmbeddedStatus = DeviceStatus.NotConnected;
            }
            _View.UpdateDeviceStatus(Device.iSTPembeddedPlatform, this.iSTPEmbeddedStatus);
            _View.EnableTestStart(this.EnvironmentReadyForTest);
        }

        /// <summary>
        /// This method is invoked by the View. It configures Climatic chamber. It makes use of the ClimaChamber component
        /// to manage the configuration process.
        /// </summary>
        public void ConfigureClimaChamber()
        {
            if (this.ClimaChamber == null)
            {
                this.ClimaChamber = new ClimaController();
            }

            if (this.ClimaChamber.Configure())
                this.ClimaChamberStatus = DeviceStatus.Connected;
            else
            {
                if (_Model.isClimaRequired)
                    this.ClimaChamberStatus = DeviceStatus.Required;
                else
                    this.ClimaChamberStatus = DeviceStatus.NotConnected;
            }
            _View.UpdateDeviceStatus(Device.ClimaChamber, this.ClimaChamberStatus);
            _View.EnableTestStart(this.EnvironmentReadyForTest);
        }

        /// <summary>
        /// This method is invoked by the View. It configures pressure generator device.
        /// </summary>
        public void ConfigurePressureGen()
        {
            if (this.Pressure == null)
            {
                this.Pressure = new PressureGeneratorController();
            }

            if (this.Pressure.Configure())
                this.PressureGeneratorStatus = DeviceStatus.Connected;
            else
            {
                if (_Model.isPressureGeneratorRequired)
                    this.PressureGeneratorStatus = DeviceStatus.Required;
                else
                    this.PressureGeneratorStatus = DeviceStatus.NotConnected;
            }
            _View.UpdateDeviceStatus(Device.PressureGen, this.PressureGeneratorStatus);
            _View.EnableTestStart(this.EnvironmentReadyForTest);
        }

        /// <summary>
        /// This method is invoked by the View. It configures Rate table device.
        /// </summary>
        public void ConfigureRateTable()
        {
            if (this.RateTable == null)
            {
                this.RateTable = new RateTableController();
            }

            if (this.RateTable.Configure())
                this.RateTableStatus = DeviceStatus.Connected;
            else
            {
                if (_Model.isRateTableRequired)
                    this.RateTableStatus = DeviceStatus.Required;
                else
                    this.RateTableStatus = DeviceStatus.NotConnected;
            }

            _View.UpdateDeviceStatus(Device.RateTable, this.RateTableStatus);
            _View.EnableTestStart(this.EnvironmentReadyForTest);
        }

        /// <summary>
        /// This method is invoked by the View. It configures data processing - in particular
        /// it tells the iSTP_DataProcessing component to manage the configuration process.
        /// </summary>
        public void ConfigureProcessing()
        {
            if (this.Processing == null)
            {
                this.Processing = new DataProcessing();
            }
            this.Processing.Configure(_View.LogDestination);
            _View.EnableTestStart(this.EnvironmentReadyForTest);
        }

        /// <summary>
        /// This method is invoked by the View when it's Start (Stop) button is pressed.
        /// It commences a test run or stops an ongoing test.
        /// </summary>
        public void StartStopTest()
        {
            
            if (!this.TestRunning)
            {
                if (this.Processing == null)
                    ConfigureProcessing();
                if (!this.Processing.InitializeLoggers(Embedded.getISTPConfiguration))
                {
                    StopTest("Test interrupted by application", true);
                    return;
                }
                if (_Model.IsEnvironmentControlRequired || Embedded.HealthDataLoggingEnabled)
                    StartEnviroLog();

                if (Embedded.FifoModeEnabled == true)
                {
                    this.Processing.SetSampleRate(Embedded.RequestedSampleRate);
                    this.Embedded.SetDataProcesing(new EmbeddedProtocol.ProcessingDelegate(this.Processing.ProcessFIFOpacket));
                }
                else
                {
                    this.Embedded.SetDataProcesing(new EmbeddedProtocol.ProcessingDelegate(this.Processing.ProcessDataPacket));
                }
                
                this.TestRunning = true;
                _View.SetTestExecutionLook(true, "Test running");
                StartLog();
                RxTimedOut = false;
                NextTestEventTime = 0;
                TestIndex = 0;
                LastCommand = new TestProcedure.CmdSet();

                if (TestTimer != null)
                    TestTimer.Dispose();
                TestTimer = new System.Timers.Timer(1000);
                TestTimer.Elapsed += new ElapsedEventHandler(this.ExecuteTest);
                Embedded.ResetRxStats();
                _Model.UpdateScheduledTimes();
                TestTimer.Start();

                TestLog(string.Format("Climatic chamber: {0}", (_Model.isClimaRequired)? ("Required") : ("Not required")));
                TestLog(string.Format("Pressure Generator: {0}", (_Model.isPressureGeneratorRequired) ? ("Required") : ("Not required")));
                TestLog(string.Format("Rate Table: {0}", (_Model.isRateTableRequired) ? ("Required") : ("Not required")));
                TestLog("-----Test Information-----");
                TestLog(string.Format("Test loaded: {0}", _Model.TestLoaded));
                TestLog(string.Format("Estimate date: {0}", _Model.TestFinishDateTime));
                TestLog(string.Format("Test name: {0}", _Model.TestName));
                TestLog(string.Format("Total time: {0}s", _Model.TotalTime));
                TestLog(string.Format("Test length (number of lines): {0}", _Model.Length));
                TestLog("-----Embedded Information-----");
                TestLog(string.Format("FIFO mode state: {0}", Embedded.FifoModeEnabled));
                TestLog(string.Format("Health data logging is enable: {0}", Embedded.HealthDataLoggingEnabled));
                TestLog(string.Format("Configure state: {0}", (Embedded.isConfigured)? ("Configured") : ("NotConfigured")));
                TestLog(string.Format("FIFO read rate: {0}Hz", Embedded.RequestedFifoReadRate));
                TestLog(string.Format("Sample rate: {0}Hz", Embedded.RequestedSampleRate));
                TestLog(string.Format("Status: {0}", Embedded.Status));
                TestLog(string.Format("Reconfiguration active: {0}", Embedded.getReconfiguredState));
                TestLog("-----Data Processing Information-----");
                TestLog(string.Format("CSV logging: {0}", Processing.GetCSVreguested));
                TestLog(string.Format("Honeyx logging:: {0}", Processing.GetHXreguested));
                TestLog(string.Format("Per-sensor logging: {0}", Processing.GetPerSensorLogging));
                TestLog(string.Format("SPS requested: {0}", Processing.GetSPSrequested));
                TestLog(string.Format("Compensation enable: {0}", Processing.GetCompensationRequested));
                TestLog(string.Format("Filtration enable: {0}", Processing.GetFiltrationRequested));
                TestLog(string.Format("Filtration cut-off frequency: {0} [Hz]", Processing.GetFiltrationCutOffFreq));
                TestLog(string.Format("Decimation enable: {0}", Processing.GetDecimationRequested));
                TestLog(string.Format("Decimation scale factor UP: {0}", Processing.GetDeciScaleUP));
                TestLog(string.Format("Decimation scale factor DOWN: {0}", Processing.GetDeciScaleDOWN));
                TestLog(string.Format("Keep-Alive enable: {0}", Processing.GetKeepAliveRequested));
                TestLog(string.Format("Virtual sensors active: {0}", Processing.GetVirtualSensRequested));
                if (this.Processing.GetSPSrequested)
                {
                    TestLog("-----SPS settings-----");
                    TestLog("SensorName_Time_TimeActive_OffsetTime[s]_StableTime[s]_Temperature[°C]_Maxtemp[°C]_Mintemp[°C]_FailTime[s]_ConditionsActive");
                    Dictionary<string, SPSLogger.ActiveAccGyroStruct> info = this.Processing.GetSPSsensorsSettings;
                    for (int y = 0; y < info.Count; y++)
                    {
                        string _key = info.Keys.ElementAt(y);
                        SPSLogger.ActiveAccGyroStruct _val = new SPSLogger.ActiveAccGyroStruct();
                        if (info.TryGetValue(_key, out _val))
                        {
                            string _line = string.Format("{0}_{1}_{2}_{3}_{4}_{5}_{6}_{7}_{8}_{9}",
                                _key,
                                _val.tempStabTimes.Time,
                                _val.tempStabTimes.isTimeActive,
                                _val.tempStabTimes.Conditions_OffsetTime,
                                _val.tempStabTimes.Conditions_TimeLimit,
                                _val.tempStabTimes.Conditions_Temp,
                                _val.tempStabTimes.Conditions_Maxlimit,
                                _val.tempStabTimes.Conditions_Minlimit,
                                _val.tempStabTimes.Conditions_FailTime,
                                _val.tempStabTimes.isConditionsActive
                                );
                            TestLog(_line);
                        }
                    }
                }
                TestLog("-----Test logs-----");
                NextTestEventScheduledTime = DateTime.Now;
            }
            else
            {
                StopTest("Test interrupted by the user", true);
            }
        }

        /// <summary>
        /// Terminates test execution
        /// </summary>
        /// <param name="status">Message that comments the test end, it gets displayed/stored in the test log and status bar.</param>
        /// <param name="error">If it's true, the status bar will show red icon signifying the fact that the test finished with a problem.</param>
        public void StopTest(string status, bool error)
        {
            if (this.TestRunning)
            {
                Embedded.ClearMemory();

                if (CurrentTestStepThread != null)
                    CurrentTestStepThread.Join();
                TestTimer.Stop();
                TestTimer.Dispose();
                TestTimer = null;
                this.TestRunning = false;
                if (Embedded.StopLogging())
                {
                    if(!Embedded.PowerSensors(false))
                        TestLog("Communication with Embedded platform failed", LogMsgType.Error);
                }
                else
                    StopTest("Communication with Embedded platform failed", true);
                
                Embedded.EnableDataProcessing(false);
                Processing.CloseLoggers();
                if (_Model.IsEnvironmentControlRequired || Embedded.HealthDataLoggingEnabled)
                    CloseEnviroLog();
                _View.SetTestExecutionLook(false, status, error);
                TestLog(status, (error? LogMsgType.Error : LogMsgType.Normal));

                CloseLog();
                if (_Model.isClimaRequired)
                {
                    ClimaChamber.Stop();
                    _View.ChangeWeatherInfo("");
                }
                string dest = string.Format("{0}{1}{2}",Processing.GetTestDestination,
                                                        Path.DirectorySeparatorChar,
                                                        _Model.GetCSVtestFileName.Split(Path.DirectorySeparatorChar)[_Model.GetCSVtestFileName.Split(Path.DirectorySeparatorChar).Length - 1]);
                File.Copy(_Model.GetCSVtestFileName, dest);
            }
        }

        /// <summary>
        /// Given the test loaded, are all devices required by the test connected and configured?
        /// </summary>
        public bool EnvironmentReadyForTest
        {
            get
            {
                bool retVal = true;
                retVal &= _Model.TestLoaded;
                retVal &= (this.iSTPEmbeddedStatus == DeviceStatus.Connected);
                retVal &= this.Processing != null;
                retVal &= (!_Model.isClimaRequired || (_Model.isClimaRequired && (this.ClimaChamberStatus == DeviceStatus.Connected)));
                retVal &= (!_Model.isPressureGeneratorRequired || (_Model.isPressureGeneratorRequired && (this.PressureGeneratorStatus == DeviceStatus.Connected)));
                retVal &= (!_Model.isRateTableRequired || (_Model.isRateTableRequired && (this.RateTableStatus == DeviceStatus.Connected)));
                return retVal;
            }
        }

        /// <summary>
        /// Detection, whether more than 3 seconds have passed since the last Data packet while the data is expected to be comming
        /// </summary>
        private bool HasDataRxTimedOut()
        {
            bool retVal = false;
            if (!RxTimedOut)
            {
                if (LastCommand.LoggingReq && CurrentCommand.LoggingReq && (Embedded.TimeSinceLastDataPacket > 3000)) // 3-second timeout [ms]
                {
                    TestLog("Platform is not sending data. Proceeding at risk.", LogMsgType.Warning);
                    RxTimedOut = true;
                }
            }
            else
            {
                if ((LastCommand.LoggingReq || CurrentCommand.LoggingReq) && (Embedded.TimeSinceLastDataPacket < 3000)) // 3-second timeout [ms]
                {
                    RxTimedOut = false;
                    TestLog("Platform is sending data again!", LogMsgType.Warning);
                }
            }
            return retVal;
        }

        /// <summary>
        /// Write errors from peripherials into logs. 
        /// </summary>
        private void CheckPeripherialErrors()
        {
            bool critical = false;

            if (DataProcessingError.isActive)
            {
                bool crit = false;
                if (DataProcessingError.GetMsg(out List<string[]>  errors) == DataProcessingError.State_e.Critical)
                {
                    critical = true;
                    crit = true;
                }
                
                if (errors != null)
                {
                    foreach(string[] ss in errors)
                        if (ss.Length >= 2)
                        {
                            if(crit)
                                TestLog(ss[0], LogMsgType.Error);
                            else
                                TestLog(ss[0], LogMsgType.Warning);
                            DebugConsole.WriteLine(ss[1]);
                        }
                        else if (ss.Length == 1)
                        {
                            DebugConsole.WriteLine(ss[0]);
                        }
                }
            }

            if (ClimError.isActive)
            {
                bool crit = false;
                if (ClimError.GetMsg(out List<string[]>  errors) == ClimError.State_e.Critical)
                {
                    critical = true;
                    crit = true;
                }

                if (errors != null)
                {
                    foreach (string[] ss in errors)
                        if (ss.Length >= 2)
                        {
                            if (crit)
                                TestLog(ss[0], LogMsgType.Error);
                            else
                                TestLog(ss[0], LogMsgType.Warning);
                            DebugConsole.WriteLine(ss[1]);
                        }
                        else if (ss.Length == 1)
                        {
                            DebugConsole.WriteLine(ss[0]);
                        }
                }
            }

            if (RateTableError.isActive)
            {
                bool crit = false;
                if (RateTableError.GetMsg(out List<string[]> errors) == RateTableError.State_e.Critical)
                {
                    critical = true;
                    crit = true;
                }

                if (errors != null)
                {
                    foreach (string[] ss in errors)
                        if (ss.Length >= 2)
                        {
                            if (crit)
                                TestLog(ss[0], LogMsgType.Error);
                            else
                                TestLog(ss[0], LogMsgType.Warning);
                            DebugConsole.WriteLine(ss[1]);
                        }
                        else if (ss.Length == 1)
                        {
                            DebugConsole.WriteLine(ss[0]);
                        }
                }
            }

            if (EmbeddedError.isActive)
            {
                bool crit = false;
                if (EmbeddedError.GetMsg(out List<string[]> errors) == EmbeddedError.State_e.Critical)
                {
                    critical = true;
                    crit = true;
                }

                if (errors != null)
                {
                    foreach (string[] ss in errors)
                        if (ss.Length >= 2)
                        {
                            if (crit)
                                TestLog(ss[0], LogMsgType.Error);
                            else
                                TestLog(ss[0], LogMsgType.Warning);
                            DebugConsole.WriteLine(ss[1]);
                        }
                        else if (ss.Length == 1)
                        {
                            DebugConsole.WriteLine(ss[0]);
                        }
                }
            }

            if (critical)
            {
                StopTest("Critical error", true);
            }
        }

        /// <summary>
        /// This method gets called every 1 second by the TestTimer elapsed event. It maintains test time and controls
        /// whether a test step is to be perfrmed. It updates the progress in the View.
        /// </summary>
        /// <param name="source">source</param>
        /// <param name="e">e</param>
        public void ExecuteTest(object source, ElapsedEventArgs e)
        {
            int remainingTime = (int)(_Model.TestFinishDateTime - DateTime.Now).TotalSeconds;
            _View.setRemainingTestTime(remainingTime < 0 ? 0 : remainingTime);
            _View.UpdateProgress(100 - (int)(((Single)remainingTime / (Single)_Model.TotalTime) * 100.0));
            _View.setLogStats(Embedded.ReceivedPackets, Embedded.LostPackets + this.Processing.GetBadFIFOpckCounter);
            if (!Embedded.SampleRateOK && !SRHighAnnounced)
            {
                TestLog("Requested sample rate too high.", LogMsgType.Warning);
                SRHighAnnounced = true;
            }

            CheckPeripherialErrors();
            
            if (!HasDataRxTimedOut())
            {
                try
                {
                    if (this.Embedded.GetACKinMem > 0 && this.Embedded.CheckTime_ACK())
                    {
                        TestLog("ACK not received ...", LogMsgType.Warning);
                        this.Embedded.ResendMSG_ACK();
                    }
                }
                catch (OperationCanceledException)
                {
                    StopTest("ACK not received ... executing test", true);
                }

                if (NextTestEventScheduledTime <= DateTime.Now)
                {
                    if (TestIndex < _Model.Length)
                    {
                        if (CurrentTestStepThread != null && CurrentTestStepThread.IsAlive)
                        {
                            TestLog("Previous test step has not finished yet!!! " + CurrentTestStepThread.Name, LogMsgType.Warning);
                            TestLog("Postponing the current step", LogMsgType.Warning);
                        }
                        else
                        {
                            CurrentTestStepThread = new Thread(this.PerformTestStep);
                            CurrentCommand = _Model.GetCmdSet(TestIndex);
                            CurrentTestStepThread.Name = "Test Step time " + CurrentCommand.TestTime;
                            CurrentTestStepThread.Start();
                            TestIndex++;
                            if (TestIndex < _Model.Length) // check if there's more test rows
                            {
                                NextTestEventTime = _Model.GetCmdSet(TestIndex).TestTime;
                                NextTestEventScheduledTime = _Model.GetCmdSet(TestIndex).ScheduledTime;
                            }
                            else // this was the last command
                            {
                                if (TestLogFile != null)
                                {
                                    TestLog(string.Format("Received packet: {0}", Embedded.ReceivedPackets));
                                    TestLog(string.Format("Packet lost: {0}", Embedded.LostPackets));
                                }
                                StopTest("Test Finished.", false);
                            }
                        }
                    }
                }
                else
                {
                    if( ((DateTime.Now - LastEnviroUpdate).TotalSeconds > 5.0) && // at least 5 seconds from last update
                        ((DateTime.Now - LastClimaCommunicationTime).TotalSeconds > 5.0) && // at least 5 seconds from last clima command
                        ((DateTime.Now + TimeSpan.FromSeconds(5)) < NextTestEventScheduledTime) ) // at least 5 seconds till next clima command
                    {
                        if (_Model.isClimaRequired)
                            ClimaChamber.ManualUpdate();
                        LastEnviroUpdate = DateTime.Now;
                    }

                    if (_Model.isPressureGeneratorRequired)
                        Pressure.ManualUpdate();
                }
                if (_Model.IsEnvironmentControlRequired || Embedded.HealthDataLoggingEnabled)
                    EnviroLog();
                if (_Model.isClimaRequired)
                    _View.ChangeWeatherInfo(ClimaChamber.ActTemp, ClimaChamber.ActHum);
                if (_Model.isPressureGeneratorRequired)
                    _View.ChangePressureGeneratorInfo(Pressure.ActStatic, Pressure.ActPitot);
            }
        }

        /// <summary>
        /// Performs the actual operations requested by the test procedure such as controlling clima chamber, rate table, 
        /// turning sensor power on/off, logging etc.
        /// </summary>
        /// <param name="CurrentCommand">Current command set</param>
        private void PerformTestStep()
        {
            lock (TestStepLocker)
            {
                TestLog(String.Format("  ..... {0}s .....", CurrentCommand.TestTime));

                if (_Model.isPressureGeneratorRequired)
                {
                    ControlPressureGenerator(CurrentCommand);
                }

                if (_Model.isRateTableRequired)
                {
                    LastRateTableCommunicationTime = DateTime.Now;
                    ControlRateTable(CurrentCommand);
                    TestLog(String.Format
                        (
                            "Rate table\n--Inner--\n\tEn: {0} Rot: {1} Pos: {2} Vel: {3}\n\t--Outer--\n\tEn: {4} Rot: {5} Pos: {6} Vel: {7}",
                            CurrentCommand.EnableAxisInner,
                            CurrentCommand.RotateAxisInner,
                            CurrentCommand.PosAxisInner,
                            CurrentCommand.VelAxisInner,
                            CurrentCommand.EnableAxisOuter,
                            CurrentCommand.RotateAxisOuter,
                            CurrentCommand.PosAxisOuter,
                            CurrentCommand.VelAxisOuter
                        ));
                }

                if (_Model.isClimaRequired)
                {
                    if (_Model.isRateTableRequired && CurrentCommand.ClimaChamberState)
                        Thread.Sleep(200);
                    LastClimaCommunicationTime = DateTime.Now;
                    ControlClimaChamber(CurrentCommand);
                }

                if (CurrentCommand.SensorPower != LastCommand.SensorPower || CurrentCommand.SensorPower && CurrentCommand.LoggingReq)
                {
                    if (CurrentCommand.SensorPower && this.Embedded.getReconfiguredState)
                    {
                        TestLog("Reconfiguring platform...");
                        this.Embedded.Configure();
                        this.Processing.reInitializeLoggers(this.Embedded.getISTPConfiguration);
                        TestLog("Reconfigure complete");
                    }

                    TestLog("Turning sensors " + (CurrentCommand.SensorPower ? "On" : "Off"));
                    if (!Embedded.PowerSensors(CurrentCommand.SensorPower))
                        StopTest("Communication with Embedded platform failed", true);
                }



                if (CurrentCommand.LoggingReq && !LastCommand.LoggingReq) // create new log(s)
                {
                    Embedded.ClearStreamBuffer();
                    TestLog(String.Format("Initiating new log at {0}s", CurrentCommand.TestTime));
                    System.Diagnostics.Stopwatch sp = new System.Diagnostics.Stopwatch();
                    sp.Start();
                    Processing.StartLogs(CurrentCommand.TestTime, CurrentCommand.Temperature);
                    sp.Stop();
                    TestLog(string.Format("All files created. Total time: {0}ms", sp.ElapsedMilliseconds));
                    Embedded.EnableDataProcessing(true);
                    TestLog("Telling platform to start logging");
                    if (!Embedded.StartLogging())
                        StopTest("Communication with Embedded platform failed", true);

                    //Set up new enviromental log in cycle folder
                    StartCycleEnviLog();
                }
                if (!CurrentCommand.LoggingReq && LastCommand.LoggingReq) // close current log(s)
                {
                    //Close enviromental log in cycle folder
                    CloseCycleEnviLog();

                    TestLog("Telling platform to stop logging");
                    if (!Embedded.StopLogging())
                        StopTest("Communication with Embedded platform failed", true);
                    Embedded.EnableDataProcessing(false);
                    Processing.CloseLoggers();
                }

                LastCommand = CurrentCommand;
                if (TestIndex < _Model.Length)
                    TestLog("next step at " + _Model.GetCmdSet(TestIndex).ScheduledTime.ToString("yyyy-MM-dd HH:mm:ss.f"));
            }
        }

        private void ControlPressureGenerator(TestProcedure.CmdSet CurrentCommand)
        {
            if (CurrentCommand.PressureState)
            {
                TestLog("Pressure generator - RUN");
                Pressure.Run(); 
                TestLog(string.Format("Pressure generator setpoints: Static {0} Pitot {1}",CurrentCommand.PressureStatic, CurrentCommand.PressurePitot));
                Pressure.SetPressure(CurrentCommand.PressureStatic, CurrentCommand.PressurePitot);

                PressureMode.SetpointStatic = CurrentCommand.PressureStatic;
                PressureMode.SetpointPitot = CurrentCommand.PressurePitot;
                PressureMode.SetpointTemp = CurrentCommand.Temperature;
            }
            else
            {
                TestLog("Pressure generator - STOP");
                Pressure.Stop();
            }
        }

        /// <summary>
        /// Performs the actual operations requested by the test procedure on the clima chamber
        /// </summary>
        /// <param name="CurrentCommand">Current command set</param>
        private bool ControlClimaChamber( TestProcedure.CmdSet CurrentCommand)
        {
            bool result = true;
            if (CurrentCommand.ClimaChamberState)
            {
                if (!_Model.isRateTableRequired)
                {
                    if (!ClimaChamber.Run())
                    {
                        TestLog("Clima chamber issue: Can't run chamber");
                        result = false;
                    }
                }

                if (CurrentCommand.HumRate != 0.0f || CurrentCommand.TempRate != 0.0f)
                {
                    TestLog(String.Format("Setting clima chamber: Temp:{0} Hum: {1}, TempRate: {2}, HumRate: {3}", CurrentCommand.Temperature, CurrentCommand.Humidity, CurrentCommand.TempRate, CurrentCommand.HumRate));

                    if (!ClimaChamber.SetAll(CurrentCommand.TempRate, CurrentCommand.TempRate, CurrentCommand.HumRate, CurrentCommand.HumRate, CurrentCommand.Temperature, CurrentCommand.Humidity))
                    {
                        TestLog("Clima chamber issue: Can't set parameters");
                        result = false;
                    }
                }
                else
                {
                    TestLog(String.Format("Setting clima chamber: Temp:{0} Hum: {1}", CurrentCommand.Temperature, CurrentCommand.Humidity));
                    if (!ClimaChamber.SetTempHumi(CurrentCommand.Temperature, CurrentCommand.Humidity))
                    {
                        TestLog("Clima chamber issue: Can't set parameters");
                        result = false;
                    }
                }
            }
            else
            {
                TestLog("Switching off clima chamber");
                if (!_Model.isRateTableRequired)
                {
                    if (!ClimaChamber.Stop())
                    {
                        TestLog("Clima chamber issue: Can't stop chamber");
                        result = false;
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// Performs the actual operations requested by the test procedure on the rate table
        /// </summary>
        /// <param name="CurrentCommand">Current command set</param>
        private bool ControlRateTable(TestProcedure.CmdSet CurrentCommand)
        {
            bool result = true;

            bool rateEnable = (CurrentCommand.EnableAxisInner || CurrentCommand.EnableAxisOuter) ? (true) : (false);

            RateTable.ProvidesCommands
                (
                    rateEnable,
                    CurrentCommand.ClimaChamberState,
                    CurrentCommand.EnableAxisInner,
                    CurrentCommand.RotateAxisInner,
                    CurrentCommand.PosAxisInner,
                    CurrentCommand.VelAxisInner,
                    CurrentCommand.EnableAxisOuter,
                    CurrentCommand.RotateAxisOuter,
                    CurrentCommand.PosAxisOuter,
                    CurrentCommand.VelAxisOuter
                );

            return result;
        }

        /// <summary>
        /// Creates a test log file for a test run.
        /// </summary>
        private void StartLog()
        {
            if (!Directory.Exists(Processing.DestinationDirectory))
                Directory.CreateDirectory(Processing.DestinationDirectory);

            TestLogFile = new StreamWriter(Processing.DestinationDirectory + "\\TestLog.txt", true);
            TestLog("...............................................................................");
            TestLog("Test " + _Model.TestName + " started");
            TestLog("Embedded Platform version " + Embedded.getISTPConfiguration.FWVersion);
            if(Embedded.getISTPConfiguration.FWInfo != "")
                TestLog("Embedded Platform version description: " + Embedded.getISTPConfiguration.FWInfo);
            TestLog("PC SW version [DEVELOPMENT] " + typeof(TestController).Assembly.GetName().Version.ToString());
            TestLog("Sample rate set to " + Embedded.RequestedSampleRate.ToString() + " Hz");
            TestLog("Health data logging " + (Embedded.HealthDataLoggingEnabled ? "enabled" : "disabled"));
            if (_Model.isClimaRequired)
                TestLog("Using clima chamber: " + ClimaChamber.Name);
            TestLog("...............................................................................");
        }

        /// <summary>
        /// Closes the test log file and visually detaches the log in the Test log window.
        /// </summary>
        private void CloseLog()
        {
            lock (TestLogLocker)
            {
                TestLogFile.Close();
                TestLogFile.Dispose();
            }
            TestLogWindow.WriteLine("--------------------\n", LogMsgType.Normal);
        }

        /// <summary>
        /// Records an entry in the test log file and shows the same entry in the Test Log window.
        /// It prepends the log entry with the current system time. Depending on the 'type' parameter,
        /// the text is prepended with "Warning" or "ERROR" and in the Test log window, the text color is 
        /// different as well.
        /// </summary>
        /// <param name="text">Log entry</param>
        /// /// <param name="type">Type of entry Normal/Warning/Error</param>
        private void TestLog(string text, LogMsgType type = LogMsgType.Normal)
        {
            String timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.f");
            String logEntry = "";
            switch(type)
            {
                case LogMsgType.Normal:
                    logEntry = "[" + timestamp + "] " + text + "\n";
                    break;
                case LogMsgType.Warning:
                    logEntry = "[" + timestamp + "] Warning: " + text + "\n";
                    break;
                case LogMsgType.Error:
                    logEntry = "[" + timestamp + "] ERROR:" + text + "\n";
                    break;
            }
            TestLogWindow.WriteLine(logEntry, type);
            lock (TestLogLocker)
            {
                if (TestLogFile.BaseStream != null)
                {
                    TestLogFile.WriteLine(logEntry);
                    TestLogFile.Flush();
                }
            }
        }

        /// <summary>
        /// Creates an environmental log file (HoneyX double time, single data) which records requested and actual conditions of the required devices
        /// such as clima chamber, pressure gen, etc.
        /// </summary>
        private void StartEnviroLog()
        {
            EnviClass = new HealthData(Embedded.HealthDataLoggingEnabled, _Model.isClimaRequired, _Model.isRateTableRequired, _Model.isPressureGeneratorRequired);
            if (Embedded.HealthDataLoggingEnabled)
                EnviClass.AddISTPHeader(Embedded.GetHealthDataHeaders);

            EnviClass.CreateMainEnvi(Processing.DestinationDirectory);
        }
        /// <summary>
        /// Closes the environmental log file.
        /// </summary>
        private void CloseEnviroLog()
        {
            if (EnviClass != null)
            {
                EnviClass.CloseMainEnvies();
                EnviClass = null;
            }
        }

        /// <summary>
        /// Start Enviromental log in cycle folder
        /// </summary>
        private void StartCycleEnviLog()
        {
            if (EnviClass != null)
            {
                EnviClass.CreateCycleEnvi(Processing.CurrentLogDestination);
            }
        }

        /// <summary>
        /// Close Enviromental log in cycle folder
        /// </summary>
        private void CloseCycleEnviLog()
        {
            if (EnviClass != null)
            {
                EnviClass.CloseCycleEnvies();
            }

            EnviroLastTime = 0;
        }

        /// <summary>
        /// Update data from all connected peripherials (except ISTP)
        /// </summary>
        private void EnviroLogAddPeripherial()
        {
            if (_Model.isClimaRequired)
                EnviClass.AddClimChamData
                    (
                    LastCommand.ClimaChamberState,
                    LastCommand.Temperature,
                    ClimaChamber.ActTemp,
                    LastCommand.Humidity,
                    ClimaChamber.ActHum
                    );
            if (_Model.isRateTableRequired)
                EnviClass.AddRatetableData
                    (
                    LastCommand.EnableAxisInner,
                    LastCommand.PosAxisInner,
                    LastCommand.VelAxisInner,
                    RateTable.ActInnerPosition,
                    RateTable.ActInnerVelocity,
                    LastCommand.EnableAxisOuter,
                    LastCommand.PosAxisOuter,
                    LastCommand.VelAxisOuter,
                    RateTable.ActOuterPosition,
                    RateTable.ActOuterVelocity
                    );
            if (_Model.isPressureGeneratorRequired)
                EnviClass.AddPressureData
                (
                    LastCommand.PressureState,
                    LastCommand.PressureStatic,
                    Pressure.ActStatic,
                    LastCommand.PressurePitot,
                    Pressure.ActPitot
                );          
        }

        /// <summary>
        /// Check input time with last time.
        /// </summary>
        /// <param name="currentTime">Current time</param>
        /// <returns>Time</returns>
        private double GetCorrectTime(double currentTime)
        {
            double _res = currentTime;
            while (EnviroLastTime >= _res)
                _res++;

            EnviroLastTime = _res;

            return _res;
        }

        /// <summary>
        /// Records current environmental conditions in the environmental log. The time is the timestamp from the last Data packet.
        /// </summary>
        private object Envilocker = new object();
        private double EnviroLastTime = 0;
        private void EnviroLog()
        {
            lock (Envilocker)
            {
                if (EnviClass != null)
                {
                    if (Embedded.HealthDataLoggingEnabled)
                    {
                        List<float[]> embeddedData;
                        List<double> embeddedTimestamp;
                        if (Embedded.Health.ReadHealthData(out embeddedData, out embeddedTimestamp))
                        {
                            for (int i = 0; i < embeddedData.Count; i++)
                            {
                                EnviroLogAddPeripherial();
                                EnviClass.AddHealthData
                                (
                                GetCorrectTime(embeddedTimestamp[i]),
                                embeddedData[i]
                                );
                                EnviClass.WriteEnviData();
                            }
                            Embedded.Health.ClearData();
                        }
                    }
                    else
                    {
                        EnviroLogAddPeripherial();
                        EnviClass.AddTimestamp(GetCorrectTime(Embedded.EmbeddedTime));
                        EnviClass.WriteEnviData();
                    }
                }
            }
        }

        /// <summary>
        /// Dispose all components
        /// </summary>
        public void Dispose()
        {
            if (TestTimer != null)
                TestTimer.Dispose();
            if(Embedded != null)
                Embedded.Dispose();
            if (ClimaChamber != null)
                ClimaChamber.Dispose();
            if (TestLogFile != null)
                TestLogFile.Dispose();
            if (Processing != null)
                Processing.StopKeepAlive();
        }
    }
}
