#region License Information (GPL v3)

/*
    ShareX - A program that allows you to take screenshots and share any file type
    Copyright (c) 2007-2025 ShareX Team

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

    Optionally you can also view the license at <http://www.gnu.org/licenses/>.
*/

#endregion License Information (GPL v3)

using Microsoft.VisualBasic.FileIO;
using ShareX.HelpersLib.Properties;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace ShareX.HelpersLib
{
    public static class FileHelpers
    {
        public static readonly string[] ImageFileExtensions = new string[] { "jpg", "jpeg", "png", "gif", "bmp", "ico", "tif", "tiff" };
        public static readonly string[] TextFileExtensions = new string[] { "txt", "log", "nfo", "c", "cpp", "cc", "cxx", "h", "hpp", "hxx", "cs", "vb",
            "html", "htm", "xhtml", "xht", "xml", "css", "js", "php", "bat", "java", "lua", "py", "pl", "cfg", "ini", "dart", "go", "gohtml" };
        public static readonly string[] VideoFileExtensions = new string[] { "mp4", "webm", "mkv", "avi", "vob", "ogv", "ogg", "mov", "qt", "wmv", "m4p",
            "m4v", "mpg", "mp2", "mpeg", "mpe", "mpv", "m2v", "m4v", "flv", "f4v" };

        public static string GetFileNameExtension(string filePath, bool includeDot = false, bool checkSecondExtension = true)
        {
            string extension = "";

            if (!string.IsNullOrEmpty(filePath))
            {
                int pos = filePath.LastIndexOf('.');

                if (pos >= 0)
                {
                    extension = filePath.Substring(pos + 1);

                    if (checkSecondExtension)
                    {
                        filePath = filePath.Remove(pos);
                        string extension2 = GetFileNameExtension(filePath, false, false);

                        if (!string.IsNullOrEmpty(extension2))
                        {
                            foreach (string knownExtension in new string[] { "tar" })
                            {
                                if (extension2.Equals(knownExtension, StringComparison.OrdinalIgnoreCase))
                                {
                                    extension = extension2 + "." + extension;
                                    break;
                                }
                            }
                        }
                    }

                    if (includeDot)
                    {
                        extension = "." + extension;
                    }
                }
            }

            return extension;
        }

        public static string GetFileNameSafe(string filePath)
        {
            if (!string.IsNullOrEmpty(filePath))
            {
                int pos = filePath.LastIndexOf('\\');

                if (pos < 0)
                {
                    pos = filePath.LastIndexOf('/');
                }

                if (pos >= 0)
                {
                    return filePath.Substring(pos + 1);
                }
            }

            return filePath;
        }

        public static string ChangeFileNameExtension(string fileName, string extension)
        {
            if (!string.IsNullOrEmpty(fileName))
            {
                int pos = fileName.LastIndexOf('.');

                if (pos >= 0)
                {
                    fileName = fileName.Remove(pos);
                }

                if (!string.IsNullOrEmpty(extension))
                {
                    pos = extension.LastIndexOf('.');

                    if (pos >= 0)
                    {
                        extension = extension.Substring(pos + 1);
                    }

                    return fileName + "." + extension;
                }
            }

            return fileName;
        }

        public static string AppendTextToFileName(string filePath, string text)
        {
            if (!string.IsNullOrEmpty(filePath))
            {
                int pos = filePath.LastIndexOf('.');

                if (pos >= 0)
                {
                    return filePath.Substring(0, pos) + text + filePath.Substring(pos);
                }
            }

            return filePath + text;
        }

        public static string AppendExtension(string filePath, string extension)
        {
            return filePath.TrimEnd('.') + '.' + extension.TrimStart('.');
        }

        public static bool CheckExtension(string filePath, IEnumerable<string> extensions)
        {
            string ext = GetFileNameExtension(filePath);

            if (!string.IsNullOrEmpty(ext))
            {
                return extensions.Any(x => ext.Equals(x, StringComparison.OrdinalIgnoreCase));
            }

            return false;
        }

        public static bool IsImageFile(string filePath)
        {
            return CheckExtension(filePath, ImageFileExtensions);
        }

        public static bool IsTextFile(string filePath)
        {
            return CheckExtension(filePath, TextFileExtensions);
        }

        public static bool IsVideoFile(string filePath)
        {
            return CheckExtension(filePath, VideoFileExtensions);
        }

        public static EDataType FindDataType(string filePath)
        {
            if (IsImageFile(filePath))
            {
                return EDataType.Image;
            }

            if (IsTextFile(filePath))
            {
                return EDataType.Text;
            }

            return EDataType.File;
        }

        public static string GetAbsolutePath(string path)
        {
            path = ExpandFolderVariables(path);

            if (!Path.IsPathRooted(path)) // Is relative path?
            {
                path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, path);
            }

            return Path.GetFullPath(path);
        }

        public static string GetPathRoot(string path)
        {
            int separator = path.IndexOf(":\\");

            if (separator > 0)
            {
                return path.Substring(0, separator + 2);
            }

            return "";
        }

        public static string SanitizeFileName(string fileName, string replaceWith = "")
        {
            char[] invalidChars = Path.GetInvalidFileNameChars();
            return SanitizeFileName(fileName, replaceWith, invalidChars);
        }

        private static string SanitizeFileName(string fileName, string replaceWith, char[] invalidChars)
        {
            fileName = fileName.Trim();

            foreach (char c in invalidChars)
            {
                fileName = fileName.Replace(c.ToString(), replaceWith);
            }

            return fileName;
        }

        public static string SanitizePath(string path, string replaceWith = "")
        {
            string root = GetPathRoot(path);

            if (!string.IsNullOrEmpty(root))
            {
                path = path.Substring(root.Length);
            }

            char[] invalidChars = Path.GetInvalidFileNameChars().Except(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }).ToArray();
            path = SanitizeFileName(path, replaceWith, invalidChars);

            return root + path;
        }

        public static bool OpenFile(string filePath)
        {
            if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
            {
                try
                {
                    using (Process process = new Process())
                    {
                        ProcessStartInfo psi = new ProcessStartInfo()
                        {
                            FileName = filePath,
                            UseShellExecute = true
                        };

                        process.StartInfo = psi;
                        process.Start();
                    }

                    DebugHelper.WriteLine("File opened: " + filePath);

                    return true;
                }
                catch (Exception e)
                {
                    DebugHelper.WriteException(e, $"OpenFile({filePath}) failed.");
                }
            }
            else
            {
                MessageBox.Show(Resources.Helpers_OpenFile_File_not_exist_ + Environment.NewLine + filePath, "ShareX",
                    MessageBoxButtons.OK, MessageBoxIcon.Information);
            }

            return false;
        }

        public static bool OpenFolder(string folderPath, bool allowMessageBox = true)
        {
            if (!string.IsNullOrEmpty(folderPath) && Directory.Exists(folderPath))
            {
                if (!folderPath.EndsWith(@"\"))
                {
                    folderPath += @"\";
                }

                try
                {
                    using (Process process = new Process())
                    {
                        ProcessStartInfo psi = new ProcessStartInfo()
                        {
                            FileName = folderPath,
                            UseShellExecute = true
                        };

                        process.StartInfo = psi;
                        process.Start();
                    }

                    DebugHelper.WriteLine("Folder opened: " + folderPath);

                    return true;
                }
                catch (Exception e)
                {
                    DebugHelper.WriteException(e, $"OpenFolder({folderPath}) failed.");
                }
            }
            else if (allowMessageBox)
            {
                MessageBox.Show(Resources.Helpers_OpenFolder_Folder_not_exist_ + Environment.NewLine + folderPath, "ShareX",
                    MessageBoxButtons.OK, MessageBoxIcon.Information);
            }

            return false;
        }

        public static bool OpenFolderWithFile(string filePath)
        {
            if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
            {
                try
                {
                    NativeMethods.OpenFolderAndSelectFile(filePath);

                    DebugHelper.WriteLine("Folder opened with file: " + filePath);

                    return true;
                }
                catch (Exception e)
                {
                    DebugHelper.WriteException(e, $"OpenFolderWithFile({filePath}) failed.");
                }
            }
            else
            {
                MessageBox.Show(Resources.Helpers_OpenFile_File_not_exist_ + Environment.NewLine + filePath, "ShareX",
                    MessageBoxButtons.OK, MessageBoxIcon.Information);
            }

            return false;
        }

        public static string GetUniqueFilePath(string filePath)
        {
            if (File.Exists(filePath))
            {
                string folderPath = Path.GetDirectoryName(filePath);
                string fileName = Path.GetFileNameWithoutExtension(filePath);
                string fileExtension = Path.GetExtension(filePath);
                int number = 1;

                Match regex = Regex.Match(fileName, @"^(.+) \((\d+)\)$");

                if (regex.Success)
                {
                    fileName = regex.Groups[1].Value;
                    number = int.Parse(regex.Groups[2].Value);
                }

                do
                {
                    number++;
                    string newFileName = $"{fileName} ({number}){fileExtension}";
                    filePath = Path.Combine(folderPath, newFileName);
                }
                while (File.Exists(filePath));
            }

            return filePath;
        }

        public static string BrowseFile(IWin32Window window = null, string title = null)
        {
            using (OpenFileDialog ofd = new OpenFileDialog())
            {
                if (!string.IsNullOrEmpty(title))
                {
                    ofd.Title = title;
                }

                if (ofd.ShowDialog(window) == DialogResult.OK)
                {
                    string filePath = ofd.FileName;

                    if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
                    {
                        return filePath;
                    }
                }
            }

            return null;
        }

        public static bool BrowseFile(TextBox tb, string initialDirectory = "", bool detectSpecialFolders = false, string filter = "")
        {
            return BrowseFile("ShareX - " + Resources.Helpers_BrowseFile_Choose_file, tb, initialDirectory, detectSpecialFolders, filter);
        }

        public static bool BrowseFile(string title, TextBox tb, string initialDirectory = "", bool detectSpecialFolders = false, string filter = "")
        {
            using (OpenFileDialog ofd = new OpenFileDialog())
            {
                ofd.Title = title;
                ofd.Filter = filter;

                try
                {
                    string path = tb.Text;

                    if (detectSpecialFolders)
                    {
                        path = ExpandFolderVariables(path);
                    }

                    if (!string.IsNullOrEmpty(path))
                    {
                        path = Path.GetDirectoryName(path);

                        if (Directory.Exists(path))
                        {
                            ofd.InitialDirectory = path;
                        }
                    }
                }
                finally
                {
                    if (string.IsNullOrEmpty(ofd.InitialDirectory) && !string.IsNullOrEmpty(initialDirectory))
                    {
                        ofd.InitialDirectory = initialDirectory;
                    }
                }

                if (ofd.ShowDialog() == DialogResult.OK)
                {
                    string fileName = ofd.FileName;

                    if (detectSpecialFolders)
                    {
                        fileName = GetVariableFolderPath(fileName);
                    }

                    tb.Text = fileName;

                    return true;
                }
            }

            return false;
        }

        public static string BrowseFolder(string title = null, string initialDirectory = null)
        {
            using (FolderBrowserDialog fbd = new FolderBrowserDialog())
            {
                if (!string.IsNullOrEmpty(title))
                {
                    fbd.Description = title;
                    fbd.UseDescriptionForTitle = true;
                }

                if (!string.IsNullOrEmpty(initialDirectory) && Directory.Exists(initialDirectory))
                {
                    fbd.InitialDirectory = initialDirectory;
                }

                if (fbd.ShowDialog() == DialogResult.OK)
                {
                    return fbd.SelectedPath;
                }
            }

            return null;
        }

        public static bool BrowseFolder(TextBox tb, string initialDirectory = null, bool detectSpecialFolders = false)
        {
            return BrowseFolder("ShareX - " + Resources.Helpers_BrowseFolder_Choose_folder, tb, initialDirectory, detectSpecialFolders);
        }

        public static bool BrowseFolder(string title, TextBox tb, string initialDirectory = null, bool detectSpecialFolders = false)
        {
            string path = tb.Text;

            if (!string.IsNullOrEmpty(path) && Directory.Exists(path))
            {
                initialDirectory = path;
            }

            string selectedPath = BrowseFolder(title, initialDirectory);

            if (!string.IsNullOrEmpty(selectedPath))
            {
                tb.Text = detectSpecialFolders ? GetVariableFolderPath(selectedPath) : selectedPath;
                return true;
            }

            return false;
        }

        public static string GetVariableFolderPath(string path, bool supportCustomSpecialFolders = false)
        {
            if (!string.IsNullOrEmpty(path))
            {
                try
                {
                    if (supportCustomSpecialFolders)
                    {
                        foreach (KeyValuePair<string, string> specialFolder in HelpersOptions.ShareXSpecialFolders)
                        {
                            path = path.Replace(specialFolder.Value, $"%{specialFolder.Key}%", StringComparison.OrdinalIgnoreCase);
                        }
                    }

                    foreach (Environment.SpecialFolder specialFolder in Helpers.GetEnums<Environment.SpecialFolder>())
                    {
                        path = path.Replace(Environment.GetFolderPath(specialFolder), $"%{specialFolder}%", StringComparison.OrdinalIgnoreCase);
                    }
                }
                catch (Exception e)
                {
                    DebugHelper.WriteException(e);
                }
            }

            return path;
        }

        public static string ExpandFolderVariables(string path, bool supportCustomSpecialFolders = false)
        {
            if (!string.IsNullOrEmpty(path))
            {
                try
                {
                    if (supportCustomSpecialFolders)
                    {
                        foreach (KeyValuePair<string, string> specialFolder in HelpersOptions.ShareXSpecialFolders)
                        {
                            path = path.Replace($"%{specialFolder.Key}%", specialFolder.Value, StringComparison.OrdinalIgnoreCase);
                        }
                    }

                    foreach (Environment.SpecialFolder specialFolder in Helpers.GetEnums<Environment.SpecialFolder>())
                    {
                        path = path.Replace($"%{specialFolder}%", Environment.GetFolderPath(specialFolder), StringComparison.OrdinalIgnoreCase);
                    }

                    path = Environment.ExpandEnvironmentVariables(path);
                }
                catch (Exception e)
                {
                    DebugHelper.WriteException(e);
                }
            }

            return path;
        }

        public static string OutputSpecialFolders()
        {
            StringBuilder sb = new StringBuilder();

            foreach (Environment.SpecialFolder specialFolder in Helpers.GetEnums<Environment.SpecialFolder>())
            {
                sb.AppendLine(string.Format("{0,-25}{1}", specialFolder, Environment.GetFolderPath(specialFolder)));
            }

            return sb.ToString();
        }

        public static bool IsFileLocked(string filePath)
        {
            try
            {
                using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None))
                {
                    fs.Close();
                }
            }
            catch (IOException)
            {
                return true;
            }

            return false;
        }

        public static long GetFileSize(string filePath)
        {
            try
            {
                return new FileInfo(filePath).Length;
            }
            catch
            {
            }

            return -1;
        }

        public static string GetFileSizeReadable(string filePath, bool binaryUnits = false)
        {
            long fileSize = GetFileSize(filePath);

            if (fileSize >= 0)
            {
                return fileSize.ToSizeString(binaryUnits);
            }

            return "";
        }

        public static void CreateDirectory(string directoryPath)
        {
            if (!string.IsNullOrEmpty(directoryPath) && !Directory.Exists(directoryPath))
            {
                try
                {
                    Directory.CreateDirectory(directoryPath);
                }
                catch (Exception e)
                {
                    DebugHelper.WriteException(e);
                    MessageBox.Show(Resources.Helpers_CreateDirectoryIfNotExist_Create_failed_ + "\r\n\r\n" + e, "ShareX - " + Resources.Error,
                        MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        public static void CreateDirectoryFromFilePath(string filePath)
        {
            if (!string.IsNullOrEmpty(filePath))
            {
                string directoryPath = Path.GetDirectoryName(filePath);
                CreateDirectory(directoryPath);
            }
        }

        public static bool IsValidFilePath(string filePath)
        {
            FileInfo fi = null;

            try
            {
                fi = new FileInfo(filePath);
            }
            catch (ArgumentException) { }
            catch (PathTooLongException) { }
            catch (NotSupportedException) { }

            return fi != null;
        }

        public static string CopyFile(string filePath, string destinationFolder, bool overwrite = true)
        {
            if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath) && !string.IsNullOrEmpty(destinationFolder))
            {
                string fileName = Path.GetFileName(filePath);
                string destinationFilePath = Path.Combine(destinationFolder, fileName);
                CreateDirectory(destinationFolder);
                File.Copy(filePath, destinationFilePath, overwrite);
                return destinationFilePath;
            }

            return null;
        }

        public static void CopyFiles(string filePath, string destinationFolder)
        {
            CopyFiles(new string[] { filePath }, destinationFolder);
        }

        public static void CopyFiles(string[] files, string destinationFolder)
        {
            if (files != null && files.Length > 0)
            {
                if (!Directory.Exists(destinationFolder))
                {
                    Directory.CreateDirectory(destinationFolder);
                }

                foreach (string filePath in files)
                {
                    string fileName = Path.GetFileName(filePath);
                    string destinationFilePath = Path.Combine(destinationFolder, fileName);
                    File.Copy(filePath, destinationFilePath);
                }
            }
        }

        public static void CopyFiles(string sourceFolder, string destinationFolder, string searchPattern = "*", string[] ignoreFiles = null)
        {
            string[] files = Directory.GetFiles(sourceFolder, searchPattern);

            if (ignoreFiles != null)
            {
                List<string> newFiles = new List<string>();

                foreach (string file in files)
                {
                    string fileName = Path.GetFileName(file);

                    if (ignoreFiles.All(x => !fileName.Equals(x, StringComparison.OrdinalIgnoreCase)))
                    {
                        newFiles.Add(file);
                    }
                }

                files = newFiles.ToArray();
            }

            CopyFiles(files, destinationFolder);
        }

        public static void CopyAll(string sourceDirectory, string targetDirectory)
        {
            DirectoryInfo diSource = new DirectoryInfo(sourceDirectory);
            DirectoryInfo diTarget = new DirectoryInfo(targetDirectory);

            CopyAll(diSource, diTarget);
        }

        public static void CopyAll(DirectoryInfo source, DirectoryInfo target)
        {
            if (!Directory.Exists(target.FullName))
            {
                Directory.CreateDirectory(target.FullName);
            }

            foreach (FileInfo fi in source.GetFiles())
            {
                fi.CopyTo(Path.Combine(target.FullName, fi.Name), true);
            }

            foreach (DirectoryInfo diSourceSubDir in source.GetDirectories())
            {
                DirectoryInfo nextTargetSubDir = target.CreateSubdirectory(diSourceSubDir.Name);
                CopyAll(diSourceSubDir, nextTargetSubDir);
            }
        }

        public static string MoveFile(string filePath, string destinationFolder, bool overwrite = true)
        {
            if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath) && !string.IsNullOrEmpty(destinationFolder))
            {
                string fileName = Path.GetFileName(filePath);
                string destinationFilePath = Path.Combine(destinationFolder, fileName);
                CreateDirectory(destinationFolder);

                if (overwrite && File.Exists(destinationFilePath))
                {
                    File.Delete(destinationFilePath);
                }

                File.Move(filePath, destinationFilePath);
                return destinationFilePath;
            }

            return null;
        }

        public static string RenameFile(string filePath, string newFileName)
        {
            try
            {
                if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
                {
                    string directory = Path.GetDirectoryName(filePath);
                    string newFilePath = Path.Combine(directory, newFileName);
                    File.Move(filePath, newFilePath);
                    return newFilePath;
                }
            }
            catch (Exception e)
            {
                MessageBox.Show("Rename file error:\r\n" + e.ToString(), "ShareX - " + Resources.Error, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

            return filePath;
        }

        public static bool DeleteFile(string filePath, bool sendToRecycleBin = false)
        {
            try
            {
                if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
                {
                    if (sendToRecycleBin)
                    {
                        FileSystem.DeleteFile(filePath, UIOption.OnlyErrorDialogs, RecycleOption.SendToRecycleBin);
                    }
                    else
                    {
                        File.Delete(filePath);
                    }

                    return true;
                }
            }
            catch (Exception e)
            {
                DebugHelper.WriteException(e);
            }

            return false;
        }

        public static string BackupFileWeekly(string filePath, string destinationFolder)
        {
            if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
            {
                string fileName = Path.GetFileNameWithoutExtension(filePath);
                DateTime dateTime = DateTime.Now;
                string extension = Path.GetExtension(filePath);
                string newFileName = string.Format("{0}-{1:yyyy-MM}-W{2:00}{3}", fileName, dateTime, dateTime.WeekOfYear(), extension);
                string newFilePath = Path.Combine(destinationFolder, newFileName);

                if (!File.Exists(newFilePath))
                {
                    CreateDirectory(destinationFolder);
                    File.Copy(filePath, newFilePath, false);
                    return newFilePath;
                }
            }

            return null;
        }

        public static void BackupFileMonthly(string filePath, string destinationFolder)
        {
            if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
            {
                string fileName = Path.GetFileNameWithoutExtension(filePath);
                string extension = Path.GetExtension(filePath);
                string newFileName = string.Format("{0}-{1:yyyy-MM}{2}", fileName, DateTime.Now, extension);
                string newFilePath = Path.Combine(destinationFolder, newFileName);

                if (!File.Exists(newFilePath))
                {
                    CreateDirectory(destinationFolder);
                    File.Copy(filePath, newFilePath, false);
                }
            }
        }

        public static string GetTempFilePath(string extension)
        {
            string path = Path.GetTempFileName();
            return Path.ChangeExtension(path, extension);
        }

        public static void CreateEmptyFile(string filePath)
        {
            File.Create(filePath).Dispose();
        }

        public static IEnumerable<string> GetFilesByExtensions(string directoryPath, params string[] extensions)
        {
            return GetFilesByExtensions(new DirectoryInfo(directoryPath), extensions);
        }

        public static IEnumerable<string> GetFilesByExtensions(DirectoryInfo directoryInfo, params string[] extensions)
        {
            HashSet<string> allowedExtensions = new HashSet<string>(extensions, StringComparer.OrdinalIgnoreCase);
            return directoryInfo.EnumerateFiles().Where(f => allowedExtensions.Contains(f.Extension)).Select(x => x.FullName);
        }
    }
}