// this alias is used for Microsoft.Win32.TaskScheduler.dll because 
// this dll defines the same type in the same namespace as the one defined in
// mscorlib.dll, mainly namespace of System.Collection.Generic, and types: IReadOnlyList, IReadOnlyCollection. 
// By using this alias this the TaskScheduler dll, we avoid the 
// warning of conflict of the same type in the same namespace being defined in
// more than one assemblies.
extern alias TaskSchedulerAlias;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.IO;
using System.Security.AccessControl;
using System.Management;
using System.Security.Principal;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using System.Drawing;
using System.Windows.Forms;

using CommonLib.Windows.Forms;
using CommonLib.IO;
using CommonLib.Misc;
using CommonLib.Windows.Misc;

namespace All_Purpose_Auto_Setup
{
   class Common
   {
      delegate string GetWindowPathDelegate();

      static Dictionary<string, GetWindowPathDelegate> getWindowPathDel = new Dictionary<string, GetWindowPathDelegate>(StringComparer.OrdinalIgnoreCase)
               {  { PathManip.enumWindowsDesignatedPaths.USER_DESKTOP.ToString(), PathManip.GetUserDesktopPath},
                  { PathManip.enumWindowsDesignatedPaths.USER_PROFILE.ToString(), PathManip.GetUserProfilePath},
                  { PathManip.enumWindowsDesignatedPaths.ALL_USER_DESKTOP.ToString(), PathManip.GetAllUserDesktopPath},
                  { PathManip.enumWindowsDesignatedPaths.USER_STARTUP_FOLDER.ToString(), PathManip.GetUserStartupFolderPath},
                  { PathManip.enumWindowsDesignatedPaths.ALL_USER_STARTUP_FOLDER.ToString(), PathManip.GetAllUserStartupFolderPath},
                  { PathManip.enumWindowsDesignatedPaths.USER_TEMP_FOLDER.ToString(), PathManip.GetUserTempPath}
               };

      public enum enum_SETUP_STEPS
      {
         NOT_VALID_STEP,   // for initialization purposes
         INSTALL_SOFTWARE_PACKAGES,
         ENABLE_INTERNET_INFORMATION_SERVICES,
         CREATE_DIRECTORIES,
         CREATE_NETWORK_SHARES,
         SET_ENVIRONMENT_VARIABLES,
         SET_DOTNET_FRAMEWORK_2_SECURITY_ZONE_LEVELS,
         PROCESS_GUTS_CONFIG_FILE,
         CREATE_WINDOWS_SHORTCUTS,
         ASSIST_BUILDING_GUTS_APP,
         TRANSFER_FILE_PACKAGES,
         REMOVE_FILES_FOLDERS,
         RUN_APPLICATIONS,
         MODIFY_WINDOWS_REGISTRY,
         CONFIGURE_NETWORK_ADAPTERS,
         PROMPT_USER
      }

      public enum EXECUTION_STATUS
      {
         SUCCESS,
         FAILURE,
         SYSTEM_SKIP,
         USER_SKIP   // user decides to skip execution, in which cas, the action is neither successful or failed
      }

      public static bool pathContainsMacro(string path)
      {
         bool pathHasMacro = false;

         foreach (PathManip.enumWindowsDesignatedPaths winPath in Enum.GetValues(typeof(PathManip.enumWindowsDesignatedPaths)))
         {
            string pattern = @"^\{" + winPath.ToString() + @"\}.*";

            if (Regex.IsMatch(path, pattern, RegexOptions.IgnoreCase))
            {
               pathHasMacro = true;
               break;
            }
         }

         return pathHasMacro;
      }

      public static bool pathContainsCdDriveMacro(string path)
      {
         bool pathHasCdMacro = false;

         Match regExMatch = Regex.Match(path, @"^\{([^\{\}]+)\}.*", RegexOptions.IgnoreCase);

         if (regExMatch.Success)
         {
            if (String.Equals(regExMatch.Groups[1].Value, PathManip.enumWindowsDesignatedPaths.CD_DRIVE.ToString(), StringComparison.OrdinalIgnoreCase))
               pathHasCdMacro = true;
         }

         return pathHasCdMacro;
      }

