using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace DataUtils
{
	[ComVisible (true)]
	[ClassInterface (ClassInterfaceType.AutoDual)]
	public class _I_Path
	{
		public string Current
		{
			get
			{
				try { return Directory.GetCurrentDirectory (); }
				catch { return string.Empty; }
			}
			set
			{
				try
				{
					if (!string.IsNullOrEmpty (value)) Directory.SetCurrentDirectory (value);
				}
				catch { /* ignore */ }
			}
		}
		public string Program
		{
			get { return Utilities.GetCurrentProgramPath (); }
		}
		public string Root
		{
			get
			{
				try
				{
					string prog = Utilities.GetCurrentProgramPath ();
					return Path.GetDirectoryName (prog) ?? string.Empty;
				}
				catch { return string.Empty; }
			}
		}
		public string Combine (string l, string r)
		{
			if (string.IsNullOrEmpty (l)) return r ?? string.Empty;
			if (string.IsNullOrEmpty (r)) return l ?? string.Empty;
			try { return Path.Combine (l, r); }
			catch { return l + Path.DirectorySeparatorChar + r; }
		}
		public string GetName (string path)
		{
			if (string.IsNullOrEmpty (path)) return string.Empty;
			try
			{
				return Path.GetFileName (path);
			}
			catch { return string.Empty; }
		}
		public string GetDirectory (string path)
		{
			if (string.IsNullOrEmpty (path)) return string.Empty;
			try
			{
				return Path.GetDirectoryName (path) ?? string.Empty;
			}
			catch { return string.Empty; }
		}
		public string GetDir (string path) { return GetDirectory (path); }
		public bool Exist (string path)
		{
			if (string.IsNullOrEmpty (path)) return false;
			return File.Exists (path) || Directory.Exists (path);
		}
		public bool FileExist (string filepath)
		{
			if (string.IsNullOrEmpty (filepath)) return false;
			return File.Exists (filepath);
		}
		public bool DirectoryExist (string dirpath)
		{
			if (string.IsNullOrEmpty (dirpath)) return false;
			return Directory.Exists (dirpath);
		}
		public bool DirExist (string dirpath) { return DirectoryExist (dirpath); }
		public string GetEnvironmentString (string str)
		{
			if (string.IsNullOrEmpty (str)) return string.Empty;
			try
			{
				return Environment.ExpandEnvironmentVariables (str);
			}
			catch { return str; }
		}
		// Valid Windows filename?
		public bool ValidName (string filename)
		{
			if (string.IsNullOrEmpty (filename)) return false;
			char [] invalid = Path.GetInvalidFileNameChars ();
			return filename.IndexOfAny (invalid) < 0;
		}
		// filter may be e.g. "*.txt;*.md" or using "\" separators per legacy code
		public string EnumFilesToJson (string dir, string filter, bool withpath, bool sort, bool includesub)
		{
			var arr = EnumFiles (dir, filter, withpath, sort, includesub);
			return Utilities.StringArrayToJson (arr);
		}
		public string EnumDirsToJson (string dir, bool withpath, bool sort, bool includesub)
		{
			var arr = EnumDirs (dir, withpath, sort, includesub);
			return Utilities.StringArrayToJson (arr);
		}
		public string EnumSubDirsToJson (string dir, bool withpath)
		{
			var arr = EnumSubDirs (dir, withpath);
			return Utilities.StringArrayToJson (arr);
		}
		public string [] EnumFiles (string dir, string filter, bool withpath, bool sort, bool includesub)
		{
			if (string.IsNullOrEmpty (dir)) return new string [0];
			var patterns = Utilities.SplitFilters (filter);
			var list = new List<string> (100);

			try
			{
				var searchOption = includesub ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
				foreach (var pat in patterns)
				{
					try
					{
						foreach (var f in Directory.EnumerateFiles (dir, pat, searchOption))
						{
							list.Add (withpath ? f : Path.GetFileName (f));
						}
					}
					catch (UnauthorizedAccessException) { /* skip */ }
					catch (DirectoryNotFoundException) { /* skip */ }
					catch (IOException) { /* skip */ }
				}

				if (sort)
				{
					list.Sort (StringComparer.OrdinalIgnoreCase);
				}
			}
			catch
			{
				// fallback: empty
			}

			return list.ToArray ();
		}
		public string [] EnumDirs (string dir, bool withpath, bool sort, bool includesub)
		{
			if (string.IsNullOrEmpty (dir)) return new string [0];
			var list = new List<string> (100);
			try
			{
				var searchOption = includesub ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
				foreach (var d in Directory.EnumerateDirectories (dir, "*", searchOption))
				{
					list.Add (withpath ? d : Path.GetFileName (d));
				}
				if (sort) list.Sort (StringComparer.OrdinalIgnoreCase);
			}
			catch { }
			return list.ToArray ();
		}
		public string [] EnumSubDirs (string dir, bool withpath)
		{
			return EnumDirs (dir, withpath, true, true);
		}
		public string CommonPrefix (string path1, string path2)
		{
			if (string.IsNullOrEmpty (path1) || string.IsNullOrEmpty (path2)) return string.Empty;
			try
			{
				string a = Utilities.NormalizeFullPath (path1);
				string b = Utilities.NormalizeFullPath (path2);
				string [] asplit = a.Split (new char [] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
				string [] bsplit = b.Split (new char [] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
				int min = Math.Min (asplit.Length, bsplit.Length);
				var sb = new StringBuilder ();
				for (int i = 0; i < min; i++)
				{
					if (!string.Equals (asplit [i], bsplit [i], StringComparison.OrdinalIgnoreCase)) break;
					sb.Append (asplit [i]);
					sb.Append (Path.DirectorySeparatorChar);
				}
				return sb.ToString ().TrimEnd (Path.DirectorySeparatorChar);
			}
			catch { return string.Empty; }
		}
		public string EnsureDirSlash (string dir)
		{
			if (string.IsNullOrEmpty (dir)) return string.Empty;
			try
			{
				if (!dir.EndsWith (Path.DirectorySeparatorChar.ToString ()))
					dir += Path.DirectorySeparatorChar;
				return dir;
			}
			catch { return dir; }
		}
		public string Normalize (string path)
		{
			if (string.IsNullOrEmpty (path)) return string.Empty;
			try { return Path.GetFullPath (path); }
			catch { return path; }
		}
		public string FullPathName (string path) { return Normalize (path); }
		public string FullPath (string path) { return FullPathName (path); }
		public string Expand (string path) { return GetEnvironmentString (path); }
		// GetFolder via SHGetFolderPath (preserves the original csidl param usage)
		public string GetFolder (int csidl)
		{
			try
			{
				// try P/Invoke to SHGetFolderPath
				return ShellHelpers.GetFolderPath (csidl);
			}
			catch
			{
				return string.Empty;
			}
		}
		// KnownFolder by GUID string (wraps SHGetKnownFolderPath)
		public string KnownFolder (string guidString)
		{
			if (string.IsNullOrWhiteSpace (guidString)) return string.Empty;
			Guid guid;
			try
			{
				guid = new Guid (guidString);
			}
			catch
			{
				return string.Empty;
			}

			try
			{
				return ShellHelpers.GetKnownFolderPath (guid);
			}
			catch
			{
				return string.Empty;
			}
		}
		public bool PEquals (string l, string r)
		{
			if (l == null && r == null) return true;
			if (l == null || r == null) return false;
			string a = Utilities.NormalizeFullPath (l);
			string b = Utilities.NormalizeFullPath (r);
			return string.Equals (a, b, StringComparison.OrdinalIgnoreCase);
		}
		public bool Open (string path)
		{
			if (string.IsNullOrEmpty (path)) return false;
			try
			{
				if (File.Exists (path))
				{
					Process.Start (path);
					return true;
				}

				if (Directory.Exists (path))
				{
					Process.Start ("explorer.exe", path);
					return true;
				}
			}
			catch
			{
			}
			return false;
		}
	}
	// Basic entry object
	[ComVisible (true)]
	[ClassInterface (ClassInterfaceType.AutoDual)]
	public class _I_Entry
	{
		protected string path;
		public _I_Entry (string path)
		{
			this.path = path ?? string.Empty;
		}
		public _I_Entry ()
		{
			this.path = string.Empty;
		}
		public virtual string Path
		{
			get { return path; }
			set { path = value ?? string.Empty; }
		}
		public virtual string Name
		{
			get
			{
				try
				{
					return System.IO.Path.GetFileName (path) ?? string.Empty;
				}
				catch { return string.Empty; }
			}
		}
		public virtual string Directory
		{
			get
			{
				try { return System.IO.Path.GetDirectoryName (path) ?? string.Empty; }
				catch { return string.Empty; }
			}
		}
		public virtual string Root { get { return Directory; } }
		public virtual bool Exist
		{
			get
			{
				return File.Exists (path) || System.IO.Directory.Exists (path);
			}
		}
		public virtual string Uri
		{
			get
			{
				try
				{
					Uri uri;
					if (System.Uri.TryCreate (path, UriKind.Absolute, out uri))
					{
						return uri.AbsoluteUri;
					}
					else
					{
						Uri u = new Uri (System.IO.Path.GetFullPath (path));
						return u.AbsoluteUri;
					}
				}
				catch
				{
					return string.Empty;
				}
			}
		}
		public virtual string FullPath
		{
			get
			{
				try { return System.IO.Path.GetFullPath (path); }
				catch { return path; }
			}
		}

		// Return relative path from frontdir to this.Path; similar semantics to C++ code
		public string RelativePath (string frontdir)
		{
			if (string.IsNullOrEmpty (path) || string.IsNullOrEmpty (frontdir)) return string.Empty;
			try
			{
				string fullFile = System.IO.Path.GetFullPath (path);
				string fullDir = System.IO.Path.GetFullPath (frontdir);
				if (!fullDir.EndsWith (System.IO.Path.DirectorySeparatorChar.ToString ()))
					fullDir += System.IO.Path.DirectorySeparatorChar;

				if (!string.Equals (System.IO.Path.GetPathRoot (fullFile), System.IO.Path.GetPathRoot (fullDir), StringComparison.OrdinalIgnoreCase))
					return string.Empty;

				if (!fullFile.StartsWith (fullDir, StringComparison.OrdinalIgnoreCase)) return string.Empty;

				return fullFile.Substring (fullDir.Length);
			}
			catch
			{
				return string.Empty;
			}
		}
	}
	[ComVisible (true)]
	[ClassInterface (ClassInterfaceType.AutoDual)]
	public class _I_File: _I_Entry
	{
		// last encoding used when reading
		protected Encoding lastEncoding;
		public _I_File () : base (string.Empty) { }
		public _I_File (string filepath) : base (filepath) { }
		// Read file contents; detect BOM if present by using StreamReader with detectEncodingFromByteOrderMarks = true
		public string Get ()
		{
			if (string.IsNullOrEmpty (path)) return string.Empty;
			try
			{
				using (FileStream fs = new FileStream (path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
				using (StreamReader sr = new StreamReader (fs, Encoding.UTF8, true))
				{
					string text = sr.ReadToEnd ();
					lastEncoding = sr.CurrentEncoding;
					return text;
				}
			}
			catch
			{
				return null;
			}
		}
		public void Set (string content)
		{
			if (string.IsNullOrEmpty (path)) return;
			try
			{
				string dir = System.IO.Path.GetDirectoryName (path);
				if (!string.IsNullOrEmpty (dir) && !System.IO.Directory.Exists (dir))
				{
					System.IO.Directory.CreateDirectory (dir);
				}
				Encoding enc = lastEncoding ?? Encoding.UTF8;
				using (FileStream fs = new FileStream (path, FileMode.Create, FileAccess.Write, FileShare.Read))
				using (StreamWriter sw = new StreamWriter (fs, enc))
				{
					sw.Write (content ?? string.Empty);
					sw.Flush ();
				}
			}
			catch
			{
				// ignore write errors
			}
		}
		public string Content
		{
			get { return Get (); }
			set { Set (value); }
		}
		public override bool Exist
		{
			get { return File.Exists (path); }
		}
		public string FilePath
		{
			get { return this.Path; }
			set { this.Path = value; }
		}
	}
	[ComVisible (true)]
	[ClassInterface (ClassInterfaceType.AutoDual)]
	public class _I_Directory: _I_Entry
	{
		public _I_Directory () : base (string.Empty) { }
		public _I_Directory (string dirpath) : base (dirpath) { }
		public _I_Directory (_I_Entry file) : base (file != null ? file.Directory : string.Empty) { }

		public string DirectoryPath
		{
			get { return this.Path; }
			set { this.Path = value; }
		}
		public string DirPath { get { return DirectoryPath; } set { DirectoryPath = value; } }
		public override bool Exist { get { return System.IO.Directory.Exists (path); } }
		public string EnumFilesToJson (string filter, bool withpath, bool sort, bool includesub)
		{
			_I_Path p = new _I_Path ();
			string [] arr = p.EnumFiles (DirPath, filter, withpath, sort, includesub);
			return Utilities.StringArrayToJson (arr);
		}
		public string EnumDirsToJson (bool withpath, bool sort, bool includesub)
		{
			_I_Path p = new _I_Path ();
			string [] arr = p.EnumDirs (DirPath, withpath, sort, includesub);
			return Utilities.StringArrayToJson (arr);
		}
		public string EnumSubDirsToJson (bool withpath)
		{
			_I_Path p = new _I_Path ();
			string [] arr = p.EnumSubDirs (DirPath, withpath);
			return Utilities.StringArrayToJson (arr);
		}
		public string [] EnumFiles (string filter, bool withpath, bool sort, bool includesub)
		{
			_I_Path p = new _I_Path ();
			return p.EnumFiles (DirPath, filter, withpath, sort, includesub);
		}
		public string [] EnumDirs (bool withpath, bool sort, bool includesub)
		{
			_I_Path p = new _I_Path ();
			return p.EnumDirs (DirPath, withpath, sort, includesub);
		}
		public string [] EnumSubDirs (bool withpath)
		{
			_I_Path p = new _I_Path ();
			return p.EnumSubDirs (DirPath, withpath);
		}
	}
	[ComVisible (true)]
	[ClassInterface (ClassInterfaceType.AutoDual)]
	public class _I_Explorer
	{
		[DllImport ("user32.dll")]
		private static extern IntPtr GetForegroundWindow ();
		class WindowWrapper: IWin32Window
		{
			private IntPtr _hwnd;
			public WindowWrapper (IntPtr handle) { _hwnd = handle; }
			public IntPtr Handle { get { return _hwnd; } }
		}
		private static IWin32Window GetActiveWindowOwner ()
		{
			IntPtr hWnd = GetForegroundWindow ();
			return hWnd != IntPtr.Zero ? new WindowWrapper (hWnd) : null;
		}
		private static void CallJS (object jsFunc, params object [] args)
		{
			if (jsFunc == null) return;
			try
			{
				object [] realArgs = new object [args.Length + 1];
				realArgs [0] = jsFunc; // thisArg
				Array.Copy (args, 0, realArgs, 1, args.Length);
				jsFunc.GetType ().InvokeMember (
					"call",
					System.Reflection.BindingFlags.InvokeMethod,
					null,
					jsFunc,
					realArgs
				);
			}
			catch { }
		}
		public void File (string filter, string initDir, object jsCallback)
		{
			IWin32Window owner = GetActiveWindowOwner ();
			Thread t = new Thread (() =>
			{
				string result = string.Empty;
				try
				{
					using (OpenFileDialog dlg = new OpenFileDialog ())
					{
						dlg.Filter = filter;
						dlg.InitialDirectory = string.IsNullOrEmpty (initDir)
							? Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments)
							: initDir;
						dlg.Multiselect = false;
						if (dlg.ShowDialog (owner) == DialogResult.OK)
							result = dlg.FileName;
					}
				}
				catch { }
				CallJS (jsCallback, result);
			});
			t.IsBackground = true;
			t.SetApartmentState (ApartmentState.STA);
			t.Start ();
		}
		public void Files (string filter, string initDir, object jsCallback)
		{
			IWin32Window owner = GetActiveWindowOwner ();
			Thread t = new Thread (() =>
			{
				string result = "[]";
				try
				{
					using (OpenFileDialog dlg = new OpenFileDialog ())
					{
						dlg.Filter = filter;
						dlg.InitialDirectory = string.IsNullOrEmpty (initDir)
							? Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments)
							: initDir;
						dlg.Multiselect = true;
						if (dlg.ShowDialog (owner) == DialogResult.OK)
							result = Newtonsoft.Json.JsonConvert.SerializeObject (dlg.FileNames);
					}
				}
				catch { }
				CallJS (jsCallback, result);
			});
			t.IsBackground = true;
			t.SetApartmentState (ApartmentState.STA);
			t.Start ();
		}
		public void Dir (string initDir, object jsCallback)
		{
			IWin32Window owner = GetActiveWindowOwner ();
			Thread t = new Thread (() =>
			{
				string result = string.Empty;
				try
				{
					using (FolderBrowserDialog dlg = new FolderBrowserDialog ())
					{
						dlg.SelectedPath = string.IsNullOrEmpty (initDir)
							? Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments)
							: initDir;
						if (dlg.ShowDialog (owner) == DialogResult.OK)
							result = dlg.SelectedPath;
					}
				}
				catch { }

				CallJS (jsCallback, result);
			});
			t.IsBackground = true;
			t.SetApartmentState (ApartmentState.STA);
			t.Start ();
		}
		public void Dirs (string initDir, object jsCallback)
		{
			IWin32Window owner = GetActiveWindowOwner ();
			Thread t = new Thread (() =>
			{
				string result = "[]";
				try
				{
					using (var dlg = new OpenFileDialog ())
					{
						// trick: 多选文件夹
						dlg.ValidateNames = false;
						dlg.CheckFileExists = false;
						dlg.CheckPathExists = true;
						dlg.FileName = "SelectFolder";
						dlg.InitialDirectory = string.IsNullOrEmpty (initDir)
							? Environment.GetFolderPath (Environment.SpecialFolder.MyDocuments)
							: initDir;
						dlg.Multiselect = true;
						if (dlg.ShowDialog (owner) == DialogResult.OK)
						{
							var dirs = dlg.FileNames.Select (f => Path.GetDirectoryName (f)).Distinct ().ToArray ();
							result = Newtonsoft.Json.JsonConvert.SerializeObject (dirs);
						}
					}
				}
				catch { }
				CallJS (jsCallback, result);
			});
			t.IsBackground = true;
			t.SetApartmentState (ApartmentState.STA);
			t.Start ();
		}
	}
	[ComVisible (true)]
	[ClassInterface (ClassInterfaceType.AutoDual)]
	public class _I_Storage
	{
		private static void CallJS (object jsFunc, params object [] args)
		{
			if (jsFunc == null) return;
			try
			{
				// 这里固定第一个参数为 thisArg（比如 1）
				object [] realArgs = new object [args.Length + 1];
				realArgs [0] = jsFunc;     // thisArg
				Array.Copy (args, 0, realArgs, 1, args.Length);

				jsFunc.GetType ().InvokeMember (
					"call",
					BindingFlags.InvokeMethod,
					null,
					jsFunc,
					realArgs
				);
			}
			catch
			{
				// ignore errors in callback invocation
			}
		}
		protected _I_Path path = new _I_Path ();
		public _I_Path Path { get { return path; } }
		public _I_File GetFile (string path) { return new _I_File (path); }
		public _I_Directory GetDirectory (string path) { return new _I_Directory (path); }
		public _I_Directory GetDir (string path) { return GetDirectory (path); }
		public _I_Explorer Explorer => new _I_Explorer ();
	}
	// Small shell helpers that P/Invoke for folder retrieval using CSIDL or Known Folder GUIDs
	internal static class ShellHelpers
	{
		[System.Runtime.InteropServices.DllImport ("shell32.dll")]
		private static extern int SHGetFolderPathW (IntPtr hwndOwner, int nFolder, IntPtr hToken, uint dwFlags, [System.Runtime.InteropServices.MarshalAs (System.Runtime.InteropServices.UnmanagedType.LPWStr)] StringBuilder pszPath);
		public static string GetFolderPath (int csidl)
		{
			StringBuilder sb = new StringBuilder (260);
			int hr = SHGetFolderPathW (IntPtr.Zero, csidl, IntPtr.Zero, 0, sb);
			if (hr == 0) return sb.ToString ();
			return string.Empty;
		}
		[System.Runtime.InteropServices.DllImport ("shell32.dll")]
		private static extern int SHGetKnownFolderPath ([System.Runtime.InteropServices.MarshalAs (System.Runtime.InteropServices.UnmanagedType.LPStruct)] Guid rfid, uint dwFlags, IntPtr hToken, out IntPtr ppszPath);
		[System.Runtime.InteropServices.DllImport ("ole32.dll")]
		private static extern void CoTaskMemFree (IntPtr pv);
		public static string GetKnownFolderPath (Guid guid)
		{
			IntPtr pathPtr;
			int hr = SHGetKnownFolderPath (guid, 0, IntPtr.Zero, out pathPtr);
			if (hr != 0 || pathPtr == IntPtr.Zero) return string.Empty;
			try
			{
				string path = Marshal.PtrToStringUni (pathPtr);
				return path ?? string.Empty;
			}
			finally
			{
				if (pathPtr != IntPtr.Zero) CoTaskMemFree (pathPtr);
			}
		}
	}
}
