//                           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.
-------------------------------------------------------------------------*/


// Ignore Spelling: Selftest ovp ocp

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using NLog;
using Raytheon.Instruments;
using Raytheon.Instruments.PowerSupply;

namespace MeasurementManagerLib
{
   /// <summary>
   /// The entry point for the library that controls power supplies.
   /// This class parses out the definition of all power supplies in a system and provides control to the host.
   /// In simple cases, the host only needs to fill in the ini file and use the OutputDisable() and OutputEnable() functions.
   /// A variety of other capability is exposed for more complex applications.
   /// </summary>
   public class PowerSupplyMeasurementManager : IDisposable
   {
      #region PublicClassMembers
      private Exception _exceptionFromSuportThread;
      private object _powerSupplySelfTestSyncOjb = new object();

      #endregion

      #region PrivateClassMembers
      // power system name to power system object
      private SortedDictionary<string, IPowerSupplySystem> _powerSystemNameToObjectMap;

      // power module name to power system object
      private SortedDictionary<string, IPowerSupplySystem> _powerModuleNameToObjectMap;

      private static NLog.ILogger _logger;
      private IInstrumentManager _instrumentManager;

      private string _powerSystemWithFailedSelfTest = String.Empty;

      #endregion

      #region PublicFuctions

      /// <summary>
      /// will create an array of power systems with power supplies
      /// </summary>
      /// <param name="instrumentManager"></param>
      /// <param name="powerSupplySystems"></param>
      public PowerSupplyMeasurementManager(IInstrumentManager instrumentManager)
      {
         _logger = LogManager.GetCurrentClassLogger();
         _instrumentManager = instrumentManager;

         _powerSystemNameToObjectMap = new SortedDictionary<string, IPowerSupplySystem>();

         // create some maps to support the functions the MAL input
         _powerSystemNameToObjectMap = new SortedDictionary<string, IPowerSupplySystem>(StringComparer.InvariantCultureIgnoreCase);
         _powerModuleNameToObjectMap = new SortedDictionary<string, IPowerSupplySystem>(StringComparer.InvariantCultureIgnoreCase);

         PerformPowerSupplySelfTests();

         if (_powerSystemWithFailedSelfTest != String.Empty)
         {
            throw new Exception($"{_powerSystemWithFailedSelfTest}'s self-test failed.");
         }
         else if (_exceptionFromSuportThread != null)
         {
            throw new Exception("Check inner exception", _exceptionFromSuportThread);
         }

         ICollection<object> powerSystemList = _instrumentManager.GetInstruments(typeof(IPowerSupplySystem));
         // populate the maps
         foreach (IPowerSupplySystem powerSystem in powerSystemList)
         {
            powerSystem.Initialize();
            _powerSystemNameToObjectMap[powerSystem.Name.ToUpper()] = powerSystem;

            List<string> moduleNames = powerSystem.GetModuleNames();

            foreach (string moduleName in moduleNames)
            {
               if (_powerModuleNameToObjectMap.ContainsKey(moduleName.ToUpper()))
               {
                  throw new Exception("There is more than 1 power system that have the same module name: " + moduleName);
               }

               _powerModuleNameToObjectMap[moduleName.ToUpper()] = powerSystem;
            }
         }
      }

      /// <summary>
      /// Perform self test on power supply system
      /// Self test for each power system takes a while, so we don't want to run self test every time we initialize the power system
      /// So we only want to run self test under 2 conditions:
      ///    1. Certain time has elapsed since last power off
      ///    2. Certain time has elapsed since last self test run ( in the absence of power off time)
      /// </summary>
      /// <param name="name"></param>
      /// <returns></returns>
      private void PerformPowerSupplySelfTests()
      {
         _logger.Debug($"Entering {this.GetType().Name}::{System.Reflection.MethodBase.GetCurrentMethod().Name}() method...");

         string errorMsg = String.Empty;

         ICollection<object> powerSystemList = _instrumentManager.GetInstruments(typeof(IPowerSupplySystem));

         Task[] taskArray = new Task[powerSystemList.Count];

         int index = 0;
         foreach (IPowerSupplySystem powerSystem in powerSystemList)
         {
            // perform self test on power system
            Task task = Task.Factory.StartNew(() => PerformPowerSupplySelfTestTask(powerSystem));
            taskArray.SetValue(task, index++);
         }

         Task.WaitAll(taskArray);
      }

