using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Vanara.Collections;
using Vanara.Extensions;
using Vanara.InteropServices;
using Vanara.PInvoke;
using static Vanara.PInvoke.Kernel32;
using static Vanara.PInvoke.PowrProf;

namespace Vanara.Diagnostics
{
	/// <summary>Indicates the status of the battery.</summary>
	public enum BatteryStatus
	{
		/// <summary>The battery or battery controller is not present.</summary>
		NotPresent,

		/// <summary>The battery is discharging.</summary>
		Discharging,

		/// <summary>The battery is idle.</summary>
		Idle,

		/// <summary>The battery is charging.</summary>
		Charging
	}

	/// <summary>Specifies the status of battery saver.</summary>
	public enum EnergySaverStatus
	{
		/// <summary>Battery saver is off permanently or the device is plugged in.</summary>
		Disabled,

		/// <summary>Battery saver is off now, but ready to turn on automatically.</summary>
		Off,

		/// <summary>Battery saver is on. Save energy where possible.</summary>
		On
	}

	/// <summary>Specifies the power capabilities of a device.</summary>
	[Flags]
	public enum PowerCapabilities
	{
		/// <summary>There is a system power button.</summary>
		PowerButtonPresent = 1 << 0,

		/// <summary>There is a system sleep button.</summary>
		SleepButtonPresent = 1 << 1,

		/// <summary>There is a lid switch.</summary>
		LidPresent = 1 << 2,

		/// <summary>The operating system supports sleep state S1.</summary>
		SystemS1 = 1 << 3,

		/// <summary>The operating system supports sleep state S2.</summary>
		SystemS2 = 1 << 4,

		/// <summary>The operating system supports sleep state S3.</summary>
		SystemS3 = 1 << 5,

		/// <summary>The operating system supports sleep state S4 (hibernation).</summary>
		SystemS4 = 1 << 6,

		/// <summary>The operating system supports power off state S5 (soft off).</summary>
		SystemS5 = 1 << 7,

		/// <summary>The system hibernation file is present.</summary>
		HiberFilePresent = 1 << 8,

		/// <summary>The system supports wake capabilities.</summary>
		FullWake = 1 << 9,

		/// <summary>The system supports video display dimming capabilities.</summary>
		VideoDimPresent = 1 << 10,

		/// <summary>The system supports APM BIOS power management features.</summary>
		ApmPresent = 1 << 11,

		/// <summary>There is an uninterruptible power supply (UPS).</summary>
		UpsPresent = 1 << 12,

		/// <summary>The system supports thermal zones.</summary>
		ThermalControl = 1 << 13,

		/// <summary>The system supports processor throttling.</summary>
		ProcessorThrottle = 1 << 14,

		/// <summary>The system supports the hybrid sleep state.</summary>
		FastSystemS4 = 1 << 15,

		/// <summary>
		/// The system supports fast startup (aka: hiberboot, hybrid boot, or hybrid shutdown) which is a setting that helps your PC start up
		/// faster after shutdown.
		/// </summary>
		Hiberboot = 1 << 16,

		/// <summary>
		/// The platform has support for ACPI wake alarm devices. For more details on wake alarm devices, please see the ACPI specification
		/// section 9.18.
		/// </summary>
		WakeAlarmPresent = 1 << 17,

		/// <summary>The system supports the S0 low power idle model.</summary>
		AoAc = 1 << 18,

		/// <summary>The system supports allowing the removal of power to fixed disk devices.</summary>
		DiskSpinDown = 1 << 19,

		/// <summary/>
		AoAcConnectivitySupported = 1 << 20,

		/// <summary>There are one or more batteries in the system.</summary>
		SystemBatteriesPresent = 1 << 21,

		/// <summary>The system batteries are short-term. Short-term batteries are used in uninterruptible power supplies (UPS).</summary>
		BatteriesAreShortTerm = 1 << 22
	}

	/// <summary>Represents the device's power supply status.</summary>
	public enum PowerSupplyStatus
	{
		/// <summary>The device has no power supply.</summary>
		NotPresent,

		/// <summary>The device has an inadequate power supply.</summary>
		Inadequate,

		/// <summary>The device has an adequate power supply.</summary>
		Adequate
	}

