using Newtonsoft.Json;
using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace FileManager
{
	public class LongPath
	{
		//https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd

		public const int MaxDirectoryLength = MaxPathLength - 13;
		public const int MaxPathLength = short.MaxValue;
		public const int MaxFilenameLength = 255;

		private const int MAX_PATH = 260;
		private const string LONG_PATH_PREFIX = "\\\\?\\";

		public string Path { get; init; }
		public override string ToString() => Path;

		private static readonly PlatformID PlatformID = Environment.OSVersion.Platform;

	
		public static implicit operator LongPath(string path)
		{
			if (PlatformID is PlatformID.Unix) return new LongPath { Path = path };

			if (path is null) return null;

			//File I/O functions in the Windows API convert "/" to "\" as part of converting
			//the name to an NT-style name, except when using the "\\?\" prefix 
			path = path.Replace(System.IO.Path.AltDirectorySeparatorChar, System.IO.Path.DirectorySeparatorChar);

			if (path.StartsWith(LONG_PATH_PREFIX))
				return new LongPath { Path = path };
			else if ((path.Length > 2 && path[1] == ':') || path.StartsWith("UNC\\"))
				return new LongPath { Path = LONG_PATH_PREFIX + path };
			else if (path.StartsWith("\\\\"))
				//The "\\?\" prefix can also be used with paths constructed according to the
				//universal naming convention (UNC). To specify such a path using UNC, use
				//the "\\?\UNC\" prefix.
				return new LongPath { Path = LONG_PATH_PREFIX + "UNC\\" + path.Substring(2) };
			else
			{
				//These prefixes are not used as part of the path itself. They indicate that
				//the path should be passed to the system with minimal modification, which
				//means that you cannot use forward slashes to represent path separators, or
				//a period to represent the current directory, or double dots to represent the
				//parent directory. Because you cannot use the "\\?\" prefix with a relative
				//path, relative paths are always limited to a total of MAX_PATH characters.
				if (path.Length > MAX_PATH)
					throw new System.IO.PathTooLongException();
				return new LongPath { Path = path };
			}
		}

		public static implicit operator string(LongPath path) => path?.Path;

		[JsonIgnore]
		public string ShortPathName
		{
			get
			{
				if (PlatformID is PlatformID.Unix) return Path;

				//Short Path names are useful for navigating to the file in windows explorer,
				//which will not recognize paths longer than MAX_PATH. Short path names are not
				//always enabled on every volume. So to check if a volume enables short path
				//names (aka 8dot3 names), run the following command from an elevated command
				//prompt:
				//
				//	fsutil 8dot3name query c:
				//
				//It will say:
				//
				//	"Based on the above settings, 8dot3 name creation is [enabled/disabled] on c:"
				//
				//To enable short names on a volume on the system, run the following command
				//from an elevated command prompt:
				//
				//	fsutil 8dot3name set c: 0
				//
				//or for all volumes on the system:
				//
				//	fsutil 8dot3name set 0
				//
				//Note that after enabling 8dot3 names on a volume, they will only be available
				//for newly-created entries in ther file system. Existing entries made while
				//8dot3 names were disabled will not be reachable by short paths.

				if (Path is null) return null;

				StringBuilder shortPathBuffer = new(MaxPathLength);
				GetShortPathName(Path, shortPathBuffer, MaxPathLength);
				return shortPathBuffer.ToString();
			}
		}

		[JsonIgnore]
		public string LongPathName
		{
			get
			{
				if (PlatformID is PlatformID.Unix) return Path;
				if (Path is null) return null;

				StringBuilder longPathBuffer = new(MaxPathLength);
				GetLongPathName(Path, longPathBuffer, MaxPathLength);
				return longPathBuffer.ToString();
			}
		}

		[JsonIgnore]
		public string PathWithoutPrefix
		{
			get
			{
				if (PlatformID is PlatformID.Unix) return Path;
				return
					Path?.StartsWith(LONG_PATH_PREFIX) == true ? Path.Remove(0, LONG_PATH_PREFIX.Length)
					:Path;
			}
		}

		[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
		private static extern int GetShortPathName([MarshalAs(UnmanagedType.LPWStr)] string path, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder shortPath, int shortPathLength);

		[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
		private static extern int GetLongPathName([MarshalAs(UnmanagedType.LPWStr)] string lpszShortPath, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpszLongPath, int cchBuffer);

	}
}