      /// <summary>
      /// Perform self test on power supply system
      /// </summary>
      /// <param name="name"></param>
      /// <returns></returns>
      private void PerformPowerSupplySelfTestTask(IPowerSupplySystem powerSystem)
      {
         SelfTestResult result = SelfTestResult.Pass;
         Exception exception = null;
         string powerSystemWithFailedSelfTest = String.Empty;

         _logger?.Debug($"{this.GetType().Name}::{System.Reflection.MethodBase.GetCurrentMethod().Name}() for {powerSystem.Name} is running...");

         try
         {
            result = powerSystem.PerformSelfTest();

            if (result == SelfTestResult.Fail)
            {
               powerSystemWithFailedSelfTest = powerSystem.Name;
            }
         }
         catch (Exception ex)
         {
            exception = ex;
         }

         lock (_powerSupplySelfTestSyncOjb)
         {
            if (String.IsNullOrEmpty(_powerSystemWithFailedSelfTest) && _exceptionFromSuportThread == null)
            {
               if (!String.IsNullOrEmpty(powerSystemWithFailedSelfTest))
                  _powerSystemWithFailedSelfTest = powerSystemWithFailedSelfTest;
               else if (exception != null)
               {
                  _exceptionFromSuportThread = exception;
               }
            }
         }

         _logger?.Debug($"{this.GetType().Name}::{System.Reflection.MethodBase.GetCurrentMethod().Name}() for {powerSystem.Name} is exiting...");
      }

      /// <summary>
      /// Disable the power supply display interface.
      /// </summary>
      /// <param name="powerSystem">The name of the system to disable, as defined in the config file</param>
      public void DisplayDisable(string powerSystem)
      {
         if (_powerSystemNameToObjectMap.ContainsKey(powerSystem.ToUpper()) == false)
         {
            throw new Exception("Could not find power system: " + powerSystem);
         }

         _powerSystemNameToObjectMap[powerSystem.ToUpper()].DisplayEnabled = false;
      }

      /// <summary>
      /// Enable the power supply display interface.
      /// </summary>
      /// <param name="powerSystem">The name of the system to disable, as defined in the config file</param>
      public void DisplayEnable(string powerSystem)
      {
         if (_powerSystemNameToObjectMap.ContainsKey(powerSystem.ToUpper()) == false)
         {
            throw new Exception("Could not find power system: " + powerSystem);
         }

         _powerSystemNameToObjectMap[powerSystem.ToUpper()].DisplayEnabled = true;
      }

      /// <summary>
      /// Dispose of this object.
      /// </summary>
      public void Dispose()
      {
         Dispose(true);

         GC.SuppressFinalize(this);
      }

      /// <summary>
      /// Get all of the power module names
      /// </summary>
      /// <returns>Array of the system/module names.</returns>
      public List<string> GetPowerModuleList()
      {
         List<string> powerModules = new List<string>();

         foreach (KeyValuePair<string, IPowerSupplySystem> item in _powerModuleNameToObjectMap)
         {
            powerModules.Add(item.Key);
         }

         return powerModules;
      }

      /// <summary>
      /// Get the power module names for a given system
      /// </summary>
      /// <returns>Array of the system/module names.</returns>
      public List<string> GetPowerSupplyList(string powerSystem)
      {
         if (_powerSystemNameToObjectMap.ContainsKey(powerSystem.ToUpper()) == false)
         {
            throw new Exception("Unknown power system: " + powerSystem);
         }

         return _powerSystemNameToObjectMap[powerSystem.ToUpper()].GetModuleNames();
      }//GetPowerSupplyModuleInfoDict