	/// <summary>
	/// Provides access to information about a device's battery and power supply status and configuration. This extends the capabilities
	/// Windows.System.Power.PowerManager to include more detail, schemes and devices.
	/// </summary>
	public static class PowerManager
	{
		/*
		public static event EventHandler BatteryStatusChanged;
		public static event EventHandler EnergySaverStatusChanged;
		public static event EventHandler PowerSupplyStatusChanged;
		public static event EventHandler RemainingChargePercentChanged;
		public static event EventHandler RemainingDischargeTimeChanged;
		*/

		/// <summary>Gets the device's battery status.</summary>
		/// <value>Returns a <see cref="BatteryStatus"/> value.</value>
		public static BatteryStatus BatteryStatus => GetStatus().BatteryFlag switch
		{
			BATTERY_STATUS.BATTERY_CHARGING => BatteryStatus.Charging,
			BATTERY_STATUS.BATTERY_NONE => BatteryStatus.NotPresent,
			_ => GetStatus().ACLineStatus == AC_STATUS.AC_OFFLINE ? BatteryStatus.Discharging : BatteryStatus.Idle,
		};

		/// <summary>Gets flags indicating the system's power capabilities.</summary>
		/// <value>Returns a <see cref="PowerCapabilities"/> value.</value>
		public static PowerCapabilities DeviceCapabilities
		{
			get
			{
				var c = GetCapabilities();
				PowerCapabilities ret = 0;
				if (c.PowerButtonPresent) ret |= PowerCapabilities.PowerButtonPresent;
				if (c.SleepButtonPresent) ret |= PowerCapabilities.SleepButtonPresent;
				if (c.LidPresent) ret |= PowerCapabilities.LidPresent;
				if (c.SystemS1) ret |= PowerCapabilities.SystemS1;
				if (c.SystemS2) ret |= PowerCapabilities.SystemS2;
				if (c.SystemS3) ret |= PowerCapabilities.SystemS3;
				if (c.SystemS4) ret |= PowerCapabilities.SystemS4;
				if (c.SystemS5) ret |= PowerCapabilities.SystemS5;
				if (c.HiberFilePresent) ret |= PowerCapabilities.HiberFilePresent;
				if (c.FullWake) ret |= PowerCapabilities.FullWake;
				if (c.VideoDimPresent) ret |= PowerCapabilities.VideoDimPresent;
				if (c.ApmPresent) ret |= PowerCapabilities.ApmPresent;
				if (c.UpsPresent) ret |= PowerCapabilities.UpsPresent;
				if (c.ThermalControl) ret |= PowerCapabilities.ThermalControl;
				if (c.ProcessorThrottle) ret |= PowerCapabilities.ProcessorThrottle;
				if (c.FastSystemS4) ret |= PowerCapabilities.FastSystemS4;
				if (c.Hiberboot) ret |= PowerCapabilities.Hiberboot;
				if (c.WakeAlarmPresent) ret |= PowerCapabilities.WakeAlarmPresent;
				if (c.AoAc) ret |= PowerCapabilities.AoAc;
				if (c.DiskSpinDown) ret |= PowerCapabilities.DiskSpinDown;
				if (c.AoAcConnectivitySupported) ret |= PowerCapabilities.AoAcConnectivitySupported;
				if (c.SystemBatteriesPresent) ret |= PowerCapabilities.SystemBatteriesPresent;
				if (c.BatteriesAreShortTerm) ret |= PowerCapabilities.BatteriesAreShortTerm;
				return ret;
			}
		}

		/// <summary>Gets the device's battery saver status, indicating when to save energy.</summary>
		/// <value>Returns a <see cref="EnergySaverStatus"/> value.</value>
		public static EnergySaverStatus EnergySaverStatus
		{
			get
			{
				var s = GetStatus();
				return s.ACLineStatus == AC_STATUS.AC_ONLINE || s.BatteryFlag == BATTERY_STATUS.BATTERY_NONE ? EnergySaverStatus.Disabled : (s.SystemStatusFlag == 0 ? EnergySaverStatus.Off : EnergySaverStatus.On);
			}
		}

		/// <summary>Gets a value indicating whether the system is on AC power (plugged in).</summary>
		/// <value><see langword="true"/> if on AC power; otherwise, <see langword="false"/>.</value>
		public static bool OnACPower => GetStatus().ACLineStatus == AC_STATUS.AC_ONLINE;

