using System;
using System.Linq;
using ImGui;
using GlitchyEngine;
using GlitchyEditor.EditWindows;
using GlitchyEngine.Events;
using GlitchyEngine.ImGui;
using GlitchyEngine.Math;
using GlitchyEngine.Renderer;
using GlitchyEngine.World;
using GlitchyEngine.Content;
using System.Collections;
using GlitchyEngine.Renderer.Animation;
using GlitchyEngine.Serialization;
using System.IO;
using GlitchyEngine.Core;
using GlitchyEditor.Assets;
using GlitchyEngine.Scripting;
using GlitchyEditor.Platform.Windows;

namespace GlitchyEditor
{
	class EditorLayer : Layer
	{
		enum SceneState
		{
			Edit,
			Play,
			Simulate
		}

		RasterizerState _rasterizerState ~ _?.ReleaseRef();
		RasterizerState _rasterizerStateClockWise ~ _?.ReleaseRef();

		// TODO: we shouldn't hold a reference to the context
		GraphicsContext _context ~ _.ReleaseRef();
		
		BlendState _alphaBlendState ~ _.ReleaseRef();
		BlendState _opaqueBlendState ~ _.ReleaseRef();
		DepthStencilState _depthStencilState ~ _.ReleaseRef();

		/// Reference to the scene that is currently being played and worked on.
		Scene _activeScene ~ _?.ReleaseRef();
		
		/**
		 * Referece to the editor scene.
		 * We hold a reference to the editor scene because we need it in order
		 * to restore the original state once we stop the game/simulation.
		 * Before starting the simulation the editor scene will be copied and
		 * the reference in _activeScene will be replaced with the new scene.
		 */
		Scene _editorScene ~ _?.ReleaseRef();

		SceneRenderer _gameSceneRenderer ~ delete _;
		SceneRenderer _editorSceneRenderer ~ delete _;
		
		/// Path of the current scene.
		append String _sceneFilePath = .();

		Editor _editor ~ delete _;

		RenderTargetGroup _cameraTarget ~ _.ReleaseRef();
		RenderTargetGroup _editorViewportTarget ~ _.ReleaseRef();
		RenderTargetGroup _gameViewportTarget ~ _.ReleaseRef();

		EditorCamera _camera ~ _.Dispose();

		EditorIcons _editorIcons ~ _.ReleaseRef();

		EditorContentManager _contentManager;

		SceneState _sceneState = .Edit;
		bool _isPaused = false;
		bool _singleStep = false;

		private float _fixedTimestep = 1.0f / 60.0f;
		
		append List<(String Name, String Path)> _recentScenePaths = .() ~ {
				for (var item in _)
				{
					delete item.Name;
					delete item.Path;
				}
			};
		
		private GameTime _simulationGameTime = new .() ~ delete _;
		
		/// Gets or sets the path of the current scene.
		public StringView SceneFilePath
		{
			get => _sceneFilePath;
			set
			{
				_sceneFilePath.Clear();

				if (!value.IsWhiteSpace)
					_sceneFilePath.Append(value);
			}
		}

		private Project _currentProject ~ delete _;

		public Project CurrentProject => _currentProject;

		public bool IsProjectLoaded => _currentProject != null;
		
		/// Returns whether or not the current scene can be saved.
		public bool CanSaveScene => _sceneState == .Edit;

		private AssetThumbnailManager _thumbnailManager ~ delete _;

#if GE_EDITOR_IMGUI_DEMO
		private bool _showImguiDemoWindow;
#endif

		[AllowAppend]
		public this(String[] args, EditorContentManager contentManager) : base("Editor")
		{
			Application.Instance.Window.IsVSync = true;

			ScriptEngine.ApplicationInfo.IsEditor = true;

			// TODO: Das muss weg
			_layer = this;

			_contentManager = contentManager;

			InitGraphics();

			_gameSceneRenderer = new SceneRenderer();
			_editorSceneRenderer = new SceneRenderer();

			_camera = EditorCamera(float3(-3f, 3f, -3f), Quaternion.FromEulerAngles(MathHelper.ToRadians(40), MathHelper.ToRadians(25), 0), MathHelper.ToRadians(75), 0.1f, 1);
			_camera.RenderTarget = _cameraTarget;

			_thumbnailManager = new AssetThumbnailManager(_editorIcons);

			InitEditor();

			RegisterAssetCreators();

			if (args.Count >= 1)
			{
				Result<void> result = LoadAndOpenProject(args[0]);

				if (result == .Err)
					Log.EngineLogger.Error($"Failed to open project \"{args[0]}\".");
			}

			// Create a new scene if no scene is loaded
			if (_activeScene == null)
				CreateAndOpenNewScene();
		}

		public ~this()
		{
			CloseCurrentProject();
		}

		private void RegisterAssetCreators()
		{
			Editor.Instance.ContentBrowserWindow.RegisterAssetCreator(new AssetCreator("Folder", "New Folder", "", new (path) =>
				{
					if (Directory.CreateDirectory(path) case .Err(let error))
					{
						Log.ClientLogger.Error($"Directory \"{path}\" could not be created ({error}).");
					}
				}, _editorIcons.Folder));

			Editor.Instance.ContentBrowserWindow.InsertAssetCreatorSeparator();

			Editor.Instance.ContentBrowserWindow.RegisterAssetCreator(new AssetCreator("Scene", "New Scene", ".scene", new (path) =>
				{
					using (Scene newScene = CreateNewScene())
					{
						SaveScene(newScene, path);
					}
				}, _editorIcons.File_Scene));
			
			Editor.Instance.ContentBrowserWindow.RegisterAssetCreator(new AssetCreator("Shader File", "New Shader", ".hlsl", new (path) =>
				{
					// TODO!
				}, _editorIcons.File_Hlsl));

			Editor.Instance.ContentBrowserWindow.RegisterAssetCreator(new AssetCreator("2D Effect", "New Effect", ".fx", new (path) =>
				{
					// TODO!
				}, _editorIcons.File_Effect));

			// TODO: 3D Effect Lit, 3D Effect unlit, etc.
			/*Editor.Instance.ContentBrowserWindow.RegisterAssetCreator(new AssetCreator("3D Effect", "New Effect", ".fx", new (path) =>
				{
					// TODO!
				}, _editorIcons.File_Effect));*/

			Editor.Instance.ContentBrowserWindow.RegisterAssetCreator(new AssetCreator("Material", "New Material", ".mat", new (path) =>
				{
					using (Material newMaterial = new Material())
					{
						_contentManager.SaveAssetToFile(newMaterial, path);
					}
				}, _editorIcons.File_Material));
			
			Editor.Instance.ContentBrowserWindow.RegisterAssetCreator(new AssetCreator("Script", "New Entity", ".cs", new (path) =>
				{
					String templatePath = scope .();
					Directory.GetCurrentDirectory(templatePath);
					Path.Combine(templatePath, "Resources/Templates/ScriptTemplate.cs");

					String scriptContent = scope .();

					if (File.ReadAllText(templatePath, scriptContent, true) case .Err(let error))
					{
						Log.EngineLogger.Error($"Failed to read template file from \"{templatePath}\". Error: {error}");
						return;
					}

					scriptContent.Replace("[ProjectName]", CurrentProject.Name);

					String fileName = scope .();
					Path.GetFileName(path, fileName);

					Log.EngineLogger.AssertDebug(fileName.EndsWith(".cs"));

					if (fileName.EndsWith(".cs"))
						fileName.RemoveFromEnd(3);

					String pascalFileName = scope .(fileName.Length);
					ToPascalCase(fileName, pascalFileName);
					String cSharpClassName = scope .(pascalFileName.Length);
					ToCSharpName(pascalFileName, cSharpClassName);

					scriptContent.Replace("[EntityName]", cSharpClassName);

					if (File.WriteAllText(path, scriptContent, false) case .Err)
					{
						Log.EngineLogger.Error($"Failed to save script file to \"{path}\".");
						return;
					}
				}, _editorIcons.File_CSharpScript));
		}