      /// <summary>
      /// Get the dictionary that contains configuration information for each module
      /// </summary>
      /// <returns></returns>
      public Dictionary<string, PowerSupplyModuleInfo> GetPowerSupplyModuleInfoDict(string powerSystem)
      {
         if (_powerSystemNameToObjectMap.ContainsKey(powerSystem.ToUpper()) == false)
         {
            throw new Exception("Unknown power system: " + powerSystem);
         }

         return _powerSystemNameToObjectMap[powerSystem.ToUpper()].GetPowerSupplyModuleInfoDict(powerSystem);
      }

      /// <summary>
      /// Gets the Power System names.
      /// </summary>
      /// <returns>Array of power system names, as defined in the config file</returns>
      public List<string> GetPowerSupplySystemList()
      {
         List<string> powerSystems = new List<string>();

         foreach (KeyValuePair<string, IPowerSupplySystem> item in _powerSystemNameToObjectMap)
         {
            powerSystems.Add(item.Key);
         }

         return powerSystems;
      }

      /// <summary>
      /// Query the supply and returns the programed OCP. Should match what was in the definition file.
      /// </summary>
      /// <param name="moduleName">The name of the power supply.</param>
      /// <returns>The programmed overcurrent protection value</returns>
      public double GetOverCurrentProtection(string moduleName)
      {
         if (_powerModuleNameToObjectMap.ContainsKey(moduleName.ToUpper()) == false)
         {
            throw new Exception("Could not find module: " + moduleName);
         }

         return _powerModuleNameToObjectMap[moduleName.ToUpper()].GetOverCurrentSetting(moduleName);
      }

      /// <summary>
      /// Query the supply and returns the programed OVP. Should match what was in the definition file.
      /// </summary>
      /// <param name="moduleName">The name of the power supply.</param>
      /// <returns>The programmed overvoltage protection value.</returns>
      public double GetOverVoltageProtection(string moduleName)
      {
         if (_powerModuleNameToObjectMap.ContainsKey(moduleName.ToUpper()) == false)
         {
            throw new Exception("Could not find module: " + moduleName);
         }

         return _powerModuleNameToObjectMap[moduleName.ToUpper()].GetOverVoltageSetting(moduleName);
      }

      /// <summary>
      /// Query the supply and returns the programed slew rate
      /// </summary>
      /// <param name="moduleName"></param>
      /// <returns>The programmed slew rate</returns>
      public double GetSlewRate(string moduleName)
      {
         if (_powerModuleNameToObjectMap.ContainsKey(moduleName.ToUpper()) == false)
         {
            throw new Exception("Could not find module: " + moduleName);
         }

         return _powerModuleNameToObjectMap[moduleName.ToUpper()].GetSlewRate(moduleName);
      }

      /// <summary>
      /// Query the system that contains the passed in module and returns the system error code.
      /// </summary>
      /// <param name="moduleName">The module to query.</param>
      /// <param name="errorCode">The error code.</param>
      /// <returns>The string form of the error code. Will be empty string if there is no error.</returns>
      public string GetSystemErrorCode(string moduleName, out int errorCode)
      {
         if (_powerModuleNameToObjectMap.ContainsKey(moduleName.ToUpper()) == false)
         {
            throw new Exception("Could not find module: " + moduleName);
         }

         return _powerModuleNameToObjectMap[moduleName.ToUpper()].GetErrorCode(out errorCode);
      }

      /// <summary>
      /// Query the supply and returns the programmed voltage setpoint.
      /// After construction, this should match what was in the definition file.
      /// The host may override the value by calling SetVoltageSetpoint().
      /// </summary>
      /// <param name="moduleName">The name of the power supply.</param>
      /// <returns>The programmed voltage setpoint.</returns>
      public double GetVoltageSetpoint(string moduleName)
      {
         if (_powerModuleNameToObjectMap.ContainsKey(moduleName.ToUpper()) == false)
         {
            throw new Exception("Could not find module: " + moduleName);
         }

         return _powerModuleNameToObjectMap[moduleName.ToUpper()].GetVoltageSetting(moduleName);
      }

