using System;
using Beefy.theme.dark;
using Beefy.gfx;
using System.Collections;
using System.Diagnostics;
using Beefy.geom;

namespace IDE.ui
{
	class SettingsDialog : PropertiesDialog
	{
		enum CategoryType
		{
		    Editor,
			Keys,
			Compiler,
		    Debugger,
		    VisualStudio,

		    COUNT
		}

		PropPage[] mPropPages = new PropPage[(int32)CategoryType.COUNT] ~
		{
			for (let propPage in _)
				delete propPage;
			delete _;
		};

		[Reflect]
		class KeyEntry
		{
			public String mCommand = new String() ~ delete _;
			public List<KeyState> mKeys = new List<KeyState>() ~ DeleteContainerAndItems!(_);
			public IDECommand.ContextFlags mContextFlags;
			public bool mHasConflict;
		}

		List<KeyEntry> mKeyEntries ~ DeleteContainerAndItems!(_);
		bool mHideVSHelper;

		protected override float TopY
		{
			get
			{
				return GS!(6);
			}
		}

		public this()
		{
		    mTitle = new String("Settings Properties");

		    var root = (DarkListViewItem)mCategorySelector.GetRoot();
		    var item = AddCategoryItem(root, "Editor");
		    item.Focused = true;
			AddCategoryItem(root, "Keys");
			AddCategoryItem(root, "Compiler");
		    AddCategoryItem(root, "Debugger");
			AddCategoryItem(root, "Visual Studio");

			if (!gApp.mSettings.mVSSettings.IsConfigured())
				gApp.mSettings.mVSSettings.SetDefaults();
			if (gApp.mSettings.mVSSettings.IsConfigured())
				mHideVSHelper = true;
		}

		void PopulateEditorOptions()
		{
			mCurPropertiesTarget = gApp.mSettings.mEditorSettings;

			var root = (DarkListViewItem)mPropPage.mPropertiesListView.GetRoot();
			var (category, propEntry) = AddPropertiesItem(root, "General");
			category.mIsBold = true;
			category.mTextColor = cHeaderColor;
			AddPropertiesItem(category, "Font", "mFonts");
			AddPropertiesItem(category, "Font Size", "mFontSize");
			AddPropertiesItem(category, "UI Scale", "mUIScale");
			AddPropertiesItem(category, "Autocomplete", "mAutoCompleteShowKind");
			AddPropertiesItem(category, "Autocomplete Require Control", "mAutoCompleteRequireControl");
			AddPropertiesItem(category, "Autocomplete Require Tab", "mAutoCompleteRequireTab");
			AddPropertiesItem(category, "Autocomplete on Enter", "mAutoCompleteOnEnter");
			AddPropertiesItem(category, "Autocomplete Show Documentation", "mAutoCompleteShowDocumentation");
			AddPropertiesItem(category, "Show Locator Animation", "mShowLocatorAnim");
			AddPropertiesItem(category, "Hilite Symbol at Cursor", "mHiliteCursorReferences");

			(?, propEntry) = AddPropertiesItem(category, "Spell Check", "mSpellCheckEnabled");
			var resetButton = new DarkButton();
			resetButton.Label = "Reset";
			resetButton.mOnMouseClick.Add(new (btn) =>
				{
					SpellChecker.ResetWordList();
					if (gApp.mSpellChecker != null)
					{
						delete gApp.mSpellChecker;
						gApp.CreateSpellChecker();
					}
				});
			var valueItem = propEntry.mListViewItem.GetSubItem(1);
			valueItem.AddWidget(resetButton);
			valueItem.mOnResized.Add(new (evt) =>
				{
					float btnWidth = DarkTheme.sDarkTheme.mSmallFont.GetWidth(resetButton.Label) + GS!(20);
					resetButton.Resize(GetValueEditWidth(valueItem) - btnWidth - GS!(28), GS!(1), btnWidth, GS!(16));
				});

			AddPropertiesItem(category, "Show Line Numbers", "mShowLineNumbers");
			AddPropertiesItem(category, "Free Cursor Movement", "mFreeCursorMovement");

			category.Open(true, true);

			(category, propEntry) = AddPropertiesItem(root, "Colors");
			category.mIsBold = true;
			category.mTextColor = cHeaderColor;
			AddPropertiesItem(category, "UI Red", "mColors.mUIColorR");
			AddPropertiesItem(category, "UI Green", "mColors.mUIColorG");
			AddPropertiesItem(category, "UI Blue", "mColors.mUIColorB");
			AddPropertiesItem(category, "Text", "mColors.mText");
			category.Open(true, true);
		}