		private void ToCSharpName(String input, String output)
		{
			for (char32 c in input.DecodedChars)
			{
				if (c.IsLetterOrDigit || c == '_' || c == '@')
				{
					output.Append(c);
				}
			}

			let decode = System.Text.UTF8.Decode(output.Ptr, output.Length);
			
			// Make sure the name starts with a letter or _ or @
			if (!(decode.c.IsLetter || decode.c == '_' || decode.c == '@'))
			{
				output.Insert(0, '@');
			}
		}

		private void ToPascalCase(String input, String output)
		{
			bool newWord = true;

			for (char32 c in input.DecodedChars)
			{
				if (c.IsLetter)
				{
					if (newWord)
					{
						output.Append(c.ToUpper);
						newWord = false;
					}
					else
					{
						output.Append(c);
					}
				}
				else
				{
					newWord = true;

					if (c.IsWhiteSpace || c == '_')
					{
						continue;
					}
					else
					{
						output.Append(c);
					}
				}
			}
		}

		private void InitGraphics()
		{
			_context = Application.Get().Window.Context..AddRef();
			
			RasterizerStateDescription rsDesc = .(.Solid, .Back, true);
			_rasterizerState = new RasterizerState(rsDesc);

			rsDesc.FrontCounterClockwise = false;
			_rasterizerStateClockWise = new RasterizerState(rsDesc);

			BlendStateDescription blendDesc = .();
			blendDesc.RenderTarget[0] = .(true, .SourceAlpha, .InvertedSourceAlpha, .Add, .SourceAlpha, .InvertedSourceAlpha, .Add, .All);
			_alphaBlendState = new BlendState(blendDesc);
			_opaqueBlendState = new BlendState(.Default);

			DepthStencilStateDescription dsDesc = .();
			_depthStencilState = new DepthStencilState(dsDesc);
			
			_cameraTarget = new RenderTargetGroup(.(){
					Width = 100,
					Height = 100,
					ColorTargetDescriptions = TargetDescription[](
						.(.R16G16B16A16_Float, ownDebugName: new String("HDR Color")),
						.(.R32_UInt, ownDebugName: new String("EntityID"))
					),
					DepthTargetDescription = .(.D24_UNorm_S8_UInt)
				});
			_cameraTarget.[Friend]Identifier = "Camera Target";

			_editorViewportTarget = new RenderTargetGroup(.()
				{
					Width = 100,
					Height = 100,
					ColorTargetDescriptions = TargetDescription[](
						.(.R8G8B8A8_UNorm))
				});
			_editorViewportTarget.[Friend]Identifier = "Editor Viewport Target";

			_gameViewportTarget = new RenderTargetGroup(.()
				{
					Width = 100,
					Height = 100,
					ColorTargetDescriptions = TargetDescription[](
						.(.R8G8B8A8_UNorm))
				});
			_gameViewportTarget.[Friend]Identifier = "Game Viewport Target";

			_editorIcons = new EditorIcons("Resources/Textures/EditorIcons.dds", .(64, 64));
			_editorIcons.SamplerState = SamplerStateManager.AnisotropicClamp;

			ContentBrowserWindow.s_FolderTexture = _editorIcons.Folder;
			ContentBrowserWindow.s_FileTexture = _editorIcons.File;

			LogWindow.s_ErrorIcon = _editorIcons.Error;
			LogWindow.s_WarningIcon = _editorIcons.Warning;
			LogWindow.s_InfoIcon = _editorIcons.Info;
			LogWindow.s_TraceIcon = _editorIcons.Trace;
		}

		private void InitEditor()
		{
			_editor = new Editor(_editorScene, _editorScene, _contentManager, _thumbnailManager);
			_editor.SceneViewportWindow.ViewportSizeChanged.Add(new (s, e) => EditorViewportSizeChanged(s, e));
			_editor.GameViewportWindow.ViewportSizeChanged.Add(new (s, e) => GameViewportSizeChanged(s, e));
			_editor.CurrentCamera = &_camera;
			_editor.GameSceneRenderer = _gameSceneRenderer;
			_editor.EditorSceneRenderer = _editorSceneRenderer;

			_editor.RequestOpenScene.Add(new (s, fileName) => {
			  	LoadSceneFile(fileName);
			});
		}

		public override void Update(GameTime gameTime)
		{
			Debug.Profiler.ProfileFunction!();

			if (IsProjectLoaded)
				UpdateScene(gameTime);
		}

