/*
 * Copyright (c) 2019 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 SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Core.OperationModel.Events;
using SafeExamBrowser.Contracts.I18n;
using SafeExamBrowser.Contracts.Logging;
using SafeExamBrowser.Contracts.WindowsApi;

namespace SafeExamBrowser.Runtime.Operations
{
	internal class KioskModeOperation : SessionOperation
	{
		private IDesktopFactory desktopFactory;
		private IExplorerShell explorerShell;
		private IProcessFactory processFactory;

		protected ILogger logger;

		private IDesktop NewDesktop
		{
			get { return Context.NewDesktop; }
			set { Context.NewDesktop = value; }
		}

		private IDesktop OriginalDesktop
		{
			get { return Context.OriginalDesktop; }
			set { Context.OriginalDesktop = value; }
		}

		protected KioskMode? ActiveMode
		{
			get { return Context.ActiveMode; }
			set { Context.ActiveMode = value; }
		}

		public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
		public override event StatusChangedEventHandler StatusChanged;

		public KioskModeOperation(
			IDesktopFactory desktopFactory,
			IExplorerShell explorerShell,
			ILogger logger,
			IProcessFactory processFactory,
			SessionContext sessionContext) : base(sessionContext)
		{
			this.desktopFactory = desktopFactory;
			this.explorerShell = explorerShell;
			this.logger = logger;
			this.processFactory = processFactory;
		}

		public override OperationResult Perform()
		{
			logger.Info($"Initializing kiosk mode '{Context.Next.Settings.KioskMode}'...");
			StatusChanged?.Invoke(TextKey.OperationStatus_InitializeKioskMode);

			ActiveMode = Context.Next.Settings.KioskMode;

			switch (Context.Next.Settings.KioskMode)
			{
				case KioskMode.CreateNewDesktop:
					CreateNewDesktop();
					break;
				case KioskMode.DisableExplorerShell:
					TerminateExplorerShell();
					break;
			}

			return OperationResult.Success;
		}

		public override OperationResult Repeat()
		{
			var newMode = Context.Next.Settings.KioskMode;

			if (ActiveMode == newMode)
			{
				logger.Info($"New kiosk mode '{newMode}' is already active, skipping initialization...");

				return OperationResult.Success;
			}

			return Perform();
		}

		public override OperationResult Revert()
		{
			logger.Info($"Reverting kiosk mode '{ActiveMode}'...");
			StatusChanged?.Invoke(TextKey.OperationStatus_RevertKioskMode);

			switch (ActiveMode)
			{
				case KioskMode.CreateNewDesktop:
					CloseNewDesktop();
					break;
				case KioskMode.DisableExplorerShell:
					RestartExplorerShell();
					break;
			}

			return OperationResult.Success;
		}

		private void CreateNewDesktop()
		{
			OriginalDesktop = desktopFactory.GetCurrent();
			logger.Info($"Current desktop is {OriginalDesktop}.");

			NewDesktop = desktopFactory.CreateNew(nameof(SafeExamBrowser));
			logger.Info($"Created new desktop {NewDesktop}.");

			NewDesktop.Activate();
			processFactory.StartupDesktop = NewDesktop;
			logger.Info("Successfully activated new desktop.");

			explorerShell.Suspend();
		}

		private void CloseNewDesktop()
		{
			if (OriginalDesktop != null)
			{
				OriginalDesktop.Activate();
				processFactory.StartupDesktop = OriginalDesktop;
				logger.Info($"Switched back to original desktop {OriginalDesktop}.");
			}
			else
			{
				logger.Warn($"No original desktop found to activate!");
			}

			if (NewDesktop != null)
			{
				NewDesktop.Close();
				logger.Info($"Closed new desktop {NewDesktop}.");
			}
			else
			{
				logger.Warn($"No new desktop found to close!");
			}

			explorerShell.Resume();
		}

		private void TerminateExplorerShell()
		{
			StatusChanged?.Invoke(TextKey.OperationStatus_WaitExplorerTermination);

			explorerShell.HideAllWindows();
			explorerShell.Terminate();
		}

		private void RestartExplorerShell()
		{
			StatusChanged?.Invoke(TextKey.OperationStatus_WaitExplorerStartup);

			explorerShell.Start();
			explorerShell.RestoreAllWindows();
		}
	}
}