      public static string resolveMacroPath(string path)
      {
         string resolvedPath = path;
         Match regExMatch;

         regExMatch = Regex.Match(path, @"^\{([^\{\}]+)\}.*", RegexOptions.IgnoreCase);

         if (regExMatch.Success)
         {
            if (getWindowPathDel.ContainsKey(regExMatch.Groups[1].Value))
            {
               resolvedPath = Regex.Replace(path, @"\{([^\{\}]+)\}", getWindowPathDel[regExMatch.Groups[1].Value].Invoke(), RegexOptions.IgnoreCase);
            }
            // if cd drive macro is define then we have to process it a little bit differently, since there can be multiple cd drives in the system
            else if (String.Equals(PathManip.enumWindowsDesignatedPaths.CD_DRIVE.ToString(), regExMatch.Groups[1].Value, StringComparison.OrdinalIgnoreCase))
            {
               resolvedPath = getValidatedCdPath(path);
            }
         }

         return resolvedPath;
      }

      public static bool validatePathInCdDrive(string path, Form parentForm)
      {
         bool successful = true;
         string msg = "";
         DriveInfo drive = new DriveInfo(path.Substring(0, 1));
         while (!drive.IsReady || (!File.Exists(path) && !Directory.Exists(path)))
         {
            Hardware.OpenCdDrive(drive.Name);

            msg = ((frmSetupStatusDisplay)parentForm).displayMessageBox(MessageBoxCustom.PopUpMsgType.OKCANCEL, null, "Please insert a disc into drive "
               + drive.Name + " that has the following path:\n" + path + "\n\n Click OK to proceed", "Info", -1);

            if (String.Equals(msg, "cancel", StringComparison.OrdinalIgnoreCase))
            {
               successful = false;
               break;
            }
         }

         return successful;
      }

      // if user specifies a path with a cd drive prefix macro (i.e. {cd_drive}\hey.txt, then we want to find the cd drive with the path (file or directory) specified
      public static string getValidatedCdPath(string path = "")
      {
         string cdPath = "";
         string emptyDrivePath = "";

         List<string> cdDrives = Hardware.GetAllCdDrives();

         foreach (string drive in cdDrives)
         {
            if (path.Length == 0)
            {
               cdPath = drive;
               break;
            }
            else
            {
               string tempPath = Regex.Replace(path, @"\{([^\{\}]+)\}", PathManip.RemoveTrailingSlashInPath(drive), RegexOptions.IgnoreCase);
               DriveInfo driveInfo = new DriveInfo(tempPath.Substring(0, 1));

               if (File.Exists(tempPath) || Directory.Exists(tempPath))
               {
                  cdPath = tempPath;
                  break;
               }
               else if (!driveInfo.IsReady && emptyDrivePath.Length == 0)
               {
                  emptyDrivePath = tempPath;
               }
            }
         }

         if (cdPath.Length == 0 && cdDrives.Count > 0)
         {
            if (emptyDrivePath.Length > 0)
               cdPath = emptyDrivePath;
            else
               cdPath = Regex.Replace(path, @"\{([^\{\}]+)\}", PathManip.RemoveTrailingSlashInPath(cdDrives[0]), RegexOptions.IgnoreCase);
         }

         return cdPath;
      }

      // return indentation in spaces
      public static string getIndentation(int indentMultiples)
      {
         string indentUnit = "     ";
         string indentation = String.Empty;

         for (int i = 1; i <= indentMultiples; i++)
            indentation += indentUnit;

         return indentation;
      }

      public static Color getSetupStatusColor(EXECUTION_STATUS status)
      {
         if (status == EXECUTION_STATUS.SUCCESS)
            return Color.Green;
         else if (status == EXECUTION_STATUS.FAILURE)
            return Color.Red;
         else
            return Color.FromArgb(224, 154, 14);
      }

      public static Color getSetupStepTitleColor()
      {
         return Color.FromArgb(125, 143, 128);
      }