		/// <summary>Indicates the OEM's preferred power management profile for this device.</summary>
		public static POWER_PLATFORM_ROLE PlatformRole => PInvokeClient.Windows8.IsPlatformSupported() ? PowerDeterminePlatformRoleEx(PowerPlatformRoleVersion.POWER_PLATFORM_ROLE_V2) : PowerDeterminePlatformRole();

		/// <summary>A collection of the powered devices on the system. Filters on this list can be set as properties.</summary>
		/// <value>Returns a <see cref="PoweredDeviceCollection"/> value.</value>
		public static PoweredDeviceCollection PoweredDevices { get; } = new PoweredDeviceCollection();

		/// <summary>Gets the device's power supply status.</summary>
		/// <value>Returns a <see cref="PowerSupplyStatus"/> value.</value>
		public static PowerSupplyStatus PowerSupplyStatus => GetStatus().ACLineStatus switch
		{
			AC_STATUS.AC_ONLINE => PowerSupplyStatus.Adequate,
			AC_STATUS.AC_LINE_BACKUP_POWER => PowerSupplyStatus.Inadequate,
			_ => PowerSupplyStatus.NotPresent,
		};

		/// <summary>Gets the total percentage of charge remaining from all batteries connected to the device.</summary>
		/// <value>Returns a <c>int?</c> value from 0 to 100, or <see langword="null"/> if the status is unknown.</value>
		public static int? RemainingChargePercent { get { var p = GetStatus().BatteryLifePercent; return p == 255 ? (int?)null : p; } }

		/// <summary>Gets the total runtime remaining from all batteries connected to the device.</summary>
		/// <value>
		/// The total runtime remaining from all batteries connected to the device, or <see langword="null"/> if the status is unknown or
		/// connected to AC power.
		/// </value>
		public static TimeSpan? RemainingDischargeTime { get { var s = GetStatus().BatteryLifeTime; return s == uint.MaxValue ? (TimeSpan?)null : TimeSpan.FromSeconds(s); } }

		/// <summary>Gets the collection of all power schemes on this device.</summary>
		/// <value>Returns a <see cref="PowerSchemeCollection"/> value.</value>
		public static PowerSchemeCollection Schemes { get; } = new PowerSchemeCollection();

		internal static string NameForGuid(Guid guid) => typeof(PowrProf).GetFields(BindingFlags.Public | BindingFlags.Static).FirstOrDefault(i => i.FieldType == typeof(Guid) && guid.Equals(i.GetValue(null)))?.Name;

		private static SYSTEM_POWER_CAPABILITIES GetCapabilities() => GetPwrCapabilities(out var c) ? c : default;

		private static SYSTEM_POWER_STATUS GetStatus() => GetSystemPowerStatus(out var s) ? s : default;
	}

	/// <summary>Represents a device on the system that has power requirements.</summary>
	public class PoweredDevice
	{
		private readonly bool hw;

		internal PoweredDevice(string id, bool isHW)
		{
			Id = id;
			hw = isHW;
		}

		/// <summary>Gets or sets the identifier for the device.</summary>
		/// <value>The identifier.</value>
		public string Id { get; protected set; }

		/// <summary>Gets or sets a value indicating whether the device is wake enabled.</summary>
		/// <value><see langword="true"/> if wake enabled; otherwise, <see langword="false"/>.</value>
		public bool WakeEnabled
		{
			get => PoweredDeviceCollection.GetEnumValues((hw ? PDQUERY.DEVICEPOWER_HARDWAREID : 0) | PDQUERY.DEVICEPOWER_FILTER_WAKEENABLED).Any(s => s == Id);
			set => DevicePowerSetDeviceState(Id, value ? PDSET.DEVICEPOWER_SET_WAKEENABLED : PDSET.DEVICEPOWER_CLEAR_WAKEENABLED);
		}

		/// <inheritdoc/>
		public override string ToString() => Id;
	}

	/// <summary>Retrieves the list, optionally filtered, of the powered devices on the system.</summary>
	/// <seealso cref="VirtualDictionary{TKey, TValue}"/>
	public class PoweredDeviceCollection : VirtualDictionary<string, PoweredDevice>
	{
		private static Finalizer finalizer;

		internal PoweredDeviceCollection() : base(true)
		{
		}