		private void UpdateScene(GameTime gameTime)
		{
			_editor.CurrentScene = _activeScene;

			Scene.UpdateMode updateMode;

			switch (_sceneState)
			{
			case .Edit:
				updateMode = .Scripts | .EditMode;
			case .Play:
				updateMode = .Scripts | .Physics;
			case .Simulate:
				updateMode = .Physics;
			}

			// Don't step if we are in in the simulation/game but paused and don't single step
			if (_sceneState != .Edit && _isPaused && !_singleStep)
				updateMode = .None;

			/*
			 * If we are in edit mode we use the "official" gametime.
			 * However in play or simulation mode we use a simulation game time
			 * which is decoupled from the actual time. This allows us to manipulate
			 * the simulation speed without potentially messing with the rest of the editor.
			 */
			GameTime gt = null;

			if (updateMode.HasFlag(.EditMode))
			{
				gt = gameTime;
			}
			else
			{
				gt = _simulationGameTime;
				
				if (_singleStep)
				{
					_singleStep = false;
					_simulationGameTime.ManualStepFrame(_fixedTimestep);
				}
				else
				{
					_simulationGameTime.ManualStepFrame(gameTime.DeltaTime);
				}
			}

			_activeScene.Update(gt, updateMode);

			// Clear the swapchain-buffer
			RenderCommand.Clear(null, .Color | .Depth, .(0.2f, 0.2f, 0.2f), 1.0f, 0);

			RenderCommand.SetBlendState(_alphaBlendState);
			RenderCommand.SetDepthStencilState(_depthStencilState);

			if (_editor.GameViewportWindow.Visible)
			{
				_gameSceneRenderer.Scene = _activeScene;

				_activeScene.SetViewportSize((uint32)_editor.GameViewportWindow.RenderedViewportSize.X, (uint32)_editor.GameViewportWindow.RenderedViewportSize.Y);
				_gameSceneRenderer.SetViewportSize((uint32)_editor.GameViewportWindow.RenderedViewportSize.X, (uint32)_editor.GameViewportWindow.RenderedViewportSize.Y);

				RenderCommand.Clear(_gameViewportTarget, .Color | .Depth, .(0.2f, 0.2f, 0.2f), 1.0f, 0);
				_gameSceneRenderer.RenderRuntime(gameTime, _gameViewportTarget);
			}

			RenderCommand.SetBlendState(_alphaBlendState);
			RenderCommand.SetDepthStencilState(_depthStencilState);

			if (_editor.SceneViewportWindow.Visible)
			{
				_camera.Update(gameTime);

				_editorSceneRenderer.Scene = _activeScene;

				// Only use aspect ratio of editor-window, if game is not being rendered
				if (!_editor.GameViewportWindow.Visible)
					_activeScene.SetViewportSize((uint32)_editor.SceneViewportWindow.ViewportSize.X, (uint32)_editor.SceneViewportWindow.ViewportSize.Y);
				
				_editorSceneRenderer.SetViewportSize((uint32)_editor.SceneViewportWindow.ViewportSize.X, (uint32)_editor.SceneViewportWindow.ViewportSize.Y);

				RenderCommand.Clear(_editorViewportTarget, .Color | .Depth, .(0.2f, 0.2f, 0.2f), 1.0f, 0);
				_editorSceneRenderer.RenderEditor(gameTime, _camera, _editorViewportTarget, scope => DebugDraw3D, scope => DebugDraw2D);
			}

			RenderCommand.UnbindRenderTargets();
			RenderCommand.SetRenderTarget(null, 0, true);
			RenderCommand.BindRenderTargets();

			RenderCommand.SetViewport(_context.SwapChain.BackbufferViewport);
		}

		private void DebugDraw3D()
		{
			/*for (var (entity, transform, camera) in _scene.[Friend]_ecsWorld.Enumerate<TransformComponent, CameraComponent>())
			{
				if (_editor.EntityHierarchyWindow.SelectedEntities.Contains(.(entity, _scene)))
				{
					DebugRenderer.DrawViewFrustum(transform.WorldTransform, camera.Camera.Projection);
				}
			}*/
		}

		private void DebugDraw2D()
		{
			RenderCommand.SetBlendState(_alphaBlendState);
			
			Matrix billboard = _camera.View.Invert();
			billboard.Translation = .Zero;

			Matrix Billboard(Matrix transform)
			{
				float3 worldPos = transform.Translation;

				return Matrix.Translation(worldPos) * billboard;
			}

			float CalculateAlpha(float3 pos)
			{
				return Math.Clamp(1.5f - distance(_editor.CurrentCamera.Position, pos) / 50, 0, 1);
			}

			for (var (entity, transform, camera) in _activeScene.GetEntities<TransformComponent, CameraComponent>())
			{
				if (_editor.EntityHierarchyWindow.IsEntitySelected(.(entity, _activeScene)))
				{
					DebugRenderer.DrawViewFrustum(transform.WorldTransform, camera.Camera.Projection, .White);
				}
				
				Matrix world = Billboard(transform.WorldTransform);
				
				float alpha = CalculateAlpha(transform.WorldTransform.Translation);
				Renderer2D.DrawQuad(world, _editorIcons.Camera, null, ColorRGBA(alpha, alpha, alpha, alpha), .(0, 0, 1, 1), entity.Index);
				//Renderer2D.DrawQuad(world, _iconCamera, .White, .(0, 0, 1, 1), entity.Index);
			}

			for (var (entity, transform, light) in _activeScene.GetEntities<TransformComponent, LightComponent>())
			{
				if (_editor.EntityHierarchyWindow.IsEntitySelected(.(entity, _activeScene)))
				{
					Renderer.DrawRay(.Zero, .(0, 0, 20), ColorRGBA(light.SceneLight.Color, 1.0f), transform.WorldTransform);

					for (float angle = 0; angle < MathHelper.TwoPi; angle += MathHelper.TwoPi / 5.0f)
					{
						float2 pos = MathHelper.CirclePoint(angle, 0.5f);

						Renderer.DrawRay(.(pos, 0), .(pos, 20), .White, transform.WorldTransform);
					}
				}

				Matrix world = Billboard(transform.WorldTransform);
				
				float alpha = CalculateAlpha(transform.WorldTransform.Translation);
				Renderer2D.DrawQuad(world, _editorIcons.DirectionalLight, null, ColorRGBA(light.SceneLight.Color.R * alpha, light.SceneLight.Color.G * alpha, light.SceneLight.Color.B * alpha, alpha), .(0, 0, 1, 1), entity.Index);
			}
			
			for (var (entity, transform, collider) in _activeScene.GetEntities<TransformComponent, BoxCollider2DComponent>())
			{
				Renderer2D.DrawRect(transform.WorldTransform * Matrix.Translation(collider.Offset.X, collider.Offset.Y, 0) * Matrix.Scaling(collider.Size.X * 2, collider.Size.Y * 2, 0), .Green, entity.Index);
			}

			for (var (entity, transform, collider) in _activeScene.GetEntities<TransformComponent, CircleCollider2DComponent>())
			{
				Renderer2D.DrawCircle(transform.WorldTransform * Matrix.Translation(collider.Offset.X, collider.Offset.Y, 0) * Matrix.Scaling(collider.Radius * 2), (Texture2D)null, ColorRGBA(0f, 1f, 0f), 0.01f, .(0, 0, 1, 1), entity.Index);
			}

			for (var (entity, transform, collider) in _activeScene.GetEntities<TransformComponent, PolygonCollider2DComponent>())
			{
				Matrix colliderTransform = transform.WorldTransform * Matrix.Translation(collider.Offset.X, collider.Offset.Y, 0);

				float3 firstPosition = (colliderTransform * float4(collider.Vertices[0], 0, 1)).XYZ;
				float3 lastPosition = firstPosition;

				for (int i = 1; i < collider.VertexCount; i++)
				{
					float3 position = (colliderTransform * float4(collider.Vertices[i], 0, 1)).XYZ;

					Renderer2D.DrawLine(lastPosition, position, .Green, entity.Index);

					lastPosition = position;
				}

				Renderer2D.DrawLine(lastPosition, firstPosition, .Green, entity.Index);
			}
		}
		
