//                           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 NLog;
using Raytheon.Common;
using Raytheon.Instruments;
using System;
using System.Collections.Generic;

namespace MeasurementManagerLib
{
   /// <summary>
   /// This class manages IFpgaComm instruments and provides an abstraction
   /// </summary>
   public class FpgaMeasurementManager : IDisposable
	{
		#region PrivateClassMembers
		// fpga name to object
		private readonly Dictionary<string, IFpgaComm> _fpgaDeviceMap;
		// fpga to MemoryMap
		private readonly Dictionary<string, MemoryMap> _fpgaMemMaps;

      private static NLog.ILogger _logger;
      #endregion

      #region PrivateFuctions

      /// <summary>
      /// Finalizer.
      /// </summary>
      ~FpgaMeasurementManager()
		{
			Dispose(false);
		}

		/// <summary>
		/// Disposing
		/// </summary>
		/// <param name="disposing"></param>
		protected virtual void Dispose(bool disposing)
		{
			if (disposing)
			{
				foreach (KeyValuePair<string, IFpgaComm> entry in _fpgaDeviceMap)
				{
					try
					{
						entry.Value.Shutdown();
					}
					catch (Exception err)
					{
						_logger?.Error(err.Message + "\r\n" + err.StackTrace);
					}
				}
			}
		}

		/// <summary>
		/// Initialize the communication to the fpga
		/// </summary>
		/// <param name="fpga">the name of the fpga as defined in the config file</param>
		private void Initialize(string fpga)
		{
			if (_fpgaDeviceMap.ContainsKey(fpga.ToUpper()) == false)
			{
				throw new Exception("could not find device: " + fpga);
			}

			_fpgaDeviceMap[fpga].Initialize(fpga);
		}

		#endregion

		#region PublicFuctions

		/// <summary>
		/// constructor with instrument manager
		/// </summary>
		/// <param name="instrumentManager"></param>
		/// <param name="instrumentNames"></param>
		/// <param name="instrumentDefFile"></param>
		public FpgaMeasurementManager(IInstrumentManager instrumentManager, List<string> instrumentNames, string instrumentDefFile)
		{
         _logger = LogManager.GetCurrentClassLogger();

         _fpgaDeviceMap = new Dictionary<string, IFpgaComm>();

			foreach (string name in instrumentNames)
			{
				_fpgaDeviceMap[name] = instrumentManager.GetInstrument<IFpgaComm>(name);
				_fpgaDeviceMap[name]?.Initialize(name);
			}

			// setup the memory maps
			_fpgaMemMaps = new Dictionary<string, MemoryMap>(StringComparer.InvariantCultureIgnoreCase);

			ConfigurationFile ini = new ConfigurationFile(instrumentDefFile);

			foreach (KeyValuePair<string, IFpgaComm> entry in _fpgaDeviceMap)
			{
				string memMapFile = ini.ReadValue<string>(entry.Key, "MEM_MAP");

				if (memMapFile.ToUpper().Trim() != "NONE")
				{
					_fpgaMemMaps.Add(entry.Key.ToUpper(), new MemoryMap(memMapFile));
				}
			}
		}

		/// <summary>
		/// Dispose of resources
		/// </summary>
		public void Dispose()
		{
			try
			{
				Dispose(true);

				GC.SuppressFinalize(this);
			}
			catch (Exception err)
			{
				try
				{
					_logger?.Error(err.Message + "\r\n" + err.StackTrace);
				}
				catch (Exception)
				{
					//Do not rethrow. Exception from error logger that has already been garbage collected
				}
			}
		}

		/// <summary>
		/// Get the comm device
		/// </summary>
		/// <param name="fpga"></param>
		/// <returns></returns>
		public IFpgaComm GetFpgaCommDevice(string fpga)
		{
			if (_fpgaDeviceMap.ContainsKey(fpga.ToUpper()) == false)
			{
				throw new Exception("could not find device: " + fpga);
			}

			return _fpgaDeviceMap[fpga.ToUpper()];
		}

