/*-------------------------------------------------------------------------
//                           UNCLASSIFIED
/*-------------------------------------------------------------------------
RAYTHEON PROPRIETARY: THIS DOCUMENT CONTAINS DATA OR INFORMATION
PROPRIETARY TO RAYTHEON COMPANY AND IS RESTRICTED TO USE ONLY BY PERSONS
AUTHORIZED BY RAYTHEON COMPANY IN WRITING TO USE IT. DISCLOSURE TO
UNAUTHORIZED PERSONS WOULD LIKELY CAUSE SUBSTANTIAL COMPETITIVE HARM TO
RAYTHEON COMPANY'S BUSINESS POSITION. NEITHER SAID DOCUMENT NOR ITS
CONTENTS SHALL BE FURNISHED OR DISCLOSED TO OR COPIED OR USED BY PERSONS
OUTSIDE RAYTHEON COMPANY WITHOUT THE EXPRESS WRITTEN APPROVAL OF RAYTHEON
COMPANY.

THIS PROPRIETARY NOTICE IS NOT APPLICABLE IF DELIVERED TO THE U.S.
GOVERNMENT.

UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
-------------------------------------------------------------------------*/
using System;
using System.IO;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;

using NationalInstruments.TestStand.Interop.API;
using NLog;
using Raytheon.Instruments;
using Raytheon.Common;
using MeasurementManagerLib;

namespace ProgramLib
{
   /// <summary>
	/// Class for interfacing with any Test Executive such as third party test executive such as Test Stand
   /// DO NOT implement IDisposable interface for this class. If there are unmanaged resources that need to be freed,
   /// do it in the destructor.
	/// </summary>
   public partial class Program
   {
      #region PrivateMemberVariables
      // the one and only program
      private static Program _program;

      private object _terminateTestSyncObj = new object();
      private bool _terminateTestInitiated = false;

      // simulation
      private readonly bool _isThereHardware;

      // file management
      private string _partNumber;
      private string _serialNumber;

      private List<BasicThread> _threadList = new List<BasicThread>();

      private IInstrumentManager _instrumentManager;

      private ILogger _logger;

      private object _fatalErrorMsgFromSupportThreadSyncObj = new object();
      private string _fatalErrorMsgFromSupportThread;

      #endregion

      internal SequenceContext TestStandSeqContext { get; private set; }
      internal UutInfo UutInfo { get; private set; }
      internal EventManager EventManager {  get; private set; }
      internal FileAndFolderManager FileAndFolderManager {  get; set; }
      internal IConfigurationFile ProgramConfig { get; private set; }
      internal MalMeasurementLibManager MalMeasurementLibManager { get; private set; }
      internal string TestMethodConfigFilePath { get; private set; }

      internal bool SttoSuccess { get; set; }

      /// <summary>
      /// Create an instance of this class. Only one instance is allowed
      /// </summary>
      /// <param name="partNumber">The UUT part number</param>
      /// <param name="serialNumber">The UUT serial number</param>
      /// <param name="isThereHardware">false for simulation</param>
      /// <returns></returns>
      public static Program Instance(string partNumber = "", string serialNumber = "", bool isThereHardware = true, object testStandSeqContext = null)
      {
         if (_program == null)
         {
            _program = new Program(partNumber, serialNumber, isThereHardware, testStandSeqContext);
         }

         return _program;
      }

      /// <summary>
		/// This function is only meant to be called from third-party test executive such as TestStand.
      /// By settting this object to null, Garbage collection is initiated on this objects and any objects created by this object
      /// with the exception of static objects.  Static objects needs to be explicitly killed by setting them to null
		/// </summary>
		/// <returns></returns>
		public static void KillInstance()
      {
         if (_program != null)
         {
            // signal to all running threads to exit
            _program.EventManager[EventManager.Events.GLOBAL_QUIT].Set();

            UutPowerAction uutPowerAction = new UutPowerAction();
            uutPowerAction.UutPowerOff();

            // needs to kill all the support threads since they could be accessing GUI
            _program.KillSupportThreads();

            // Because the GuiManager class starts a STA thread to manage WPF GUIs
            // the destructor doesn't get called if we don't shut down the STA thread first
            // so we explicitly call Dispose here to shutdown the thread.  This is a special case.
            // All other classes call Dispose() in their Destructor
            _program.GetGuiManager()?.Dispose();

            // this starts garbage collection on this object which then in turn start garbage collection
            // on all objects it created
            _program = null;
         }
      }

      /// <summary>
		/// The private constructor
		/// </summary>
		/// <param name="partNumber">the UUT part number</param>
		/// <param name="serialNumber">the UUT serial number</param>
		/// <param name="isThereHardware">false for simulation</param>
		private Program(string partNumber, string serialNumber, bool isThereHardware, object testStandSeqContext)
      {
         // this line is for when this is called from third-party test executive such as TestStand.  Without this, NLOG library won't be able to find the NLog.config file
         NLog.LogManager.Setup().LoadConfigurationFromFile(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "NLog.config"));

         _logger = LogManager.GetCurrentClassLogger();

         string assemblyFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
         ProgramConfig = new ConfigurationFile(Path.Combine(assemblyFolder, GeneralConstants.ProgramConfigFilename));