      /// <summary>
      /// Query the supply and returns true if output is enabled.
      /// </summary>
      /// <param name="moduleName">The name of the power supply.</param>
      /// <returns>True if output is enabled, false if it is not enabled.</returns>
      public bool IsPowerSupplyOn(string moduleName)
      {
         if (_powerModuleNameToObjectMap.ContainsKey(moduleName.ToUpper()) == false)
         {
            throw new Exception("Could not find module: " + moduleName);
         }

         return _powerModuleNameToObjectMap[moduleName.ToUpper()].IsOutputOn(moduleName);
      }

      /// <summary>
      /// Sends a SCPI command to the power system and reads the response
      /// </summary>
      /// <param name="powerSystem">The name of the power system.</param>
      /// <param name="command">The SCPI command to send</param>
      /// <returns></returns>
      public string IOQuery(string powerSystem, string command)
      {
         if (_powerSystemNameToObjectMap.ContainsKey(powerSystem.ToUpper()) == false)
         {
            throw new Exception("Could not find power system: " + powerSystem);
         }

         return _powerSystemNameToObjectMap[powerSystem.ToUpper()].IOQuery(command);
      }

      /// <summary>
      /// Sends a SCPI command to the power system
      /// </summary>
      /// <param name="powerSystem">The name of the power system.</param>
      /// <param name="command">The SCPI command to send</param>
      public void IOWrite(string powerSystem, string command)
      {
         if (_powerSystemNameToObjectMap.ContainsKey(powerSystem.ToUpper()) == false)
         {
            throw new Exception("Could not find power system: " + powerSystem);
         }

         _powerSystemNameToObjectMap[powerSystem.ToUpper()].IOWrite(command);
      }

      /// <summary>
      /// Read the current of a module.
      /// </summary>
      /// <param name="moduleName">The module to read.</param>
      /// <returns>The current (Amps).</returns>
      public double MeasureCurrent(string moduleName)
      {
         if (_powerModuleNameToObjectMap.ContainsKey(moduleName.ToUpper()) == false)
         {
            throw new Exception("Could not find module: " + moduleName);
         }

         return _powerModuleNameToObjectMap[moduleName.ToUpper()].MeasureCurrent(moduleName);
      }

      /// <summary>
      /// Query the supply for its voltage.
      /// </summary>
      /// <param name="moduleName">The module to read.</param>
      /// <returns>The voltage (Volts).</returns>
      public double MeasureVoltage(string moduleName)
      {
         if (_powerModuleNameToObjectMap.ContainsKey(moduleName.ToUpper()) == false)
         {
            throw new Exception("Could not find module: " + moduleName);
         }

         return _powerModuleNameToObjectMap[moduleName.ToUpper()].MeasureVoltage(moduleName);
      }

      /// <summary>
      /// Disable the output of the power supply.
      /// </summary>
      /// <param name="moduleName">The name of the power supply module.</param>
      public void OutputDisable(string moduleName)
      {
         if (_powerModuleNameToObjectMap.ContainsKey(moduleName.ToUpper()) == false)
         {
            throw new Exception("Could not find module: " + moduleName);
         }

         _powerModuleNameToObjectMap[moduleName.ToUpper()].Off(moduleName);
      }

      /// <summary>
      /// Enable the output of the power supply.
      /// </summary>
      /// <param name="moduleName">The name of the module to enable.</param>
      public void OutputEnable(string moduleName)
      {
         if (_powerModuleNameToObjectMap.ContainsKey(moduleName.ToUpper()) == false)
         {
            throw new Exception("Could not find module: " + moduleName);
         }

         _powerModuleNameToObjectMap[moduleName.ToUpper()].On(moduleName);
      }