      public static void formatConfigIniFailureMessage(string message, List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor> textPropList)
      {
         CommonLib.Windows.Forms.TextFormat.TextFontAndColor defaultTextProp = new CommonLib.Windows.Forms.TextFormat.TextFontAndColor("");
         CommonLib.Windows.Forms.TextFormat.TextFontAndColor textProp = new CommonLib.Windows.Forms.TextFormat.TextFontAndColor("");

         // set font to default text and color
         textProp.textFont = defaultTextProp.textFont;
         textProp.textColor = defaultTextProp.textColor;
         textProp.textColor = getSetupStatusColor(Common.EXECUTION_STATUS.FAILURE);
         textProp.wordIndex = 0;
         textProp.wordCount = 2;
         // set font size
         textProp.textFont = new Font(textProp.textFont.Name, 10);
         // set font style
         textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
         textPropList.Add(textProp);

         // set font to default text and color
         textProp.textFont = defaultTextProp.textFont;
         textProp.textColor = defaultTextProp.textColor;
         textProp.wordIndex = StringManip.GetPhraseWordIndexInText(message, "File:");
         textProp.wordCount = 1;
         // set font style
         textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
         textPropList.Add(textProp);

         // set font to default text and color
         textProp.textFont = defaultTextProp.textFont;
         textProp.textColor = defaultTextProp.textColor;
         textProp.wordIndex = StringManip.GetPhraseWordIndexInText(message, "Section:");
         textProp.wordCount = 1;
         // set font style
         textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
         textPropList.Add(textProp);

         if (StringManip.GetPhraseWordIndexInText(message, "Key:") > -1)
         {
            // set font to default text and color
            textProp.textFont = defaultTextProp.textFont;
            textProp.textColor = defaultTextProp.textColor;
            textProp.wordIndex = StringManip.GetPhraseWordIndexInText(message, "Key:");
            textProp.wordCount = 1;
            // set font style
            textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
            textPropList.Add(textProp);
         }

         if (StringManip.GetPhraseWordIndexInText(message, "Value:") > -1)
         {
            // set font to default text and color
            textProp.textFont = defaultTextProp.textFont;
            textProp.textColor = defaultTextProp.textColor;
            textProp.wordIndex = StringManip.GetPhraseWordIndexInText(message, "Value:");
            textProp.wordCount = 1;
            // set font style
            textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
            textPropList.Add(textProp);
         }

         // set font to default text and color
         textProp.textFont = defaultTextProp.textFont;
         textProp.textColor = getSetupStatusColor(Common.EXECUTION_STATUS.FAILURE);
         textProp.wordIndex = StringManip.GetPhraseWordIndexInText(message, "Description:") - 1;
         textProp.wordCount = 2;
         // set font style
         textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
         textPropList.Add(textProp);
      }

      public static void enableAutoRunAfterReboot(string taskSchedulerName, string appPath)
      {
         Properties.Settings.Default["AutoRunOnStartup"] = true;

         createTaskSchedulerForAutoRunAfterReboot(taskSchedulerName, appPath);
      }

      private static void createTaskSchedulerForAutoRunAfterReboot(string taskSchedulerName, string appPath)
      {
         using (TaskSchedulerAlias::Microsoft.Win32.TaskScheduler.TaskService ts = new TaskSchedulerAlias::Microsoft.Win32.TaskScheduler.TaskService())
         {
            if (ts.GetTask(taskSchedulerName) == null)
            {
               TaskSchedulerAlias::Microsoft.Win32.TaskScheduler.TaskDefinition td = ts.NewTask();
               td.RegistrationInfo.Description = taskSchedulerName;

               // when running task, use the following account
               td.Principal.UserId = String.Concat(Environment.UserDomainName, "\\", Environment.UserName);

               // only run after user has logged on
               td.Principal.LogonType = TaskSchedulerAlias::Microsoft.Win32.TaskScheduler.TaskLogonType.InteractiveToken;

               // run this task with highest privileges
               td.Principal.RunLevel = TaskSchedulerAlias::Microsoft.Win32.TaskScheduler.TaskRunLevel.Highest;

               // creating the trigger, this actually executes the task
               TaskSchedulerAlias::Microsoft.Win32.TaskScheduler.LogonTrigger trigger = new TaskSchedulerAlias::Microsoft.Win32.TaskScheduler.LogonTrigger();
               trigger.UserId = String.Concat(Environment.UserDomainName, "\\", Environment.UserName);
               trigger.Delay = TimeSpan.FromSeconds(60.0);
               td.Triggers.Add(trigger);

               td.Actions.Add(new TaskSchedulerAlias::Microsoft.Win32.TaskScheduler.ExecAction(appPath, null, null));
               ts.RootFolder.RegisterTaskDefinition(taskSchedulerName, td);
            }
         }
      }

      public static void deleteTaskSchedulerForAutoRunAfterReboot(string taskSchedulerName)
      {
         using (TaskSchedulerAlias::Microsoft.Win32.TaskScheduler.TaskService ts = new TaskSchedulerAlias::Microsoft.Win32.TaskScheduler.TaskService())
         {
            if (ts.GetTask(taskSchedulerName) != null)
            {
               ts.RootFolder.DeleteTask(taskSchedulerName);
            }
         }
      }
   }
}
