using GlitchyEditor.EditWindows;
using ImGui;
using GlitchyEngine;
using System;
using System.Collections;
using System.Reflection;

namespace GlitchyEditor
{
	class SettingsWindow : EditorWindow
	{
		class Binding
		{
			public Object SettingsObject;
			public String Name ~ delete _;
			public String FieldName ~ delete _;
			public StringView Tooltip;

			public this(StringView name, StringView fieldName, Object settingsObject, StringView tooltip)
			{
				Name = new String(name);
				FieldName = new String(fieldName);
				SettingsObject = settingsObject;
				Tooltip = tooltip;
			}
		}

		class Category
		{
			public List<Binding> _bindings = new .() ~ DeleteContainerAndItems!(_);

			public String Header ~ delete _;

			public Object SettingsObject;

			public this(String header)
			{
				Header = new String(header);
			}

			public void AddSetting(StringView name, StringView fieldName, Object settingsObject = null, StringView tooltip = "")
			{
				Binding binding = new .(name, fieldName, settingsObject ?? SettingsObject, tooltip);
				_bindings.Add(binding);
			}
		}

		Settings _settings;

		bool _settingsChanged = false;

		private Dictionary<String, Category> _categories = new .() ~ DeleteDictionaryAndValues!(_);

		public this()
		{
			_open = false;

			_settings = Application.Get().Settings;

			Create();
		}

		void Create()
		{
			ScanForSettings(_settings);

			/*for (ISettings settings in _settings.[Friend]_userSettings)
			{
				ScanForSettings(settings);
			}*/
		}

		Category AddCategory(String header)
		{
			if (_categories.TryGetValue(header, let category))
				return category;

			Category newCat = new Category(header);
			_categories.Add(newCat.Header, newCat);

			return newCat;
		}

		void ScanForSettings(Object container)
		{
			Type type = container.GetType();

			for (var field in type.GetFields())
			{
				Result<SettingAttribute> settingResult = field.GetCustomAttribute<SettingAttribute>();

				if (settingResult case .Ok(let settingInfo))
				{
					AddSetting(settingInfo.Category, settingInfo.Name, field.Name, container, settingInfo.Tooltip);
				}
				
				Result<SettingContainerAttribute> containerResult = field.GetCustomAttribute<SettingContainerAttribute>();
				
				if (containerResult case .Ok(let containerInfo))
				{
					var res = field.GetValue<Object>(container, let childContainer);

					if (res case .Err(let error))
					{
						Log.EngineLogger.Error($"Failed to get settings container. Error: {error}");

						continue;
					}

					ScanForSettings(childContainer);
				}
			}
		}

		void AddSetting(String categoryName, String name, StringView fieldName, Object container, StringView tooltip)
		{
			Category category = AddCategory(categoryName);

			category.AddSetting(name, fieldName, container, tooltip);
		}

		protected override void InternalShow()
		{
			ImGui.Begin("Settings", &_open, .NoDocking);
			defer ImGui.End();

			// Leave room for 1 line below us
			ImGui.BeginChild("item view", ImGui.Vec2(0, -ImGui.GetFrameHeightWithSpacing()));

			ImGui.BeginTabBar("##Tabs");

			for (Category category in _categories.Values)
			{
				if (ImGui.BeginTabItem(category.Header))
				{
					ShowCategory(category);
					ImGui.EndTabItem();
				}
			}

			ImGui.EndTabBar();

			ImGui.EndChild();

			ImGui.BeginDisabled(!_settingsChanged);

			if (ImGui.Button("Save"))
			{
				_settings.Save();
				_settings.Apply();
				_open = false;
			}

			ImGui.SameLine();

			if (ImGui.Button("Apply"))
			{
				_settings.Apply();
			}
			
			ImGui.EndDisabled();
	
			ImGui.SameLine();

			if (ImGui.Button("Cancel"))
			{
				Settings.Load();
				_open = false;
			}
		}

		private void ShowCategory(Category category)
		{
			if (ImGui.BeginTable("SettingsTable", 2, .SizingFixedFit | .RowBg))
			{
				ImGui.TableSetupColumn("Settings");
				ImGui.TableSetupColumn("Values", .WidthStretch);

				ImGui.TableNextRow();
				ImGui.TableSetColumnIndex(1);
				ImGui.PushItemWidth(ImGui.GetContentRegionAvail().x);

				for (Binding setting in category._bindings)
				{
					ImGui.TableNextRow();

                	ImGui.TableNextColumn();

					// Name of setting
					ImGui.TextUnformatted(setting.Name);
					
					if (!setting.Tooltip.IsWhiteSpace)
						ImGui.AttachTooltip(setting.Tooltip);

					ImGui.TableNextColumn();

					Type settingsObjectType = setting.SettingsObject.GetType();

					Result<FieldInfo> result = settingsObjectType.GetField(setting.FieldName);

					if (result case .Err)
					{
						ImGui.PushStyleColor(.Text, ImGui.Vec4(1f, 0f, 0f, 1f));
						ImGui.Text($"Field {setting.FieldName} not found.");
						ImGui.PopStyleColor();

						continue;
					}

					FieldInfo fieldInfo = result.Get();

					Type fieldType = fieldInfo.FieldType;

					mixin GetSettingValue<T>()
					{
						var error = fieldInfo.GetValue<T>(setting.SettingsObject, var value);

						if (error case .Err(let err))
						{
							Log.EngineLogger.Error($"Could not get value of setting {setting.FieldName}. Error: {err}");

							ImGui.PushStyleColor(.Text, ImGui.Vec4(1f, 0f, 0f, 1f));
							ImGui.Text($"Could not get value of setting {setting.FieldName}. Error: {err}");
							ImGui.PopStyleColor();

							break;
						}

						value
					}

					mixin SetSettingValue<T>(T value)
					{
						var error = fieldInfo.SetValue(setting.SettingsObject, value);

						if (error case .Err(let err))
						{
							Log.EngineLogger.Error($"Could not set value of setting {setting.FieldName}. Error: {err}");
						}
					}

					switch (fieldType)
					{
					case typeof(bool):
						let value = GetSettingValue!<bool>();

						if (!ImGui.Checkbox(scope $"##{setting.Name}", &value))
							break;

						SetSettingValue!(value);

						_settingsChanged = true;
					case typeof(int32):
						int32 value = GetSettingValue!<int32>();

						if (!ImGui.InputInt(scope $"##{setting.Name}", &value))
							break;

						SetSettingValue!(value);

						_settingsChanged = true;
					case typeof(String):
						String value = GetSettingValue!<String>();

						char8[256] buffer = .();

						value.CopyTo(buffer);

						if (ImGui.InputText(scope $"##{setting.Name}", &buffer, buffer.Count))
						{
							value..Clear().Append(&buffer);

							_settingsChanged = true;
						}
					}
				}
				
				ImGui.EndTable();
			}
		}
	}
}