/*
 * Copyright (c) 2024 ETH Zürich, IT Services
 * 
 * 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.Linq;
using System.Text;
using SafeExamBrowser.Browser.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Applications;
using SafeExamBrowser.Settings.Proctoring;
using SafeExamBrowser.WindowsApi.Contracts.Events;

namespace SafeExamBrowser.Proctoring.ScreenProctoring.Data
{
	internal class MetaDataAggregator
	{
		private readonly IApplicationMonitor applicationMonitor;
		private readonly IBrowserApplication browser;
		private readonly ILogger logger;
		private readonly MetaDataSettings settings;

		private string applicationInfo;
		private string browserInfo;
		private string browserInfoWithoutUrls;
		private TimeSpan elapsed;
		private string triggerInfo;
		private string urls;
		private int urlCount;
		private string windowTitle;

		internal MetaData Data => new MetaData
		{
			ApplicationInfo = applicationInfo,
			BrowserInfo = browserInfo,
			Elapsed = elapsed,
			TriggerInfo = triggerInfo,
			Urls = urls,
			WindowTitle = windowTitle
		};

		internal MetaDataAggregator(
			IApplicationMonitor applicationMonitor,
			IBrowserApplication browser,
			TimeSpan elapsed,
			ILogger logger,
			MetaDataSettings settings)
		{
			this.applicationMonitor = applicationMonitor;
			this.browser = browser;
			this.elapsed = elapsed;
			this.logger = logger;
			this.settings = settings;
		}

		internal void Capture(IntervalTrigger interval = default, KeyboardTrigger keyboard = default, MouseTrigger mouse = default)
		{
			Initialize();
			CaptureApplicationData();
			CaptureBrowserData();

			if (interval != default)
			{
				CaptureIntervalTrigger(interval);
			}
			else if (keyboard != default)
			{
				CaptureKeyboardTrigger(keyboard);
			}
			else if (mouse != default)
			{
				CaptureMouseTrigger(mouse);
			}

			logger.Debug($"Captured metadata: {applicationInfo} / {browserInfoWithoutUrls} / {triggerInfo} / {urlCount} URL(s) / {windowTitle}.");
		}

		private void CaptureApplicationData()
		{
			if (applicationMonitor.TryGetActiveApplication(out var application))
			{
				if (settings.CaptureApplicationData)
				{
					applicationInfo = BuildApplicationInfo(application);
				}

				if (settings.CaptureWindowTitle)
				{
					windowTitle = string.IsNullOrEmpty(application.Window.Title) ? "-" : application.Window.Title;
				}
			}
		}

		private void CaptureBrowserData()
		{
			if (settings.CaptureBrowserData)
			{
				var windows = browser.GetWindows();

				browserInfo = string.Join(", ", windows.Select(w => $"{(w.IsMainWindow ? "Main" : "Additional")} Window: {w.Title} ({w.Url})"));
				browserInfoWithoutUrls = string.Join(", ", windows.Select(w => $"{(w.IsMainWindow ? "Main" : "Additional")} Window: {w.Title}"));
				urls = string.Join(", ", windows.Select(w => w.Url));
				urlCount = windows.Count();
			}
		}

		private void CaptureIntervalTrigger(IntervalTrigger interval)
		{
			triggerInfo = $"Maximum interval of {interval.ConfigurationValue}ms has been reached.";
		}

		private void CaptureKeyboardTrigger(KeyboardTrigger keyboard)
		{
			var flags = Enum.GetValues(typeof(KeyModifier)).OfType<KeyModifier>().Where(m => m != KeyModifier.None && keyboard.Modifier.HasFlag(m));
			var modifiers = flags.Any() ? string.Join(" + ", flags) + " + " : string.Empty;

			triggerInfo = $"'{modifiers}{keyboard.Key}' has been {keyboard.State.ToString().ToLower()}.";
		}

		private void CaptureMouseTrigger(MouseTrigger mouse)
		{
			if (mouse.Info.IsTouch)
			{
				triggerInfo = $"Tap as {mouse.Button} mouse button has been {mouse.State.ToString().ToLower()} at ({mouse.Info.X}/{mouse.Info.Y}).";
			}
			else
			{
				triggerInfo = $"{mouse.Button} mouse button has been {mouse.State.ToString().ToLower()} at ({mouse.Info.X}/{mouse.Info.Y}).";
			}
		}

		private string BuildApplicationInfo(ActiveApplication application)
		{
			var info = new StringBuilder();

			info.Append(application.Process.Name);

			if (application.Process.OriginalName != default)
			{
				info.Append($" ({application.Process.OriginalName}{(application.Process.Signature == default ? ")" : "")}");
			}

			if (application.Process.Signature != default)
			{
				info.Append($"{(application.Process.OriginalName == default ? "(" : ", ")}{application.Process.Signature})");
			}

			return info.ToString();
		}

		private void Initialize()
		{
			applicationInfo = "-";
			browserInfo = "-";
			browserInfoWithoutUrls = "-";
			triggerInfo = "-";
			urls = "-";
			windowTitle = "-";
		}
	}
}