		/// <inheritdoc/>
		public override ICollection<string> Keys => GetEnumValues(PDQUERY).ToList();

		/// <summary>Gets or sets a value indicating whether to display only devices that are currently present in the system.</summary>
		/// <value><see langword="true"/> to retrieve only present devices; otherwise, <see langword="false"/>.</value>
		public bool OnlyPresentDevices { get; set; }

		/// <summary>Gets or sets a value indicating whether to display only devices that are wake-enabled.</summary>
		/// <value><see langword="true"/> if to retrieve only wake enabled devices; otherwise, <see langword="false"/>.</value>
		public bool OnlyWakeEnabledDevices { get; set; }

		/// <summary>Gets or sets a value indicating whether to display the hardware identifier rather than a friendly name.</summary>
		/// <value>
		/// <see langword="true"/> to display the hardware identifier; otherwise, <see langword="false"/> to display the friendly name.
		/// </value>
		public bool UseHardwareId { get; set; }

		/// <inheritdoc/>
		public override ICollection<PoweredDevice> Values => GetEnumValues(PDQUERY).Select(s => new PoweredDevice(s, UseHardwareId)).ToList();

		/// <inheritdoc/>
		protected override IEnumerable<KeyValuePair<string, PoweredDevice>> Items => GetEnumValues(PDQUERY).Select(s => new KeyValuePair<string, PoweredDevice>(s, new PoweredDevice(s, UseHardwareId)));

		private PDQUERY PDQUERY => (UseHardwareId ? PDQUERY.DEVICEPOWER_HARDWAREID : 0) | (OnlyPresentDevices ? PDQUERY.DEVICEPOWER_FILTER_DEVICES_PRESENT : 0) | (OnlyWakeEnabledDevices ? PDQUERY.DEVICEPOWER_FILTER_WAKEENABLED : 0);

		/// <inheritdoc/>
		public override bool TryGetValue(string key, out PoweredDevice value)
		{
			var ret = GetEnumValues(UseHardwareId ? PDQUERY.DEVICEPOWER_HARDWAREID : 0).Contains(key);
			value = ret ? new PoweredDevice(key, UseHardwareId) : null;
			return ret;
		}

		internal static IEnumerable<string> GetEnumValues(PDQUERY filter = 0, PDCAP flags = 0)
		{
			const uint bufSz = 1024;

			ValidateOpened();
			var sz = bufSz;
			var sb = new StringBuilder((int)sz / 2, (int)sz / 2);
			for (var i = 0U; ; i++)
			{
				sb.Clear();
				if (DevicePowerEnumDevices(i, filter, flags, sb, ref sz))
					yield return sb.ToString();
				else
					break;
			}
		}

		private static void ValidateOpened()
		{
			if (finalizer is null) finalizer = new Finalizer();
		}

		private class Finalizer
		{
			internal Finalizer() => DevicePowerOpen();

			~Finalizer() => DevicePowerClose();
		}
	}

	/// <summary>Represents a system power scheme (power plan).</summary>
	public class PowerScheme : IEquatable<Guid>, IEquatable<PowerScheme>
	{
		/// <summary>The well-known, system defined Balance (or Typical) power scheme with fairly aggressive power savings measures.</summary>
		public static readonly PowerScheme Balanced = new PowerScheme(GUID_TYPICAL_POWER_SAVINGS);

		/// <summary>The well-known, system defined High Performance (or Minimum) power scheme with almost no power savings measures.</summary>
		public static readonly PowerScheme HighPerformance = new PowerScheme(GUID_MIN_POWER_SAVINGS);

		/// <summary>
		/// The well-known, system defined Power Saver (or Max) power scheme with very aggressive power savings measures to help stretch
		/// battery life.
		/// </summary>
		public static readonly PowerScheme PowerSaver = new PowerScheme(GUID_MAX_POWER_SAVINGS);

		internal PowerScheme(Guid g)
		{
			Guid = g;
			Groups = new PowerSchemeGroupCollection(g);
		}

		/// <summary>Gets the active power scheme for the system.</summary>
		/// <value>Returns a <see cref="PowerScheme"/> value for the active scheme.</value>
		public static PowerScheme Active => PowerGetActiveScheme(out var g).Succeeded ? new PowerScheme(g) : throw new SystemException();