		/// Updates the title of the window.
		private void UpdateWindowTitle()
		{
			String title = scope String("Glitchy Engine");

			if (_currentProject != null)
			{
				title.AppendF($" - {_currentProject.Name}");
			}

			if (_editorScene != null)
			{
				title.AppendF($" | {_editorScene.Name}");
			}

			Application.Instance.Window.Title = title;
		}

#region Project Management

		/// Creates a new Project and opens it.
		/// @param workspaceDirectory The workspace directory of the new project (The directory that all files will be in).
		/// @param projectName Name of the new project.
		/// @remarks If the creation fails at any point the entire directory will be deleted.
		private Result<void> CreateNewProject(StringView workspaceDirectory, StringView projectName)
		{
			if (Directory.CreateDirectory(workspaceDirectory) case .Err(let error))
			{
				Log.ClientLogger.Error($"Failed to create directory \"{workspaceDirectory}\". ({error})");
				return .Err;
			}

			if (!Directory.IsEmpty(workspaceDirectory))
			{
				Log.ClientLogger.Error($"Target directory is not empty.");
				return .Err;
			}
			
			String templatePath = scope .();
			Directory.GetCurrentDirectory(templatePath);
			Path.Combine(templatePath, "Resources/ProjectTemplate");

			Result<Project> newProjectResult = Project.CreateNewFromTemplate(workspaceDirectory, projectName, templatePath);

			// If the project couldn't be created or initialization failed, we delete the workspace directory.
			if (newProjectResult == .Err)
			{
				if (Directory.DelTree(workspaceDirectory) case .Err(let error))
				{
					Log.EngineLogger.Error($"Failed to delete workspace ({workspaceDirectory}).");
				}

				return .Err;
			}
			
			if (OpenProject(newProjectResult.Get()) case .Err)
			{
				Log.ClientLogger.Error($"Failed to open newly created project.");
				return .Err;
			}

			return .Ok;
		}

		public void OpenCreateNewProjectModal()
		{
			PopupService.Instance.OpenPopup("Create new Project", new => ShowCreateNewProjectModal);
		}

		private void ShowCreateNewProjectModal(out bool close)
		{
			close = false;

			static char8[128] projectNameBuffer = .();
			static char8[1024] projectDirectoryBuffer = .();

			// Always center this window when appearing
			var center = ImGui.GetMainViewport().GetCenter();
			ImGui.SetNextWindowPos(center, .Appearing, .(0.5f, 0.5f));

			ImGui.TextUnformatted("Project Name:");
			ImGui.InputText("##projectName", &projectNameBuffer, projectNameBuffer.Count - 1);
			
			StringView projectName = StringView(&projectNameBuffer);

			if (projectName.IsWhiteSpace)
			{
				ImGui.TextColored(.(1, 0, 0, 1), "Project Name required!");
			}

			ImGui.NewLine();

			ImGui.TextUnformatted("Directory:");
			ImGui.InputText("##directory", &projectDirectoryBuffer, projectDirectoryBuffer.Count - 1);
			ImGui.SameLine();

			if (ImGui.Button("..."))
			{
				FolderBrowserDialog folderDialog = scope FolderBrowserDialog();
				Result<DialogResult> result = folderDialog.ShowDialog();

				if (result case .Ok(let dialogResult) && dialogResult case .OK)
				{
					folderDialog.SelectedPath.CopyTo(projectDirectoryBuffer);
				}
			}

			StringView directory = StringView(&projectDirectoryBuffer);

			String target = scope String();
			Path.Combine(target, directory, projectName);
			
			ImGui.NewLine();

			ImGui.Text($"The Project will be in:\n{target}");

			if (Directory.Exists(target) && !Directory.IsEmpty(target))
			{
				ImGui.TextColored(.(1, 1, 0, 1), "The directory is not empty!");
			}

			ImGui.NewLine();

			ImGui.BeginDisabled(directory.IsWhiteSpace || projectName.IsWhiteSpace);

			if (ImGui.Button("Create"))
			{
				Result<void> result = CreateNewProject(target, projectName);

				if (result case .Ok)
				close = true;

			}

			ImGui.EndDisabled();

			ImGui.SameLine();

			if (ImGui.Button("Cancel"))
			{
				close = true;
			}
		}

		private void ShowOpenProjectDialog()
		{
			FolderBrowserDialog folderDialog = scope FolderBrowserDialog();
			Result<DialogResult> result = folderDialog.ShowDialog();

			if (result case .Ok(let dialogResult) && dialogResult case .OK)
			{
				LoadAndOpenProject(folderDialog.SelectedPath);
			}
		}

		private void CloseCurrentProject()
		{
			CloseCurrentScene();

			SetEditorScene(null);
	
			_currentProject?.SaveUserSettings();

			// TODO: Do actual work here!
			delete _currentProject;
			_currentProject = null;
		}

		private Result<void> LoadAndOpenProject(StringView workspacePath)
		{
			Project openedProject = Project.Load(workspacePath);

			if (OpenProject(openedProject) case .Err)
			{
				Log.ClientLogger.Error($"Failed to open project {workspacePath}.");
				return .Err;
			}

			return .Ok;
		}

		/// Opens the given project and makes it the current project.
		private Result<void> OpenProject(Project project)
		{
			if (project == null)
				return .Err;
			
			CloseCurrentProject();

			_currentProject = project;

			_editor.CurrentProject = _currentProject;

			String appAssemblyPath = scope String();
			_contentManager.SetAssetCacheDirectory(_currentProject.GetScopedPath!(".cache"));
			_contentManager.SetAssetDirectory(_currentProject.AssetsFolder);

			_currentProject.PathInProject(appAssemblyPath, scope $".cache/bin/{_currentProject.Name}.dll");

			ScriptEngine.SetAppAssemblyPath(appAssemblyPath);

			if (!_currentProject.UserSettings.LastOpenedScene.IsWhiteSpace)
			{
				String lastSceneFile = _currentProject.GetScopedPath!(_currentProject.UserSettings.LastOpenedScene);

				if (File.Exists(lastSceneFile))
					LoadSceneFile(lastSceneFile);
				else
					CreateAndOpenNewScene();
			}
			else
				CreateAndOpenNewScene();

			UpdateWindowTitle();

			if (Directory.Exists(project.WorkspacePath))
			{
				Application.Instance.Settings.EditorSettings.LastOpenedProject = project.WorkspacePath;
				Application.Instance.Settings.Save();
			}

			return .Ok;
		}

#endregion Project Management

#region Scene Management

