/*
 * Copyright (c) 2023 ETH Zürich, Educational Development and Technology (LET)
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

using System;
using System.Collections.Concurrent;
using System.Timers;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.SystemComponents.Contracts.Registry;
using SafeExamBrowser.SystemComponents.Contracts.Registry.Events;

namespace SafeExamBrowser.SystemComponents.Registry
{
	public class Registry : IRegistry
	{
		private readonly ILogger logger;
		private readonly ConcurrentBag<(string key, string name, object value)> values;

		private Timer timer;

		public event RegistryValueChangedEventHandler ValueChanged;

		public Registry(ILogger logger)
		{
			this.logger = logger;
			this.values = new ConcurrentBag<(string key, string name, object value)>();
		}

		public void StartMonitoring(string key, string name)
		{
			const int ONE_SECOND = 1000;

			if (timer?.Enabled != true)
			{
				timer = new Timer(ONE_SECOND);
				timer.AutoReset = true;
				timer.Elapsed += Timer_Elapsed;
				timer.Start();
			}

			if (TryRead(key, name, out var value))
			{
				values.Add((key, name, value));
				logger.Debug($"Started monitoring value '{name}' from registry key '{key}'. Initial value: '{value}'.");
			}
			else
			{
				logger.Error($"Failed to start monitoring value '{name}' from registry key '{key}'!");
			}
		}

		public void StopMonitoring()
		{
			while (!values.IsEmpty)
			{
				values.TryTake(out _);
			}

			if (timer != null)
			{
				timer.Stop();
				logger.Debug("Stopped monitoring the registry.");
			}
		}

		public bool TryRead(string key, string name, out object value)
		{
			var success = true;

			value = default;

			try
			{
				value = Microsoft.Win32.Registry.GetValue(key, name, default);
			}
			catch (Exception e)
			{
				success = false;
				logger.Error($"Failed to read value '{name}' from registry key '{key}'!", e);
			}

			return success;
		}

		private void Timer_Elapsed(object sender, ElapsedEventArgs e)
		{
			foreach (var item in values)
			{
				if (TryRead(item.key, item.name, out var value))
				{
					if (item.value != value)
					{
						logger.Debug($"Value '{item.name}' from registry key '{item.key}' has changed from '{item.value}' to '{value}'!");
						ValueChanged?.Invoke(item.value, value);
					}
				}
				else
				{
					logger.Error($"Failed to monitor value '{item.name}' from registry key '{item.key}'!");
				}
			}
		}
	}
}