		/// <summary>
		/// Returns all of the fpga names
		/// </summary>
		/// <returns>A list of fpga device names</returns>
		public List<string> GetFpgaNames()
		{
			List<string> fpgaList = new List<string>();

			foreach (KeyValuePair<string, IFpgaComm> entry in _fpgaDeviceMap)
			{
				fpgaList.Add(entry.Key);
			}

			return fpgaList;
		}

		/// <summary>
		/// Read a register that is defined by a memory map logical name
		/// If a subitem is commanded, this returns just the value of the subitems
		/// </summary>
		/// <param name="fpga">the name of the fpga as defined in the config file</param>
		/// <param name="memMapItemName">The name of the mem map item in the memory map file</param>
		/// <returns></returns>
		public uint RegisterRead(string fpga, string memMapItemName)
		{
			if (_fpgaMemMaps.ContainsKey(fpga.ToUpper()) == false)
			{
				throw new Exception("no memory map exists for fpga: " + fpga);
			}

			MemoryMap.MemoryMapFields fields = _fpgaMemMaps[fpga.ToUpper()].GetRegisterInfoByString(memMapItemName);

			uint registerValue = _fpgaDeviceMap[fpga.ToUpper()].Read(fpga, fields._address);

			uint itemValue = Util.ParseRegisterItem(registerValue, fields._startBit, fields._numBits);

			return itemValue;
		}

		/// <summary>
		/// Read a register given an address
		/// </summary>
		/// <param name="fpga">the name of the fpga as defined in the config file</param>
		/// <param name="address">the address to read</param>
		/// <returns></returns>
		public uint RegisterRead(string fpga, uint address)
		{
			if (_fpgaDeviceMap.ContainsKey(fpga.ToUpper()) == false)
			{
				throw new Exception("could not find device: " + fpga);
			}

			return _fpgaDeviceMap[fpga.ToUpper()].Read(fpga, address);
		}

		/// <summary>
		/// Read a register given an address with optional offset 
		/// </summary>
		/// <param name="fpga"></param>
		/// <param name="memMapItemName"></param>
		/// <param name="offset"></param>
		/// <returns></returns>
		/// <exception cref="Exception"></exception>
		public uint RegisterRead(string fpga, string memMapItemName, uint offset = 0)
		{
			if (_fpgaMemMaps.ContainsKey(fpga.ToUpper()) == false)
			{
				throw new Exception("no memory map exists for fpga: " + fpga);
			}

			MemoryMap.MemoryMapFields fields = _fpgaMemMaps[fpga.ToUpper()].GetRegisterInfoByString(memMapItemName);

			uint registerValue = _fpgaDeviceMap[fpga.ToUpper()].Read(fpga, fields._address + offset);

			uint itemValue = Util.ParseRegisterItem(registerValue, fields._startBit, fields._numBits);

			return itemValue;
		}

		/// <summary>
		/// reads block from fpga
		/// </summary>
		/// <param name="fpga"></param>
		/// <param name="address"></param>
		/// <param name="numberOfWordsToRead"></param>
		/// <param name="shallWeIncrementAddress"></param>
		/// <param name="dataRead"></param>
		/// <exception cref="Exception"></exception>
		public void ReadBlock(string fpga, uint address, uint numberOfWordsToRead, bool shallWeIncrementAddress, ref uint[] dataRead)
        {
			if (_fpgaDeviceMap.ContainsKey(fpga.ToUpper()) == false)
			{
				throw new Exception("FpgaMeasurementManager::ReadBlock - could not find device: " + fpga);
			}

			if (address == 0)
			{
				_fpgaDeviceMap[fpga.ToUpper()].ReadBlock(fpga, address, numberOfWordsToRead, shallWeIncrementAddress, ref dataRead);
			}
			else
			{
				throw new Exception("Not yet implemented");
			}
		}
		
		/// <summary>
		/// Write a register given an address
		/// </summary>
		/// <param name="fpga">the name of the fpga as defined in the config file</param>
		/// <param name="address">The address to write to</param>
		/// <param name="dataToWrite">The data to write to the register</param>
		public void RegisterWrite(string fpga, uint address, uint dataToWrite)
		{
			if (_fpgaDeviceMap.ContainsKey(fpga.ToUpper()) == false)
			{
				throw new Exception("could not find device: " + fpga);
			}

			_fpgaDeviceMap[fpga.ToUpper().ToUpper()].Write(fpga, address, dataToWrite);
		}