      /// <summary>
      /// Control the power supply internal mechanical relay state
      /// </summary>
      /// <param name="shallWeConnect">True to connect, false to disconnect</param>
      public void MechanicalRelayOutputControl(string moduleName, bool shallWeConnect)
      {
         if (_powerModuleNameToObjectMap.ContainsKey(moduleName.ToUpper()) == false)
         {
            throw new Exception("Could not find module: " + moduleName);
         }

         _powerModuleNameToObjectMap[moduleName.ToUpper()].MechanicalRelayOutputControl(moduleName, shallWeConnect);
      }

      /// <summary>
      /// Query all power supply data in a single call.
      /// </summary>
      /// <param name="moduleName">The name of the module to query.</param>
      /// <param name="voltage">The voltage that was read.</param>
      /// <param name="voltageSetpoint">The voltage setpoint that was read.</param>
      /// <param name="current">The current that was read.</param>
      /// <param name="isOutputOn">The output status. True if output is enabled, false if output is disabled.</param>
      /// <param name="faultStatus">The value of the fault status register. See power supply docs for meaning (0 means no fault).</param>
      /*public void ReadPowerData(string moduleName, out double voltage, out double voltageSetpoint, out double current, out bool isOutputOn, out int faultStatus)
		{
		   if (_powerModuleNameToObjectMap.ContainsKey(moduleName.ToUpper()) == false)
		   {
			  throw new Exception("PowerSupplyManager::ReadPowerData() - could not find module: " + moduleName);
		   }

		   _powerModuleNameToObjectMap[moduleName.ToUpper()].ReadPowerData(moduleName, out voltage, out voltageSetpoint, out current, out isOutputOn, out faultStatus);
		}*/

