using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using AppxPackage.Info;
using NativeWrappers;
//using PriFormat;
namespace AppxPackage
{
	internal static partial class ConstData
	{
		public static readonly string [] FilePathItems = new string []
		{
				"LockScreenLogo",
				"Logo",
				"SmallLogo",
				"Square150x150Logo",
				"Square30x30Logo",
				"Square310x310Logo",
				"Square44x44Logo",
				"Square70x70Logo",
				"Square71x71Logo",
				"StartPage",
				"Tall150x310Logo",
				"VisualGroup",
				"WideLogo",
				"Wide310x150Logo",
				"Executable"
		};
		public static bool IsFilePathKey (string key)
		{
			foreach (var i in FilePathItems)
				if ((i?.Trim ()?.ToLower () ?? "") == (key?.Trim ()?.ToLower () ?? ""))
					return true;
			return false;
		}
	}
	internal static partial class CacheData
	{
		private static readonly Dictionary<string, string> g_capnamemap = new Dictionary<string, string> (StringComparer.OrdinalIgnoreCase);
		/// <summary>
		/// 获取 Capability 的显示名称，带缓存
		/// </summary>
		/// <param name="capName">Capability 名称</param>
		/// <returns>显示名称字符串</returns>
		public static string GetPackageCapabilityDisplayName (string capName)
		{
			if (string.IsNullOrEmpty (capName)) return string.Empty;
			string cached;
			lock (g_capnamemap)
			{
				if (g_capnamemap.TryGetValue (capName, out cached) && !string.IsNullOrEmpty (cached))
					return cached;
			}
			IntPtr ptr = IntPtr.Zero;
			string ret = string.Empty;
			try
			{
				ptr = PackageReadHelper.GetPackageCapabilityDisplayName (capName);
				ret = ptr != IntPtr.Zero ? PackageReadHelper.GetStringAndFreeFromPkgRead (ptr) : string.Empty;
				ptr = IntPtr.Zero; // 已由 GetStringAndFreeFromPkgRead 释放
				lock (g_capnamemap)
				{
					g_capnamemap [capName] = ret;
				}
			}
			catch
			{
				// 再次尝试，不抛异常
				try
				{
					ptr = PackageReadHelper.GetPackageCapabilityDisplayName (capName);
					ret = ptr != IntPtr.Zero ? PackageReadHelper.GetStringAndFreeFromPkgRead (ptr) : string.Empty;
					ptr = IntPtr.Zero;
					lock (g_capnamemap)
					{
						g_capnamemap [capName] = ret;
					}
				}
				catch
				{
					ret = string.Empty;
				}
			}
			return ret;
		}
	}
	[ComVisible (true)]
	[ClassInterface (ClassInterfaceType.AutoDual)]
	public class BaseInfoSection: IDisposable, DataUtils.Utilities.IJsonBuild
	{
		protected Ref<IntPtr> m_hReader = IntPtr.Zero;
		public BaseInfoSection (ref IntPtr hReader) { m_hReader = hReader; }
		public bool IsValid { get { return m_hReader != null && m_hReader != IntPtr.Zero; } }
		public void Dispose ()
		{
			try { m_hReader.Set (IntPtr.Zero); } catch (Exception) { }
		}
		public virtual object BuildJSON () { return this; }
		~BaseInfoSection () { Dispose (); }
	}
	[ComVisible (true)]
	[ClassInterface (ClassInterfaceType.AutoDual)]
	public class BaseInfoSectWithPRI: BaseInfoSection
	{
		protected Ref<PackageReader> m_reader = new Ref<PackageReader> (null);
		protected Ref<PriReaderBundle> m_priBundle = new Ref<PriReaderBundle> (null);
		protected Ref<bool> m_usePri = new Ref<bool> (false);
		protected Ref<bool> m_enablePri = new Ref<bool> (false);
		public BaseInfoSectWithPRI (ref IntPtr hReader, PackageReader reader, ref PriReaderBundle priBundle, ref bool usePri, ref bool enablePri) : base (ref hReader)
		{
			m_reader.Set (reader);
			m_priBundle.Set (priBundle);
			m_usePri.Set (usePri);
			m_enablePri.Set (enablePri);
		}
		public new void Dispose ()
		{
			try { m_reader.Set (null); } catch (Exception) { }
			try { m_priBundle.Set (null); } catch (Exception) { }
			m_usePri = null;
			m_enablePri = null;
			m_hReader = null;
		}
		~BaseInfoSectWithPRI () { Dispose (); }
	}
	[ComVisible (true)]
	[ClassInterface (ClassInterfaceType.AutoDual)]
	public class PRIdentity: BaseInfoSection, Info.IIdentity
	{
		public PRIdentity (ref IntPtr hReader) : base (ref hReader) { }
		protected string StringValue (uint name)
		{
			var ptr = PackageReadHelper.GetPackageIdentityStringValue (m_hReader, name);
			return PackageReadHelper.GetStringAndFreeFromPkgRead (ptr) ?? "";
		}
		public string FamilyName { get { return StringValue (2); } }
		public string FullName { get { return StringValue (3); } }
		public string Name { get { return StringValue (0); } }
		public List<Architecture> ProcessArchitecture
		{
			get
			{
				var list = new List<Architecture> ();
				uint uarch = 0;
				PackageReadHelper.GetPackageIdentityArchitecture (m_hReader, out uarch);
				var t = PackageReadHelper.GetPackageType (m_hReader);
				switch (t)
				{
					case 1:
						switch (uarch)
						{
							case 0x1: list.Add (Architecture.x86); break;
							case 0x2: list.Add (Architecture.x64); break;
							case 0x4: list.Add (Architecture.ARM); break;
							case 0x8: list.Add (Architecture.ARM64); break;
							case 0xE: list.Add (Architecture.Neutral); break;
						}
						break;
					case 2:
						if ((uarch & 0x1) != 0) list.Add (Architecture.x86);
						if ((uarch & 0x2) != 0) list.Add (Architecture.x64);
						if ((uarch & 0x4) != 0) list.Add (Architecture.ARM);
						if ((uarch & 0x8) != 0) list.Add (Architecture.ARM64);
						break;
				}
				return list;
			}
		}
		public string Publisher { get { return StringValue (1); } }
		public string ResourceId { get { return StringValue (4); } }
		public DataUtils.Version Version
		{
			get
			{
				PackageReadHelper.VERSION ver = new PackageReadHelper.VERSION ();
				PackageReadHelper.GetPackageIdentityVersion (m_hReader, out ver, false);
				return new DataUtils.Version (ver.major, ver.minor, ver.build, ver.revision);
			}
		}
		public DataUtils.Version RealVersion
		{
			get
			{
				PackageReadHelper.VERSION ver = new PackageReadHelper.VERSION ();
				PackageReadHelper.GetPackageIdentityVersion (m_hReader, out ver, true);
				return new DataUtils.Version (ver.major, ver.minor, ver.build, ver.revision);
			}
		}
		public override object BuildJSON ()
		{
			return new
			{
				name = Name,
				package_full_name = FullName,
				package_family_name = FamilyName,
				publisher = Publisher,
				resource_id = ResourceId,
				architecture = ProcessArchitecture.Select (e => (int)e).ToList (),
				version = Version.BuildJSON (),
				realver = RealVersion.BuildJSON ()
			};
		}
	}
	[ComVisible (true)]
	[ClassInterface (ClassInterfaceType.AutoDual)]
	public class PRProperties: BaseInfoSectWithPRI, Info.IProperties
	{
		public PRProperties (ref IntPtr hReader, PackageReader reader, ref PriReaderBundle priBundle, ref bool usePri, ref bool enablePri) : base (ref hReader, reader, ref priBundle, ref usePri, ref enablePri) { }
		protected string StringValue (string attr)
		{
			var ptr = PackageReadHelper.GetPackagePropertiesStringValue (m_hReader, attr);
			return PackageReadHelper.GetStringAndFreeFromPkgRead (ptr) ?? "";
		}
		protected bool BoolValue (string attr, bool defaultValue = false)
		{
			int ret = 0;
			HRESULT hr = PackageReadHelper.GetPackagePropertiesBoolValue (m_hReader, attr, out ret);
			if (hr.Succeeded) return ret != 0;
			else return defaultValue;
		}
#if DEBUG
		public string Debug_StringValue (string attr) => StringValue (attr);
#endif
		protected string StringResValue (string attr)
		{
			var res = StringValue (attr);
			try
			{
				if (m_usePri && m_enablePri)
				{
					if (PriFileHelper.IsMsResourcePrefix (res))
						return m_priBundle.Value.String (res) ?? res;
				}
			}
			catch (Exception) { }
			return res;
		}
		protected string PathResValue (string attr)
		{
			var res = StringValue (attr);
			//var id = new PriFormat.PriResourceIdentifier (res);
			try
			{
				if (m_usePri && m_enablePri)
				{
					var resvalue = m_priBundle.Value.String (res);
					if (!string.IsNullOrEmpty (resvalue)) return resvalue;
					else return res;
				}
			}
			catch (Exception) { }
			return res;
		}
		public string Description { get { return StringResValue ("Description"); } }
		public string DisplayName { get { return StringResValue ("DisplayName"); } }
		public bool Framework { get { return BoolValue ("Framework"); } }
		public string Logo { get { return PathResValue ("Logo"); } }
		public string LogoBase64
		{
			get
			{
				var logopath = Logo;
				switch (PackageReadHelper.GetPackageType (m_hReader))
				{
					case 1:
						{
							IntPtr pic = PackageReadHelper.GetAppxFileFromAppxPackage (m_hReader, logopath);
							try
							{
								IntPtr base64Head = IntPtr.Zero;
								var base64s = PackageReadHelper.StreamToBase64W (pic, null, 0, out base64Head);
								//if (base64Head != IntPtr.Zero) { PackageReadHelper.GetStringAndFreeFromPkgRead (base64Head); base64Head = IntPtr.Zero; }
								return PackageReadHelper.GetStringAndFreeFromPkgRead (base64s);
							}
							catch (Exception) { return ""; }
							finally
							{
								if (pic != IntPtr.Zero) PackageReadHelper.DestroyAppxFileStream (pic);
							}
						}
						break;
					case 2:
						{
							IntPtr pkg = IntPtr.Zero, pic = IntPtr.Zero, pkglang = IntPtr.Zero;
							try
							{
								PackageReadHelper.GetSuitablePackageFromBundle (m_hReader, out pkglang, out pkg);
								if (pkglang != IntPtr.Zero && pkglang != pkg) PackageReadHelper.DestroyAppxFileStream (pkglang);
								pkglang = IntPtr.Zero;
								pic = PackageReadHelper.GetFileFromPayloadPackage (pkg, logopath);
								IntPtr base64Head = IntPtr.Zero;
								var lpstr = PackageReadHelper.StreamToBase64W (pic, null, 0, out base64Head);
								//if (base64Head != IntPtr.Zero) { PackageReadHelper.GetStringAndFreeFromPkgRead (base64Head); base64Head = IntPtr.Zero; }
								if (!(lpstr != IntPtr.Zero && !string.IsNullOrEmpty (PackageReadHelper.GetStringFromPkgRead (lpstr))))
								{
									if (lpstr != IntPtr.Zero) PackageReadHelper.GetStringAndFreeFromPkgRead (lpstr);
									IntPtr pkg1 = IntPtr.Zero, pic1 = IntPtr.Zero;
									try
									{
										pkg1 = PackageReadHelper.GetAppxBundleApplicationPackageFile (m_hReader);
										if (pkg1 != IntPtr.Zero)
										{
											pic1 = PackageReadHelper.GetFileFromPayloadPackage (pkg1, logopath);
											lpstr = PackageReadHelper.StreamToBase64W (pic1, null, 0, out base64Head);
											//if (base64Head != IntPtr.Zero) { PackageReadHelper.GetStringAndFreeFromPkgRead (base64Head); base64Head = IntPtr.Zero; }
										}
									}
									catch (Exception) { }
									finally
									{
										if (pic1 == pkg1) pkg1 = IntPtr.Zero;
										if (pic1 != IntPtr.Zero) PackageReadHelper.DestroyAppxFileStream (pic1);
										pic1 = IntPtr.Zero;
										if (pkg1 != IntPtr.Zero) PackageReadHelper.DestroyAppxFileStream (pkg1);
										pkg1 = IntPtr.Zero;
									}
								}
								return PackageReadHelper.GetStringAndFreeFromPkgRead (lpstr) ?? "";
							}
							catch (Exception) { return ""; }
							finally
							{
								if (pic == pkg) pkg = IntPtr.Zero;
								if (pic != IntPtr.Zero) PackageReadHelper.DestroyAppxFileStream (pic);
								pic = IntPtr.Zero;
								if (pkg != IntPtr.Zero) PackageReadHelper.DestroyAppxFileStream (pkg);
								pkg = IntPtr.Zero;
							}
						}
						break;
				}
				return "";
			}
		}
		public string Publisher { get { return StringResValue ("PublisherDisplayName"); } }
		public bool ResourcePackage { get { return BoolValue ("ResourcePackage"); } }
		public override object BuildJSON ()
		{
			return new
			{
				display_name = DisplayName,
				description = Description,
				publisher_display_name = Publisher,
				Framework = Framework,
				resource_package = ResourcePackage,
				logo = Logo,
				logo_base64 = LogoBase64
			};
		}
	}
	[ComVisible (true)]
	[ClassInterface (ClassInterfaceType.AutoDual)]
	public class PRApplication: Dictionary<string, string>
	{
		protected Ref<IntPtr> m_hReader = IntPtr.Zero;
		protected Ref<PriReaderBundle> m_priBundle = null;
		protected Ref<bool> m_usePri = false;
		protected Ref<bool> m_enablePri = false;
		public PRApplication (ref IntPtr hReader, ref PriReaderBundle priBundle, ref bool usePri, ref bool enablePri) : base (StringComparer.OrdinalIgnoreCase)
		{
			m_hReader = hReader;
			m_priBundle = priBundle;
			m_usePri = usePri;
			m_enablePri = enablePri;
		}
		public PRApplication (ref Ref<IntPtr> m_hReader, ref Ref<PriReaderBundle> m_priBundle, ref Ref<bool> m_usePri, ref Ref<bool> m_enablePri)
		{
			this.m_hReader = m_hReader;
			this.m_priBundle = m_priBundle;
			this.m_usePri = m_usePri;
			this.m_enablePri = m_enablePri;
		}
		public string UserModelID { get { return this ["AppUserModelID"]; } }
		protected bool EnablePri ()
		{
			if (m_priBundle == null || m_priBundle.Value == null) return false;
			if (!m_usePri.Value) return false;
			return m_enablePri.Value;
		}
		protected string StringResValue (string attr)
		{
			try
			{
				var res = this [attr];
				try
				{
					if (m_usePri && m_enablePri)
					{
						if (PriFileHelper.IsMsResourcePrefix (res))
							return m_priBundle.Value.String (res) ?? res;
					}
				}
				catch (Exception) { }
				return res;
			}
			catch (Exception) { return String.Empty; }
		}
		protected string PriGetRes (string resName)
		{
			if (string.IsNullOrEmpty (resName)) return string.Empty;
			if (m_priBundle == null || m_priBundle.Value == null) return string.Empty;
			return m_priBundle.Value.Resource (resName);
		}
		protected bool IsFilePathKey (string key)
		{
			foreach (var i in ConstData.FilePathItems)
				if ((i?.Trim ()?.ToLower () ?? "") == (key?.Trim ()?.ToLower () ?? ""))
					return true;
			return false;
		}
		public new string this [string key]
		{
			get
			{
				string value;
				if (!TryGetValue (key, out value))
				{
					value = string.Empty;
					base [key] = value;
				}
				if (!EnablePri ()) return value;
				if (PriFileHelper.IsMsResourcePrefix (value))
				{
					string pri = PriGetRes (value);
					return string.IsNullOrEmpty (pri) ? value : pri;
				}
				if (IsFilePathKey (key) && !string.IsNullOrEmpty (value))
				{
					string pri = PriGetRes (value);
					return string.IsNullOrEmpty (pri) ? value : pri;
				}
				return value;
			}
		}
		public string At (string key)
		{
			string value;
			if (!TryGetValue (key, out value)) throw new KeyNotFoundException ($"PRBaseApplication.At: key \"{key}\" not found");
			if (!EnablePri ()) return value;
			if (PriFileHelper.IsMsResourcePrefix (value))
			{
				string pri = PriGetRes (value);
				if (!string.IsNullOrEmpty (pri))
					return pri;
			}
			return value;
		}
		public string NewAt (string key, bool toPriString)
		{
			string value;
			if (!TryGetValue (key, out value))
			{
				value = string.Empty;
				base [key] = value;
			}
			if (!EnablePri () && toPriString) return value;
			if (PriFileHelper.IsMsResourcePrefix (value))
			{
				string pri = PriGetRes (value);
				return string.IsNullOrEmpty (pri) ? value : pri;
			}
			if (IsFilePathKey (key) && !string.IsNullOrEmpty (value))
			{
				string pri = PriGetRes (value);
				return string.IsNullOrEmpty (pri) ? value : pri;
			}
			return value;
		}
		public string NewAtBase64 (string key)
		{
			string value = NewAt (key, true);
			if (!IsFilePathKey (key) || string.IsNullOrEmpty (value)) return "";
			switch (PackageReadHelper.GetPackageType (m_hReader))
			{
				case 1: // PKGTYPE_APPX
					{
						IntPtr pic = IntPtr.Zero;
						try
						{
							pic = PackageReadHelper.GetAppxFileFromAppxPackage (m_hReader, value);
							IntPtr base64Head = IntPtr.Zero;
							IntPtr lpstr = PackageReadHelper.StreamToBase64W (pic, null, 0, out base64Head);
							//if (base64Head != IntPtr.Zero) { PackageReadHelper.GetStringAndFreeFromPkgRead (base64Head); base64Head = IntPtr.Zero; }
							return lpstr != IntPtr.Zero
								? PackageReadHelper.GetStringAndFreeFromPkgRead (lpstr)
								: "";
						}
						catch (Exception)
						{
							return "";
						}
						finally
						{
							if (pic != IntPtr.Zero)
							{
								PackageReadHelper.DestroyAppxFileStream (pic);
								pic = IntPtr.Zero;
							}
						}
					}
				case 2: // PKGTYPE_BUNDLE
					{
						IntPtr pkg = IntPtr.Zero;
						IntPtr pic = IntPtr.Zero;
						try
						{
							IntPtr header = IntPtr.Zero;
							PackageReadHelper.GetSuitablePackageFromBundle (m_hReader, out header, out pkg);
							//if (header != IntPtr.Zero) PackageReadHelper.GetStringAndFreeFromPkgRead (header);
							header = IntPtr.Zero;
							pic = PackageReadHelper.GetFileFromPayloadPackage (pkg, value);
							IntPtr base64Head = IntPtr.Zero;
							IntPtr lpstr = PackageReadHelper.StreamToBase64W (pic, null, 0, out base64Head);
							//if (base64Head != IntPtr.Zero) { PackageReadHelper.GetStringAndFreeFromPkgRead (base64Head); base64Head = IntPtr.Zero; }
							return lpstr != IntPtr.Zero
								? PackageReadHelper.GetStringAndFreeFromPkgRead (lpstr)
								: "";
						}
						catch (Exception)
						{
							return "";
						}
						finally
						{
							if (pic == pkg) pkg = IntPtr.Zero;
							if (pic != IntPtr.Zero)
							{
								PackageReadHelper.DestroyAppxFileStream (pic);
								pic = IntPtr.Zero;
							}
							if (pkg != IntPtr.Zero)
							{
								PackageReadHelper.DestroyAppxFileStream (pkg);
								pkg = IntPtr.Zero;
							}
						}
					}
			}
			return "";
		}
		public static bool operator == (PRApplication a, PRApplication b)
		{
			if (ReferenceEquals (a, b)) return true;
			if ((object)a == null || (object)b == null) return false;
			return string.Equals (a.UserModelID, b.UserModelID, StringComparison.OrdinalIgnoreCase);
		}
		public static bool operator != (PRApplication a, PRApplication b)
		{
			return !(a == b);
		}
		public override bool Equals (object obj)
		{
			PRApplication other = obj as PRApplication;
			if (other == null) return false;
			return this == other;
		}
		public override int GetHashCode ()
		{
			return (UserModelID ?? "").ToLowerInvariant ().GetHashCode ();
		}
	}
	[ComVisible (true)]
	[ClassInterface (ClassInterfaceType.AutoDual)]
	public class PRApplications: BaseInfoSectWithPRI, IEnumerable<PRApplication>
	{
		private IntPtr _hList = IntPtr.Zero;
		private List<PRApplication> _apps;
		public PRApplications (
			ref IntPtr hReader,
			PackageReader reader,
			ref PriReaderBundle priBundle,
			ref bool usePri,
			ref bool enablePri)
			: base (ref hReader, reader, ref priBundle, ref usePri, ref enablePri)
		{
			if (IsValid)
			{
				_hList = PackageReadHelper.GetPackageApplications (m_hReader.Value);
			}
		}
		#region Dispose
		public new void Dispose ()
		{
			if (_hList != IntPtr.Zero)
			{
				PackageReadHelper.DestroyPackageApplications (_hList);
				_hList = IntPtr.Zero;
			}
			base.Dispose ();
		}
		~PRApplications ()
		{
			Dispose ();
		}
		#endregion
		#region 属性：Applications
		public List<PRApplication> Applications
		{
			get
			{
				if (_apps == null)
					_apps = ReadApplications ();
				return _apps;
			}
		}
		#endregion
		#region 索引器
		public PRApplication this [int index]
		{
			get { return Applications [index]; }
		}
		public PRApplication this [string key]
		{
			get
			{
				foreach (var app in Applications)
				{
					if (string.Equals (app.UserModelID, key, StringComparison.OrdinalIgnoreCase))
					{
						return app;
					}
				}
				return null;
			}
		}
		#endregion
		#region IEnumerable
		public IEnumerator<PRApplication> GetEnumerator ()
		{
			return Applications.GetEnumerator ();
		}
		IEnumerator IEnumerable.GetEnumerator ()
		{
			return GetEnumerator ();
		}
		#endregion
		#region 内部解析逻辑（核心等价 C++ get）
		private List<PRApplication> ReadApplications ()
		{
			var list = new List<PRApplication> ();
			if (_hList == IntPtr.Zero) return list;
			IntPtr hMapList = PackageReadHelper.ApplicationsToMap (_hList);
			if (hMapList == IntPtr.Zero) return list;
			try
			{
				uint count = (uint)Marshal.ReadInt32 (hMapList);
				int baseOffset = Marshal.SizeOf (typeof (uint));
				for (int i = 0; i < count; i++)
				{
					IntPtr hKeyValues = Marshal.ReadIntPtr (hMapList, baseOffset + i * IntPtr.Size);
					if (hKeyValues == IntPtr.Zero) continue;
					list.Add (ReadSingleApplication (hKeyValues));
				}
			}
			finally
			{
				PackageReadHelper.DestroyApplicationsMap (hMapList);
			}
			return list;
		}
		private PRApplication ReadSingleApplication (IntPtr hKeyValues)
		{
			var app = new PRApplication (ref m_hReader, ref m_priBundle, ref m_usePri, ref m_enablePri);
			int pairCount = Marshal.ReadInt32 (hKeyValues);
			IntPtr arrayBase = IntPtr.Add (hKeyValues, sizeof (uint));
			for (int i = 0; i < pairCount; i++)
			{
				IntPtr pPairPtr = Marshal.ReadIntPtr (arrayBase, i * IntPtr.Size);
				if (pPairPtr == IntPtr.Zero) continue;
				PackageReadHelper.PAIR_PVOID pair =
					(PackageReadHelper.PAIR_PVOID)Marshal.PtrToStructure (pPairPtr, typeof (PackageReadHelper.PAIR_PVOID));
				if (pair.lpKey == IntPtr.Zero) continue;
				string key = Marshal.PtrToStringUni (pair.lpKey);
				if (string.IsNullOrEmpty (key)) continue;
				string value = pair.lpValue != IntPtr.Zero
					? Marshal.PtrToStringUni (pair.lpValue)
					: string.Empty;
				app.Add (key, value);
			}
			return app;
		}
		#endregion
		public override object BuildJSON ()
		{
			using (var apps = this)
			{
				return apps.Select (app =>
				{
					var dict = new Dictionary<string, object> (StringComparer.OrdinalIgnoreCase);
					foreach (var kv in app)
					{
						dict [kv.Key] = kv.Value;
						if (ConstData.IsFilePathKey (kv.Key))
						{
							dict [(kv.Key?.Trim () ?? "") + "_Base64"] = app.NewAtBase64 (kv.Key);
						}
						else
						{
							dict [kv.Key] = app.NewAt (kv.Key, m_usePri.Value && m_enablePri.Value) ?? kv.Value;
						}
					}
					dict ["AppUserModelID"] = app.UserModelID;
					return dict;
				}).ToList ();
			}
		}
	}
	[ComVisible (true)]
	[ClassInterface (ClassInterfaceType.AutoDual)]
	public class PRCapabilities: BaseInfoSection, ICapabilities
	{
		public PRCapabilities (ref IntPtr hReader) : base (ref hReader) {}
		public List<string> Capabilities
		{
			get
			{
				var ret = new List<string> ();
				if (!IsValid) return ret;
				IntPtr hList = PackageReadHelper.GetCapabilitiesList (m_hReader.Value);
				if (hList == IntPtr.Zero) return ret;
				try
				{
					uint count = (uint)Marshal.ReadInt32 (hList);
					int baseOffset = Marshal.SizeOf (typeof (uint)); // dwSize 后
					for (int i = 0; i < count; i++)
					{
						IntPtr pStr = Marshal.ReadIntPtr (hList, baseOffset + i * IntPtr.Size);
						if (pStr == IntPtr.Zero) continue;
						string s = Marshal.PtrToStringUni (pStr);
						if (!string.IsNullOrEmpty (s)) ret.Add (s);
					}
				}
				finally
				{
					PackageReadHelper.DestroyWStringList (hList);
				}
				return ret;
			}
		}
		public List<string> DeviceCapabilities
		{
			get
			{
				var ret = new List<string> ();
				if (!IsValid) return ret;
				IntPtr hList = PackageReadHelper.GetDeviceCapabilitiesList (m_hReader.Value);
				if (hList == IntPtr.Zero) return ret;
				try
				{
					uint count = (uint)Marshal.ReadInt32 (hList);
					int baseOffset = Marshal.SizeOf (typeof (uint)); // dwSize 后
					for (int i = 0; i < count; i++)
					{
						IntPtr pStr = Marshal.ReadIntPtr (hList, baseOffset + i * IntPtr.Size);
						if (pStr == IntPtr.Zero) continue;
						string s = Marshal.PtrToStringUni (pStr);
						if (!string.IsNullOrEmpty (s)) ret.Add (s);
					}
				}
				finally
				{
					PackageReadHelper.DestroyWStringList (hList);
				}
				return ret;
			}
		}
		public List<string> CapabilityDisplayNames
		{
			get
			{
				var caps = Capabilities;
				var dev = DeviceCapabilities;
				var ret = new List<string> ();
				foreach (var c in caps)
				{
					var capname = CacheData.GetPackageCapabilityDisplayName (c);
					if (String.IsNullOrWhiteSpace (capname)) ret.Add (c);
					else ret.Add (capname);
				}
				foreach (var d in dev)
				{
					var dcapname = CacheData.GetPackageCapabilityDisplayName (d);
					if (!String.IsNullOrWhiteSpace (dcapname)) ret.Add (dcapname);
				}
				return ret;
			}
		}
		public override object BuildJSON ()
		{
			return new
			{
				capabilities_name = Capabilities,
				device_capabilities = DeviceCapabilities,
				display_capabilities_name = CapabilityDisplayNames
			};
		}
	}
	[ComVisible (true)]
	[ClassInterface (ClassInterfaceType.AutoDual)]
	public class PRDependencies: BaseInfoSection, IEnumerable<DependencyInfo>
	{
		public PRDependencies (ref IntPtr hReader) : base (ref hReader) { }
		public List<DependencyInfo> Dependencies
		{
			get
			{
				var output = new List<DependencyInfo> ();
				if (!IsValid) return output;
				IntPtr hList = PackageReadHelper.GetDependencesInfoList (m_hReader);
				if (hList == IntPtr.Zero) return output;
				try
				{
					var deps = PackageReadHelper.ReadDependencyInfoList (hList);
					foreach (var dep in deps)
					{
						// dep.lpName / dep.lpPublisher 是 IntPtr
						string name = Marshal.PtrToStringUni (dep.lpName) ?? "";
						string publisher = Marshal.PtrToStringUni (dep.lpPublisher) ?? "";
						// VERSION 直接映射为 System.Version
						var ver = new DataUtils.Version (dep.verMin.major, dep.verMin.minor, dep.verMin.build, dep.verMin.revision);
						output.Add (new DependencyInfo (name, publisher, ver));
					}
				}
				finally
				{
					PackageReadHelper.DestroyDependencesInfoList (hList);
				}
				return output;
			}
		}
		public DependencyInfo this [int index] => Dependencies [index];
		public IEnumerator<DependencyInfo> GetEnumerator ()
		{
			return Dependencies.GetEnumerator ();
		}
		IEnumerator IEnumerable.GetEnumerator ()
		{
			return GetEnumerator ();
		}
		public override object BuildJSON ()
		{
			return this.Select (d => new
			{
				name = d.Name,
				publisher = d.Publisher,
				vermin = d.Version.BuildJSON ()
			}).ToList ();
		}
	}
	[ComVisible (true)]
	[ClassInterface (ClassInterfaceType.AutoDual)]
	public class PRResources: BaseInfoSection, IResources
	{
		public PRResources (ref IntPtr hReader) : base (ref hReader) {}
		public List<DXFeatureLevel> DXFeatures
		{
			get
			{
				var ret = new List<DXFeatureLevel> ();
				try
				{
					var dw = PackageReadHelper.GetResourcesDxFeatureLevels (m_hReader);
					if ((dw & 0x1) != 0) ret.Add (DXFeatureLevel.Level9);
					if ((dw & 0x2) != 0) ret.Add (DXFeatureLevel.Level10);
					if ((dw & 0x4) != 0) ret.Add (DXFeatureLevel.Level11);
					if ((dw & 0x8) != 0) ret.Add (DXFeatureLevel.Level12);
				}
				catch (Exception) { }
				return ret;
			}
		}
		public List <string> Languages
		{
			get
			{
				var ret = new List<string> ();
				if (!IsValid) return ret;
				IntPtr hList = PackageReadHelper.GetResourcesLanguages (m_hReader.Value);
				if (hList == IntPtr.Zero) return ret;
				try
				{
					ret = PackageReadHelper.ReadWStringList (hList).ToList ();
				}
				finally
				{
					PackageReadHelper.DestroyWStringList (hList);
				}
				return ret;
			}
		}
		public List<int> Languages_LCID
		{
			get
			{
				var ret = new List<int> ();
				if (!IsValid) return ret;
				IntPtr hList = PackageReadHelper.GetResourcesLanguagesToLcid (m_hReader.Value);
				if (hList == IntPtr.Zero) return ret;
				try
				{
					ret = PackageReadHelper.ReadLcidList (hList).ToList ();
				}
				finally
				{
					PackageReadHelper.DestroyResourcesLanguagesLcidList (hList);
				}
				return ret;
			}
		}
		public List<int> Scales
		{
			get
			{
				var ret = new List<int> ();
				if (!IsValid) return ret;
				IntPtr hList = PackageReadHelper.GetResourcesScales (m_hReader.Value);
				if (hList == IntPtr.Zero) return ret;
				try
				{
					ret = PackageReadHelper.ReadUInt32List (hList).Select (e => (int)e).ToList ();
				}
				finally
				{
					PackageReadHelper.DestroyUInt32List (hList);
				}
				return ret;
			}
		}
		public override object BuildJSON ()
		{
			return new
			{
				dx_feature_levels = DXFeatures.Select (e => (int)e).ToList (),
				languages = Languages,
				scales = Scales
			};
		}
	}
	public class PRPrerequisites: BaseInfoSection, IPrerequisites
	{
		public PRPrerequisites (ref IntPtr hReader) : base (ref hReader) {}
		protected DataUtils.Version GetVersion (string name)
		{
			PackageReadHelper.VERSION ver;
			bool res = PackageReadHelper.GetPackagePrerequisite (m_hReader, name, out ver);
			if (res) return new DataUtils.Version (ver.major, ver.minor, ver.build, ver.revision);
			else return new DataUtils.Version ();
		}
		protected string GetVersionDescription (string name)
		{
			var ptr = PackageReadHelper.GetPackagePrerequistieSystemVersionName (m_hReader, name);
			return PackageReadHelper.GetStringAndFreeFromPkgRead (ptr) ?? "";
		}
		public string OSMaxVersionDescription { get { return GetVersionDescription ("OSMaxVersionTested"); } }
		public DataUtils.Version OSMaxVersionTested { get { return GetVersion ("OSMaxVersionTested"); } }
		public DataUtils.Version OSMinVersion { get { return GetVersion ("OSMinVersion"); } }
		public string OSMinVersionDescription { get { return GetVersionDescription ("OSMinVersion"); } }
		public override object BuildJSON ()
		{
			return new
			{
				os_min_version = OSMinVersion.BuildJSON (),
				os_min_version_description = OSMinVersionDescription,
				os_max_version_tested = OSMaxVersionTested.BuildJSON (),
				os_max_version_tested_description = OSMaxVersionDescription
			};
		}
	}
	[ComVisible (true)]
	[ClassInterface (ClassInterfaceType.AutoDual)]
	public class PackageReader: IDisposable
	{
		private IntPtr m_hReader = PackageReadHelper.CreatePackageReader ();
		private string m_filePath = string.Empty;
		private bool m_usePRI = false;
		private bool m_enablePRI = false;
		private PriReaderBundle m_priBundle = new PriReaderBundle ();
		private HashSet<IntPtr> m_priStreams = new HashSet<IntPtr> ();
		public IntPtr Instance => m_hReader;
		private void InitPri ()
		{
			m_priBundle.Dispose ();
			foreach (var i in m_priStreams)
				if (i != IntPtr.Zero)
				{
					PackageReadHelper.DestroyAppxFileStream (i);
				}
			m_priStreams.Clear ();
			if (!m_usePRI) return;
			#region Get PRI IStream
			switch (Type)
			{
				case PackageType.Appx:
					{
						var istream = PackageReadHelper.GetAppxPriFileStream (m_hReader);
						if (istream != IntPtr.Zero)
						{
							m_priStreams.Add (istream);
							m_priBundle.Set (3, istream);
						}
					} break;
				case PackageType.Bundle:
					{
						IntPtr hls = IntPtr.Zero, hss = IntPtr.Zero;
						try
						{
							PackageReadHelper.GetSuitablePackageFromBundle (m_hReader, out hls, out hss);
							IntPtr hlpri = IntPtr.Zero, hspri = IntPtr.Zero;
							try
							{
								hlpri = PackageReadHelper.GetPriFileFromPayloadPackage (hls);
								hspri = PackageReadHelper.GetPriFileFromPayloadPackage (hss);
								IntPtr ls = hls, ss = hss;
								if (ls != IntPtr.Zero && ss != IntPtr.Zero)
								{
									if (ls != IntPtr.Zero) { m_priBundle.Set (1, hlpri); m_priStreams.Add (hlpri); }
									if (ss != IntPtr.Zero) { m_priBundle.Set (2, hspri); m_priStreams.Add (hspri); }
								}
								else if (ls != IntPtr.Zero || ss != IntPtr.Zero)
								{
									if (hlpri != IntPtr.Zero) { m_priBundle.Set (1, hlpri); m_priStreams.Add (hlpri); }
									if (hspri != IntPtr.Zero) { m_priBundle.Set (2, hspri); m_priStreams.Add (hspri); }
									IntPtr hd = IntPtr.Zero;
									try
									{
										hd = PackageReadHelper.GetAppxBundleApplicationPackageFile (m_hReader);
										IntPtr hdpri = PackageReadHelper.GetPriFileFromPayloadPackage (hd);
										if (hd != IntPtr.Zero) { m_priBundle.Set (3, hd); m_priStreams.Add (hd); }
									}
									finally
									{
										if (hd != IntPtr.Zero) PackageReadHelper.DestroyAppxFileStream (hd);
									}
								}
								else
								{
									IntPtr pkgstream = IntPtr.Zero;
									try
									{
										pkgstream = PackageReadHelper.GetAppxBundleApplicationPackageFile (m_hReader);
										IntPtr pristream = PackageReadHelper.GetPriFileFromPayloadPackage (pkgstream);
										if (pristream != IntPtr.Zero)
										{
											m_priStreams.Add (pristream);
											m_priBundle.Set (3, pristream);
										}
									}
									finally
									{
										if (pkgstream != IntPtr.Zero) PackageReadHelper.DestroyAppxFileStream (pkgstream);
									}
								}
							}
							finally {}
						}
						finally
						{
							if (hls == hss) hss = IntPtr.Zero;
							if (hls != IntPtr.Zero) PackageReadHelper.DestroyAppxFileStream (hls);
							if (hss != IntPtr.Zero) PackageReadHelper.DestroyAppxFileStream (hss);
							IntPtr hlpri = IntPtr.Zero, hspri = IntPtr.Zero;
						}
					} break;
			}
			#endregion
			return;
		}
#if DEBUG
		public PriReaderBundle PriInstance => m_priBundle;
#endif
		public PackageType Type
		{
			get
			{
				var value = PackageReadHelper.GetPackageType (m_hReader);
				switch (value)
				{
					case 0: return PackageType.Unknown;
					case 1: return PackageType.Appx;
					case 2: return PackageType.Bundle;
				}
				return PackageType.Unknown;
			}
		}
		public PackageRole Role
		{
			get
			{
				var value = PackageReadHelper.GetPackageRole (m_hReader);
				switch (value)
				{
					case 0: return PackageRole.Unknown;
					case 1: return PackageRole.Application;
					case 2: return PackageRole.Framework;
					case 3: return PackageRole.Resource;
				}
				return PackageRole.Unknown;
			}
		}
		public bool IsApplicationPackage { get { return Role == PackageRole.Application; } }
		public bool IsValid { get { return m_hReader != IntPtr.Zero && Type != PackageType.Unknown; } }
		/// <summary>使用 PRI，启用后会预先处理 PRI 文件。</summary>
		public bool UsePri { get { return m_usePRI; } set { m_usePRI = value; InitPri (); } }
		/// <summary>允许 PRI，启用后会返回读取的 PRI 文件结果，需保证 UsePri 开启。</summary>
		public bool EnablePri { get { return m_enablePRI; } set { m_enablePRI = value; } }
		public PRIdentity Identity { get { return new PRIdentity (ref m_hReader); } }
		public PRProperties Properties { get { return new PRProperties (ref m_hReader, this, ref m_priBundle, ref m_usePRI, ref m_enablePRI); } }
		public PRPrerequisites Prerequisites { get { return new PRPrerequisites (ref m_hReader); } }
		public PRResources Resources { get { return new PRResources (ref m_hReader); } }
		public PRApplications Applications { get { return new PRApplications (ref m_hReader, this, ref m_priBundle, ref m_usePRI, ref m_enablePRI); } }
		public PRCapabilities Capabilities { get { return new PRCapabilities (ref m_hReader); } }
		public PRDependencies Dependencies { get { return new PRDependencies (ref m_hReader); } }
		public void Dispose ()
		{
			if (m_hReader != IntPtr.Zero)
			{
				PackageReadHelper.DestroyPackageReader (m_hReader);
				m_hReader = IntPtr.Zero;
			}
			var lastvalue = m_usePRI;
			m_usePRI = false;
			InitPri ();
			m_usePRI = lastvalue;
		}
		~PackageReader () { Dispose (); }
		public string FilePath
		{
			get
			{
				return m_filePath;
			}
			set
			{
				PackageReadHelper.LoadPackageFromFile (m_hReader, m_filePath = value);
			}
		}
		public PackageReader (string filePath) { FilePath = filePath; }
		public PackageReader () { }
		public string JSONText { get { return BuildJsonText (); } }
		public string BuildJsonText ()
		{
			var obj = BuildJsonObject ();
			return Newtonsoft.Json.JsonConvert.SerializeObject (
				obj,
				Newtonsoft.Json.Formatting.Indented
			);
		}
		public void BuildJsonTextAsync (object callback)
		{
			if (callback == null) return;
			Thread thread = new Thread (() =>
			{
				string json = string.Empty;
				try
				{
					json = BuildJsonText ();
				}
				catch
				{
					json = string.Empty;
				}
				JSHelper.CallJS (callback, json);
			});
			thread.SetApartmentState (ApartmentState.MTA);
			thread.IsBackground = true;
			thread.Start ();
		}
		private object BuildJsonObject ()
		{
			return new
			{
				valid = IsValid,
				filepath = FilePath,
				type = (int)Type,
				role = (int)Role,
				identity = Identity.BuildJSON (),
				properties = Properties.BuildJSON (),
				prerequisites = Prerequisites.BuildJSON (),
				resources = Resources.BuildJSON (),
				capabilities = Capabilities.BuildJSON (),
				dependencies = Dependencies.BuildJSON (),
				applications = Applications.BuildJSON ()
			};
		}
		public static bool AddApplicationItem (string itemName) => PackageReadHelper.AddPackageApplicationItemGetName (itemName);
		public static bool RemoveApplicationItem (string itemName) => PackageReadHelper.RemovePackageApplicationItemGetName (itemName);
	}
}