		/// <summary>Gets the variable name of the GUID defined in the Windows SDK for this scheme.</summary>
		/// <value>The API based variable name.</value>
		public string ApiName => PowerManager.NameForGuid(Guid);

		/// <summary>Gets or sets the description of the scheme.</summary>
		/// <value>The description.</value>
		public string Description
		{
			get => PowerReadDescription(Guid);
			set => PowerWriteDescription(Guid, null, null, value).ThrowIfFailed();
		}

		/// <summary>Gets the subgroups defined for this scheme.</summary>
		/// <value>Returns a <see cref="PowerSchemeGroupCollection"/> value.</value>
		public PowerSchemeGroupCollection Groups { get; }

		/// <summary>Gets or sets a value indicating whether this scheme is the active scheme for the system.</summary>
		/// <value><see langword="true"/> if this instance is active; otherwise, <see langword="false"/>.</value>
		public bool IsActive
		{
			get => PowerGetActiveScheme(out var g).Succeeded && Guid.Equals(g);
			set => PowerSetActiveScheme(default, Guid).ThrowIfFailed();
		}

		/// <summary>Gets or sets the friendly name of the scheme.</summary>
		/// <value>The friendly name.</value>
		public string Name
		{
			get => PowerReadFriendlyName(Guid);
			set => PowerWriteFriendlyName(Guid, null, null, value).ThrowIfFailed();
		}

		/// <summary>Gets the underlying GUID for the scheme.</summary>
		/// <value>Returns a <see cref="Guid"/> value.</value>
		protected internal Guid Guid { get; }

		/// <summary>Returns a duplicates this scheme that can be edited and set as the new active scheme.</summary>
		/// <returns>An editable duplicate of the current scheme.</returns>
		public PowerScheme Duplicate()
		{
			PowerDuplicateScheme(default, Guid, out var h).ThrowIfFailed();
			return new PowerScheme(h.ToStructure<Guid>());
		}

		/// <summary>Determines whether the specified <see cref="Guid"/>, is equal to this instance.</summary>
		/// <param name="other">The <see cref="Guid"/> to compare with this instance.</param>
		/// <returns><see langword="true"/> if the specified <see cref="Guid"/> is equal to this instance; otherwise, <see langword="false"/>.</returns>
		public bool Equals(Guid other) => Guid.Equals(other);

		/// <summary>Determines whether the specified <see cref="PowerScheme"/>, is equal to this instance.</summary>
		/// <param name="other">The <see cref="PowerScheme"/> to compare with this instance.</param>
		/// <returns><see langword="true"/> if the specified <see cref="PowerScheme"/> is equal to this instance; otherwise, <see langword="false"/>.</returns>
		public bool Equals(PowerScheme other) => !(other is null) && Equals(other.Guid);

		/// <summary>Determines whether the specified <see cref="object"/>, is equal to this instance.</summary>
		/// <param name="obj">The <see cref="object"/> to compare with this instance.</param>
		/// <returns><see langword="true"/> if the specified <see cref="object"/> is equal to this instance; otherwise, <see langword="false"/>.</returns>
		public override bool Equals(object obj) => obj is Guid g ? Equals(g) : obj is PowerScheme s && Equals(s);

		/// <summary>Returns a hash code for this instance.</summary>
		/// <returns>A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.</returns>
		public override int GetHashCode() => Guid.GetHashCode();

		//public System.Drawing.Icon Icon { get; set; }
	}

	/// <summary>Represents a collection of all the power schemes available on the system.</summary>
	/// <seealso cref="VirtualDictionary{Guid, PowerScheme}"/>
	public class PowerSchemeCollection : VirtualDictionary<Guid, PowerScheme>
	{
		internal PowerSchemeCollection() : base(!CanUserWritePwrScheme())
		{
		}

		/// <inheritdoc/>
		public override ICollection<Guid> Keys => PowerEnumerate<Guid>(null, null).ToList();

		/// <summary>Imports a power scheme from a file.</summary>
		/// <param name="importFilePath">The path to a power scheme backup file created by <c>PowerCfg.Exe /Export</c>.</param>
		/// <returns>A <see cref="PowerScheme"/> derived from the file.</returns>
		public static PowerScheme ImportFromFile(string importFilePath)
		{
			PowerImportPowerScheme(default, importFilePath, out var memG).ThrowIfFailed();
			return new PowerScheme(memG.ToStructure<Guid>());
		}