		/// Focuses the Play window, so that its tab will be shown.
		private void SwitchToPlayWindow()
		{
			let window = ImGui.FindWindowByName(GameViewportWindow.s_WindowTitle);
			ImGui.FocusWindow(window);
		}

		/// Focuses the Editor window, so that its tab will be shown.
		private void SwitchToEditorWindow()
		{
			let window = ImGui.FindWindowByName(EditorViewportWindow.s_WindowTitle);
			ImGui.FocusWindow(window);
		}
		
		append ScriptInstanceSerializer _scriptSerializer = .();

		/// Starts the play mode for the current scene
		private void OnScenePlay()
		{
			Debug.Profiler.ProfileFunction!();

			Log.EngineLogger.AssertDebug(_scriptSerializer.SerializedObjectCount == 0, "Somehow some entities are serialized.");
			
			if (Application.Instance.Settings.EditorSettings.ClearLogOnPlay)
				Editor.Instance.LogWindow.ClearLog();

			_scriptSerializer.SerializeScriptInstances();
			
			_editor.SceneViewportWindow.EditorMode = false;
			_sceneState = .Play;

			using (Scene runtimeScene = new Scene())
			{
				_editorScene.CopyTo(runtimeScene, true);

				SetActiveScene(runtimeScene, startRuntime: true, startSimulation: true, newPlayMode: .Play);
				
				_scriptSerializer.DeserializeScriptInstances();
			}

			_editor.CurrentScene = _activeScene;

			if (Application.Instance.Settings.EditorSettings.SwitchToPlayerOnPlay)
				SwitchToPlayWindow();
		}

		enum PlayMode
		{
			Play,
			Editor,
			Simulation
		}

		/// Activates the given scene.
		/// @param scene The scene to be activated.
		/// @param startRuntime If set to true, the script runtime will be initialized for the given scene.
		/// @param startSimulation If set to true, the physics simulation will be initialized for the given scene.
		private void SetActiveScene(Scene scene, bool startRuntime, bool startSimulation, PlayMode? newPlayMode = null)
		{
			_activeScene?.Stop();

			if (newPlayMode != null)
			{
				switch (newPlayMode.Value)
				{
				case .Play:
					ScriptEngine.ApplicationInfo.IsInPlayMode = true;
				case .Editor:
					ScriptEngine.ApplicationInfo.IsInEditMode = true;
				case .Simulation:
					// No scripts in Simulation
					ScriptEngine.ApplicationInfo.IsInEditMode = false;
					ScriptEngine.ApplicationInfo.IsInPlayMode = false;
				}
			}

			if (scene != null)
			{
				scene.Start(startRuntime, startSimulation);
			}

			SetReference!(_activeScene, scene);
		}

		/// Starts the physics simulation mode for the current scene
		private void OnSceneSimulate()
		{
			_editor.SceneViewportWindow.EditorMode = false;
			_sceneState = .Simulate;

			using (Scene simulationScene = new Scene())
			{
				_editorScene.CopyTo(simulationScene, false);

				SetActiveScene(simulationScene, startRuntime: false, startSimulation: true, newPlayMode: .Simulation);
			}

			_editor.CurrentScene = _activeScene;
			
			if (Application.Instance.Settings.EditorSettings.SwitchToPlayerOnSimulate)
				SwitchToPlayWindow();
		}

		/// Pauses the scene
		private void OnScenePause()
		{
			_isPaused = true;
			
			if (Application.Instance.Settings.EditorSettings.SwitchToEditorOnPause)
				SwitchToEditorWindow();
		}

		/// Resumes the simulation / game
		private void OnSceneResume()
		{
			_isPaused = false;
			
			if (Application.Instance.Settings.EditorSettings.SwitchToPlayerOnResume)
				SwitchToPlayWindow();
		}

		/// Requests execution of a single simulation / game tick
		private void DoSingleStep()
		{
			_singleStep = true;
		}

		/// Stops the simulation or game and returns to edit mode.
		private void OnSceneStop()
		{
			SetActiveScene(_editorScene, startRuntime: true, startSimulation: false, newPlayMode: .Editor);

			// TODO: _editorScene.DeserializeScripts(scriptData);

			_editor.SceneViewportWindow.EditorMode = true;
			_sceneState = .Edit;

			_editor.CurrentScene = _activeScene;
			
			_isPaused = false;

			if (_activeScene != null)
			{
				/*
				 * Update the viewport size because if the game windows size changed in
				 * "Game"-mode the updated aspect-ratios will reset once we go back
				 * into "Editor"-mode (because Game-Mode works on a copy of the scene).
				 */
				GameViewportSizeChanged(null, _editor.GameViewportWindow.RenderedViewportSize);
			}
			
			if (Application.Instance.Settings.EditorSettings.SwitchToEditorOnStop)
				SwitchToEditorWindow();

			// Reconstruct state before play
			_scriptSerializer.DeserializeScriptInstances();
			_scriptSerializer.Clear();
		}

		/// Stops the scene and cleans up the subsystems to allow loading another scene.
		private void CloseCurrentScene()
		{
			// Clear serialized data, so that we don't waste time deserializing it.
			_scriptSerializer.Clear();
			OnSceneStop();
		}

		/// Creates a new scene and openes it.
		private void CreateAndOpenNewScene()
		{
			CloseCurrentScene();

			SceneFilePath = null;

			using (Scene newScene = CreateNewScene())
			{
				_camera.Position = .(-1.5f, 1.5f, -2.5f);
				_camera.RotationEuler = .(MathHelper.ToRadians(25), MathHelper.ToRadians(35), 0);

				SetEditorScene(newScene);
			}
		}

		/// Sets the current editor scene
		private void SetEditorScene(Scene scene, ScriptInstanceSerializer serializedObjects = null)
		{
			_editorScene?.Stop();

			SetReference!(_editorScene, scene);
			_editor.CurrentScene = _editorScene;
			_editor.EditorScene = _editorScene;
			SetReference!(_activeScene, _editorScene);

			if (_editorScene != null)
			{
				_editorScene.Start(startRuntime: true, startSimulation: false);

				if (serializedObjects != null)
				{
					// Reconstruct state
					serializedObjects.DeserializeScriptInstances();
				}
			}

			UpdateWindowTitle();
		}

