using Microsoft.Win32;
using System;
using System.Collections.Generic;
using static Vanara.PInvoke.ShlwApi;

namespace Vanara.Windows.Shell.Registration
{
	/// <summary>Manages registry entries related to file types and file associations.</summary>
	public class FileTypeAssociation : RegBasedSettings
	{
		internal FileTypeAssociation(string ext, RegistryKey key, bool readOnly) : base(key, readOnly)
		{
			Extension = ext;
			OpenWithProgIds = new RegBasedKeyCollection(key.OpenSubKey("OpenWithProgIds", !readOnly), readOnly);
		}

		/// <summary>Gets or sets the Content Type value to the file type's MIME content type.</summary>
		/// <value>The MIME content type.</value>
		public string ContentType
		{
			get => key.GetValue("ContentType")?.ToString();
			set => UpdateValue("ContentType", value);
		}

		/// <summary>Gets or sets the default value of the extension subkey to the ProgID to which it is linked.</summary>
		/// <value>The default ProgID for this extension.</value>
		/// <exception cref="InvalidOperationException">The specified ProgId is not registered with the system.</exception>
		public string DefaultProgId
		{
			get => key.GetValue(null)?.ToString();
			set
			{
				if (!Registry.ClassesRoot.HasSubKey(value))
					throw new InvalidOperationException("The specified ProgId is not registered with the system.");
				var old = DefaultProgId;
				if (old != null) OpenWithProgIds.Add(old);
				UpdateValue(null, value);
				if (value != null) OpenWithProgIds.Add(value);
			}
		}

		/// <summary>Gets the extension of this file association.</summary>
		/// <value>The extension.</value>
		public string Extension { get; }

		/// <summary>
		/// Gets a list of alternate ProgIDs for this file type. The programs for these ProgIDs appear in the Open with menu and are
		/// available as default Windows Store apps for the file type. Whenever an application takes over this file type by changing the
		/// default value, it should also add an entry to this list.
		/// </summary>
		/// <value>The open with prog ids.</value>
		public ICollection<string> OpenWithProgIds { get; }

		/// <summary>
		/// Gets or sets the PerceivedType to which the file belongs, if any. This value is not used by Windows versions prior to Windows
		/// Vista. For more information, see "Perceived Types and Application Registration".
		/// </summary>
		public PERCEIVED? PerceivedType
		{
			get
			{
				var value = key.GetValue("PerceivedType")?.ToString();
				return value is null ? null : (PERCEIVED?)(PERCEIVED)Enum.Parse(typeof(PERCEIVED), "PERCEIVED_TYPE_" + value.ToUpper());
			}
			set => UpdateValue("PerceivedType", value?.ToString().Substring(15).ToLower());
		}

		//public string LongExtension;
		// .ext => ProgId => shellnew ??

		/// <summary>Opens the specified extension for reading or editing.</summary>
		/// <param name="extension">The file extension to examine. This value must be in the format ".ext".</param>
		/// <param name="systemWide">
		/// If <see langword="true"/>, examine the file association system-wide. If <see langword="false"/>, examine the file association
		/// for the current user only.
		/// </param>
		/// <param name="readOnly">
		/// If <see langword="true"/>, provides read-only access to the registration; If <see langword="false"/>, the properties can be set
		/// to update the registration values.
		/// </param>
		/// <returns>The requested <see cref="FileTypeAssociation"/> instance.</returns>
		public static FileTypeAssociation Open(string extension, bool systemWide = false, bool readOnly = true)
		{
			var key = ShellRegistrar.GetRoot(systemWide, readOnly, extension ?? throw new ArgumentNullException(nameof(extension)));
			if (key is null) throw new ArgumentException("Unable to load specified extension", nameof(extension));
			return new FileTypeAssociation(extension, key, readOnly);
		}

		/// <summary>Registers the specified files extension.</summary>
		/// <param name="extension">The file extension to register. This value must be in the format ".ext".</param>
		/// <param name="systemWide">
		/// If <see langword="true"/>, register the file association system-wide. If <see langword="false"/>, register the file association
		/// for the current user only.
		/// </param>
		/// <returns>A <see cref="FileTypeAssociation"/> instance to continue definition of file extension settings.</returns>
		public static FileTypeAssociation Register(string extension, bool systemWide = false)
		{
			if (extension is null) throw new ArgumentNullException(nameof(extension));
			if (!extension.StartsWith(".")) throw new ArgumentException("The value must be in the format \".ext\"", nameof(extension));
			var root = ShellRegistrar.GetRoot(systemWide, true, extension);
			return new FileTypeAssociation(extension, root, false);
		}

		/// <summary>
		/// <para>Unregisters the file association.</para>
		/// <note type="warning">Removing a file association can break multiple applications since this will remove all ProgId associations.
		/// Do this with extreme caution and forethought. Consider just removing the ProgId for your application using <see cref="FileTypeAssociation.OpenWithProgIds"/>.</note>
		/// </summary>
		/// <param name="extension">The file extension to unregister. This value must be in the format ".ext".</param>
		/// <param name="systemWide">
		/// If <span class="keyword"><span class="languageSpecificText"><span class="cs">true</span><span class="vb">True</span><span
		/// class="cpp">true</span></span></span><span class="nu"><span class="keyword">true</span> ( <span class="keyword">True</span> in
		/// Visual Basic)</span>, unregister the file association system-wide. If <span class="keyword"><span
		/// class="languageSpecificText"><span class="cs">false</span><span class="vb">False</span><span
		/// class="cpp">false</span></span></span><span class="nu"><span class="keyword">false</span> ( <span class="keyword">False</span>
		/// in Visual Basic)</span>, unregister the file association for the current user only.
		/// </param>
		/// <exception cref="InvalidOperationException">Unable to find association key in the registry.</exception>
		public static void Unregister(string extension, bool systemWide = false)
		{
			if (extension is null) throw new ArgumentNullException(nameof(extension));
			if (!extension.StartsWith(".")) throw new ArgumentException("The value must be in the format \".ext\"", nameof(extension));
			using var sk = ShellRegistrar.GetRoot(systemWide, true);
			try { sk.DeleteSubKeyTree(extension); } catch { sk.DeleteSubKey(extension, false); }
			ShellRegistrar.NotifyShell();
		}

		/// <inheritdoc/>
		public override void Dispose()
		{
			base.Dispose();
			ShellRegistrar.NotifyShell();
		}
	}
}