		/// <summary>
		/// Replaces the default power schemes with the current user's power schemes. This allows an administrator to change the default
		/// power schemes for the system. Replacing the default schemes enables users to use the <c>Restore Defaults</c> option in the
		/// Control Panel <c>Power Options</c> application to restore customized power scheme defaults instead of the original Windows power
		/// scheme defaults.
		/// </summary>
		/// <remarks>The caller must be a member of the local Administrators group.</remarks>
		public static void ReplaceDefaultPowerSchemes() => PowerReplaceDefaultPowerSchemes().ThrowIfFailed();

		/// <summary>
		/// Replaces the power schemes for the system with default power schemes. All current power schemes and settings are deleted and
		/// replaced with the default system power schemes.
		/// </summary>
		/// <remarks>The caller must be a member of the local Administrators group.</remarks>
		public static void RestoreDefaultPowerSchemes() => PowerRestoreDefaultPowerSchemes().ThrowIfFailed();

		/// <inheritdoc/>
		public override bool Remove(Guid key) => PowerDeleteScheme(default, key).Succeeded;

		/// <inheritdoc/>
		public override bool TryGetValue(Guid key, out PowerScheme value)
		{
			value = new PowerScheme(key); return true;
		}

		// public static POWER_POLICY ActivePowerPolicy => GetCurrentPowerPolicies(out var _, out var p) ? p : default; public static
		// GLOBAL_POWER_POLICY GlobalPowerPolicy => GetCurrentPowerPolicies(out var p, out var _) ? p : default;
	}

	/// <summary>Represents a subgroup of a system power scheme (power plan).</summary>
	public class PowerSchemeGroup
	{
		/// <summary>The scheme's guid.</summary>
		protected Guid scheme;

		/// <summary>The scheme's subgroup's guid.</summary>
		protected Guid subgroup;

		internal PowerSchemeGroup(Guid scheme, Guid group)
		{
			this.scheme = scheme;
			subgroup = group;
			Settings = new PowerSchemeSettingCollection(scheme, subgroup);
		}

		/// <summary>Gets the variable name of the GUID defined in the Windows SDK for this subgroup.</summary>
		/// <value>The API based variable name.</value>
		public string ApiName => PowerManager.NameForGuid(subgroup);

		/// <summary>Gets or sets the description of the subgroup.</summary>
		/// <value>The description.</value>
		public string Description
		{
			get => PowerReadDescription(scheme, subgroup);
			set => PowerWriteDescription(scheme, subgroup, null, value).ThrowIfFailed();
		}

		/// <summary>Gets or sets the friendly name of the subgroup.</summary>
		/// <value>The friendly name.</value>
		public string Name
		{
			get => PowerReadFriendlyName(scheme, subgroup);
			set => PowerWriteFriendlyName(scheme, subgroup, null, value).ThrowIfFailed();
		}

		/// <summary>Gets the settings defined for this subgroup.</summary>
		/// <value>Returns a <see cref="PowerSchemeSettingCollection"/> value.</value>
		public PowerSchemeSettingCollection Settings { get; }
	}

	/// <summary>Represents a collection of all the subgroups available under a power scheme on the system.</summary>
	public class PowerSchemeGroupCollection : VirtualDictionary<Guid, PowerSchemeGroup>
	{
		/// <summary>The scheme's guid.</summary>
		protected Guid scheme;

		internal PowerSchemeGroupCollection(Guid scheme) : base(false) => this.scheme = scheme;

		/// <inheritdoc/>
		public override ICollection<Guid> Keys => PowerEnumerate<Guid>(scheme, null).Concat(new[] { NO_SUBGROUP_GUID }).ToList();

		/// <inheritdoc/>
		public override bool TryGetValue(Guid key, out PowerSchemeGroup value)
		{
			value = new PowerSchemeGroup(scheme, key); return true;
		}
	}

	/// <summary>Represents a setting on a subgroup.</summary>
	public class PowerSchemeSetting
	{
		/// <summary>The scheme's guid.</summary>
		protected Guid scheme;
		/// <summary>The scheme's setting guid.</summary>
		protected Guid setting;
		/// <summary>The scheme's subgroup guid.</summary>
		protected Guid subgroup;