		/// <summary>
		/// write a register that is defined by a memory map logical name
		/// If a sub item is commanded, the current values of the other items remain unchanged
		/// </summary>
		/// <param name="fpga">the name of the fpga as defined in the config file</param>
		/// <param name="memMapItemName">The name of the mem map item in the memory map file</param>
		/// <param name="dataToWrite">The data to write to the register</param>
		/// <param name="offset">The offset off of memMapItemName</param>
		public void RegisterWrite(string fpga, string memMapItemName, uint dataToWrite, uint offset = 0)
		{
			if (_fpgaMemMaps.ContainsKey(fpga.ToUpper()) == false)
			{
				throw new Exception("no memory map exists for fpga: " + fpga);
			}

			MemoryMap.MemoryMapFields fields = _fpgaMemMaps[fpga.ToUpper()].GetRegisterInfoByString(memMapItemName);

			uint address = fields._address + offset;

			// if commanded to write to a sub register, only write out those bits
			if (fields._numBits < 32)
			{
				// read existing value
				uint originalValue = RegisterRead(fpga, address);

				//Apply inverse mask to current register contents to zero out just the bits we want to write
				uint maskValue = Util.ApplyBitMask(originalValue, fields._startBit, fields._numBits, true);

				// Shift the desired data to set to match the zeroed register
				dataToWrite = dataToWrite << fields._startBit;

				dataToWrite = maskValue | dataToWrite;
			}

			_fpgaDeviceMap[fpga.ToUpper()].Write(fpga, address, dataToWrite);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="fpga"></param>
		/// <param name="memMapItemName"></param>
		/// <param name="dataToWrite"></param>
		/// <param name="offset"></param>
		public void RegisterWriteBlock(string fpga, string memMapItemName, uint numWordsToWrite, uint[] dataToWrite, bool shallWeIncrementAddress, uint offset = 0)
		{
			if (_fpgaMemMaps.ContainsKey(fpga.ToUpper()) == false)
			{
				throw new Exception("no memory map exists for fpga: " + fpga);
			}

			MemoryMap.MemoryMapFields fields = _fpgaMemMaps[fpga.ToUpper()].GetRegisterInfoByString(memMapItemName);

			uint address = fields._address + offset;

			// if commanded to write to a sub register, only write out those bits
			if (fields._numBits < 32)
			{
				throw new Exception("can only write a block of data to a 32 bit register. fpga: " + fpga);
			}

			_fpgaDeviceMap[fpga.ToUpper()].WriteBlock(fpga, address, numWordsToWrite, dataToWrite, shallWeIncrementAddress);
		}

		/// <summary>
		/// Writes the default value (as specified in the memory map) to the register
		/// If a sub item is commanded, the current values of the other items remain unchanged
		/// </summary>
		/// <param name="fpga">The name of the FPGA</param>
		/// <param name="memMapItemName">The memory map item which we will restore the default.</param>
		public void RegisterWriteDefault(string fpga, string memMapItemName)
		{
			if (_fpgaMemMaps.ContainsKey(fpga.ToUpper()) == false)
			{
				throw new Exception("no memory map exists for fpga: " + fpga);
			}

			MemoryMap.MemoryMapFields fields = _fpgaMemMaps[fpga.ToUpper()].GetRegisterInfoByString(memMapItemName);

			uint dataToWrite = fields._regDefault;

			// if commanded to write to a sub register, only write out those bits
			if (fields._numBits < 32)
			{
				// read existing value
				uint originalValue = RegisterRead(fpga, fields._address);

				//Apply inverse mask to current register contents to zero out just the bits we want to write
				uint maskValue = Util.ApplyBitMask(originalValue, fields._startBit, fields._numBits, true);

				// Shift the desired data to set to match the zeroed register
				dataToWrite = dataToWrite << fields._startBit;

				dataToWrite = maskValue | dataToWrite;
			}

			_fpgaDeviceMap[fpga.ToUpper()].Write(fpga, fields._address, dataToWrite);
		}
		#endregion
	}
}