		void PopulateCompilerOptions()
		{
			mCurPropertiesTarget = gApp.mSettings.mCompilerSettings;

			var root = (DarkListViewItem)mPropPage.mPropertiesListView.GetRoot();
			var (category, propEntry) = AddPropertiesItem(root, "General");
			category.mIsBold = true;
			category.mTextColor = cHeaderColor;
			AddPropertiesItem(category, "Worker Threads", "mWorkerThreads");
			category.Open(true, true);
		}

		void CommandContextToString(IDECommand.ContextFlags contextFlags, String str)
		{
			bool isFirst = true;

			void AddFlagStr(StringView flagStr)
			{
				if (isFirst)
				{
					str.Append(", ");
					isFirst = false;
				}
				str.Append(flagStr);
			}

			if (contextFlags.HasFlag(.Editor))
				AddFlagStr("Editor");
		}

		void UpdateKeyStates()
		{
			Debug.Assert((CategoryType)mPropPage.mCategoryType == .Keys);

			Dictionary<String, KeyEntry> mappedEntries = scope .();

			for (var propEntries in mPropPage.mPropEntries.Values)
			{
				var propEntry = propEntries[0];
				let keyEntry = (KeyEntry)propEntry.mTarget;
				keyEntry.mHasConflict = false;

				let keys = propEntry.mCurValue.Get<List<KeyState>>();
				if (keys.Count == 0)
					continue;

				let keyEntryStr = new String();
				KeyState.ToString(keys, keyEntryStr);
				keyEntryStr.Append(" ");
				CommandContextToString(keyEntry.mContextFlags, keyEntryStr);

				String* keyPtr;
				KeyEntry* valuePtr;
				if (mappedEntries.TryAdd(keyEntryStr, out keyPtr, out valuePtr))
				{
					*valuePtr = keyEntry;
				}
				else
				{
					let other = *valuePtr;
					other.mHasConflict = true;
					keyEntry.mHasConflict = true;
					delete keyEntryStr;
				}
			}

			for (let keyEntryStr in mappedEntries.Keys)
				delete keyEntryStr;

			for (var propEntries in mPropPage.mPropEntries.Values)
			{
				var propEntry = propEntries[0];
				let keyEntry = (KeyEntry)propEntry.mTarget;

				let listViewItem = (DarkListViewItem)propEntry.mListViewItem.GetSubItem(1);
				listViewItem.mTextColor = keyEntry.mHasConflict ? 0xFFFF8080 : 0xFFFFFFFF;
			}
		}

		void PopulateKeyOptions()
		{
			mCurPropertiesTarget = gApp.mSettings.mKeySettings;
			var root = (DarkListViewItem)mPropPage.mPropertiesListView.GetRoot();
			
			var map = scope Dictionary<String, KeyEntry>();
			mKeyEntries = new List<KeyEntry>();

			for (var kv in gApp.mCommands.mCommandMap)
			{
				var keyEntry = new KeyEntry();
				keyEntry.mCommand.Set(kv.key);
				keyEntry.mContextFlags = kv.value.mContextFlags;
				
				mKeyEntries.Add(keyEntry);
				map[keyEntry.mCommand] = keyEntry;
			}

			for (var mappedEntry in gApp.mSettings.mKeySettings.mEntries)
			{
				KeyEntry keyEntry;
				if (!map.TryGetValue(mappedEntry.mCommand, out keyEntry))
					continue;

				for (let keyState in mappedEntry.mKeys)
					keyEntry.mKeys.Add(keyState.Clone());
			}

			mKeyEntries.Sort(scope (lhs, rhs) => String.Compare(lhs.mCommand, rhs.mCommand, true));

			for (var keyEntry in mKeyEntries)
			{
				mCurPropertiesTarget = keyEntry;
				var (category, propEntry) = AddPropertiesItem(root, keyEntry.mCommand, "mKeys");
			}

			UpdateKeyStates();
		}