		internal PowerSchemeSetting(Guid scheme, Guid group, Guid setting)
		{
			this.scheme = scheme;
			subgroup = group;
			this.setting = setting;
		}

		private delegate Win32Error IdxWriter(HKEY rootSystemPowerKey, in Guid schemePersonalityGuid, in Guid subGroupOfPowerSettingsGuid, in Guid powerSettingGuid, uint index);

		/// <summary>Retrieves the AC power value for the specified power setting.</summary>
		/// <value>Returns the data value.</value>
		public object ACValue
		{
			get
			{
				var sz = 0U;
				PowerReadACValue(default, scheme, subgroup, setting, out _, IntPtr.Zero, ref sz).ThrowIfFailed();
				using var mem = new SafeHGlobalHandle((int)sz);
				REG_VALUE_TYPE regType;
				PowerReadACValue(default, scheme, subgroup, setting, out regType, mem, ref sz).ThrowIfFailed();
				return regType.GetValue(mem, sz);
			}
		}

		/// <summary>Gets or sets the default AC index of the specified power setting.</summary>
		/// <value>The default AC index.</value>
		public int ACValueDefaultIndex
		{
			get => PowerReadACDefaultIndex(default, scheme, subgroup, setting, out var i).Succeeded ? (int)i : -1;
			set => WriteIndex(PowerWriteACDefaultIndex, value);
		}

		/// <summary>Gets or sets the AC index of the specified power setting.</summary>
		/// <value>The AC index.</value>
		public int ACValueIndex
		{
			get => PowerReadACValueIndex(default, scheme, subgroup, setting, out var i).Succeeded ? (int)i : -1;
			set => WriteIndex(PowerWriteACValueIndex, value);
		}

		/// <summary>Gets the variable name of the GUID defined in the Windows SDK for this setting.</summary>
		/// <value>The API based variable name.</value>
		public string ApiName => PowerManager.NameForGuid(setting);

		/// <summary>Retrieves the DC power value for the specified power setting.</summary>
		/// <value>Returns the data value.</value>
		public object DCValue
		{
			get
			{
				var sz = 0U;
				PowerReadDCValue(default, scheme, subgroup, setting, out _, IntPtr.Zero, ref sz).ThrowIfFailed();
				using var mem = new SafeHGlobalHandle((int)sz);
				REG_VALUE_TYPE regType;
				PowerReadDCValue(default, scheme, subgroup, setting, out regType, mem, ref sz).ThrowIfFailed();
				return regType.GetValue(mem, sz);
			}
		}

		/// <summary>Gets or sets the default DC index of the specified power setting.</summary>
		/// <value>The default DC index.</value>
		public int DCValueDefaultIndex
		{
			get => PowerReadDCDefaultIndex(default, scheme, subgroup, setting, out var i).Succeeded ? (int)i : -1;
			set => WriteIndex(PowerWriteDCDefaultIndex, value);
		}

		/// <summary>Gets or sets the DC index of the specified power setting.</summary>
		/// <value>The DC index.</value>
		public int DCValueIndex
		{
			get => PowerReadDCValueIndex(default, scheme, subgroup, setting, out var i).Succeeded ? (int)i : -1;
			set => WriteIndex(PowerWriteDCValueIndex, value);
		}

		/// <summary>Gets or sets the description of the setting.</summary>
		/// <value>The description.</value>
		public string Description
		{
			get => PowerReadDescription(scheme, subgroup, setting);
			set { PowerWriteDescription(scheme, subgroup, setting, value).ThrowIfFailed(); PowerSetActiveScheme(default, scheme).ThrowIfFailed(); }
		}

		/// <summary>Gets a value indicating whether a range is defined.</summary>
		/// <value><see langword="true"/> if a range is defined; otherwise, <see langword="false"/>.</value>
		public bool IsRange => PowerIsSettingRangeDefined(subgroup, setting);

		/// <summary>Gets or sets the friendly name of the setting.</summary>
		/// <value>The friendly name.</value>
		public string Name
		{
			get => PowerReadFriendlyName(scheme, subgroup, setting);
			set { PowerWriteFriendlyName(scheme, subgroup, setting, value).ThrowIfFailed(); PowerSetActiveScheme(default, scheme).ThrowIfFailed(); }
		}

