// **********************************************************************************************************
//    TelemetryMeasurementManager.cs
//    2/19/2024
//    NGI - Next Generation Interceptor
//    
//    Contract No. HQ0856-21-C-0003/1022000209
//    
//    THIS DOCUMENT DOES NOT CONTAIN TECHNOLOGY OR TECHNICAL DATA CONTROLLED UNDER EITHER THE U.S. 
//    INTERNATIONAL TRAFFIC IN ARMS REGULATIONS OR THE U.S. EXPORT ADMINISTRATION REGULATIONS.
//    
//    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.
//    
//    UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.
//    
//    DESTRUCTION NOTICE: FOR CLASSIFIED DOCUMENTS FOLLOW THE PROCEDURES IN DOD 5220.22-M, 
//    NATIONAL INDUSTRIAL SECURITY PROGRAM OPERATING MANUAL, FEBRUARY 2006, 
//    INCORPORATING CHANGE 1, MARCH 28, 2013, CHAPTER 5, SECTION 7, OR DODM 5200.01-VOLUME 3, 
//    DOD INFORMATION SECURITY PROGRAM: PROTECTION OF CLASSIFIED INFORMATION, ENCLOSURE 3, 
//    SECTION 17. FOR CONTROLLED UNCLASSIFIED INFORMATION FOLLOW THE PROCEDURES IN DODM 5200.01-VOLUME 4, 
//    INFORMATION SECURITY PROGRAM: CONTROLLED UNCLASSIFIED INFORMATION.
//    
//    CONTROLLED BY: MISSILE DEFENSE AGENCY
//    CONTROLLED BY: GROUND-BASED MIDCOURSE DEFENSE PROGRAM OFFICE
//    CUI CATEGORY: CTI 
//    DISTRIBUTION/DISSEMINATION CONTROL: F 
//    POC: Alex Kravchenko (1118268)
// **********************************************************************************************************
using NLog;
using Raytheon.Common;
using Raytheon.Instruments;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace MeasurementManagerLib
{
   internal class TelemetryClient
	{
		public ICommAsync Client { get; set; }
		public CancellationTokenSource TokenSource { get; set; }
		public string BaseDirectory { get; set; }
		public string BaseFileName { get; set; }
		public int BufferSize { get; set; }
		public FileRecordingMode FileRecordingMode { get; set; }
	}

	// create a resulting file based on mode
	public enum FileRecordingMode
	{
		/// <summary>
		/// use default configuration value
		/// </summary>
		Undefined,
		/// <summary>
		/// user client provided name as is
		/// overwrite the existing file
		/// </summary>
		None,
		/// <summary>
		/// append date time at the end of the base file name
		/// </summary>
		UnigueDateTime,
		/// <summary>
		/// append rolling index number at the end of the file name
		/// </summary>
		UniqueIndex
	}

	public class TelemetryMeasurementManager : IDisposable
	{
		private readonly ILogger _logger;
		private readonly Dictionary<string, TelemetryClient> _clients = new Dictionary<string, TelemetryClient>();

		private const string DefaultConfigurationName = "TelemetryManager.xml";

		/// <summary>
		/// adds multiple instruments for Telemetry or any other data collection
		/// </summary>
		/// <param name="instrumentManager"></param>
		/// <param name="instrumentNames"></param>
		/// <param name="configName"></param>
		/// <exception cref="ArgumentOutOfRangeException"></exception>
		public TelemetryMeasurementManager(IInstrumentManager instrumentManager, List<string> instrumentNames, string configName = "")
		{
			_logger = LogManager.GetCurrentClassLogger();

			if (instrumentNames == null)
			{
				throw new ArgumentOutOfRangeException(nameof(instrumentNames));
			}

			if (string.IsNullOrEmpty(configName))
			{
				configName = DefaultConfigurationName;
			}
			IConfigurationFile config = new ConfigurationFile(configName);

			instrumentNames.ForEach(i => AddInstrument(instrumentManager, i, config));
		}

		/// <summary>
		/// starts recording for the first instrument
		/// </summary>
		/// <param name="fileName"></param>
		/// <param name="recordingMode"></param>
		public void StartRecording(string fileName = "", FileRecordingMode recordingMode = FileRecordingMode.UnigueDateTime)
		{
			StartRecording(_clients.FirstOrDefault().Key, fileName, recordingMode);
		}

		/// <summary>
		/// starts recording for the specified instrument
		/// </summary>
		/// <param name="fileName"></param>
		/// <param name="instrumentName"></param>
		/// <param name="recordingMode"></param>
		/// <exception cref="InvalidOperationException"></exception>
		public void StartRecording(string instrumentName, string fileName = "", FileRecordingMode recordingMode = FileRecordingMode.Undefined)
		{
			if (string.IsNullOrEmpty(instrumentName) || !_clients.ContainsKey(instrumentName))
			{
				throw new InvalidOperationException($"No Telemetry Instrument Found for {instrumentName}.");
			}

			var telemetryClient = _clients[instrumentName];
			
			var client = telemetryClient.Client;
			var token = telemetryClient.TokenSource.Token;
			if(string.IsNullOrEmpty(fileName))
			{
				fileName = telemetryClient.BaseFileName;
			}
			if(recordingMode == FileRecordingMode.Undefined)
			{
				recordingMode = telemetryClient.FileRecordingMode;
			}

			if (!Directory.Exists(telemetryClient.BaseDirectory))
			{
				// The directory does not exist, so create it
				Directory.CreateDirectory(telemetryClient.BaseDirectory);
				Console.WriteLine($"Directory '{telemetryClient.BaseDirectory}' created successfully.");
			}

			string filePath = Path.Combine(telemetryClient.BaseDirectory, fileName);
			string uniqueFileName;
			
			if(recordingMode == FileRecordingMode.UnigueDateTime)
			{
				uniqueFileName = GenerateUniqueFileNameDyDateTime(filePath);
			}
			else if(recordingMode == FileRecordingMode.UniqueIndex)
			{
				uniqueFileName = GenerateUniqueFileNameByIndex(filePath);
			}
			else
			{
				uniqueFileName = fileName;
				if(File.Exists(uniqueFileName))
				{
					File.Delete(uniqueFileName);
				}
			}

			_logger.Debug($"Starting Recording in {uniqueFileName}");

			try
			{
				client.Initialize();
			}
			catch (Exception ex)
			{
				_logger.Error(ex, ex.Message);
				throw;
			}

			var fileStream = new FileStream(uniqueFileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, bufferSize: telemetryClient.BufferSize, useAsync: true);
			
			// use a thread from the thread pool and provide a function to write incoming data into a file
			Task.Run(async () => await client.KeepReadingAsync(token, async (byte[] incomingData) =>
			{
				await fileStream?.WriteAsync(incomingData, 0, incomingData.Length);
			}).ContinueWith((r) =>
			{
				// done recording
				if (r.IsFaulted)
				{
					_logger.Error(r.Exception, r.Exception.Message);
				}
				else
				{
					_logger.Info($"Telemetry Manager Recording Completed for {uniqueFileName}");
				}

				try
				{
					client?.Close();
					client = null;
				}
				catch (Exception ex)
				{
					_logger.Error(ex, ex.Message);
				}
				finally 
				{ 
					fileStream?.Dispose();
					fileStream = null;
				}
			}));
		}

		/// <summary>
		/// stops recording
		/// </summary>
		public void StopRecording()
		{
			_logger.Debug("Stopping Recording.");

			foreach (var instrumentName in _clients.Keys)
			{
				StopRecording(instrumentName);
			}
		}

		/// <summary>
		/// stops recording for specific instrument name
		/// </summary>
		/// <param name="instrumentName"></param>
		public void StopRecording(string instrumentName)
		{
			if (!_clients.ContainsKey(instrumentName))
				return;

			_logger.Debug($"Stopping Recording for {instrumentName}.");

			var telemetryClient = _clients[instrumentName];
			var tokenSource = telemetryClient.TokenSource;

			if (!tokenSource.IsCancellationRequested)
			{
				tokenSource.Cancel();
			}
			tokenSource.Dispose();
		}

		/// <summary>
		/// Cleanup code
		/// </summary>
		public void Dispose()
		{
			StopRecording();

			foreach (var tokenClient in _clients.Values)
			{
				var client = tokenClient?.Client;
				client?.Close();
			}
		}

		/// <summary>
		/// generates unique file name
		/// </summary>
		/// <param name="existingFilePath"></param>
		/// <returns></returns>
		private static string GenerateUniqueFileNameDyDateTime(string existingFilePath)
		{
			string directory = Path.GetDirectoryName(existingFilePath);
			string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(existingFilePath);
			string extension = Path.GetExtension(existingFilePath);

			// Append a timestamp to the original filename
			string uniqueFileName = $"{fileNameWithoutExtension}_{DateTime.Now:MMddHHmmss}{extension}";

			// Combine with the directory path
			return Path.Combine(directory, uniqueFileName);
		}

		/// <summary>
		/// generates unique file name based on rolling index
		/// </summary>
		/// <param name="existingFilePath"></param>
		/// <returns></returns>
		private static string GenerateUniqueFileNameByIndex(string existingFilePath)
		{
			string directoryPath = Path.GetDirectoryName(existingFilePath);
			string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(existingFilePath);
			string extension = Path.GetExtension(existingFilePath);

			int index = 1; // Initialize the rolling index
			while (File.Exists(Path.Combine(directoryPath, $"{fileNameWithoutExtension}_{index}{extension}")))
			{
				index++; // Increment the index until a unique file name is found
			}

			// return appended index to the original filename
			return Path.Combine(directoryPath, $"{fileNameWithoutExtension}_{index}{extension}");
		}

		/// <summary>
		/// adds single instrument to the collection
		/// </summary>
		/// <param name="instrumentManager"></param>
		/// <param name="instrumentName"></param>
		/// <param name="config"></param>
		private void AddInstrument(IInstrumentManager instrumentManager, string instrumentName, IConfigurationFile config)
		{
			_logger.Info($"TelemetryMeasurementManager - Adding Instrument Name {instrumentName}\nConfiguration {config.FileName}");

			string baseDirectory = config.ReadValue($"Telemetry_{instrumentName}", "Directory", $"./{instrumentName}");
			string baseFileName = config.ReadValue($"Telemetry_{instrumentName}", "BaseFileName", $"./{instrumentName}");
			FileRecordingMode fileRecordingMode = config.ReadValue($"Telemetry_{instrumentName}", "FileRecordingMode", FileRecordingMode.UnigueDateTime);

			int bufferSize = config.ReadValue($"Telemetry_{instrumentName}", "BufferSize", 4096);

			_clients.Add(instrumentName, new TelemetryClient
			{
				Client = (ICommAsync)instrumentManager.GetGenericInstrument(instrumentName),
				TokenSource = new CancellationTokenSource(),
				BaseDirectory = baseDirectory,
				BaseFileName = baseFileName,
				BufferSize = bufferSize,
				FileRecordingMode = fileRecordingMode,
			});
		}

	}
}