		/// Creates a new default scene.
		private static Scene CreateNewScene()
		{
			Scene newScene = new Scene();

			// Create a default camera
			{
				let cameraEntity = newScene.CreateEntity("Camera");
				let transform = cameraEntity.Transform;
				transform.Position = float3(0, 2, -5);
				transform.RotationEuler = float3(0, MathHelper.ToRadians(25), 0);
				
				let camera = cameraEntity.AddComponent<CameraComponent>();
				camera.Primary = true;
				camera.Camera.ProjectionType = .InfinitePerspective;
				camera.Camera.PerspectiveFovY = MathHelper.ToRadians(75);
				camera.Camera.PerspectiveNearPlane = 0.1f;
			}

			// Create a default light source
			{
				let lightEntity = newScene.CreateEntity("Light");
				let transform = lightEntity.Transform;
				transform.Position = .(-3, 4, -1.5f);
				transform.RotationEuler = .(MathHelper.ToRadians(20), MathHelper.ToRadians(75), MathHelper.ToRadians(20));

				let light = lightEntity.AddComponent<LightComponent>();
				light.SceneLight.Illuminance = 10.0f;
				light.SceneLight.Color = .(1.0f, 0.95f, 0.8f);
			}

			return newScene;
		}
		
		/// Saves the scene in the file that is was loaded from or saved to last. If there is no such path (i.e. it is a new scene) the save file dialog will open.
		private void SaveCurrentScene()
		{
			if (!CanSaveScene)
			{
				Log.ClientLogger.Error("Scene can't be saved while playing the game!");
				return;
			}

			if (SceneFilePath.IsWhiteSpace)
			{
				SaveCurrentSceneAs();
			}
			else
			{
				SaveScene(_editorScene, SceneFilePath);
			}

			// Note: We only update the last opened scene if the CURRENT scene is saved, NOT when ANY scene is saved.
			String relativePath = scope .();
			Path.GetRelativePath(SceneFilePath, _currentProject.WorkspacePath, relativePath);
			_currentProject.UserSettings.LastOpenedScene = relativePath;
		}

		/// Saves the given scene with the specified file name.
		private static void SaveScene(Scene scene, StringView fileName)
		{
			SceneSerializer serializer = scope .(scene);
			serializer.Serialize(fileName);
		}
		
		/// Opens a save file dialog and saves the scene at the user specified location.
		private void SaveCurrentSceneAs()
		{
			if (!CanSaveScene)
			{
				Log.ClientLogger.Error("Scene can't be saved while playing the game!");
				return;
			}

			SaveFileDialog sfd = scope .();
			sfd.InitialDirectory = _currentProject.AssetsFolder;
			sfd.SetFilter("scene file (*.scene)|*.scene");

			if (sfd.ShowDialog() case .Ok(let result) && result == .OK)
			{
				SceneFilePath = sfd.FileNames[0];
				SaveCurrentScene();
			}
		}
		
		/// Opens a open file dialog and load the scene selected by the user specified.
		private void OpenScene()
		{
			OpenFileDialog ofd = scope .();
			ofd.InitialDirectory = _currentProject.AssetsFolder;
			ofd.SetFilter("scene file (*.scene)|*.scene");

			if (ofd.ShowDialog() case .Ok(let result) && result == .OK)
			{
				LoadSceneFile(ofd.FileNames[0]);
			}
		}

		/// Loads the given scene file and opens it as the current scene.
		private void LoadSceneFile(StringView filename)
		{
			CloseCurrentScene();

			SceneFilePath = scope String(filename);

			using (Scene newScene = new Scene())
			{
				SceneSerializer serializer = scope .(newScene);
				let result = serializer.Deserialize(SceneFilePath);

				// Make sure we actually loaded something!
				if (result case .Ok)
				{
					SetEditorScene(newScene, serializer.ScriptSerializer);

					String relativePath = scope .();
					Path.GetRelativePath(filename, _currentProject.WorkspacePath, relativePath);
					_currentProject.UserSettings.LastOpenedScene = relativePath;
				}
				else
				{
					Log.ClientLogger.Error("Failed to load scene file.");
				}
			}
		}

#endregion Scene Management

#region ImGui
		
		private bool OnImGuiRender(ImGuiRenderEvent event)
		{
			ImGui.Viewport* viewport = ImGui.GetMainViewport();
			ImGui.DockSpaceOverViewport(viewport);

			if (_isDraggingFromOutside not case .None)
			{
				// Reset drop effect, so the drop targets have to set it themselves (and don't "leak" it).
				SetDropEffect(.None);

				if (ImGui.BeginDragDropSource(.SourceExtern | .SourceAutoExpirePayload | .SourceNoPreviewTooltip))
				{
					void* ptr = Internal.UnsafeCastToPtr(_droppedFiles);
					ImGui.SetDragDropPayload(.ExternFiles, &ptr, sizeof(int));
					
					ImGui.EndDragDropSource();
				}

				if (_isDraggingFromOutside case .Dropping)
				{
					_isDraggingFromOutside = .None;
				}
			}
			else
			{
				// TODO: Can Imgui handle this, somehow?
				DeleteContainerAndItems!(_droppedFiles);
				_droppedFiles = null;
			}

			DrawMainMenuBar();

#if GE_EDITOR_IMGUI_DEMO
			if (_showImguiDemoWindow)
			{
				ImGui.ShowDemoWindow();
			}
#endif

			EditorApp.Instance.BackgroundTaskManager.ImGuiRender();

			_editor.SceneViewportWindow.RenderTarget = _editorViewportTarget;
			_editor.GameViewportWindow.RenderTarget = _gameViewportTarget;

			_editor.Update();

			return false;
		}