		/// <summary>Gets the possible values for this setting.</summary>
		/// <value>Returns a <see cref="IEnumerable{T}"/> value.</value>
		public IEnumerable<(object value, string name, string description)> PossibleValues
		{
			get
			{
				if (IsRange) yield break;
				for (var i = 0U; ; i++)
				{
					var err = FunctionHelper.CallMethodWithStrBuf((StringBuilder sb, ref uint ssz) => PowerReadPossibleFriendlyName(default, subgroup, setting, i, sb, ref ssz), out var name, (ssz, r) => ssz > 0);
					if (err.Failed)
						break;
					err = FunctionHelper.CallMethodWithStrBuf((StringBuilder sb, ref uint ssz) => PowerReadPossibleDescription(default, subgroup, setting, i, sb, ref ssz), out var desc, (ssz, r) => ssz > 0);
					if (err.Failed)
						break;
					var sz = 0U;
					object obj = null;
					err = PowerReadPossibleValue(default, subgroup, setting, out var regType, i, IntPtr.Zero, ref sz);
					if (err.Succeeded)
					{
						using var mem = new SafeHGlobalHandle((int)sz);
						err = PowerReadPossibleValue(default, subgroup, setting, out regType, i, mem, ref sz);
						if (err.Failed)
							break;
						obj = regType.GetValue(mem, sz);
					}
					yield return (obj, name, desc);
				}
			}
		}

		/// <summary>Gets or sets the range information for this setting.</summary>
		/// <value>The range detail (min/max value, increment and unit specifier.</value>
		/// <exception cref="InvalidOperationException">Setting is not a range value.</exception>
		public (uint min, uint max, uint increment, string unitsSpecifier) Range
		{
			get
			{
				try
				{
					if (IsRange)
					{
						PowerReadValueMin(default, subgroup, setting, out var min).ThrowIfFailed();
						PowerReadValueMax(default, subgroup, setting, out var max).ThrowIfFailed();
						PowerReadValueIncrement(default, subgroup, setting, out var incr).ThrowIfFailed();
						FunctionHelper.CallMethodWithStrBuf((StringBuilder sb, ref uint sz) => PowerReadValueUnitsSpecifier(default, subgroup, setting, sb, ref sz), out var spec, (sz, r) => sz > 0).ThrowIfFailed();
						return (min, max, incr, spec);
					}
				}
				catch { }
				return (0, 0, 0, null);
			}
			set
			{
				if (!IsRange) throw new InvalidOperationException("Setting is not a range value.");
				PowerWriteValueMin(default, subgroup, setting, value.min).ThrowIfFailed();
				PowerWriteValueMax(default, subgroup, setting, value.max).ThrowIfFailed();
				PowerWriteValueIncrement(default, subgroup, setting, value.increment).ThrowIfFailed();
				PowerWriteValueUnitsSpecifier(default, subgroup, setting, value.unitsSpecifier, (uint)((value.unitsSpecifier?.Length + 1) * 2 ?? 0)).ThrowIfFailed();
				PowerSetActiveScheme(default, scheme).ThrowIfFailed();
			}
		}

		private void WriteIndex(IdxWriter func, int value)
		{
			func(default, scheme, subgroup, setting, unchecked((uint)value)).ThrowIfFailed();
			PowerSetActiveScheme(default, scheme).ThrowIfFailed();
		}
	}

	/// <summary>Represents a collection of all settings for a subgroup and power scheme on the system.</summary>
	public class PowerSchemeSettingCollection : VirtualDictionary<Guid, PowerSchemeSetting>
	{
		/// <summary>The scheme's guid.</summary>
		protected Guid scheme;
		/// <summary>The scheme's subgroup guid.</summary>
		protected Guid subgroup;

		internal PowerSchemeSettingCollection(Guid scheme, Guid subgroup) : base(false)
		{
			this.scheme = scheme; this.subgroup = subgroup;
		}

		/// <inheritdoc/>
		public override ICollection<Guid> Keys => PowerEnumerate<Guid>(scheme, subgroup).ToList();

		/// <inheritdoc/>
		public override bool TryGetValue(Guid key, out PowerSchemeSetting value)
		{
			value = new PowerSchemeSetting(scheme, subgroup, key); return true;
		}
	}
}