/*
    Copyright (C) 2014-2019 de4dot@gmail.com
    This file is part of dnSpy
    dnSpy is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    dnSpy is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with dnSpy.  If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Linq;
using dnSpy.Contracts.Controls.ToolWindows;
using dnSpy.Contracts.Debugger;
using dnSpy.Contracts.Debugger.Evaluation;
using dnSpy.Contracts.Debugger.Text;
using dnSpy.Contracts.MVVM;
using dnSpy.Contracts.Settings.AppearanceCategory;
using dnSpy.Contracts.Text;
using dnSpy.Contracts.Text.Classification;
using dnSpy.Contracts.ToolWindows.Search;
using dnSpy.Debugger.Properties;
using dnSpy.Debugger.UI;
using dnSpy.Debugger.UI.Wpf;
using Microsoft.VisualStudio.Text.Classification;
namespace dnSpy.Debugger.ToolWindows.Threads {
	interface IThreadsVM : IGridViewColumnDescsProvider {
		bool IsOpen { get; set; }
		bool IsVisible { get; set; }
		BulkObservableCollection<ThreadVM> AllItems { get; }
		ObservableCollection<ThreadVM> SelectedItems { get; }
		void ResetSearchSettings();
		string GetSearchHelpText();
		IEnumerable<ThreadVM> Sort(IEnumerable<ThreadVM> threads);
	}
	[Export(typeof(IThreadsVM))]
	sealed class ThreadsVM : ViewModelBase, IThreadsVM, ILazyToolWindowVM, IComparer<ThreadVM> {
		public BulkObservableCollection<ThreadVM> AllItems { get; }
		public ObservableCollection<ThreadVM> SelectedItems { get; }
		public GridViewColumnDescs Descs { get; }
		public bool IsOpen {
			get => lazyToolWindowVMHelper.IsOpen;
			set => lazyToolWindowVMHelper.IsOpen = value;
		}
		public bool IsVisible {
			get => lazyToolWindowVMHelper.IsVisible;
			set => lazyToolWindowVMHelper.IsVisible = value;
		}
		IEditValueProvider NameEditValueProvider {
			get {
				threadContext.UIDispatcher.VerifyAccess();
				if (nameEditValueProvider is null)
					nameEditValueProvider = editValueProviderService.Create(ContentTypes.ThreadsWindowName, Array.Empty<string>());
				return nameEditValueProvider;
			}
		}
		IEditValueProvider? nameEditValueProvider;
		public object ProcessCollection => processes;
		readonly ObservableCollection<SimpleProcessVM> processes;
		public object? SelectedProcess {
			get => selectedProcess;
			set {
				if (selectedProcess != value) {
					selectedProcess = (SimpleProcessVM?)value;
					OnPropertyChanged(nameof(SelectedProcess));
					FilterList_UI(filterText, selectedProcess);
				}
			}
		}
		SimpleProcessVM? selectedProcess;
		public string FilterText {
			get => filterText;
			set {
				if (filterText == value)
					return;
				filterText = value;
				OnPropertyChanged(nameof(FilterText));
				FilterList_UI(filterText, selectedProcess);
			}
		}
		string filterText = string.Empty;
		public bool SomethingMatched => !nothingMatched;
		public bool NothingMatched {
			get => nothingMatched;
			set {
				if (nothingMatched == value)
					return;
				nothingMatched = value;
				OnPropertyChanged(nameof(NothingMatched));
				OnPropertyChanged(nameof(SomethingMatched));
			}
		}
		bool nothingMatched;
		sealed class ProcessState {
			/// <summary>
			/// Set to true when <see cref="DbgProcess.DelayedIsRunningChanged"/> gets raised
			/// and cleared when the process is paused.
			/// </summary>
			public bool IgnoreThreadsChangedEvent { get; set; }
		}
		readonly Lazy<DbgManager> dbgManager;
		readonly Lazy<DbgLanguageService> dbgLanguageService;
		readonly ThreadContext threadContext;
		readonly ThreadFormatterProvider threadFormatterProvider;
		readonly DebuggerSettings debuggerSettings;
		readonly ThreadCategoryService threadCategoryService;
		readonly EditValueProviderService editValueProviderService;
		readonly LazyToolWindowVMHelper lazyToolWindowVMHelper;
		readonly List<ThreadVM> realAllItems;
		int threadOrder;
		[ImportingConstructor]
		ThreadsVM(Lazy<DbgManager> dbgManager, Lazy<DbgLanguageService> dbgLanguageService, DebuggerSettings debuggerSettings, UIDispatcher uiDispatcher, ThreadFormatterProvider threadFormatterProvider, IClassificationFormatMapService classificationFormatMapService, ITextBlockContentInfoFactory textBlockContentInfoFactory, ThreadCategoryService threadCategoryService, EditValueProviderService editValueProviderService) {
			uiDispatcher.VerifyAccess();
			realAllItems = new List<ThreadVM>();
			AllItems = new BulkObservableCollection<ThreadVM>();
			SelectedItems = new ObservableCollection<ThreadVM>();
			processes = new ObservableCollection<SimpleProcessVM>();
			this.dbgManager = dbgManager;
			this.dbgLanguageService = dbgLanguageService;
			this.threadFormatterProvider = threadFormatterProvider;
			this.debuggerSettings = debuggerSettings;
			lazyToolWindowVMHelper = new DebuggerLazyToolWindowVMHelper(this, uiDispatcher, dbgManager);
			this.threadCategoryService = threadCategoryService;
			this.editValueProviderService = editValueProviderService;
			var classificationFormatMap = classificationFormatMapService.GetClassificationFormatMap(AppearanceCategoryConstants.UIMisc);
			threadContext = new ThreadContext(uiDispatcher, classificationFormatMap, textBlockContentInfoFactory, new SearchMatcher(searchColumnDefinitions), threadFormatterProvider.Create()) {
				SyntaxHighlight = debuggerSettings.SyntaxHighlight,
				UseHexadecimal = debuggerSettings.UseHexadecimal,
				DigitSeparators = debuggerSettings.UseDigitSeparators,
				FullString = debuggerSettings.FullString,
			};
			Descs = new GridViewColumnDescs {
				Columns = new GridViewColumnDesc[] {
					new GridViewColumnDesc(ThreadsWindowColumnIds.Icon, string.Empty),
					new GridViewColumnDesc(ThreadsWindowColumnIds.ThreadID, dnSpy_Debugger_Resources.Column_ThreadID),
					new GridViewColumnDesc(ThreadsWindowColumnIds.ThreadManagedId, dnSpy_Debugger_Resources.Column_ThreadManagedId),
					new GridViewColumnDesc(ThreadsWindowColumnIds.ThreadCategory, dnSpy_Debugger_Resources.Column_ThreadCategory),
					new GridViewColumnDesc(ThreadsWindowColumnIds.Name, dnSpy_Debugger_Resources.Column_Name),
					new GridViewColumnDesc(ThreadsWindowColumnIds.ThreadLocation, dnSpy_Debugger_Resources.Column_ThreadLocation),
					new GridViewColumnDesc(ThreadsWindowColumnIds.ThreadPriority, dnSpy_Debugger_Resources.Column_ThreadPriority),
					new GridViewColumnDesc(ThreadsWindowColumnIds.ThreadAffinityMask, dnSpy_Debugger_Resources.Column_ThreadAffinityMask),
					new GridViewColumnDesc(ThreadsWindowColumnIds.ThreadSuspendedCount, dnSpy_Debugger_Resources.Column_ThreadSuspendedCount),
					new GridViewColumnDesc(ThreadsWindowColumnIds.ProcessName, dnSpy_Debugger_Resources.Column_ProcessName),
					new GridViewColumnDesc(ThreadsWindowColumnIds.AppDomain, dnSpy_Debugger_Resources.Column_AppDomain),
					new GridViewColumnDesc(ThreadsWindowColumnIds.ThreadState, dnSpy_Debugger_Resources.Column_ThreadState),
				},
			};
			Descs.SortedColumnChanged += (a, b) => SortList();
		}
		// Don't change the order of these instances without also updating input passed to SearchMatcher.IsMatchAll()
		static readonly SearchColumnDefinition[] searchColumnDefinitions = new SearchColumnDefinition[] {
			new SearchColumnDefinition(PredefinedTextClassifierTags.ThreadsWindowId, "i", dnSpy_Debugger_Resources.Column_ThreadID),
			new SearchColumnDefinition(PredefinedTextClassifierTags.ThreadsWindowManagedId, "m", dnSpy_Debugger_Resources.Column_ThreadManagedId),
			new SearchColumnDefinition(PredefinedTextClassifierTags.ThreadsWindowCategoryText, "cat", dnSpy_Debugger_Resources.Column_ThreadCategory),
			new SearchColumnDefinition(PredefinedTextClassifierTags.ThreadsWindowName, "n", dnSpy_Debugger_Resources.Column_Name),
			new SearchColumnDefinition(PredefinedTextClassifierTags.ThreadsWindowLocation, "o", dnSpy_Debugger_Resources.Column_ThreadLocation),
			new SearchColumnDefinition(PredefinedTextClassifierTags.ThreadsWindowPriority, "pri", dnSpy_Debugger_Resources.Column_ThreadPriority),
			new SearchColumnDefinition(PredefinedTextClassifierTags.ThreadsWindowAffinityMask, "a", dnSpy_Debugger_Resources.Column_ThreadAffinityMask),
			new SearchColumnDefinition(PredefinedTextClassifierTags.ThreadsWindowSuspended, "sc", dnSpy_Debugger_Resources.Column_ThreadSuspendedCount),
			new SearchColumnDefinition(PredefinedTextClassifierTags.ThreadsWindowProcess, "p", dnSpy_Debugger_Resources.Column_ProcessName),
			new SearchColumnDefinition(PredefinedTextClassifierTags.ThreadsWindowAppDomain, "ad", dnSpy_Debugger_Resources.Column_AppDomain),
			new SearchColumnDefinition(PredefinedTextClassifierTags.ThreadsWindowUserState, "s", dnSpy_Debugger_Resources.Column_ThreadState),
		};
		// UI thread
		public string GetSearchHelpText() {
			threadContext.UIDispatcher.VerifyAccess();
			return threadContext.SearchMatcher.GetHelpText();
		}
		// random thread
		void DbgThread(Action callback) =>
			dbgManager.Value.Dispatcher.BeginInvoke(callback);
		// UI thread
		void ILazyToolWindowVM.Show() {
			threadContext.UIDispatcher.VerifyAccess();
			InitializeDebugger_UI(enable: true);
		}
		// UI thread
		void ILazyToolWindowVM.Hide() {
			threadContext.UIDispatcher.VerifyAccess();
			InitializeDebugger_UI(enable: false);
		}
		// UI thread
		void InitializeDebugger_UI(bool enable) {
			threadContext.UIDispatcher.VerifyAccess();
			if (processes.Count == 0)
				InitializeProcesses_UI();
			ResetSearchSettings();
			if (enable) {
				threadContext.ClassificationFormatMap.ClassificationFormatMappingChanged += ClassificationFormatMap_ClassificationFormatMappingChanged;
				debuggerSettings.PropertyChanged += DebuggerSettings_PropertyChanged;
				threadContext.UIVersion++;
				RecreateFormatter_UI();
				threadContext.SyntaxHighlight = debuggerSettings.SyntaxHighlight;
				threadContext.UseHexadecimal = debuggerSettings.UseHexadecimal;
				threadContext.DigitSeparators = debuggerSettings.UseDigitSeparators;
				threadContext.FullString = debuggerSettings.FullString;
			}
			else {
				processes.Clear();
				threadContext.ClassificationFormatMap.ClassificationFormatMappingChanged -= ClassificationFormatMap_ClassificationFormatMappingChanged;
				debuggerSettings.PropertyChanged -= DebuggerSettings_PropertyChanged;
			}
			DbgThread(() => InitializeDebugger_DbgThread(enable));
		}
		// UI thread
		void InitializeProcesses_UI() {
			threadContext.UIDispatcher.VerifyAccess();
			if (processes.Count != 0)
				return;
			processes.Add(new SimpleProcessVM(dnSpy_Debugger_Resources.Threads_AllProcesses));
			SelectedProcess = processes[0];
		}
		// DbgManager thread
		void InitializeDebugger_DbgThread(bool enable) {
			dbgManager.Value.Dispatcher.VerifyAccess();
			if (enable) {
				dbgManager.Value.ProcessesChanged += DbgManager_ProcessesChanged;
				dbgManager.Value.CurrentThreadChanged += DbgManager_CurrentThreadChanged;
				dbgManager.Value.DelayedIsRunningChanged += DbgManager_DelayedIsRunningChanged;
				dbgLanguageService.Value.LanguageChanged += DbgLanguageService_LanguageChanged;
				var threads = new List<DbgThread>();
				var processes = dbgManager.Value.Processes;
				foreach (var p in processes) {
					InitializeProcess_DbgThread(p);
					if (!p.IsRunning)
						threads.AddRange(p.Threads);
					foreach (var r in p.Runtimes) {
						InitializeRuntime_DbgThread(r);
						foreach (var a in r.AppDomains)
							InitializeAppDomain_DbgThread(a);
					}
				}
				if (threads.Count > 0 || processes.Length > 0) {
					UI(() => {
						AddItems_UI(threads);
						AddItems_UI(processes);
					});
				}
			}
			else {
				dbgManager.Value.ProcessesChanged -= DbgManager_ProcessesChanged;
				dbgManager.Value.CurrentThreadChanged -= DbgManager_CurrentThreadChanged;
				dbgManager.Value.DelayedIsRunningChanged -= DbgManager_DelayedIsRunningChanged;
				dbgLanguageService.Value.LanguageChanged -= DbgLanguageService_LanguageChanged;
				foreach (var p in dbgManager.Value.Processes) {
					DeinitializeProcess_DbgThread(p);
					foreach (var r in p.Runtimes) {
						DeinitializeRuntime_DbgThread(r);
						foreach (var a in r.AppDomains)
							DeinitializeAppDomain_DbgThread(a);
					}
				}
				UI(() => RemoveAllThreads_UI());
			}
		}
		// DbgManager thread
		void DbgLanguageService_LanguageChanged(object? sender, DbgLanguageChangedEventArgs e) => UI(() => RefreshLanguageFields_UI());
		// DbgManager thread
		void DbgManager_DelayedIsRunningChanged(object? sender, EventArgs e) {
			// If all processes are running and the window is hidden, hide it now
			if (!IsVisible)
				UI(() => lazyToolWindowVMHelper.TryHideWindow());
		}
		// DbgManager thread
		void InitializeProcess_DbgThread(DbgProcess process) {
			process.DbgManager.Dispatcher.VerifyAccess();
			var state = process.GetOrCreateData<ProcessState>();
			state.IgnoreThreadsChangedEvent = process.IsRunning;
			process.IsRunningChanged += DbgProcess_IsRunningChanged;
			process.DelayedIsRunningChanged += DbgProcess_DelayedIsRunningChanged;
			process.ThreadsChanged += DbgProcess_ThreadsChanged;
			process.RuntimesChanged += DbgProcess_RuntimesChanged;
		}
		// DbgManager thread
		void DeinitializeProcess_DbgThread(DbgProcess process) {
			process.DbgManager.Dispatcher.VerifyAccess();


Answer: 			process.IsRunningChanged -= DbgProcess_IsRunningChanged;