		/// Shows the window with Play/Pause, etc. buttons
		private void ShowPlayControls()
		{
			ImGui.PushStyleVar(.ItemInnerSpacing, ImGui.Vec2(0, 0));
			ImGui.PushStyleColor(.Button, ImGui.Vec4(0, 0, 0, 0));

			let colors = ImGui.GetStyle().Colors;

			ImGui.Vec4 hoveredColor = colors[(int)ImGui.Col.ButtonHovered];
			hoveredColor.w = 0.5f;
			
			ImGui.Vec4 activeColor = colors[(int)ImGui.Col.ButtonActive];
			activeColor.w = 0.5f;

			ImGui.PushStyleColor(.ButtonHovered, hoveredColor);
			ImGui.PushStyleColor(.ButtonActive, activeColor);

			float padding = 2.0f;

			float size = ImGui.GetWindowHeight() - 2 * padding;

			float centerX = ImGui.GetContentRegionMax().x / 2;

			if (_sceneState == .Edit)
			EditorButtons:
			{
				// Display the buttons for edit state

				float totalWidth = size * 3 + padding * 4;

				ImGui.SameLine();
				ImGui.SetCursorPosX(centerX - totalWidth / 2);
				
				ImGui.PushID(0);

				if (ImGui.ImageButton("", _editorIcons.Play, .(size, size), .Zero, .Ones))
					OnScenePlay();
				
				ImGui.PopID();

				ImGui.AttachTooltip("Play the game.");
				
				ImGui.SameLine();

				ImGui.PushID(1);

				if (ImGui.ImageButton("", _editorIcons.Simulate, .(size, size), .Zero, .Ones))
					OnSceneSimulate();
				
				ImGui.PopID();

				ImGui.AttachTooltip("Enter simulation mode.\nThis only runs the physics engine.");

				if (_isPaused)
				{
					ImGui.PushStyleColor(.Button, *ImGui.GetStyleColorVec4(.ButtonActive));
					
					defer:EditorButtons { ImGui.PopStyleColor(); }
				}
				
				ImGui.PushID(2);

				//ImGui.SameLine(penX += size + 2 * padding);
				ImGui.SameLine();

				if (ImGui.ImageButton("", _editorIcons.Pause, .(size, size), .Zero, .Ones))
					_isPaused = !_isPaused;
				
				ImGui.PopID();

				ImGui.AttachTooltip("If enabled the game or simulation will be started in paused state.");

			}
			else
			{
				float totalWidth = size * 2 + padding * 2;

				if (_isPaused)
				{
					totalWidth = size * 3 + padding * 4;
				}

				ImGui.SameLine();
				ImGui.SetCursorPosX(centerX - totalWidth / 2.0f);

				SubTexture2D pauseButtonIcon = _isPaused ? _editorIcons.Play : _editorIcons.Pause;

				if (ImGui.ImageButton("PlayPause", pauseButtonIcon, .(size, size), .Zero, .Ones))
				{
					if (_isPaused)
						OnSceneResume();
					else
						OnScenePause();
				}

				ImGui.AttachTooltip(_isPaused ? "Resume" : "Pause");

				if (_isPaused)
				{
					ImGui.PushID(2);

					ImGui.SameLine();

					SubTexture2D singleStepButtonIcon = _editorIcons.SingleStep;

					if (ImGui.ImageButton("Step", singleStepButtonIcon, .(size, size), .Zero, .Ones))
					{
						DoSingleStep();
					}

					if (ImGui.BeginPopupContextItem())
					{
						if (ImGui.BeginTable("time_step_table", 2))
						{
							ImGui.TableNextRow();
							ImGui.TableNextColumn();

							ImGui.TextUnformatted("Time step:");

							ImGui.TableNextColumn();

							float itemWidth = ImGui.CalcTextSize("1000.000 FPS").x;

							ImGui.SetNextItemWidth(itemWidth);

							ImGui.DragFloat("##timestep", &_fixedTimestep, 0.001f, 0.001f, 1000.0f, "%.4fs");

							ImGui.TableNextRow();
							ImGui.TableNextColumn();

							float fps = 1.0f / _fixedTimestep;
							
							ImGui.TextUnformatted("Frame rate:");

							ImGui.TableNextColumn();

							ImGui.SetNextItemWidth(itemWidth);

							if (ImGui.DragFloat("##fps", &fps, 1.0f, 0.001f, 1000.0f, "%.3f FPS"))
							{
								_fixedTimestep = 1.0f / fps;
							}

							ImGui.EndTable();
						}

						ImGui.EndPopup();
					}

					ImGui.AttachTooltip("Step one Frame (Right-click to change time step)");

					ImGui.PopID();
				}

				ImGui.SameLine();
				ImGui.PushID(1);

				if (ImGui.ImageButton("Stop", _editorIcons.Stop, .(size, size), .Zero, .Ones))
					OnSceneStop();

				ImGui.PopID();

				ImGui.AttachTooltip("Stop");
			}

			ImGui.PopStyleColor(3);
			ImGui.PopStyleVar(1);
		}

		private void ShowOpenRecentSceneMenu()
		{
			if (_currentProject == null || _currentProject.UserSettings.RecentScenes == null)
				return;

			int i = 0;

			for (let scenePath in _currentProject.UserSettings.RecentScenes)
			{
				i++;

				String fullScenePath = _currentProject.GetScopedPath!(scenePath);

				if (!File.Exists(fullScenePath))
				{
					delete scenePath;
					@scenePath.Remove();
				}

				if (ImGui.MenuItem(scope $"{i}: {scenePath}"))
				{
					LoadSceneFile(fullScenePath);
				}
			}
		}

		private void ShowOpenRecentProjectMenu()
		{
			let recentProjects = Application.Instance.Settings.EditorSettings.RecentProjects;

			if (recentProjects == null)
				return;

			int i = 0;

			for (let workspacePath in recentProjects)
			{
				i++;

				if (!Directory.Exists(workspacePath))
				{
					delete workspacePath;
					@workspacePath.Remove();
					continue;
				}

				if (ImGui.MenuItem(scope $"{i}: {workspacePath}"))
				{
					LoadAndOpenProject(workspacePath);
				}
			}
		}

		private void DrawMainMenuBar()
		{
			ImGui.BeginMainMenuBar();

			if(ImGui.BeginMenu("File", true))
			{
				if (ImGui.MenuItem("New Scene", "Ctrl+N"))
					CreateAndOpenNewScene();
				
				ImGui.AttachTooltip("Creates a new (almost) empty scene.");

				if (ImGui.MenuItem("Open Scene...", "Ctrl+O"))
					OpenScene();
				
				ImGui.AttachTooltip("Opens an existing Scene.");

				if (ImGui.BeginMenu("Open recent Scene"))
				{
					ShowOpenRecentSceneMenu();

					ImGui.EndMenu();
				}

				ImGui.Separator();

				ImGui.BeginDisabled(!CanSaveScene);

				if (ImGui.MenuItem("Save Scene", "Ctrl+S"))
					SaveCurrentScene();
				
				ImGui.AttachTooltip("Saves the current scene.");

				if (ImGui.MenuItem("Save Scene as...", "Ctrl+Shift+S"))
					SaveCurrentSceneAs();
				
				ImGui.AttachTooltip("Saves the scene under the given file name.");

				ImGui.EndDisabled();

				ImGui.Separator();
				
				if (ImGui.MenuItem("Create new Project...", "Ctrl+ALT+N"))
					OpenCreateNewProjectModal();

				if (ImGui.MenuItem("Open Project...", "Ctrl+ALT+O"))
					ShowOpenProjectDialog();

				if (ImGui.BeginMenu("Open recent project"))
				{
					ShowOpenRecentProjectMenu();
					ImGui.EndMenu();
				}

				ImGui.Separator();

				if (ImGui.MenuItem("Settings"))
					Editor.Instance.SettingsWindow.Open = true;
				
				ImGui.Separator();
				
				if (ImGui.MenuItem("Exit"))
					Application.Instance.Close();

				ImGui.EndMenu();
			}
			
			if(ImGui.BeginMenu("View", true))
			{
				if(ImGui.MenuItem(ContentBrowserWindow.s_WindowTitle))
					_editor.ContentBrowserWindow.Open = true;
				
				if(ImGui.MenuItem(EditorViewportWindow.s_WindowTitle))
					_editor.SceneViewportWindow.Open = true;

				if(ImGui.MenuItem(EntityHierarchyWindow.s_WindowTitle))
					_editor.EntityHierarchyWindow.Open = true;

				if(ImGui.MenuItem(GameViewportWindow.s_WindowTitle))
					_editor.GameViewportWindow.Open = true;

				if(ImGui.MenuItem(InspectorWindow.s_WindowTitle))
					_editor.InspectorWindow.Open = true;

				if(ImGui.MenuItem(AssetViewer.s_WindowTitle))
					_editor.AssetViewer.Open = true;
				
				if(ImGui.MenuItem(LogWindow.s_WindowTitle))
					_editor.LogWindow.Open = true;

				ImGui.EndMenu();
			}
			
			if(ImGui.BeginMenu("Tools", true))
			{
				if(ImGui.MenuItem("Reload Scripts"))
					ScriptEngine.ReloadAssemblies();

#if GE_EDITOR_IMGUI_DEMO
				ImGui.Checkbox("Show ImGui Demo", &_showImguiDemoWindow);
#endif

				ImGui.EndMenu();
			}

			ShowPlayControls();

			ImGui.EndMainMenuBar();
		}

#endregion ImGui

#region Events

