using Flowframes.Data;
using Flowframes.Magick;
using Flowframes.Main;
using Flowframes.Media;
using Flowframes.MiscUtils;
using Flowframes.UI;
using Force.Crc32;
using ImageMagick;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Flowframes.IO
{
    class IOUtils
    {
		public static Image GetImage(string path)
		{
            try
            {
				using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read))
					return Image.FromStream(stream);
			}
			catch
            {
                try
                {
					MagickImage img = new MagickImage(path);
					Bitmap bitmap = img.ToBitmap();
					Logger.Log($"GetImage: Native image reading for '{Path.GetFileName(path)}' failed - Using Magick.NET fallback instead.", true);
					return bitmap;
				}
				catch (Exception e)
                {
					Logger.Log($"GetImage failed: {e.Message}", true);
					return null;
                }
            }
		}

		public static string[] ReadLines(string path)
		{
			List<string> lines = new List<string>();
			using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 0x1000, FileOptions.SequentialScan))
			
            using (var reader = new StreamReader(stream, Encoding.UTF8))
			{
				string line;
				while ((line = reader.ReadLine()) != null)
					lines.Add(line);
			}

			return lines.ToArray();
		}

		public static bool IsPathDirectory(string path)
		{
			if (path == null)
				throw new ArgumentNullException("path");

            path = path.Trim();

			if (Directory.Exists(path))
				return true;

            if (File.Exists(path))
				return false;

            if (new string[2] {"\\", "/"}.Any((string x) => path.EndsWith(x)))
				return true;

            return string.IsNullOrWhiteSpace(Path.GetExtension(path));
		}

		public static bool IsFileValid(string path)
		{
			if (path == null)
				return false;

			if (!File.Exists(path))
				return false;

			return true;
		}

		public static bool IsDirValid(string path)
		{
			if (path == null)
				return false;

			if (!Directory.Exists(path))
				return false;

			return true;
		}

		public static bool IsPathValid(string path)
		{
			if (path == null)
				return false;

			if (IsPathDirectory(path))
				return IsDirValid(path);
			else
				return IsFileValid(path);
		}

		public static void CopyDir(string sourceDirectoryName, string targetDirectoryName, bool move = false)
		{
			Directory.CreateDirectory(targetDirectoryName);
			DirectoryInfo source = new DirectoryInfo(sourceDirectoryName);
			DirectoryInfo target = new DirectoryInfo(targetDirectoryName);
			CopyWork(source, target, move);
		}

		private static void CopyWork(DirectoryInfo source, DirectoryInfo target, bool move)
		{
			DirectoryInfo[] directories = source.GetDirectories();
			foreach (DirectoryInfo directoryInfo in directories)
			{
				CopyWork(directoryInfo, target.CreateSubdirectory(directoryInfo.Name), move);
			}
			FileInfo[] files = source.GetFiles();
			foreach (FileInfo fileInfo in files)
			{
				if (move)
					fileInfo.MoveTo(Path.Combine(target.FullName, fileInfo.Name));
				else
					fileInfo.CopyTo(Path.Combine(target.FullName, fileInfo.Name), overwrite: true);
			}
		}

		/// <summary>
		/// Async version of DeleteContentsOfDir, won't block main thread.
		/// </summary>
		public static async Task<bool> DeleteContentsOfDirAsync(string path)
		{
			ulong taskId = BackgroundTaskManager.Add($"DeleteContentsOfDirAsync {path}");
			bool returnVal = await Task.Run(async () => { return DeleteContentsOfDir(path); });
			BackgroundTaskManager.Remove(taskId);
			return returnVal;
		}

		/// <summary>
		/// Delete everything inside a directory except the dir itself.
		/// </summary>
		public static bool DeleteContentsOfDir(string path)
		{
            try
            {
				DeleteIfExists(path);
				Directory.CreateDirectory(path);
				return true;
			}
			catch(Exception e)
            {
				Logger.Log("DeleteContentsOfDir Error: " + e.Message, true);
				return false;
            }
		}

		public static void ReplaceInFilenamesDir(string dir, string textToFind, string textToReplace, bool recursive = true, string wildcard = "*")
		{
			int counter = 1;
			DirectoryInfo d = new DirectoryInfo(dir);
			FileInfo[] files;

			if (recursive)
				files = d.GetFiles(wildcard, SearchOption.AllDirectories);
			else
				files = d.GetFiles(wildcard, SearchOption.TopDirectoryOnly);

			foreach (FileInfo file in files)
			{
				ReplaceInFilename(file.FullName, textToFind, textToReplace);
				counter++;
			}
		}

		public static void ReplaceInFilename(string path, string textToFind, string textToReplace)
		{
			string ext = Path.GetExtension(path);
			string newFilename = Path.GetFileNameWithoutExtension(path).Replace(textToFind, textToReplace);
			string targetPath = Path.Combine(Path.GetDirectoryName(path), newFilename + ext);
			if (File.Exists(targetPath))
			{
				//Program.Print("Skipped " + path + " because a file with the target name already exists.");
				return;
			}
			File.Move(path, targetPath);
		}

        public static int GetAmountOfFiles (string path, bool recursive, string wildcard = "*")
        {
            try
            {
                DirectoryInfo d = new DirectoryInfo(path);
				FileInfo[] files = null;

				if (recursive)
					files = d.GetFiles(wildcard, SearchOption.AllDirectories);
				else
					files = d.GetFiles(wildcard, SearchOption.TopDirectoryOnly);

				return files.Length;
			}
			catch
            {
				return 0;
            }
		}

		static bool TryCopy(string source, string target, bool overwrite = true, bool showLog = false)
		{
			try
			{
				File.Copy(source, target, overwrite);
			}
			catch (Exception e)
			{
				if(showLog)
				    Logger.Log($"Failed to move '{source}' to '{target}' (Overwrite: {overwrite}): {e.Message}, !showLog");

				return false;
			}

			return true;
		}

		public static bool TryMove(string source, string target, bool overwrite = true, bool showLog = false)
		{
			try
			{
				if (overwrite && File.Exists(target))
					File.Delete(target);

				File.Move(source, target);
			}
			catch (Exception e)
			{
				Logger.Log($"Failed to move '{source}' to '{target}' (Overwrite: {overwrite}): {e.Message}", !showLog);
				return false;
			}

			return true;
		}

		public static async Task RenameCounterDir(string path, int startAt = 0, int zPad = 8, bool inverse = false)
		{
            Stopwatch sw = new Stopwatch();
            sw.Restart();
			int counter = startAt;
			DirectoryInfo d = new DirectoryInfo(path);
			FileInfo[] files = d.GetFiles();
			var filesSorted = files.OrderBy(n => n);

			if (inverse)
				filesSorted.Reverse();

			foreach (FileInfo file in files)
			{
				string dir = new DirectoryInfo(file.FullName).Parent.FullName;
				File.Move(file.FullName, Path.Combine(dir, counter.ToString().PadLeft(zPad, '0') + Path.GetExtension(file.FullName)));
				counter++;

                if (sw.ElapsedMilliseconds > 100)
                {
                    await Task.Delay(1);
                    sw.Restart();
                }
			}
		}

		public static async Task ReverseRenaming(string basePath, Dictionary<string, string> oldNewMap)	// Relative -> absolute paths
		{
			Dictionary<string, string> absPaths = oldNewMap.ToDictionary(x => Path.Combine(basePath, x.Key), x => Path.Combine(basePath, x.Value));
			await ReverseRenaming(absPaths);
		}

		public static async Task ReverseRenaming(Dictionary<string, string> oldNewMap)	// Takes absolute paths only
		{
			if (oldNewMap == null || oldNewMap.Count < 1) return;
			int counter = 0;
			int failCount = 0;

			foreach (KeyValuePair<string, string> pair in oldNewMap)
            {
				bool success = TryMove(pair.Value, pair.Key);

				if (!success)
					failCount++;

				if (failCount >= 100)
					break;

				counter++;

				if (counter % 1000 == 0)
					await Task.Delay(1);
			}
		}

		public static async Task<Fraction> GetVideoFramerate (string path)
        {
			string[] preferFfmpegReadoutFormats = new string[] { ".gif", ".png", ".apng", ".webp" };
			bool preferFfmpegReadout = preferFfmpegReadoutFormats.Contains(Path.GetExtension(path).ToLower());

            Fraction fps = new Fraction();

			try
			{
				fps = await FfmpegCommands.GetFramerate(path, preferFfmpegReadout);
				Logger.Log("Detected FPS of " + Path.GetFileName(path) + " as " + fps + " FPS", true);
			}
			catch
            {
				Logger.Log("Failed to read FPS - Please enter it manually.");
			}

			return fps;
		}

		public static Fraction GetVideoFramerateForDir(string path)
		{
            Fraction fps = new Fraction();

            try
            {
				string parentDir = path.GetParentDir();
				string fpsFile = Path.Combine(parentDir, "fps.ini");
                fps = new Fraction(float.Parse(ReadLines(fpsFile)[0]));
				Logger.Log($"Got {fps} FPS from file: " + fpsFile);

				Fraction guiFps = Program.mainForm.GetCurrentSettings().inFps;

				DialogResult dialogResult = MessageBox.Show("A frame rate file has been found in the parent directory.\n\n" +
					$"Click \"Yes\" to use frame rate from the file ({fps}) or \"No\" to use current FPS set in GUI ({guiFps})", "Load Frame Rate From fps.ini?", MessageBoxButtons.YesNo);
				if (dialogResult == DialogResult.Yes)
					return fps;
				else if (dialogResult == DialogResult.No)
					return guiFps;
			}
			catch { }
			return fps;
		}

		public static async Task<Size> GetVideoOrFramesRes (string path)
        {
			Size res = new Size();

            try
            {
				if (!IsPathDirectory(path))     // If path is video
				{
					res = GetVideoRes(path);
				}
				else     // Path is frame folder
				{
					Image thumb = await MainUiFunctions.GetThumbnail(path);
					res = new Size(thumb.Width, thumb.Height);
				}
			}
			catch (Exception e)
            {
				Logger.Log("GetVideoOrFramesRes Error: " + e.Message);
            }

			return res;
		}

		public static Size GetVideoRes (string path)
		{
			Size size = new Size(0, 0);

			try
			{
				size = FfmpegCommands.GetSize(path);
				Logger.Log($"Detected video size of {Path.GetFileName(path)} as {size.Width}x{size.Height}", true);
			}
			catch
			{
				Logger.Log("Failed to read video size!");
			}

			return size;
		}

		/// <summary>
		/// Async (background thread) version of TryDeleteIfExists. Safe to run without awaiting.
		/// </summary>
		public static async Task<bool> TryDeleteIfExistsAsync(string path)
		{
			string renamedPath = path;

			if (IsPathDirectory(path))
            {
				while (Directory.Exists(renamedPath))
					renamedPath += "_";

				if (path != renamedPath)
					Directory.Move(path, renamedPath);
			}
            else
            {
				while (File.Exists(renamedPath))
					renamedPath += "_";

				if (path != renamedPath)
					File.Move(path, renamedPath);
			}

			path = renamedPath;

			ulong taskId = BackgroundTaskManager.Add($"TryDeleteIfExistsAsync {path}");
			bool returnVal = await Task.Run(async () => { return TryDeleteIfExists(path); });
			BackgroundTaskManager.Remove(taskId);
			return returnVal;
		}

		/// <summary>
		/// Delete a path if it exists. Works for files and directories. Returns success status.
		/// </summary>
		public static bool TryDeleteIfExists(string path)      // Returns true if no exception occurs
		{
            try
            {
				if (path == null)
					return false;

				DeleteIfExists(path);
				return true;
			}
			catch (Exception e)
            {
				Logger.Log($"TryDeleteIfExists: Error trying to delete {path}: {e.Message}", true);
				return false;
            }
		}

		public static bool DeleteIfExists (string path)		// Returns true if the file/dir exists
        {
			Logger.Log($"DeleteIfExists({path})", true);

			if (!IsPathDirectory(path) && File.Exists(path))
            {
				File.Delete(path);
				return true;
            }

			if (IsPathDirectory(path) && Directory.Exists(path))
			{
				Directory.Delete(path, true);
				return true;
			}

			return false;
        }

		/// <summary>
		/// Add ".old" suffix to an existing file to avoid it getting overwritten. If one already exists, it will be ".old.old" etc.
		/// </summary>
		public static void RenameExistingFile(string path)
		{
			if (!File.Exists(path))
				return;

            try
            {
				string ext = Path.GetExtension(path);
				string renamedPath = path;

				while (File.Exists(renamedPath))
					renamedPath = Path.ChangeExtension(renamedPath, null) + ".old" + ext;

				File.Move(path, renamedPath);
			}
			catch(Exception e)
            {
				Logger.Log($"RenameExistingFile: Failed to rename '{path}': {e.Message}", true);
			}
		}

		/// <summary>
		/// Add ".old" suffix to an existing folder to avoid it getting overwritten. If one already exists, it will be ".old.old" etc.
		/// </summary>
		public static void RenameExistingFolder(string path)
		{
			if (!Directory.Exists(path))
				return;

			try
			{
				string renamedPath = path;

				while (Directory.Exists(renamedPath))
					renamedPath += ".old";

				Directory.Move(path, renamedPath);
			}
			catch (Exception e)
			{
				Logger.Log($"RenameExistingFolder: Failed to rename '{path}': {e.Message}", true);
			}
		}

		/// <summary>
		/// Easily rename a file without needing to specify the full move path
		/// </summary>
		public static bool RenameFile (string path, string newName, bool alsoRenameExtension = false)
        {
            try
            {
				string dir = Path.GetDirectoryName(path);
				string ext = Path.GetExtension(path);
				string movePath = Path.Combine(dir, newName);

				if (!alsoRenameExtension)
					movePath += ext;

				File.Move(path, movePath);
				return true;
            }
			catch(Exception e)
            {
				Logger.Log($"Failed to rename '{path}' to '{newName}': {e.Message}", true);
				return false;
            }
        }

		public static async Task<string> GetCurrentExportFilename(bool fpsLimit, bool withExt)
		{
			InterpSettings curr = Interpolate.current;
			string max = Config.Get(Config.Key.maxFps);
			Fraction maxFps = max.Contains("/") ? new Fraction(max) : new Fraction(max.GetFloat());
			float fps = fpsLimit ? maxFps.GetFloat() : curr.outFps.GetFloat();

            if (curr.outMode == Interpolate.OutMode.VidGif && fps > 50f)
                fps = 50f;

            Size outRes = await InterpolateUtils.GetOutputResolution(curr.inPath, false, false);
			string pattern = Config.Get(Config.Key.exportNamePattern);
			string inName = Interpolate.current.inputIsFrames ? Path.GetFileName(curr.inPath) : Path.GetFileNameWithoutExtension(curr.inPath);
            bool encodeBoth = Config.GetInt(Config.Key.maxFpsMode) == 0;
			bool addSuffix = fpsLimit && (!pattern.Contains("[FPS]") && !pattern.Contains("[ROUNDFPS]")) && encodeBoth;
			string filename = pattern;

			filename = filename.Replace("[NAME]", inName);
			filename = filename.Replace("[FULLNAME]", Path.GetFileName(curr.inPath));
            filename = filename.Replace("[FACTOR]", curr.interpFactor.ToStringDot());
			filename = filename.Replace("[AI]", curr.ai.aiNameShort.ToUpper());
			filename = filename.Replace("[MODEL]", curr.model.name.Remove(" "));
			filename = filename.Replace("[FPS]", fps.ToStringDot());
            filename = filename.Replace("[ROUNDFPS]", fps.RoundToInt().ToString());
			filename = filename.Replace("[RES]", $"{outRes.Width}x{outRes.Height}");
			filename = filename.Replace("[H]", $"{outRes.Height}p");

            if (addSuffix)
				filename += Paths.fpsLimitSuffix;

			if (withExt)
				filename += FFmpegUtils.GetExt(curr.outMode);

			return filename;
		}

		public static string GetHighestFrameNumPath (string path)
        {
			FileInfo highest = null;
			int highestInt = -1;
			foreach(FileInfo frame in new DirectoryInfo(path).GetFiles("*.*", SearchOption.TopDirectoryOnly))
            {
				int num = frame.Name.GetInt();
				if (num > highestInt)
                {
					highest = frame;
					highestInt = frame.Name.GetInt();
				}
            }
			return highest.FullName;
        }

		public static string FilenameSuffix (string path, string suffix)
        {
            try
            {
				string ext = Path.GetExtension(path);
				return Path.Combine(path.GetParentDir(), $"{Path.GetFileNameWithoutExtension(path)}{suffix}{ext}");
			}
            catch
            {
				return path;
            }
		}

		public static async Task<Fraction> GetFpsFolderOrVideo(string path)
		{
            try
            {
				if (IsPathDirectory(path))
				{
                    Fraction dirFps = GetVideoFramerateForDir(path);

					if (dirFps.GetFloat() > 0)
						return dirFps;
				}
				else
				{
                    Fraction vidFps = await GetVideoFramerate(path);

					if (vidFps.GetFloat() > 0)
						return vidFps;
				}
			}
			catch (Exception e)
            {
				Logger.Log("GetFpsFolderOrVideo() Error: " + e.Message);
            }

			return new Fraction();
		}

		public enum ErrorMode { HiddenLog, VisibleLog, Messagebox }
		public static bool CanWriteToDir (string dir, ErrorMode errMode)
        {
			string tempFile = Path.Combine(dir, "flowframes-testfile.tmp");
            try
            {
				File.Create(tempFile);
				File.Delete(tempFile);
				return true;
			}
			catch (Exception e)
            {
				Logger.Log($"Can't write to {dir}! {e.Message}", errMode == ErrorMode.HiddenLog);
				if (errMode == ErrorMode.Messagebox && !BatchProcessing.busy)
					MessageBox.Show($"Can't write to {dir}!\n\n{e.Message}", "Error");
				return false;
            }
        }

		public static bool CopyTo (string file, string targetFolder, bool overwrite = true)
        {
			string targetPath = Path.Combine(targetFolder, Path.GetFileName(file));
            try
            {
				if (!Directory.Exists(targetFolder))
					Directory.CreateDirectory(targetFolder);
				File.Copy(file, targetPath, overwrite);
				return true;
            }
			catch (Exception e)
            {
				Logger.Log($"Failed to copy {file} to {targetFolder}: {e.Message}");
				return false;
            }
        }

		public static bool MoveTo(string file, string targetFolder, bool overwrite = true)
		{
			string targetPath = Path.Combine(targetFolder, Path.GetFileName(file));
			try
			{
				if (!Directory.Exists(targetFolder))
					Directory.CreateDirectory(targetFolder);
				if (overwrite)
					DeleteIfExists(targetPath);
				File.Move(file, targetPath);
				return true;
			}
			catch (Exception e)
			{
				Logger.Log($"Failed to move {file} to {targetFolder}: {e.Message}");
				return false;
			}
		}

		public enum Hash { MD5, CRC32 }
		public static string GetHash (string path, Hash hashType, bool log = true)
		{
			string hashStr = "";

            if (IsPathDirectory(path))
            {
				Logger.Log($"Path '{path}' is directory! Returning empty hash.", true);
				return hashStr;
            }
            try
            {
				var stream = File.OpenRead(path);

				if (hashType == Hash.MD5)
				{
					MD5 md5 = MD5.Create();
					var hash = md5.ComputeHash(stream);
					hashStr = BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
				}

				if (hashType == Hash.CRC32)
				{
					var crc = new Crc32Algorithm();
					var crc32bytes = crc.ComputeHash(stream);
					hashStr = BitConverter.ToUInt32(crc32bytes, 0).ToString();
				}

                stream.Close();
			}
			catch (Exception e)
            {
				Logger.Log($"Error getting file hash for {Path.GetFileName(path)}: {e.Message}", true);
				return "";
            }

			if (log)
				Logger.Log($"Computed {hashType} for '{Path.GetFileNameWithoutExtension(path).Trunc(40) + Path.GetExtension(path)}' ({GetFilesizeStr(path)}): {hashStr}", true);
			
			return hashStr;
		}

		public static async Task<string> GetHashAsync(string path, Hash hashType, bool log = true)
		{
			await Task.Delay(1);
			return GetHash(path, hashType, log);
		}

		public static bool CreateDir (string path)		// Returns whether the dir already existed
        {
			if (!Directory.Exists(path))
            {
				Directory.CreateDirectory(path);
				return false;
			}

			return true;
        }

		public static void ZeroPadDir(string path, string ext, int targetLength, bool recursive = false)
		{
			FileInfo[] files;
			if (recursive)
				files = new DirectoryInfo(path).GetFiles($"*.{ext}", SearchOption.AllDirectories);
			else
				files = new DirectoryInfo(path).GetFiles($"*.{ext}", SearchOption.TopDirectoryOnly);

			ZeroPadDir(files.Select(x => x.FullName).ToList(), targetLength);
		}

		public static void ZeroPadDir(List<string> files, int targetLength, List<string> exclude = null, bool noLog = true)
		{
			if(exclude != null)
				files = files.Except(exclude).ToList();

			foreach (string file in files)
			{
				string fname = Path.GetFileNameWithoutExtension(file);
				string targetFilename = Path.Combine(Path.GetDirectoryName(file), fname.PadLeft(targetLength, '0') + Path.GetExtension(file));
                try
                {
					if (targetFilename != file)
						File.Move(file, targetFilename);
				}
				catch (Exception e)
				{
					if(!noLog)
						Logger.Log($"Failed to zero-pad {file} => {targetFilename}: {e.Message}", true);
				}
			}
		}

		public static bool CheckImageValid (string path)
        {
            try
            {
				Image img = GetImage(path);
                return (img.Width > 0 && img.Height > 0);
            }
			catch
            {
				return false;
            }
        }

		public static string[] GetFilesSorted (string path, bool recursive = false, string pattern = "*")
        {
			SearchOption opt = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
			return Directory.GetFiles(path, pattern, opt).OrderBy(x => Path.GetFileName(x)).ToArray();
        }

		public static string[] GetFilesSorted(string path, string pattern = "*")
		{
			return GetFilesSorted(path, false, pattern);
		}

		public static string[] GetFilesSorted(string path)
		{
			return GetFilesSorted(path, false, "*");
		}

		public static FileInfo[] GetFileInfosSorted(string path, bool recursive = false, string pattern = "*")
		{
			SearchOption opt = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
			DirectoryInfo dir = new DirectoryInfo(path);
			return dir.GetFiles(pattern, opt).OrderBy(x => x.Name).ToArray();
		}

		public static bool CreateFileIfNotExists (string path)
        {
			if (File.Exists(path))
				return false;

            try
            {
				File.Create(path).Close();
				return true;
            }
			catch (Exception e)
            {
				Logger.Log($"Failed to create file at '{path}': {e.Message}");
				return false;
            }
        }

		public static long GetDirSize(string path, bool recursive, string[] includedExtensions = null)
		{
			long size = 0;
			// Add file sizes.
			string[] files;
			StringComparison ignCase = StringComparison.OrdinalIgnoreCase;

			if (includedExtensions == null)
				files = Directory.GetFiles(path);
			else
				files = Directory.GetFiles(path).Where(file => includedExtensions.Any(x => file.EndsWith(x, ignCase))).ToArray();

			foreach (string file in files)
				size += new FileInfo(file).Length;

			if (!recursive)
				return size;

			// Add subdirectory sizes.
			DirectoryInfo[] dis = new DirectoryInfo(path).GetDirectories();
			foreach (DirectoryInfo di in dis)
				size += GetDirSize(di.FullName, true, includedExtensions);

			return size;
		}

		public static long GetFilesize(string path)
		{
            try
            {
				return new FileInfo(path).Length;
			}
            catch
            {
				return -1;
            }
		}

		public static string GetFilesizeStr (string path)
        {
			try
			{
				return FormatUtils.Bytes(GetFilesize(path));
			}
			catch
			{
				return "?";
			}
		}

        public static void OverwriteFileWithText (string path, string text = "THIS IS A DUMMY FILE - DO NOT DELETE ME")
        {
            try
            {
				File.WriteAllText(path, text);
			}
			catch (Exception e)
            {
				Logger.Log($"OverwriteWithText failed for '{path}': {e.Message}", true);
            }
		}

		public static long GetDiskSpace(string path, bool mbytes = true)
		{
			try
			{
				string driveLetter = path.Substring(0, 2);      // Make 'C:/some/random/path' => 'C:' etc
				DriveInfo[] allDrives = DriveInfo.GetDrives();

				foreach (DriveInfo d in allDrives)
				{
					if (d.IsReady && d.Name.StartsWith(driveLetter))
					{
						if (mbytes)
							return (long)(d.AvailableFreeSpace / 1024f / 1000f);
						else
							return d.AvailableFreeSpace;
					}
				}
			}
			catch (Exception e)
			{
				Logger.Log("Error trying to get disk space: " + e.Message, true);
			}

			return 0;
		}
    }
}
