context: #region header
  // ========================================================================
  // Copyright (c) 2018 - Julien Caillon (julien.caillon@gmail.com)
  // This file (Utils.cs) is part of 3P.
  // 
  // 3P is a 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 3 of the License, or
  // (at your option) any later version.
  // 
  // 3P 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 3P. If not, see <http://www.gnu.org/licenses/>.
  // ========================================================================
  #endregion
  using System;
  using System.Collections.Generic;
  using System.ComponentModel;
  using System.Diagnostics;
  using System.Drawing;
  using System.Drawing.Drawing2D;
  using System.Drawing.Imaging;
  using System.Globalization;
  using System.IO;
  using System.Linq;
  using System.Linq.Expressions;
  using System.Security.AccessControl;
  using System.Security.Cryptography;
  using System.Security.Principal;
  using System.Text;
  using System.Text.RegularExpressions;
  using System.Windows.Forms;
  using WixToolset.Dtf.Compression.Zip;
  using YamuiFramework.Helper;
  using YamuiFramework.HtmlRenderer.Core.Core.Entities;
  using _3PA.MainFeatures;
  using _3PA.MainFeatures.Appli;
  using _3PA.MainFeatures.FileExplorer;
  using _3PA.NppCore;
  using _3PA.WindowsCore;
  using _3PA._Resource;
  
  namespace _3PA.Lib {
      /*        
         #region Events
  
          public delegate void NotificationEvent(SCNotification notification);
          public static event NotificationEvent OnNppNotification;
  
          // execute events
          if (OnUpdatedUi != null) {
              OnUpdatedUi(null, new EventArgs());
          }
  
          #endregion
       */
  
      /// <summary>
      /// Class that exposes utility methods
      /// </summary>
      internal static class Utils {
  
          #region File manipulation wrappers
  
          /// <summary>
          /// File write all bytes
          /// </summary>
          public static bool FileWriteAllBytes(string path, byte[] bytes) {
              try {
                  File.WriteAllBytes(path, bytes);
                  return true;
              } catch (Exception e) {
                  UserCommunication.Notify("Unable to write the following file :<br>" + path + "<br>Please check that you have the appropriate rights on this folder" + "<div class='AlternatBackColor' style='padding: 5px; margin: 5px;'>\"" + e.Message + "\"</div>", MessageImg.MsgError, "Write file", "Failed");
              }
              return false;
          }
  
          /// <summary>
          /// File write all text
          /// </summary>
          public static bool FileWriteAllText(string path, string text, Encoding encoding = null) {
              try {
                  if (encoding == null)
                      encoding = Encoding.Default;
                  File.WriteAllText(path, text, encoding);
                  return true;
              } catch (Exception e) {
                  UserCommunication.Notify("Unable to write the following file :<br>" + path + "<br>Please check that you have the appropriate rights on this folder" + "<div class='AlternatBackColor' style='padding: 5px; margin: 5px;'>\"" + e.Message + "\"</div>", MessageImg.MsgError, "Write file", "Failed");
              }
              return false;
          }
  
          /// <summary>
          /// File write all text
          /// </summary>
          public static bool FileAppendAllText(string path, string text, Encoding encoding = null) {
              try {
                  if (encoding == null)
                      encoding = Encoding.Default;
                  File.AppendAllText(path, text, encoding);
                  return true;
              } catch (Exception e) {
                  UserCommunication.Notify("Unable to write the following file :<br>" + path + "<br>Please check that you have the appropriate rights on this folder" + "<div class='AlternatBackColor' style='padding: 5px; margin: 5px;'>\"" + e.Message + "\"</div>", MessageImg.MsgError, "Write file", "Failed");
              }
              return false;
          }
  
          /// <summary>
          /// Reads all the line of either the filePath (if the file exists) or from byte array dataResources,
          /// Apply the action toApplyOnEachLine(int lineNumber, string lineString) to each line
          /// Uses encoding as the Encoding to read the file or convert the byte array to a string
          /// Uses the char # as a comment in the file (must be the first char of a line)
          /// Returns the line w/o the \r\n
          /// </summary>
          public static void ForEachLine(string filePath, byte[] dataResources, Action<int, string> toApplyOnEachLine, Encoding encoding = null) {
              try {
                  Exception ex = new Exception("Undetermined");
                  if (!Utilities.ForEachLine(filePath, dataResources, toApplyOnEachLine, encoding ?? TextEncodingDetect.GetFileEncoding(filePath), exception => ex = exception)) {
                      ErrorHandler.ShowErrors(ex, "Error reading file", filePath);
                  }
              } catch (Exception e) {
                  ErrorHandler.ShowErrors(e, "Error while reading an internal ressource!");
              }
          }
  
          /// <summary>
          /// Read all the text of a file in one go, same as File.ReadAllText expect it's truly a read only function
          /// </summary>
          public static string ReadAllText(string path, Encoding encoding = null) {
              try {
                  using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
                      using (var textReader = new StreamReader(fileStream, encoding ?? TextEncodingDetect.GetFileEncoding(path))) {
                          return textReader.ReadToEnd();
                      }
                  }
              } catch (Exception e) {
                  ErrorHandler.ShowErrors(e, "Error while reading the following file : " + path);
              }
              return null;
          }
  
          /// <summary>
          /// Allows to hide a directory
          /// </summary>
          /// <param name="path"></param>
          /// <returns></returns>
          public static bool HideDirectory(string path) {
              if (string.IsNullOrEmpty(path) || !Directory.Exists(path))
                  return false;
  
              var dirInfo = new DirectoryInfo(path);
  
              // See if directory has hidden flag, if not, make hidden
              if ((dirInfo.Attributes & FileAttributes.Hidden) != FileAttributes.Hidden) {
                  // Add Hidden flag    
                  dirInfo.Attributes |= FileAttributes.Hidden;
              }
  
              return true;
          }
  
  	    /// <summary>
          /// Returns true if a file path is writable with the current user.
          /// </summary>
          /// <param name="directoryPath">Full path to file to test.</param>
          /// <returns>State [bool]</returns>
          public static bool DirectoryPathHasWritePermission(string directoryPath) {
              if (string.IsNullOrEmpty(directoryPath)) {
                  return false;
              }
              var accessRight = FileSystemRights.Write;
              while (!Directory.Exists(directoryPath)) {
                  directoryPath = Path.GetDirectoryName(directoryPath);
                  accessRight = FileSystemRights.CreateDirectories | FileSystemRights.Write;
              }
              try {
                  return IsCurrentUserMatchingRule(File.GetAccessControl(directoryPath).GetAccessRules(true, true, typeof(SecurityIdentifier)), accessRight);
              } catch {
                  return false;
              }
          }
  	
  	    /// <summary>
          /// Returns true if a file path is writable with the current user.
          /// </summary>
          /// <param name="filePath">Full path to file to test.</param>
          /// <returns>State [bool]</returns>
          public static bool FilePathHasWritePermission(string filePath) {
              if (string.IsNullOrEmpty(filePath)) {
                  return false;
              }
              FileSystemRights accessRight;
              if (!File.Exists(filePath)) {
  				filePath = Path.GetDirectoryName(filePath);
                  accessRight = FileSystemRights.CreateFiles;					
  	            while (!Directory.Exists(filePath)) {
  	                filePath = Path.GetDirectoryName(filePath);
  	                accessRight = FileSystemRights.CreateDirectories | FileSystemRights.CreateFiles;
  	            }
              } else {
                  accessRight = FileSystemRights.Write;
              }
              try {
                  return IsCurrentUserMatchingRule(File.GetAccessControl(filePath).GetAccessRules(true, true, typeof(SecurityIdentifier)), accessRight);
              } catch {
                  return false;
              }
          }
  		
  		
          private static bool IsCurrentUserMatchingRule(AuthorizationRuleCollection rules, FileSystemRights accessRight) {
              WindowsIdentity identity = WindowsIdentity.GetCurrent();
              foreach (FileSystemAccessRule rule in rules) {
                  if (identity?.Groups != null && identity.Groups.Contains(rule.IdentityReference)) {
                      if ((accessRight & rule.FileSystemRights) == accessRight) {
                          if (rule.AccessControlType == AccessControlType.Allow)
                              return true;
                      }
                  }
              }
              return false;
          }
  
          /// <summary>
          /// Delete a dir, recursively
          /// </summary>
          public static bool DeleteDirectory(string path, bool recursive) {
              try {
                  if (string.IsNullOrEmpty(path) || !Directory.Exists(path))
                      return true;
                  Directory.Delete(path, recursive);
              } catch (Exception e) {
                  UserCommunication.Notify("Failed to delete the following directory :<br>" + path.ToHtmlLink() + "<div class='AlternatBackColor' style='padding: 5px; margin: 5px;'>\"" + e.Message + "\"</div>", MessageImg.MsgHighImportance, "Delete folder", "Can't delete a folder!");
                  return false;
              }
              return true;
          }
  
          /// <summary>
          /// Delete a file
          /// </summary>
          public static bool DeleteFile(string path) {
              try {
                  if (string.IsNullOrEmpty(path) || !File.Exists(path))
                      return true;
                  File.Delete(path);
              } catch (Exception e) {
                  UserCommunication.Notify("Failed to delete the following file :<br>" + path.ToHtmlLink() + "<div class='AlternatBackColor' style='padding: 5px; margin: 5px;'>\"" + e.Message + "\"</div>", MessageImg.MsgHighImportance, "Delete file", "Can't delete a file!");
                  return false;
              }
              return true;
          }
  
          /// <summary>
          /// Creates the directory, can apply attributes
          /// </summary>
          public static bool CreateDirectory(string path, FileAttributes attributes = FileAttributes.Directory) {
              try {
                  if (Directory.Exists(path))
                      return true;
                  var dirInfo = Directory.CreateDirectory(path);
                  dirInfo.Attributes |= attributes;
              } catch (Exception e) {
                  UserCommunication.Notify("There was a problem when i tried to create the directory:<br>" + path + "<br><br><i>Please make sure that you have the privileges to create this directory</i>" + "<div class='AlternatBackColor' style='padding: 5px; margin: 5px;'>\"" + e.Message + "\"</div>", MessageImg.MsgError, "Create directory", "Couldn't create the directory");
                  return false;
              }
              return true;
          }
  
          /// <summary>
          /// Move a file, ensures the user gets a feedback is something goes wrong
          /// return true if ok, false otherwise
          /// </summary>
          public static bool MoveFile(string sourceFile, string targetFile, bool silent = false) {
              try {
                  if (!File.Exists(sourceFile)) {
                      if (!silent)
                          UserCommunication.Notify("There was a problem when trying to move a file, the source doesn't exist :<br>" + sourceFile, MessageImg.MsgError, "Move file", "Couldn't find source file");
                      return false;
                  }
                  if (sourceFile.Equals(targetFile))
                      return true;
                  File.Delete(targetFile);
                  File.Move(sourceFile, targetFile);
              } catch (Exception e) {
                  if (!silent)
                      UserCommunication.Notify("There was a problem when i tried to write the following file:<br>" + targetFile.ToHtmlLink() + "<br><br><i>Please make sure that you have the privileges to write in the targeted directory / file</i>" + "<div class='AlternatBackColor' style='padding: 5px; margin: 5px;'>\"" + e.Message + "\"</div>", MessageImg.MsgError, "Move file", "Couldn't write target file");
                  return false;
              }
              return true;
          }
  
          /// <summary>
          /// Copy a file (erase existing target), ensures the user gets a feedback is something goes wrong
          /// return true if ok, false otherwise
          /// </summary>
          public static bool CopyFile(string sourceFile, string targetFile) {
              try {
                  if (sourceFile.Equals(targetFile))
                      return true;
                  if (!File.Exists(sourceFile)) {
                      UserCommunication.Notify("There was a problem when trying to copy a file, the source doesn't exist :<br>" + sourceFile, MessageImg.MsgError, "Copy file", "Couldn't find source file");
                      return false;
                  }
                  File.Copy(sourceFile, targetFile, true);
              } catch (Exception e) {
                  UserCommunication.Notify("There was a problem when i tried to write the following file:<br>" + targetFile.ToHtmlLink() + "<br><br><i>Please make sure that you have the privileges to write in the targeted directory / file</i>" + "<div class='AlternatBackColor' style='padding: 5px; margin: 5px;'>\"" + e.Message + "\"</div>", MessageImg.MsgError, "Copy file", "Couldn't write target file");
                  return false;
              }
              return true;
          }
  
          /// <summary>
          /// Copy a directory, ensures the user gets a feedback is something goes wrong
          /// returns true if ok, false otherwise
          /// </summary>
          public static bool CopyDirectory(string sourceFolder, string targetFolder, bool deleteExistingTarget = false) {
              try {
                  sourceFolder = Path.GetFullPath(sourceFolder);
                  targetFolder = Path.GetFullPath(targetFolder);
                  if (!Directory.Exists(sourceFolder)) {
                      UserCommunication.Notify("There was a problem when trying to copy a folder, the source doesn't exist :<br>" + sourceFolder, MessageImg.MsgError, "Copy folder", "Couldn't find source folder");
                      return false;
                  }
                  if (deleteExistingTarget)
                      DeleteDirectory(targetFolder, true);
  
                  Directory.CreateDirectory(targetFolder);
  
                  //Copy all the files & Replaces any files with the same name
                  foreach (string newPath in Directory.GetFiles(sourceFolder, "*.*", SearchOption.AllDirectories).ToList()) {
                      var target = newPath.Replace(sourceFolder, targetFolder);
                      CreateDirectory(Path.GetDirectoryName(target));
                      File.Copy(newPath, target, true);
                  }
              } catch (Exception e) {
                  UserCommunication.Notify("There was a problem when i tried to copy the following folder:<br>" + targetFolder.ToHtmlLink() + "<br><br><i>Please make sure that you have the privileges to write in the targeted directory</i>" + "<div class='AlternatBackColor' style='padding: 5px; margin: 5px;'>\"" + e.Message + "\"</div>", MessageImg.MsgError, "Copy folder", "Couldn't write target folder");
                  return false;
              }
              return true;
          }
  
          /// <summary>
          /// Move a directory, ensures the user gets a feedback is something goes wrong
          /// returns true if ok, false otherwise
          /// </summary>
          public static bool MoveDirectory(string sourceFolder, string targetFolder, bool deleteExistingTarget = false) {
              try {
                  sourceFolder = Path.GetFullPath(sourceFolder);
                  targetFolder = Path.GetFullPath(targetFolder);
                  if (!Directory.Exists(sourceFolder)) {
                      UserCommunication.Notify("There was a problem when trying to move a folder, the source doesn't exist :<br>" + sourceFolder, MessageImg.MsgError, "Copy folder", "Couldn't find source folder");
                      return false;
                  }
                  if (deleteExistingTarget)
                      DeleteDirectory(targetFolder, true);
  
                  Directory.CreateDirectory(targetFolder);
  
                  //Copy all the files & Replaces any files with the same name
                  foreach (string newPath in Directory.GetFiles(sourceFolder, "*.*", SearchOption.AllDirectories).ToList()) {
                      var target = newPath.Replace(sourceFolder, targetFolder);
                      CreateDirectory(Path.GetDirectoryName(target));
                      File.Move(newPath, target);
                  }
              } catch (Exception e) {
                  UserCommunication.Notify("There was a problem when i tried to move the following folder:<br>" + targetFolder.ToHtmlLink() + "<br><br><i>Please make sure that you have the privileges to write in the targeted directory</i>" + "<div class='AlternatBackColor' style='padding: 5px; margin: 5px;'>\"" + e.Message + "\"</div>", MessageImg.MsgError, "Move folder", "Couldn't write target folder");
                  return false;
              }
              return true;
          }
  
          /// <summary>
          /// Same as Directory.EnumerateDirectories but doesn't list hidden folders
          /// </summary>
          public static IEnumerable<string> EnumerateFolders(string folderPath, string pattern, SearchOption options) {
              var hiddenDirList = new List<string>();
              foreach (var dir in Directory.EnumerateDirectories(folderPath, pattern, options)) {
                  if (new DirectoryInfo(dir).Attributes.HasFlag(FileAttributes.Hidden)) {
                      hiddenDirList.Add(dir);
                  }
                  bool hidden = false;
                  foreach (var hiddenDir in hiddenDirList) {
                      if (dir.StartsWith(hiddenDir)) {
                          hidden = true;
                      }
                  }
                  if (!hidden)
                      yield return dir;
              }
          }
  
          /// <summary>
          /// Same as Directory.EnumerateFiles but doesn't list files in hidden folders
          /// </summary>
          public static IEnumerable<string> EnumerateFiles(string folderPath, string pattern, SearchOption options) {
              foreach (var file in Directory.EnumerateFiles(folderPath, pattern, SearchOption.TopDirectoryOnly)) {
                  yield return file;
              }
              if (options == SearchOption.AllDirectories) {
                  foreach (var folder in EnumerateFolders(folderPath, "*", SearchOption.AllDirectories)) {
                      foreach (var file in Directory.EnumerateFiles(folder, pattern, SearchOption.TopDirectoryOnly)) {
                          yield return file;
                      }
                  }
              }
          }
  
          #endregion
  
          #region File/directory selection
  
          /// <summary>
          /// Shows a dialog that allows the user to pick a file
          /// </summary>
          /// <param name="initialFile"></param>
          /// <param name="filter">txt files (*.txt)|*.txt|All files (*.*)|*.*</param>
          /// <returns></returns>
          public static string ShowFileSelection(string initialFile, string filter) {
              using (OpenFileDialog dialog = new OpenFileDialog {
                  Multiselect = false,
                  Filter = string.IsNullOrEmpty(filter) ? "All files (*.*)|*.*" : filter,
                  Title = @"Select a file"
              }) {
                  var initialFolder = (!File.Exists(initialFile)) ? null : Path.GetDirectoryName(initialFile);
                  if (!string.IsNullOrEmpty(initialFolder) && Directory.Exists(initialFolder))
                      dialog.InitialDirectory = initialFolder;
                  if (File.Exists(initialFile))
                      dialog.FileName = initialFile;
                  return dialog.ShowDialog() == DialogResult.OK ? dialog.FileName : string.Empty;
              }
          }
  
          /// <summary>
          /// Show a dialog that allows the user to pick a folder
          /// </summary>
          /// <param name="initialFolder"></param>
          /// <returns></returns>
          public static string ShowFolderSelection(string initialFolder) {
              var fsd = new FolderSelectDialog();
              if (!string.IsNullOrEmpty(initialFolder) && Directory.Exists(initialFolder))
                  fsd.InitialDirectory = initialFolder;
              if (Appli.IsVisible) {
                  try {
                      Win32Api.EnableWindow(Npp.Handle, false);
                      fsd.ShowDialog(Appli.Form.Handle);
                  } finally {
                      Win32Api.EnableWindow(Npp.Handle, true);
                  }
              } else
                  fsd.ShowDialog(Npp.Handle);
              return fsd.FileName ?? string.Empty;
          }
  
          #endregion
  
          #region Link/file/directories opening
  
          /// <summary>
          /// Opens a file's folder and select the file in it
          /// </summary>
          /// <param name="filePath"></param>
          public static bool OpenFileInFolder(string filePath) {
              if (!File.Exists(filePath))
                  return false;
              if (!Win32Api.OpenFolderAndSelectItem(Path.GetDirectoryName(filePath), filePath)) {
                  string argument = "/select, \"" + filePath + "\"";
                  Process.Start("explorer.exe", argument);
              }
              return true;
          }
  
          /// <summary>
          /// Opens a folder in the explorer
          /// </summary>
          /// <param name="folderPath"></param>
          public static bool OpenFolder(string folderPath) {
              if (!Directory.Exists(folderPath))
                  return false;
              Process.Start(folderPath);
              return true;
          }
  
          /// <summary>
          /// Open the given link either in notepad++ (if the file extension is know)
          /// or with window (opens a folder if it is a folder, or open a file with correct program
          /// using shell extension)
          /// also works for urls
          /// </summary>
          /// <param name="link"></param>
          public static bool OpenAnyLink(string link) {
              if (string.IsNullOrEmpty(link)) return false;
              try {
                  // open the file if it has a progress extension or Known extension
                  if ((link.TestAgainstListOfPatterns(Config.Instance.FilesPatternNppOpenable) || link.TestAgainstListOfPatterns(Config.Instance.FilesPatternProgress)) && File.Exists(link)) {
                      Npp.Goto(link);
                      return true;
                  }
  
                  // url?
                  if (new Regex(@"^(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&amp;%\$#_]*)?$").Match(link).Success) {
                      Process.Start(link);
                      return true;
                  }
  
                  // open with default shell action
                  if (!File.Exists(link)) {
                      if (!Directory.Exists(link))
                          return false;
                      if (OpenFolder(link))
                          return true;
                  }
  
                  var process = new ProcessStartInfo(link) {
                      UseShellExecute = true
                  };
                  Process.Start(process);
              } catch (Exception e) {
  
gt:                 if (!(e is Win32Exception))