using Nerfed.Runtime.Graphics;
using SDL2;

namespace Nerfed.Runtime;


/// <summary>
/// Represents a window in the client operating system. <br/>
/// Every Game has a MainWindow automatically. <br/>
/// You can create additional Windows if you desire. They must be Claimed by the GraphicsDevice to be rendered to.
/// </summary>
public class Window
{
	public event Action<Window, uint, uint> OnResizedEvent;
	public event Action<Window, int, int> OnMovedEvent;
	public event Action<Window> OnCloseEvent;

    internal IntPtr Handle { get; }
	public ScreenMode ScreenMode { get; private set; }
	public uint Width { get; private set; }
	public uint Height { get; private set; }
	internal Texture SwapchainTexture { get; set; }

	public bool Claimed { get; internal set; }
	public SwapchainComposition SwapchainComposition { get; internal set; }
	public TextureFormat SwapchainFormat { get; internal set; }

	public (int, int) Position
	{
		get
		{
			SDL.SDL_GetWindowPosition(Handle, out int x, out int y);
			return (x, y);
		}
	}

	public string Title { get; private set;}

	private bool IsDisposed;
    private static readonly Dictionary<uint, Window> windowsById = new Dictionary<uint, Window>();

	public Window(GraphicsDevice graphicsDevice, WindowCreateInfo windowCreateInfo)
	{
		SDL.SDL_WindowFlags flags = SDL.SDL_WindowFlags.SDL_WINDOW_SHOWN;

		if (graphicsDevice.Backend == BackendFlags.Vulkan)
		{
			flags |= SDL.SDL_WindowFlags.SDL_WINDOW_VULKAN;
		}
		else if (graphicsDevice.Backend == BackendFlags.Metal)
		{
			flags |= SDL.SDL_WindowFlags.SDL_WINDOW_METAL;
		}

		if (windowCreateInfo.ScreenMode == ScreenMode.Fullscreen)
		{
			flags |= SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
		}
		else if (windowCreateInfo.ScreenMode == ScreenMode.FullscreenBorderless)
		{
			flags |= SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
		}
		else if(windowCreateInfo.ScreenMode == ScreenMode.WindowedBorderless)
		{
			flags |= SDL.SDL_WindowFlags.SDL_WINDOW_BORDERLESS;
		}

		if (windowCreateInfo.SystemResizable)
		{
			flags |= SDL.SDL_WindowFlags.SDL_WINDOW_RESIZABLE;
		}

		if (windowCreateInfo.StartMaximized)
		{
			flags |= SDL.SDL_WindowFlags.SDL_WINDOW_MAXIMIZED;
		}

		ScreenMode = windowCreateInfo.ScreenMode;

		SDL.SDL_GetDesktopDisplayMode(0, out SDL.SDL_DisplayMode displayMode);

		Handle = SDL.SDL_CreateWindow(
			windowCreateInfo.WindowTitle,
			SDL.SDL_WINDOWPOS_CENTERED,
			SDL.SDL_WINDOWPOS_CENTERED,
			windowCreateInfo.ScreenMode == ScreenMode.Windowed ? (int) windowCreateInfo.WindowWidth : displayMode.w,
			windowCreateInfo.ScreenMode == ScreenMode.Windowed ? (int) windowCreateInfo.WindowHeight : displayMode.h,
			flags
		);

		/* Requested size might be different in fullscreen, so let's just get the area */
		SDL.SDL_GetWindowSize(Handle, out int width, out int height);
		Width = (uint) width;
		Height = (uint) height;

		windowsById.Add(SDL.SDL_GetWindowID(Handle), this);
	}

	internal static void ProcessEvent(ref SDL.SDL_Event ev)
	{
		uint windowId = ev.window.windowID;
		if (!windowsById.TryGetValue(windowId, out Window window))
		{
			Log.Error($"Received window event for unknown window id {windowsById}");
			return;
		}

		switch (ev.window.windowEvent)
		{
			case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED:
				window.ProcessSizeChangedEvent(ref ev.window);
				break;
			case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_MOVED:
				window.ProcessMovedChangedEvent(ref ev.window);
				break;
			case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_CLOSE:
				window.ProcessCloseEvent(ref ev.window);
				break;
		}
	}

    private void ProcessSizeChangedEvent(ref SDL.SDL_WindowEvent ev)
	{
		uint newWidth = (uint)ev.data1;
		uint newHeight = (uint)ev.data2;
		Width = newWidth;
		Height = newHeight;

		OnResizedEvent?.Invoke(this, Width, Height);
	}

    private void ProcessMovedChangedEvent(ref SDL.SDL_WindowEvent ev)
    {
		OnMovedEvent?.Invoke(this, ev.data1, ev.data2);
    }

    private void ProcessCloseEvent(ref SDL.SDL_WindowEvent ev)
	{
		OnCloseEvent?.Invoke(this);
		Engine.GraphicsDevice.UnclaimWindow(this);
		Dispose();
	}

	/// <summary>
	/// Changes the ScreenMode of this window.
	/// </summary>
	public void SetScreenMode(ScreenMode screenMode)
	{
		SDL.SDL_WindowFlags windowFlag = 0;

		if (screenMode == ScreenMode.Fullscreen)
		{
			windowFlag = SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN;
		}
		else if (screenMode == ScreenMode.FullscreenBorderless)
		{
			windowFlag = SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP;
		}

		SDL.SDL_SetWindowFullscreen(Handle, (uint) windowFlag);

		if (screenMode == ScreenMode.Windowed)
		{
			SDL.SDL_SetWindowPosition(Handle, SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED);
		}

		ScreenMode = screenMode;
	}

	/// <summary>
	/// Resizes the window. <br/>
	/// Note that you are responsible for recreating any graphics resources that need to change as a result of the size change.
	/// </summary>
	/// <param name="width"></param>
	/// <param name="height"></param>
	public void SetSize(uint width, uint height)
	{
		SDL.SDL_SetWindowSize(Handle, (int) width, (int) height);
		Width = width;
		Height = height;

		if (ScreenMode == ScreenMode.Windowed)
		{
			SDL.SDL_SetWindowPosition(Handle, SDL.SDL_WINDOWPOS_CENTERED, SDL.SDL_WINDOWPOS_CENTERED);
		}
	}

	/// <summary>
	/// Sets the window position.
	/// </summary>
	public void SetPosition(int x, int y)
	{
		SDL.SDL_SetWindowPosition(Handle, x, y);
	}

	/// <summary>
	/// Sets the window title.
	/// </summary>
	public void SetTitle(string title)
	{
		SDL.SDL_SetWindowTitle(Handle, title);
		Title = title;
	}
	
	internal void Show()
	{
		SDL.SDL_ShowWindow(Handle);
	}

    protected virtual void Dispose(bool disposing)
	{
		if (!IsDisposed)
		{
			if (disposing)
			{
				// dispose managed state (managed objects)
			}

			windowsById.Remove(SDL.SDL_GetWindowID(Handle));
			SDL.SDL_DestroyWindow(Handle);

			IsDisposed = true;
		}
	}

	~Window()
	{
		// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
		Dispose(disposing: false);
	}

	public void Dispose()
	{
		// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
		Dispose(disposing: true);
		GC.SuppressFinalize(this);
	}
}
