using Gma.System.MouseKeyHook;
using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.ServiceProcess;
using System.Timers;
using System.Windows.Forms;

namespace Sleepy
{
	partial class Service : ServiceBase
	{
		private const long MOVEMENT_SPAN = TimeSpan.TicksPerSecond;
		private const int MOVEMENT_DISTANCE = 50;

		private bool locked = false;
		private System.Timers.Timer timer;
		private bool hooked = false;
		private IKeyboardMouseEvents kmHooks = Hook.GlobalEvents();
		private Tuple<DateTime, Point> mouseLocation;
		private Config config;

		public Service(Config config)
		{
			this.config = config;
			
			timer = new System.Timers.Timer(config.Timeout * 1000);
			timer.Elapsed += suspend;

			CanHandlePowerEvent = true;
			CanHandleSessionChangeEvent = true;
			
			InitializeComponent();
		}

		/// <summary>
		/// Service startup handler.
		/// </summary>
		protected override void OnStart(string[] args)
		{
		}

		/// <summary>
		/// Service shutdown handler.
		/// </summary>
		protected override void OnStop()
		{
			stop();
		}

		/// <summary>
		/// Fired on computer suspend/resume.
		/// </summary>
		protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus)
		{
			switch (powerStatus)
			{
				case PowerBroadcastStatus.Suspend:
					Console.WriteLine("Workstation has been suspended.");
					stop();
					break;
				case PowerBroadcastStatus.ResumeAutomatic:
				case PowerBroadcastStatus.ResumeCritical:
				case PowerBroadcastStatus.ResumeSuspend:
					Console.WriteLine("Workstation has been resumed.");
					start();
					break;
			}

			return true;
		}

		/// <summary>
		/// Fired on session lock/unlock.
		/// </summary>
		protected override void OnSessionChange(SessionChangeDescription changeDescription)
		{
			switch (changeDescription.Reason)
			{
				case SessionChangeReason.SessionLock:
					Console.WriteLine("Workstation has been locked.");
					locked = true;
					break;
				case SessionChangeReason.SessionUnlock:
				case SessionChangeReason.SessionLogon:
					Console.WriteLine("Workstation has been unlocked.");
					locked = false;
					stop();
					break;

			}
		}

		/// <summary>
		/// Puts the computer back to sleep.
		/// </summary>
		private void suspend(object sender, ElapsedEventArgs e)
		{
			if (config.WindowsUpdate && isWindowsUpdateRunning())
			{
				Console.WriteLine("Windows Update is seems to be running.");
				start();
				return;
			}

			Console.WriteLine("Timer elapsed, returning to sleep.");
			stop();

			Application.SetSuspendState(PowerState.Suspend, true, true);
		}

		/// <summary>
		/// Attaches keyboard/mouse hook.
		/// </summary>
		private void hook()
		{
			if (hooked)
			{
				return;
			}

			kmHooks.KeyPress += onKeyPress;
			kmHooks.MouseMove += onMouseMove;
			kmHooks.MouseClick += onMouseClick;

			mouseLocation = null;
		}

		/// <summary>
		/// Removes keyboard/mouse hook.
		/// </summary>
		private void unhook()
		{
			if (!hooked)
			{
				return;
			}

			kmHooks.KeyPress -= onKeyPress;
			kmHooks.MouseMove -= onMouseMove;
			kmHooks.MouseClick -= onMouseClick;

			mouseLocation = null;
		}

		/// <summary>
		/// Invoked on key press.
		/// </summary>
		private void onKeyPress(object sender, KeyPressEventArgs e)
		{
			onActivity();
		}

		/// <summary>
		/// Invoked on mouse move. Triggers an activity handler when cursor moves N pixels in a second.
		/// </summary>
		private void onMouseMove(object sender, MouseEventArgs e)
		{
			if (mouseLocation == null)
			{
				mouseLocation = new Tuple<DateTime, Point>(DateTime.Now, e.Location);
			}
			else if (DateTime.Now.Ticks > mouseLocation.Item1.Ticks + MOVEMENT_SPAN)
			{

				var distance
					= (e.Location.X - mouseLocation.Item2.X) * (e.Location.X - mouseLocation.Item2.X)
					+ (e.Location.Y - mouseLocation.Item2.Y) * (e.Location.Y - mouseLocation.Item2.Y);

				mouseLocation = new Tuple<DateTime, Point>(DateTime.Now, e.Location);

				if (distance > MOVEMENT_DISTANCE * MOVEMENT_DISTANCE)
				{
					onActivity();
				}

			}
		}

		/// <summary>
		/// Invoked on mouse click.
		/// </summary>
		private void onMouseClick(object sender, MouseEventArgs e)
		{
			onActivity();
		}

		/// <summary>
		/// Elongates or stops the timeout.
		/// </summary>
		private void onActivity()
		{
			if (timer.Enabled && !locked)
			{
				Console.WriteLine("An activity detected on unlocked workstation.");
				stop();
			}
		}

		/// <summary>
		/// Starts or restarts the return countdown.
		/// </summary>
		private void start()
		{
			Console.WriteLine(timer.Enabled ? "Restarting the timer." : "Starting the timer.");

			hook();
			mouseLocation = null;
			timer.Stop();
			timer.Start();
		}

		/// <summary>
		/// Stops the return countdown.
		/// </summary>
		private void stop()
		{
			if (timer.Enabled)
			{
				Console.WriteLine("Stopping the timer.");
			}

			mouseLocation = null;
			timer.Stop();
			unhook();
		}

		/// <summary>
		/// Determines whether there is an update running.
		/// </summary>
		static private bool isWindowsUpdateRunning()
		{
			foreach (var process in Process.GetProcesses())
			{
				try {
					var file = Path.GetFileName(process.MainModule.FileName).ToLower();
					if (file == "msiexec.exe" || file == "trustedinstaller.exe")
					{
						return true;
					}
				} catch {
					// Restricted process?
				}
			}

			return false;
		}
	}
}