		void PopulateVSOptions()
		{
			mCurPropertiesTarget = gApp.mSettings.mVSSettings;

			var root = (DarkListViewItem)mPropPage.mPropertiesListView.GetRoot();
			/*var (category, propEntry) = AddPropertiesItem(root, "General");
			category.mIsBold = true;
			category.mTextColor = cHeaderColor;*/

			var (category, propEntry) = AddPropertiesItem(root, "Tool Path (x86)", "mBin32Path", null, .BrowseForFolder);
			propEntry.mRelPath = new String(gApp.mInstallDir);
			(category, propEntry) = AddPropertiesItem(root, "Tool Path (x64)", "mBin64Path", null, .BrowseForFolder);
			propEntry.mRelPath = new String(gApp.mInstallDir);
			(category, propEntry) = AddPropertiesItem(root, "Library Paths (x86)", "mLib32Paths");
			propEntry.mRelPath = new String(gApp.mInstallDir);
			(category, propEntry) = AddPropertiesItem(root, "Library Paths (x64)", "mLib64Paths");
			propEntry.mRelPath = new String(gApp.mInstallDir);

			//category.Open(true, true);
		}

		void PopulateDebuggerOptions()
		{
			mCurPropertiesTarget = gApp.mSettings.mDebuggerSettings;

			var root = (DarkListViewItem)mPropPage.mPropertiesListView.GetRoot();
			
			AddPropertiesItem(root, "Use Symbol Servers", "mUseSymbolServers");
			AddPropertiesItem(root, "Symbol Cache Path", "mSymCachePath");
			AddPropertiesItem(root, "Symbol File Locations", "mSymbolSearchPath");
			AddPropertiesItem(root, "Auto Find Paths", "mAutoFindPaths");
			AddPropertiesItem(root, "Profile Sample Rate", "mProfileSampleRate");
			AddPropertiesItem(root, "Auto Evaluate Properties", "mAutoEvaluateProperties");
		}

		protected override void ResetSettings()
		{
			base.ResetSettings();

			var targetDict = scope Dictionary<Object, Object>();
			switch ((CategoryType)mPropPage.mCategoryType)
			{
			case .Editor:
				Settings.EditorSettings editorSettings = scope .();
				editorSettings.SetDefaults();
				targetDict[gApp.mSettings.mEditorSettings] = editorSettings;
				targetDict[gApp.mSettings.mEditorSettings.mColors] = editorSettings.mColors;
				UpdateFromTarget(targetDict);
			case .Keys:
				Settings.KeySettings keySettings = scope .();
				keySettings.SetDefaults();
				Dictionary<String, KeyEntry> defaultKeyMap = scope .();
				for (var keySettingEntry in keySettings.mEntries)
				{
					KeyEntry keyEntry = scope:: .();
					keyEntry.mCommand.Set(keySettingEntry.mCommand);
					for (let keyState in keySettingEntry.mKeys)
						keyEntry.mKeys.Add(keyState.Clone());
					defaultKeyMap[keyEntry.mCommand] = keyEntry;
				}

				for (var keyEntry in mKeyEntries)
				{
					KeyEntry defaultKeyEntry;
					defaultKeyMap.TryGetValue(keyEntry.mCommand, out defaultKeyEntry);
					if (defaultKeyEntry == null)
					{
						defaultKeyEntry = scope:: .();
					}

					targetDict[keyEntry] = defaultKeyEntry;
				}
				UpdateFromTarget(targetDict);
			case .Compiler:
				Settings.CompilerSettings compilerSettings = scope .();
				compilerSettings.SetDefaults();
				targetDict[gApp.mSettings.mCompilerSettings] = compilerSettings;
				UpdateFromTarget(targetDict);
			case .Debugger:
				Settings.DebuggerSettings debuggerSettings = scope .();
				debuggerSettings.SetDefaults();
				targetDict[gApp.mSettings.mDebuggerSettings] = debuggerSettings;
				UpdateFromTarget(targetDict);
			case .VisualStudio:
				Settings.VSSettings vsSettings = scope .();
				vsSettings.SetDefaults();
				targetDict[gApp.mSettings.mVSSettings] = vsSettings;
				UpdateFromTarget(targetDict);
			default:
			}
		}

