﻿using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Windows.Input;
using SharpDX;
using SharpDX.Win32;
using SharpDX.Windows;
using SharpDX.Direct2D1;
using DXGI = SharpDX.DXGI;

namespace DSD
{
	public partial class GraphicsWindow : RenderForm
	{
		// Singleton
		private static GraphicsWindow instance;
		public static GraphicsWindow Instance { get { return instance ?? (instance = new GraphicsWindow()); } }

		// Window message handling
		[DllImport("user32.dll")] static extern bool PeekMessage(out NativeMessage message, HandleRef window, uint filterMin, uint filterMax, uint remove);
		[DllImport("user32.dll")] static extern IntPtr DispatchMessage([In] ref NativeMessage lpmsg);

		private const UInt32 PM_REMOVE = 0x0001;

		// Direct2D
		public RenderTarget RenderTarget2D { get; private set; }
		public Device Device2D { get; private set; }

		// Input and event tracking
		public bool ShouldQuit { get; set; }
		protected volatile InputStates inputStates;
		protected volatile InputStates asyncStates;
		protected volatile bool leftMouseDetected;
		protected volatile bool rightMouseDetected;

		// Timer setup
		private Stopwatch stopwatch;
		private double lastPhysicsStepTicks;
		private double physicsDelayTicks;
		private double ticksToMilliFactor;
		private double ticksToPhysicsStepPercentFactor;
		private double updatesPerSecond = 40.0;

		public GraphicsWindow()
		{
			InitializeComponent();
			instance = this;
			inputStates = new InputStates();
		}

		private void GraphicsWindow_Load(object sender, EventArgs e)
		{
			Console.WriteLine("Window Load");
			Initialize();
		}

		protected void Initialize()
		{
			// Load all of the factories
			Factories.LoadFactories();

			// Setup the graphics
			Console.WriteLine("Setup Graphics");

			var hwndprop = new HwndRenderTargetProperties();
			hwndprop.Hwnd = Handle;
			hwndprop.PixelSize = new Size2(Constants.Width, Constants.Height);
			hwndprop.PresentOptions = PresentOptions.Immediately;

			var rendprop = new RenderTargetProperties();
			rendprop.PixelFormat = new PixelFormat(DXGI.Format.R8G8B8A8_UNorm, AlphaMode.Premultiplied);
			rendprop.Type = RenderTargetType.Hardware;
			
			SetClientSizeCore(Constants.Width, Constants.Height);
            RenderTarget2D = new WindowRenderTarget(Factories.Factory2D, rendprop, hwndprop);
			
			// Tick timer
			Console.WriteLine("Setup Timer");
			stopwatch = new Stopwatch();
			stopwatch.Start();
			physicsDelayTicks = Stopwatch.Frequency / updatesPerSecond;
			ticksToMilliFactor = 1.0 / (Stopwatch.Frequency / 1000.0);
			ticksToPhysicsStepPercentFactor = 1.0 / physicsDelayTicks;
			lastPhysicsStepTicks = 0.0;

			// Load all assets and resources
			Constants.Load();

			// Setup the main menu
			Console.WriteLine("Setup Menu");
			GameController.LoadInitialLevel(new LevelMenu());

			// Start the loop
			MainLoop();
		}

		protected void MainLoop()
		{
			HandleRef handleRef = new HandleRef(this, Handle);
			long framesElapsed = 0;

			Show();
			Application.DoEvents();

			Console.WriteLine("Loop Start");
			while (! ShouldQuit)
			{
				// Handle events
				NativeMessage message;
				if (PeekMessage(out message, handleRef, (uint)0, (uint)0, PM_REMOVE))
				{
					DispatchMessage(ref message);
					continue;
				}

				var ticksAhead = stopwatch.ElapsedTicks - lastPhysicsStepTicks;

				while (ticksAhead >= physicsDelayTicks)
				{
					lastPhysicsStepTicks += physicsDelayTicks;
					ticksAhead -= physicsDelayTicks;

					// Detect mouse input
					inputStates.MousePosition = PointToClient(MousePosition);

					// Detect keyboard input
					inputStates.Up = InputStates.GetKey(Keys.W);
					inputStates.Down = InputStates.GetKey(Keys.S);
					inputStates.Left = InputStates.GetKey(Keys.A);
					inputStates.Right = InputStates.GetKey(Keys.D);
					inputStates.Fire = InputStates.GetKey(Keys.C);
					inputStates.Secondary = InputStates.GetKey(Keys.X);
					inputStates.Upgrade = InputStates.GetKey(Keys.Tab);
					inputStates.Quit = InputStates.GetKey(Keys.Escape);

					// Loop once
					inputStates.DetectPresses();
					GameController.Update(inputStates, framesElapsed++);

					// Mouse flag reset
					leftMouseDetected = true;
					rightMouseDetected = true;
					inputStates.QuickClickLeft = false;
					inputStates.QuickClickRight = false;
                }

				var percent = ticksAhead * ticksToPhysicsStepPercentFactor;

				// Render as fast as possible
				GameController.Render(RenderTarget2D, (float)percent);
			}
			
			RenderTarget2D.Dispose();
			Application.Exit();
		}

		private void GraphicsWindow_FormClosed(object sender, FormClosedEventArgs e) { ShouldQuit = true; }
		private void GraphicsWindow_FormClosing(object sender, FormClosingEventArgs e) { ShouldQuit = true; }

		private void GraphicsWindow_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
		{
			// Detect presses
			switch (e.Button)
			{
				case MouseButtons.Left:
					inputStates.MouseLeft = true;
					leftMouseDetected = false;
					break;
				case MouseButtons.Right:
					inputStates.MouseRight = true;
					rightMouseDetected = false;
                    break;
				default:
					break;
			}
		}

		private void GraphicsWindow_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
		{
			// Detect presses
			switch (e.Button)
			{
				case MouseButtons.Left:
					inputStates.MouseLeft = false;
					if (!leftMouseDetected) inputStates.QuickClickLeft = true; // Log the missed keydown
					break;
				case MouseButtons.Right:
					inputStates.MouseRight = false;
					if (!rightMouseDetected) inputStates.QuickClickRight = true; // Log the missed keydown
					break;
				default:
					break;
			}
		}
	}
}