      /// <summary>
      /// Query all power supply data in a single call.
      /// </summary>
      /// <param name="moduleName">The name of the module to query.</param>
      public PowerData ReadPowerData(string moduleName)
      {
         if (_powerModuleNameToObjectMap.ContainsKey(moduleName.ToUpper()) == false)
         {
            throw new Exception("Could not find module: " + moduleName);
         }

         return _powerModuleNameToObjectMap[moduleName.ToUpper()].ReadPowerData(moduleName);
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="moduleName"></param>
      /// <returns></returns>
      public int ReadProtectionStatus(string moduleName)
      {
         if (_powerModuleNameToObjectMap.ContainsKey(moduleName.ToUpper()) == false)
         {
            throw new Exception("Could not find module: " + moduleName);
         }

         return _powerModuleNameToObjectMap[moduleName.ToUpper()].ReadProtectionStatus(moduleName);
      }

      /// <summary>
      /// Resets the setpoint voltage to the value contained in the ini file
      /// </summary>
      /// <param name="moduleName">The module to reset</param>
      public void SetInitialVoltage(string moduleName)
      {
         if (_powerModuleNameToObjectMap.ContainsKey(moduleName.ToUpper()) == false)
         {
            throw new Exception("Could not find module: " + moduleName);
         }

         _powerModuleNameToObjectMap[moduleName.ToUpper()].SetInitialVoltage(moduleName);
      }

      /// <summary>
      /// Every power supply gets its voltage set to what was in the ini file
      /// </summary>
      public void SetInitialVoltageAll()
      {
         List<string> powerModules = GetPowerModuleList();
         foreach (string powerModule in powerModules)
         {
            SetInitialVoltage(powerModule);
         }
      }

      /// <summary>
      /// Sets the slew rate in volts per second
      /// </summary>
      /// <param name="moduleName">The name of the power module</param>
      /// <param name="slewRate">slew in volts per second</param>
      /*public void SetSlewRate(string moduleName, double slewRate)
		{
		   if (_powerModuleNameToObjectMap.ContainsKey(moduleName.ToUpper()) == false)
		   {
			  throw new Exception("PowerSupplyManager::SetSlewRate() - could not find module: " + moduleName);
		   }

		   _powerModuleNameToObjectMap[moduleName.ToUpper()].SetSlewRate(moduleName, slewRate);
		}*/

      /// <summary>
      /// 
      /// </summary>
      /// <param name="moduleName"></param>
      /// <param name="ocpValue"></param>
      public void SetOverCurrentProtection(string moduleName, double ocpValue)
      {
         if (_powerModuleNameToObjectMap.ContainsKey(moduleName.ToUpper()) == false)
         {
            throw new Exception("Could not find module: " + moduleName);
         }

         _powerModuleNameToObjectMap[moduleName.ToUpper()].SetOverCurrentProtection(moduleName, ocpValue);
      }

      /// <summary>
      /// 
      /// </summary>
      /// <param name="moduleName"></param>
      /// <param name="ovpValue"></param>
      public void SetOverVoltageProtection(string moduleName, double ovpValue)
      {
         if (_powerModuleNameToObjectMap.ContainsKey(moduleName.ToUpper()) == false)
         {
            throw new Exception("Could not find module: " + moduleName);
         }

         _powerModuleNameToObjectMap[moduleName.ToUpper()].SetOverVoltageProtection(moduleName, ovpValue);
      }

      /// <summary>
      /// Set the setpoint voltage of a module.
      /// </summary>
      /// <param name="moduleName">The module to set.</param>
      /// <param name="setpointVoltage">The setpoint voltage.</param>
      public void SetVoltageSetpoint(string moduleName, double setpointVoltage)
      {
         if (_powerModuleNameToObjectMap.ContainsKey(moduleName.ToUpper()) == false)
         {
            throw new Exception("Could not find module: " + moduleName);
         }

         _powerModuleNameToObjectMap[moduleName.ToUpper()].SetVoltageSetpoint(moduleName, setpointVoltage);
      }

      /// <summary>
      /// Disable the power system watchdog.
      /// </summary>
      /// <param name="powerSystem">The power system to act on.</param>
      public void WatchdogDisable(string powerSystem)
      {
         if (_powerSystemNameToObjectMap.ContainsKey(powerSystem.ToUpper()) == false)
         {
            throw new Exception("Could not find power system: " + powerSystem);
         }

         _powerSystemNameToObjectMap[powerSystem.ToUpper()].WatchdogDisable();
      }

      /// <summary>
      /// Enable the power system watchdog.
      /// </summary>
      /// <param name="powerSystem">The system to act on.</param>
      /// <param name="timeInSeconds">The number of seconds for the watchdog.</param>
      public void WatchdogEnable(string powerSystem, uint timeInSeconds)
      {
         if (_powerSystemNameToObjectMap.ContainsKey(powerSystem.ToUpper()) == false)
         {
            throw new Exception("Could not find power system: " + powerSystem);
         }

         _powerSystemNameToObjectMap[powerSystem.ToUpper()].WatchdogEnable(timeInSeconds);
      }

      #endregion

      #region PrivateClassFunctions
      /// <summary>
      /// The Finalizer
      /// </summary>
      ~PowerSupplyMeasurementManager()
      {
         Dispose(false);
      }

      /// <summary>
      /// Dispose of this object.
      /// </summary>
      /// <param name="disposing">True = currently disposing, False = not disposing.</param>
      protected virtual void Dispose(bool disposing)
      {
         if (disposing)
         {
            _logger.Debug($"Entering {this.GetType().Name}::{System.Reflection.MethodBase.GetCurrentMethod().Name}() method...");

            // dispose of the other resources
            foreach (KeyValuePair<string, IPowerSupplySystem> entry in _powerSystemNameToObjectMap)
            {
               try
               {
                  entry.Value.Shutdown();
               }
               catch (Exception ex)
               {
                  _logger.Error(ex.Message + "\r\n" + ex.StackTrace);
               }
            }
         }
      }
      #endregion
   }
}