         TestMethodConfigFilePath = Path.Combine(assemblyFolder, GeneralConstants.TestMethodConfigFolderName, GeneralConstants.TestMethodConfigFileName);

         EventManager = new EventManager();
         FileAndFolderManager = new FileAndFolderManager(ProgramConfig);

         if (testStandSeqContext != null)
         {
            TestStandSeqContext = testStandSeqContext as SequenceContext;
         }

         // make sure PN/SN are valid (TS may not pass in the PN)
         if (partNumber == "" || partNumber == null)
         {
            partNumber = "DefaultPN";
         }

         if (serialNumber == "" || serialNumber == null)
         {
            serialNumber = "DefaultSN";
         }

         _partNumber = partNumber;
         _serialNumber = serialNumber;
         _isThereHardware = isThereHardware;

         SttoSuccess = false;

         // Initialze all other configuration that the program needs
         UutInfo = new UutInfo(_partNumber, _serialNumber);

         try
         {
            var configFolder = Path.Combine(assemblyFolder, Raytheon.Common.Constants.InstrumentConfigFolder);
            _instrumentManager = new GeneralInstrumentManager(assemblyFolder, configFolder, _isThereHardware);
            _instrumentManager.Initialize();

            MalMeasurementLibManager = new MalMeasurementLibManager(_instrumentManager);
         }
         catch (Exception)
         {
            throw;
         }
      }

      /// <summary>
      /// Finalizer
      /// </summary>
      ~Program()
      {
         _logger?.Debug($"Entering {this.GetType().Name}::{System.Reflection.MethodBase.GetCurrentMethod().Name}() ...");

         if (TestStandSeqContext != null)
         {
            // clear any setting that we have added for any steps in the Setup Group
            for (int i = 0; i < TestStandSeqContext.Sequence.GetNumSteps(StepGroups.StepGroup_Setup); i++)
            {
               TestStandSeqContext.Sequence.GetStep(i, StepGroups.StepGroup_Setup).PostExpression = "";
            }

            // clear any setting that we have added for any steps in the Main Group
            for (int i = 0; i < TestStandSeqContext.Sequence.GetNumSteps(StepGroups.StepGroup_Main); i++)
            {
               TestStandSeqContext.Sequence.GetStep(i, StepGroups.StepGroup_Main).PostExpression = "";
               TestStandSeqContext.Sequence.GetStep(i, StepGroups.StepGroup_Main).AdditionalResults.CustomResults.Clear();
            }
         }
      }

      private void KillSupportThreads()
      {
         foreach (BasicThread thread in _threadList)
         {
            thread.Quit();
            thread.WaitForExit();
         }
      }

      /// <summary>
      /// All code module should not throw exception into teststand and let teststand handle the exception
      /// We give the teststand step a nice error message and tell teststand to go to cleanup
      /// If we are not calling into code from teststand, then we throw exception as usual
      /// Call this only from code executing on the main TestStand thread
      /// </summary>
      /// <param name="ex"></param>
      /// <returns></returns>
      private void TerminateTestOnMainThreadError(Exception ex)
      {
         if (TestStandSeqContext != null)
         {
            _logger.Error(ex.Message + "\n" + ex.StackTrace);

            lock (_terminateTestSyncObj)
            {
               if (!_terminateTestInitiated)
               {
                  // tells teststand there's a exception occurred and give it the error message
                  TestStandSeqContext.Step.PostExpression = $"Step.Result.Error.Msg=\"{ex.Message} \", Step.Result.Error.Occurred=True";

                  TestStandSeqContext.Step.ResultStatus = "Error";

                  // tells TestStand to go to clean up
                  TestStandSeqContext.StepGroup = StepGroups.StepGroup_Cleanup;
                  TestStandSeqContext.NextStepIndex = 0;

                  _terminateTestInitiated = true;
               }
            }
         }
         else
            throw ex;
      }

      /// <summary>
      /// Support threads such as monitor threads need a way to stop the sequence if it encounters an error
      /// This function is meant to be called from FailureMonitorThread which monitors any error signaled by any
      /// support thread.
      /// </summary>
      /// <param name=""></param>
      /// <returns></returns>
      internal void TerminateTestOnSupportThreadError()
      {
         if (TestStandSeqContext != null)
         {
            lock (_terminateTestSyncObj)
            {
               if (!_terminateTestInitiated)
               {
                  // tells teststand there's a exception occurred and give it the error message
                  TestStandSeqContext.SequenceErrorMessage = _fatalErrorMsgFromSupportThread + " ";
                  TestStandSeqContext.SequenceErrorOccurred = true;

                  // tells TestStand to go to clean up
                  TestStandSeqContext.StepGroup = StepGroups.StepGroup_Cleanup;
                  TestStandSeqContext.NextStepIndex = 0;

                  _terminateTestInitiated = true;
               }
            }
         }
      }

      /// <summary>
      /// Save error message originated from other threads
      /// </summary>
      /// <param name=""></param>
      /// <returns></returns>
      internal void SetFatalErrorMsgFromSupportThread(string errorMsg)
      {
         lock(_fatalErrorMsgFromSupportThreadSyncObj)
         {
            if (String.IsNullOrEmpty(_fatalErrorMsgFromSupportThread))
            {
               _fatalErrorMsgFromSupportThread = errorMsg;
            }
         }
      }
   }
}