		protected override void ShowPropPage(int32 categoryTypeInt)
		{
			base.ShowPropPage(categoryTypeInt);

			let categoryType = (CategoryType)categoryTypeInt;

			if (mPropPages[(int32)categoryType] == null)
			{
				CreatePropPage(categoryTypeInt, .AllowSearch | .AllowReset);
				mPropPages[categoryTypeInt] = mPropPage;
				mPropPage.mPropertiesListView.InitScrollbars(false, true);
				//mPropPage.mPropertiesListView.mAutoFocus = true;
				mPropPage.mPropertiesListView.mShowColumnGrid = true;
				mPropPage.mPropertiesListView.mShowGridLines = true;

				switch ((CategoryType)categoryTypeInt)
				{
				case .Editor:
					PopulateEditorOptions();
				case .Keys:
					PopulateKeyOptions();
				case .Compiler:
					PopulateCompilerOptions();
				case .Debugger:
					PopulateDebuggerOptions();
				case .VisualStudio:
					PopulateVSOptions();
				default:
					/*mCurPropertiesTarget = gApp.mSettings.mEditorSettings;
					PopulateEditorOptions();*/
				}
			}
			mPropPage = mPropPages[(int32)categoryType];
			AddPropPageWidget();
			ResizeComponents();
		}

		protected override void UpdatePropertyValue(PropEntry[] propEntries)
		{
			base.UpdatePropertyValue(propEntries);
			var propEntry = propEntries[0];
			if (propEntry.mTarget is KeyEntry)
				UpdateKeyStates();
		}

		protected override bool ApplyChanges()
		{
		    bool hadChanges = false;
            for (var propPage in mPropPages)
            {
                if (propPage == null)
                    continue;
                ApplyChanges(propPage, ref hadChanges);
            }

			if (hadChanges)
			{
				if (mKeyEntries != null)
				{
					gApp.mSettings.mKeySettings.Clear();
					for (let inEntry in mKeyEntries)
					{
						if (inEntry.mKeys.IsEmpty)
							continue;

						let outEntry = new Settings.KeySettings.Entry();
						outEntry.mCommand = new String(inEntry.mCommand);

						let keyArr = new KeyState[inEntry.mKeys.Count];
						for (int i < inEntry.mKeys.Count)
							keyArr[i] = inEntry.mKeys[i].Clone();
						outEntry.mKeys = keyArr;

						gApp.mSettings.mKeySettings.mEntries.Add(outEntry);
					}
				}

				gApp.mSettings.Apply();
			}

		    return true;
		}

		public override void CalcSize()
		{
		    mWidth = GS!(660);
		    mHeight = GS!(512);
		}

		public override void Update()
		{
		    base.Update();
			CheckForChanges(mPropPages);

			if (mPropPage?.mCategoryType == 4)
			{
				if (mWidgetWindow.mFocusWidget?.HasParent(mPropPage.mPropertiesListView) == true)
				{
					mHideVSHelper = true;
				}
				
			}
		}

		public override void DrawAll(Graphics g)
		{
			base.DrawAll(g);

			if ((mPropPage?.mCategoryType == 4) && (!mHideVSHelper))
			{
				var rect = mPropPage.mPropertiesListView.GetRect();
				rect.Top += GS!(120);

				rect.Inflate(-GS!(20), -GS!(20));
				rect.mWidth -= GS!(10);

				g.SetFont(DarkTheme.sDarkTheme.mSmallFont);
				String helpStr = "NOTE: These settings will be auto-detected if you have Visual Studio 2013 or later installed. You should not need to manually configure these settings.";
				rect.mHeight = g.mFont.GetWrapHeight(helpStr, rect.mWidth - GS!(40)) + GS!(40);

				using (g.PushClip(0, 0, mWidth, mPropPage.mPropertiesListView.mY + mPropPage.mPropertiesListView.mHeight))
				{
					using (g.PushColor(0x80000000))
						g.FillRect(rect.mX, rect.mY, rect.mWidth, rect.mHeight);

					g.DrawString(helpStr, rect.mX + GS!(20), rect.mY + GS!(20), .Left, rect.mWidth - GS!(40), .Wrap);
				}
			}
		}
	}
}