		/// Receive and dispatch global events
		public override void OnEvent(Event event)
		{
			EventDispatcher dispatcher = EventDispatcher(event);

			dispatcher.Dispatch<ImGuiRenderEvent>(scope (e) => OnImGuiRender(e));
			dispatcher.Dispatch<WindowResizeEvent>(scope (e) => OnWindowResize(e));
			dispatcher.Dispatch<KeyPressedEvent>(scope (e) => OnKeyPressed(e));
			dispatcher.Dispatch<MouseScrolledEvent>(scope (e) => OnMouseScrolled(e));
			dispatcher.Dispatch<DragDropEvent>(scope (e) => OnDragDrop(e));
			dispatcher.Dispatch<SettingsAppliedEvent>(scope (e) => OnSettingsApplied(e));
		}

		private bool OnWindowResize(WindowResizeEvent e)
		{
			return false;
		}

		private void EditorViewportSizeChanged(Object sender, float2 viewportSize)
		{
			if(viewportSize.X <= 0 || viewportSize.Y <= 0)
				return;

			uint32 sizeX = (uint32)viewportSize.X;
			uint32 sizeY = (uint32)viewportSize.Y;

			_editorViewportTarget.Resize(sizeX, sizeY);
			_cameraTarget.Resize(sizeX, sizeY);

			_camera.OnViewportResize(sizeX, sizeY);

			_activeScene?.SetViewportSize(sizeX, sizeY);
		}
		
		private void GameViewportSizeChanged(Object sender, float2 viewportSize)
		{
			if(viewportSize.X <= 0 || viewportSize.Y <= 0)
				return;

			uint32 sizeX = (uint32)viewportSize.X;
			uint32 sizeY = (uint32)viewportSize.Y;

			_gameViewportTarget.Resize(sizeX, sizeY);

			_activeScene?.SetViewportSize(sizeX, sizeY);
		}

		private bool OnKeyPressed(KeyPressedEvent e)
		{
			// TODO: We need some kind of input system for stuff like this...

			bool control = Input.IsKeyPressed(Key.Control);
			bool alt = Input.IsKeyPressed(Key.Alt);
			bool shift = Input.IsKeyPressed(Key.Shift);
			
			if (!_camera.[Friend]BindMouse && control)
			{
				switch (e.KeyCode)
				{
				case .N:
					if (alt)
						// "Create new Project..."
						OpenCreateNewProjectModal();
					else
						// "New Scene"
						CreateAndOpenNewScene();

					return true;
				case .O:
					if (alt && control)
						// "Open Project..."
						ShowOpenProjectDialog();
					else if (!alt && !control)
						// "Open Scene..."
						OpenScene();

					return true;
				case .S:
					if (shift)
						// "Save Scene as..."
						SaveCurrentSceneAs();
					else
						// "Save Scene"
						SaveCurrentScene();
					
					return true;
				default:
				}
			}

			return false;
		}

		private bool OnMouseScrolled(MouseScrolledEvent e)
		{
			if (_camera.OnMouseScrolled(e))
				return true;

			return false;
		}

		enum DragDropState
		{
			None,
			Dragging,
			Dropping
		}

		private DragDropState _isDraggingFromOutside;
		private DropEffect _effectWhenDropping = .None;

		private static EditorLayer _layer;

		private List<String> _droppedFiles;

		public static void SetDropEffect(DropEffect effect)
		{
			if (_layer._isDraggingFromOutside == .None)
			{
				Log.EngineLogger.Warning("Setting drop effect, even though the user is currently not dragging.");
			}

			_layer._effectWhenDropping = effect;
		}

		private bool OnDragDrop(DragDropEvent e)
		{
			if (all(e.CursorPosition != .(-1,-1)))
			{
				ImGui.GetIO().MousePos = .(e.CursorPosition.X, e.CursorPosition.Y);
			}

			if (e.FileNames.IsEmpty)
			{
				DeleteContainerAndItems!(_droppedFiles);
				_droppedFiles = null;
			}
			else
			{
				if (_droppedFiles == null)
				{
					_droppedFiles = new List<String>();
				}

				int firstIndexToRemove = 0;

				for (StringView filePath in e.FileNames)
				{
					int index = _droppedFiles.IndexOfAlt(filePath);
					if (index == -1)
					{
						index = _droppedFiles.Count;
						_droppedFiles.Add(new String(filePath));
					}

					// Make sure, all paths that are in e.FileNames are before firstIndexToRemove
					Swap!(_droppedFiles[firstIndexToRemove], _droppedFiles[index]);

					firstIndexToRemove++;
				}

				// Remove all paths beyond firstIndexToRemove
				for (int i = _droppedFiles.Count; i > firstIndexToRemove; i--)
				{
					delete _droppedFiles.PopBack();
				}
			}

			switch (e.DragDropType)
			{
			case .Enter:
				_isDraggingFromOutside = .Dragging;
				ImGui.GetIO().MouseDown[0] = true;
			case .Over:
			case .Leave:
				_isDraggingFromOutside = .None;
				ImGui.ClearDragDrop();
			case .Drop:
				_isDraggingFromOutside = .Dropping;
				ImGui.GetIO().MouseDown[0] = false;
			}
			
			e.OutDropEffect = _effectWhenDropping;

			return true;
		}

#endregion

		bool OnSettingsApplied(SettingsAppliedEvent settingsAppliedEvent)
		{
#if DEBUG
			// In DEBUG we can change whether we use ScriptCore as .dll or .csproj
			_currentProject?.FixupScriptCorePath();
#endif

			return false;
		}
	}
}
