using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Management;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Forms;
using CommonLib.IO;
using CommonLib.Misc;
using CommonLib.Windows.Forms;
using CommonLib.Windows.Misc;
using Microsoft.Win32;

namespace All_Purpose_Auto_Setup
{
   class SoftwareInstallManager
   {

      // An event that can be raised, allowing other classes to
      // subscribe to it and do what they like with the message.
      public event Action<string, string, List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor>> UpdateStatusDisplayAndLogEvent;

      public event Action<string, int, List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor>> UpdateStatusDisplayAtLine;

      public event Action RebootComputer;

      public event Action SetupProcessNeedRestartToProceed;

      // event for thread to notify other threads that it has exited
      private AutoResetEvent _resetEvent = new AutoResetEvent(false);

      // this dictionary stores application name and its associated setup file and exe file
      // it would look like this:
      // [ app1_name ]  ->  List[setup_file exe_file setup_args]
      // [ app2_name ]  ->  List[setup_file setup_args reg_path reg_type ...]
      public Dictionary<ConfigFileManager.Ini_KeyValue<string>, List<ConfigFileManager.Ini_KeyValue<string>>> appNameToInstallerAndExe = new Dictionary<ConfigFileManager.Ini_KeyValue<string>, List<ConfigFileManager.Ini_KeyValue<string>>>();

      int m_progressLine = 0;

      Form m_parentForm;

      public SoftwareInstallManager(Form parentForm)
      {
         m_parentForm = parentForm;
      }

      public bool manageRequiredSoftwarePackages(string sectionName)
      {
         bool setupSuccessful = true;
         string msg, indentation = String.Empty;
         string setupFile, exeFile, registryPath, registryDisplayName, registryType, registryName, registryValue, registryCompareOperator, installArgs,
            exitCodesToIgnore, spawnedProcessesToIgnore;
         bool promptBeforeInstallation = false, rebootComputerAtCompletionRequested = false, rebootComputerImmediatelyRequested = false;

         CommonLib.Windows.Forms.TextFormat.TextFontAndColor defaultTextProp = new CommonLib.Windows.Forms.TextFormat.TextFontAndColor("");
         CommonLib.Windows.Forms.TextFormat.TextFontAndColor textProp = new CommonLib.Windows.Forms.TextFormat.TextFontAndColor("");
         List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor> textPropList = new List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor>();

         // set font to default text and color
         textProp.textFont = defaultTextProp.textFont;
         textProp.textColor = Common.getSetupStepTitleColor();
         textProp.wordIndex = 0;
         textProp.wordCount = 100;
         textPropList.Add(textProp);

         DateTime dat1 = DateTime.Now;
         msg = "\n\n<-------------------Date and Time: " + dat1.ToString(@"MM/dd/yyyy     hh:mm:ss tt", System.Globalization.CultureInfo.InvariantCulture) + "------------------->";
         UpdateStatusDisplayAndLogEvent(msg, msg, textPropList);

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

         msg = "\n-=Installing/checking required software packages=-";
         UpdateStatusDisplayAndLogEvent(msg, msg, textPropList);

         appNameToInstallerAndExe.Clear();

         ConfigFileManager.readOneToManyDictionary(ConfigFileManager.ms_configGeneralInfo.iniFile,
                    sectionName, @"(app\d+)_name$", @"(app\d+)_.+", appNameToInstallerAndExe);

         if (setupSuccessful)
         {
            if (appNameToInstallerAndExe.Count == 0 ||
               (appNameToInstallerAndExe.Count == 1 && appNameToInstallerAndExe.First().Value.Count == 0))
            {
               // set font to default text and color
               textProp.textFont = defaultTextProp.textFont;
               textProp.textColor = Common.getSetupStatusColor(Common.EXECUTION_STATUS.SYSTEM_SKIP);
               textProp.wordIndex = 0;
               textProp.wordCount = 2;
               // set font style
               textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
               textPropList.Add(textProp);

               indentation = Common.getIndentation(1);
               msg = "\n" + indentation + "- SKIPPED. Nothing to install/check";
               UpdateStatusDisplayAndLogEvent(msg, msg, textPropList);
            }

            KeyValuePair<ConfigFileManager.Ini_KeyValue<string>, List<ConfigFileManager.Ini_KeyValue<string>>> processedConfigInfo = new KeyValuePair<ConfigFileManager.Ini_KeyValue<string>, List<ConfigFileManager.Ini_KeyValue<string>>>();

            // for every app name to be installed/checked
            foreach (KeyValuePair<ConfigFileManager.Ini_KeyValue<string>, List<ConfigFileManager.Ini_KeyValue<string>>> entry in appNameToInstallerAndExe)
            {
               promptBeforeInstallation = false;
               setupFile = "";
               exeFile = "";
               registryPath = "";
               registryDisplayName = "";
               registryType = "";
               registryName = "";
               registryValue = "";
               registryCompareOperator = "";
               installArgs = "";
               exitCodesToIgnore = "";
               spawnedProcessesToIgnore = "";
               rebootComputerAtCompletionRequested = false;
               rebootComputerImmediatelyRequested = false;


               setupSuccessful = processConfigInfo(entry, ref processedConfigInfo);

               if (!setupSuccessful)
                  break;

               getInfoUsingExeVerification(processedConfigInfo.Value, ref setupFile, ref exeFile, ref promptBeforeInstallation, ref rebootComputerAtCompletionRequested, ref rebootComputerImmediatelyRequested);
               getInfoUsingRegistryVerification(processedConfigInfo.Value, ref registryPath, ref registryDisplayName, ref registryType, ref registryName, ref registryValue, ref registryCompareOperator, ref installArgs, ref exitCodesToIgnore, ref spawnedProcessesToIgnore);

               if (exeFile.Length > 0)
               {
                  setupSuccessful = checkSoftwareExe(processedConfigInfo.Key.iniValue, setupFile, installArgs, exeFile, exitCodesToIgnore, promptBeforeInstallation, rebootComputerAtCompletionRequested, rebootComputerImmediatelyRequested);
               }
               else
               {
                  setupSuccessful = checkSoftwareRegistry(processedConfigInfo.Key.iniValue, setupFile, promptBeforeInstallation, installArgs, registryPath,
                                                            registryDisplayName, registryType, registryName, registryValue, registryCompareOperator,
                                                      exitCodesToIgnore, spawnedProcessesToIgnore, rebootComputerAtCompletionRequested, rebootComputerImmediatelyRequested);
               }

               if (!setupSuccessful || ((frmSetupStatusDisplay)m_parentForm).applicationNeedsToBeRestarted())
                  break;
            }
         }
         return setupSuccessful;
      }

      // check if software is installed
      public bool checkSoftwareExe(string softwareName, string setupFile, string setupArgs, string exeFile, string exitCodesToIgnore, bool promptBeforeInstall, bool rebootComputerAtCompletionRequested, bool rebootComputerImmediatelyRequested)
      {
         bool setupSuccessful = true;
         bool installSuccess = true;
         bool installExecuted = false;
         string msg1, msg2 = "", indentation = String.Empty;

         CommonLib.Windows.Forms.TextFormat.TextFontAndColor defaultTextProp = new CommonLib.Windows.Forms.TextFormat.TextFontAndColor("");
         CommonLib.Windows.Forms.TextFormat.TextFontAndColor textProp = new CommonLib.Windows.Forms.TextFormat.TextFontAndColor("");
         List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor> textPropList1 = new List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor>();
         List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor> textPropList2 = new List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor>();

         // set font to default text and color
         textProp.textFont = defaultTextProp.textFont;
         textProp.textColor = Color.FromArgb(233, 31, 195);
         textProp.wordIndex = 3;
         textProp.wordCount = 100;
         // set font style
         textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
         textPropList1.Add(textProp);

         indentation = Common.getIndentation(1);
         msg1 = "\n" + indentation + "Checking installation of " + softwareName;

         for (int checkAttempt = 1; checkAttempt <= 2; checkAttempt++)
         {
            // if software application does not exist
            if (!File.Exists(exeFile))
            {
               if (checkAttempt == 1 && setupFile.Length > 0)
               {
                  installSuccess = installSoftware(softwareName, setupFile, setupArgs, exitCodesToIgnore, "", promptBeforeInstall);

                  installExecuted = true;

                  if (!installSuccess)
                     setupSuccessful = false;

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

                  msg2 = " - FAILED. Unable to detect presence of file " + exeFile;
                  setupSuccessful = false;
               }

            }
            else if (checkAttempt == 2)
            {
               // set font to default text and color
               textProp.textFont = defaultTextProp.textFont;
               textProp.textColor = Common.getSetupStatusColor(Common.EXECUTION_STATUS.SUCCESS);
               textProp.wordIndex = 0;
               textProp.wordCount = 2;
               // set font style
               textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
               textPropList2.Add(textProp);

               msg2 = " - PASSED";
            }
         }

         if (installSuccess)
         {
            UpdateStatusDisplayAndLogEvent(msg1, msg1, textPropList1);
            UpdateStatusDisplayAndLogEvent(msg2, msg2, textPropList2);

            if (installExecuted && rebootComputerAtCompletionRequested)
            {
               RebootComputer();

               if (rebootComputerImmediatelyRequested)
               {
                  Common.enableAutoRunAfterReboot("Auto_Win_Setup_Task", Application.ExecutablePath);

                  // raise event to signal that we need to stop the setup process for changes to take effect
                  SetupProcessNeedRestartToProceed();
               }
            }
         }

         return setupSuccessful;
      }

      public bool installSoftware(string softwareName, string setupFile, string setupArgs, string exitCodesToIgnore, string spawnedProcessesToIgnore, bool promptBeforeInstall)
      {
         string msg1 = String.Empty, msg2 = String.Empty, msg3, indentation = String.Empty;
         bool setupSuccessful = true;

         CommonLib.Windows.Forms.TextFormat.TextFontAndColor defaultTextProp = new CommonLib.Windows.Forms.TextFormat.TextFontAndColor("");
         CommonLib.Windows.Forms.TextFormat.TextFontAndColor textProp = new CommonLib.Windows.Forms.TextFormat.TextFontAndColor("");
         List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor> textPropList1 = new List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor>();
         List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor> textPropList2 = new List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor>();
         List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor> textPropList3 = new List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor>();

         // set font to default text and color
         textProp.textFont = defaultTextProp.textFont;
         textProp.textColor = Color.FromArgb(233, 31, 195);
         textProp.wordIndex = 1;
         textProp.wordCount = 100;
         // set font style
         textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
         textPropList1.Add(textProp);

         indentation = Common.getIndentation(1);
         msg1 = "\n" + indentation + "Installing " + softwareName;

         if (setupFile.Length > 0)
         {
            setupFile = Path.GetFullPath(setupFile);
         }
         else
         {
            // set font to default text and color
            textProp.textFont = defaultTextProp.textFont;
            textProp.textColor = Common.getSetupStatusColor(Common.EXECUTION_STATUS.FAILURE);
            textProp.wordIndex = 0;
            textProp.wordCount = 2;
            // set font style
            textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
            textPropList2.Add(textProp);

            msg2 = " - FAILED. " + softwareName + " is not installed on system. Please install it.";
            setupSuccessful = false;
         }

         if (setupSuccessful)
         {
            foreach (var drive in DriveInfo.GetDrives()
               .Where(d => d.DriveType == DriveType.CDRom))
            {
               if (Regex.IsMatch(setupFile, @"^" + Regex.Escape(drive.Name) + @".+", RegexOptions.IgnoreCase))
               {
                  while (!drive.IsReady || !File.Exists(setupFile))
                  {
                     Hardware.OpenCdDrive(drive.Name);

                     msg3 = ((frmSetupStatusDisplay)m_parentForm).displayMessageBox(MessageBoxCustom.PopUpMsgType.OKCANCEL, null, "Please insert the " + softwareName + " disc into drive " + drive.Name + "\n\n Click OK to proceed", "Info", -1);

                     if (String.Equals(msg3, "cancel", StringComparison.OrdinalIgnoreCase))
                     {
                        break;
                     }
                  }

                  break;
               }
            }

            if (File.Exists(setupFile))
            {
               if (promptBeforeInstall)
                  msg3 = ((frmSetupStatusDisplay)m_parentForm).displayMessageBox(MessageBoxCustom.PopUpMsgType.YESNO, null, "Required software \"" + softwareName + "\" is not detected in the system.\n\n Would you like to perform the install now?", "Info", -1);
               else
                  msg3 = "yes";

               if (String.Equals(msg3, "yes", StringComparison.OrdinalIgnoreCase))
               {

                  BackgroundWorker bgwInformUserToWait;

                  bgwInformUserToWait = new BackgroundWorker();
                  bgwInformUserToWait.WorkerSupportsCancellation = true;
                  bgwInformUserToWait.DoWork += new DoWorkEventHandler(bgwInformUserToWait_DoWork);
                  bgwInformUserToWait.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgwInformUserToWait_RunWorkerCompleted);

                  frmSetupStatusDisplay displayFrm = (frmSetupStatusDisplay)m_parentForm;
                  m_progressLine = displayFrm.updateStatusDisplayAndLog("\n  ", "", textPropList3);
                  if (!bgwInformUserToWait.IsBusy)
                  {
                     indentation = Common.getIndentation(1);
                     bgwInformUserToWait.RunWorkerAsync(indentation + "Installing " + softwareName + ". Please wait");

                  }

                  try
                  {
                     System.Diagnostics.Process process = null;

                     // if we have to execute install from a command line
                     if (Regex.IsMatch(setupFile, @"\.bat$", RegexOptions.IgnoreCase))
                     {
                        ProcessStartInfo ProcStartInfo = new ProcessStartInfo(@"C:\Windows\System32\cmd.exe");
                        ProcStartInfo.RedirectStandardOutput = false;
                        ProcStartInfo.UseShellExecute = false;
                        ProcStartInfo.CreateNoWindow = true;
                        ProcStartInfo.RedirectStandardError = true;
                        process = new System.Diagnostics.Process();
                        ProcStartInfo.Arguments = "/c start /wait /d \"" + Path.GetDirectoryName(setupFile) + "\" " + Path.GetFileName(setupFile);
                        process.StartInfo = ProcStartInfo;
                        process.Start();
                     }
                     else if (setupArgs.Length > 0)
                     {
                        ProcessStartInfo ProcStartInfo = new ProcessStartInfo(setupFile);
                        ProcStartInfo.RedirectStandardOutput = false;
                        ProcStartInfo.UseShellExecute = true;
                        ProcStartInfo.CreateNoWindow = true;
                        ProcStartInfo.RedirectStandardError = false;
                        process = new System.Diagnostics.Process();
                        ProcStartInfo.Arguments = setupArgs;
                        process.StartInfo = ProcStartInfo;
                        process.Start();
                     }
                     else
                     {
                        process = Process.Start(setupFile);
                     }

                     int exitCode = WaitForAllToExit(process, spawnedProcessesToIgnore);

                     process.Close();

                     if (exitCodesToIgnore.Length == 0)
                        exitCodesToIgnore = ",0,";
                     else
                        exitCodesToIgnore = ",0," + exitCodesToIgnore + ",";

                     if (!Regex.IsMatch(exitCodesToIgnore, @",\s*" + exitCode.ToString() + @"\s*,", RegexOptions.IgnoreCase))
                     {
                        throw new Exception(Path.GetFileName(setupFile) + " exited with code: " + exitCode.ToString());
                     }
                  }
                  catch (Exception e)
                  {
                     // set font to default text and color
                     textProp.textFont = defaultTextProp.textFont;
                     textProp.textColor = Common.getSetupStatusColor(Common.EXECUTION_STATUS.FAILURE);
                     textProp.wordIndex = 0;
                     textProp.wordCount = 2;
                     // set font style
                     textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
                     textPropList2.Add(textProp);

                     msg2 = " - FAILED. Error executing setup file " + setupFile + ". " + e.Message;

                     setupSuccessful = false;
                  }
                  finally
                  {
                     if (!bgwInformUserToWait.CancellationPending && bgwInformUserToWait.IsBusy)
                     {
                        bgwInformUserToWait.CancelAsync();
                        _resetEvent.WaitOne();
                        _resetEvent.Reset();
                     }

                     displayFrm.removeStatusDisplayLine(m_progressLine, true);
                  }
               }
               else
               {
                  // set font to default text and color
                  textProp.textFont = defaultTextProp.textFont;
                  textProp.textColor = Common.getSetupStatusColor(Common.EXECUTION_STATUS.FAILURE);
                  textProp.wordIndex = 0;
                  textProp.wordCount = 2;
                  // set font style
                  textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
                  textPropList2.Add(textProp);

                  msg2 = " - FAILED. User chooses to not install required software.";

                  setupSuccessful = false;
               }
            }
            else
            {
               // set font to default text and color
               textProp.textFont = defaultTextProp.textFont;
               textProp.textColor = Common.getSetupStatusColor(Common.EXECUTION_STATUS.FAILURE);
               textProp.wordIndex = 0;
               textProp.wordCount = 2;
               // set font style
               textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
               textPropList2.Add(textProp);

               msg2 = " - FAILED. Setup file " + setupFile + " was not found.";

               setupSuccessful = false;
            }
         }

         if (setupSuccessful)
         {
            // set font to default text and color
            textProp.textFont = defaultTextProp.textFont;
            textProp.textColor = Common.getSetupStatusColor(Common.EXECUTION_STATUS.SUCCESS);
            textProp.wordIndex = 0;
            textProp.wordCount = 2;
            // set font style
            textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
            textPropList2.Add(textProp);

            msg2 = " - SUCCESS";
         }

         UpdateStatusDisplayAndLogEvent(msg1, msg1, textPropList1);
         UpdateStatusDisplayAndLogEvent(msg2, msg2, textPropList2);

         return setupSuccessful;
      }

      private void bgwInformUserToWait_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
      {
         // First, handle the case where an exception was thrown. 
         if (e.Error != null)
         {

         }
         else if (e.Cancelled)  // user cancelled
         {

         }
         else // thread completed on its own
         {

         }
         _resetEvent.Set();   // signal that execution of this thread is done
      }

      private int WaitForAllToExit(Process process, string spawnedProcessesToIgnore)
      {
         int exitCode = -1;

         // even though this line doesn't appear to do anything, it
         // helps in getting the exit code of the child process
         // without this line, if we try to get exit code of child process
         // exception will be thrown
         var sh = process.SafeHandle;

         while (true)
         {
            ManagementObjectSearcher searcher = new ManagementObjectSearcher(
                "SELECT * " +
                "FROM Win32_Process " +
                "WHERE ParentProcessId=" + process.Id);
            ManagementObjectCollection collection = searcher.Get();
            if (collection.Count > 0)
            {
               foreach (var item in collection)
               {
                  UInt32 childProcessId = (UInt32)item["ProcessId"];
                  if ((int)childProcessId != Process.GetCurrentProcess().Id)
                  {
                     Process childProcess = null;
                     try
                     {
                        childProcess = Process.GetProcessById((int)childProcessId);
                     }
                     catch (Exception)
                     {
                        continue;
                     }

                     string indentation = Common.getIndentation(1);
                     ((frmSetupStatusDisplay)m_parentForm).writeToLog($"{indentation}Child process spawned: {childProcess.ProcessName}\r\n");

                     string[] processArray = Array.ConvertAll(spawnedProcessesToIgnore.Split(','), p => p.Trim());

                     if (!processArray.Contains(childProcess.ProcessName, StringComparer.OrdinalIgnoreCase))
                     {
                        exitCode = WaitForAllToExit(childProcess, spawnedProcessesToIgnore);
                     }
                     else
                        ((frmSetupStatusDisplay)m_parentForm).writeToLog($"{indentation}Ignoring spawned process: {childProcess.ProcessName}\r\n");
                  }
               }
            }

            if (process.HasExited)
            {
               if (exitCode == -1)
                  exitCode = process.ExitCode;

               break;
            }
            else
               Thread.Sleep(500);
         }

         return exitCode;
      }

      private void bgwInformUserToWait_DoWork(object sender, DoWorkEventArgs e)
      {
         BackgroundWorker bgw = sender as BackgroundWorker;

         CommonLib.Windows.Forms.TextFormat.TextFontAndColor defaultTextProp = new CommonLib.Windows.Forms.TextFormat.TextFontAndColor("");
         CommonLib.Windows.Forms.TextFormat.TextFontAndColor textProp = new CommonLib.Windows.Forms.TextFormat.TextFontAndColor("");
         List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor> textPropList = new List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor>();

         string text = (string)e.Argument;
         string dots = "";

         while (true)
         {
            if (bgw != null && bgw.CancellationPending)
            {
               if (e != null)
                  e.Cancel = true;

               break;
            }

            if (dots.Length < 3)
               dots += ".";
            else
               dots = ".";

            if (StringManip.GetPhraseWordIndexInText(text, "installing") > -1)
            {
               // set font to default text and color
               textProp.textFont = defaultTextProp.textFont;
               textProp.textColor = Color.FromArgb(157, 150, 19);
               textProp.wordIndex = StringManip.GetPhraseWordIndexInText(text, "installing");
               textProp.wordCount = 1;
               // set font style
               textProp.textFont = new Font(textProp.textFont.FontFamily, 13, FontStyle.Bold);
               textPropList.Add(textProp);

               // set font to default text and color
               textProp.textFont = defaultTextProp.textFont;
               textProp.textColor = Color.FromArgb(42, 176, 173);
               textProp.wordIndex = StringManip.GetPhraseWordIndexInText(text, "installing") + 1;
               textProp.wordCount = StringManip.GetPhraseWordIndexInText(text, "please wait") - textProp.wordIndex;
               // set font style
               textProp.textFont = new Font(textProp.textFont.FontFamily, 13, FontStyle.Bold);
               textPropList.Add(textProp);

               // set font to default text and color
               textProp.textFont = defaultTextProp.textFont;
               textProp.textColor = Color.FromArgb(157, 150, 19);
               textProp.wordIndex = StringManip.GetPhraseWordIndexInText(text, "please wait");
               textProp.wordCount = 10;
               // set font style
               textProp.textFont = new Font(textProp.textFont.FontFamily, 13, FontStyle.Bold);
               textPropList.Add(textProp);
            }
            else
            {
               // set font to default text and color
               textProp.textFont = defaultTextProp.textFont;
               textProp.textColor = Color.FromArgb(17, 75, 237);
               textProp.wordIndex = 0;
               textProp.wordCount = 100;
               // set font style
               textProp.textFont = new Font(textProp.textFont.FontFamily, 13, FontStyle.Bold);
               textPropList.Add(textProp);
            }

            UpdateStatusDisplayAtLine(text + dots, m_progressLine, textPropList);

            Thread.Sleep(500);
         }
      }

      public bool checkSoftwareRegistry(string softwareName, string setupFile, bool promptBeforeInstall, string setupArgs, string registryPath, string registryDisplayName, string registryType,
                                        string registryName, string registryValue, string registryCompareOperator,
                                      string exitCodesToIgnore, string spawnedProcessesToIgnore, bool rebootComputerAtCompletionRequested, bool rebootComputerImmediatelyRequested)
      {
         bool setupSuccessful = true;
         bool installSuccessful = true;
         bool installExecuted = false;
         string msg, indentation = String.Empty, statusMsg = "";

         CommonLib.Windows.Forms.TextFormat.TextFontAndColor defaultTextProp = new CommonLib.Windows.Forms.TextFormat.TextFontAndColor("");
         CommonLib.Windows.Forms.TextFormat.TextFontAndColor textProp = new CommonLib.Windows.Forms.TextFormat.TextFontAndColor("");
         List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor> textPropList1 = new List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor>();
         List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor> textPropList2 = new List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor>();

         // set font to default text and color
         textProp.textFont = defaultTextProp.textFont;
         textProp.textColor = Color.FromArgb(233, 31, 195);
         textProp.wordIndex = 3;
         textProp.wordCount = 100;
         // set font style
         textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
         textPropList1.Add(textProp);

         indentation = Common.getIndentation(1);
         msg = "\n" + indentation + "Checking installation of " + softwareName;

         int maxSoftwareCheckRetries = 15;
         int retriesCount = 1;
         while (true)
         {
            // if software application does not exist
            if (!checkSoftwareRegistrySetting(registryPath, registryDisplayName, registryType, registryName, registryValue, registryCompareOperator, ref statusMsg))
            {
               if (retriesCount == 1 && setupFile.Length > 0)
               {
                  installSuccessful = installSoftware(softwareName, setupFile, setupArgs, exitCodesToIgnore, spawnedProcessesToIgnore, promptBeforeInstall);

                  installExecuted = true;

                  if (!installSuccessful)
                     setupSuccessful = false;

               }

               if (!installExecuted || retriesCount == maxSoftwareCheckRetries || !installSuccessful)
               {
                  // set font to default text and color
                  textProp.textFont = defaultTextProp.textFont;
                  textProp.textColor = Common.getSetupStatusColor(Common.EXECUTION_STATUS.FAILURE);
                  textProp.wordIndex = 0;
                  textProp.wordCount = 2;
                  // set font style
                  textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
                  textPropList2.Add(textProp);

                  setupSuccessful = false;
                  break;
               }

               if (installExecuted && installSuccessful)
               {
                  // wait for all the registry settings to be applied before we check again
                  Thread.Sleep(1000);
               }
            }
            else
            {
               // set font to default text and color
               textProp.textFont = defaultTextProp.textFont;
               textProp.textColor = Common.getSetupStatusColor(Common.EXECUTION_STATUS.SUCCESS);
               textProp.wordIndex = 0;
               textProp.wordCount = 2;
               // set font style
               textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
               textPropList2.Add(textProp);

               statusMsg = " - PASSED";

               break;
            }

            retriesCount++;
         }

         if (installSuccessful)
         {
            UpdateStatusDisplayAndLogEvent(msg, msg, textPropList1);
            UpdateStatusDisplayAndLogEvent(statusMsg, statusMsg, textPropList2);

            if (installExecuted && rebootComputerAtCompletionRequested)
            {
               RebootComputer();

               if (rebootComputerImmediatelyRequested)
               {
                  Common.enableAutoRunAfterReboot("Auto_Win_Setup_Task", Application.ExecutablePath);

                  // raise event to signal that we need to stop the setup process for changes to take effect
                  SetupProcessNeedRestartToProceed();
               }
            }
         }

         return setupSuccessful;
      }

      bool checkSoftwareRegistrySetting(string registryPath, string registryDisplayName, string registryType,
         string registryName, string registryValue, string registryCompareOperator, ref string statusMsg)
      {
         bool setupSuccessful = true;
         string indentation = String.Empty, actualRegistryValue = "";
         RegistryHive regHive = RegistryHive.LocalMachine;

         RegistryKey rkSubKey = null;

         statusMsg = "";

         RegistryKey hklm = null;

         if (String.Equals(registryType, "local_machine", StringComparison.OrdinalIgnoreCase))
         {
            regHive = RegistryHive.LocalMachine;
         }
         else if (String.Equals(registryType, "classes_root", StringComparison.OrdinalIgnoreCase))
         {
            regHive = RegistryHive.ClassesRoot;
         }
         else if (String.Equals(registryType, "current_user", StringComparison.OrdinalIgnoreCase))
         {
            regHive = RegistryHive.CurrentUser;
         }
         else
         {
            statusMsg = " - FAILED. Registry type of \"" + registryType + "\" is invalid";
            setupSuccessful = false;
         }

         if (setupSuccessful)
         {
            List<string> registryPaths = new List<string>();
            if (registryDisplayName.Length > 0)
            {
               registryPaths = WindowsRegistry.GetRegistryDynamicUninstallKeys(regHive, registryDisplayName);

               if (registryPaths.Count == 0)
               {
                  statusMsg = " - FAILED. Unable to find any registry key associated with \"" + registryDisplayName + "\"";
                  setupSuccessful = false;
               }
            }
            else if (registryPath.Length > 0)
            {
               registryPaths.Add(registryPath);
            }

            hklm = RegistryKey.OpenBaseKey(regHive, RegistryView.Registry64);

            foreach (string regPath in registryPaths)
            {
               setupSuccessful = true;

               rkSubKey = hklm.OpenSubKey(regPath, false);

               if (rkSubKey != null)
               {
                  if (registryName.Length > 0)
                  {
                     List<string> list = new List<string>(rkSubKey.GetValueNames());

                     if (list.FindIndex(x => x.Equals(registryName, StringComparison.OrdinalIgnoreCase)) != -1)
                     {
                        actualRegistryValue = rkSubKey.GetValue(registryName).ToString();

                        if (Regex.IsMatch(actualRegistryValue, @"^[\.0-9]+$", RegexOptions.IgnoreCase) && Regex.IsMatch(registryValue, @"^[\.0-9]+$", RegexOptions.IgnoreCase))
                        {
                           if (softwareVersionComparisonSuccessful(registryValue, actualRegistryValue, registryCompareOperator))
                           {
                              statusMsg = " - PASSED";
                           }
                           else
                           {
                              statusMsg = " - FAILED. Actual registry value of \"" + actualRegistryValue + "\" is not " + getStringEquivalentOfComparisonOperator(registryCompareOperator) + " the base registry value of \"" + registryValue + "\"";
                              setupSuccessful = false;
                           }
                        }
                        else if (!String.Equals(actualRegistryValue, registryValue, StringComparison.OrdinalIgnoreCase))
                        {
                           statusMsg = " - FAILED. Actual registry value of \"" + actualRegistryValue + "\" is not " + getStringEquivalentOfComparisonOperator(registryCompareOperator) + " the base registry value of \"" + registryValue + "\"";
                           setupSuccessful = false;
                        }
                     }
                     else
                     {
                        statusMsg = " - FAILED. Unable to find registry key's value name \"" + registryName + "\"";
                        setupSuccessful = false;
                     }
                  }
               }
               else
               {
                  statusMsg = " - FAILED. Unable to find registry key \"" + registryPath + "\". Either the registry key is wrong or the software is not installed.";
                  setupSuccessful = false;
               }
            }
         }

         if (rkSubKey != null)
            rkSubKey.Close();

         if (hklm != null)
            hklm.Close();

         return setupSuccessful;
      }

      static bool softwareVersionComparisonSuccessful(string baseVersion, string actualVersion, string compareOperator)
      {
         bool compareSuccess = false;
         List<string> baseVersionParts = new List<string>(baseVersion.Split('.').ToList());
         List<string> actualVersionParts = new List<string>(actualVersion.Split('.').ToList());
         int actualVal = 0, expectedVal = 0;

         // remove any trailing .0
         for (int i = baseVersionParts.Count - 1; i >= 0; --i)
         {
            if (i != 0 && Regex.IsMatch(baseVersionParts[i], @"^[0]+$", RegexOptions.IgnoreCase))
            {
               baseVersionParts.RemoveAt(i);
            }
            else
               break;
         }

         // remove any trailing .0
         for (int i = actualVersionParts.Count - 1; i >= 0; --i)
         {
            if (i != 0 && Regex.IsMatch(actualVersionParts[i], @"^[0]+$", RegexOptions.IgnoreCase))
            {
               actualVersionParts.RemoveAt(i);
            }
            else
               break;
         }

         if (baseVersionParts.Count == 0)
            baseVersionParts.Add("0");

         if (actualVersionParts.Count == 0)
            actualVersionParts.Add("0");

         if (compareOperator.Length == 0)
            compareOperator = "eq";

         if (String.Equals(compareOperator, "eq", StringComparison.OrdinalIgnoreCase))
         {
            compareSuccess = actualAndBaseVersionsAreEqual(baseVersionParts, actualVersionParts);
         }
         else if (String.Equals(compareOperator, "ne", StringComparison.OrdinalIgnoreCase))
         {
            compareSuccess = !actualAndBaseVersionsAreEqual(baseVersionParts, actualVersionParts);
         }
         else if (String.Equals(compareOperator, "lte", StringComparison.OrdinalIgnoreCase))
         {
            bool isEqual = false;

            for (int i = 0; i < actualVersionParts.Count; i++)
            {
               if (baseVersionParts.Count >= i + 1)
               {
                  if (Int32.TryParse(actualVersionParts[i], out actualVal) && Int32.TryParse(baseVersionParts[i], out expectedVal))
                  {
                     if (actualVal < expectedVal)
                     {
                        isEqual = false;
                        compareSuccess = true;
                        break;
                     }
                     else if (actualVal > expectedVal)
                     {
                        isEqual = false;
                        break;
                     }
                     else if (i == actualVersionParts.Count - 1 && actualVersionParts.Count == baseVersionParts.Count)
                     {
                        isEqual = false;
                        compareSuccess = true;
                        break;
                     }
                     else
                        isEqual = true;
                  }
               }
            }

            if (isEqual && baseVersionParts.Count > actualVersionParts.Count)
               compareSuccess = true;
         }
         else if (String.Equals(compareOperator, "gte", StringComparison.OrdinalIgnoreCase))
         {
            for (int i = 0; i < actualVersionParts.Count; i++)
            {
               if (baseVersionParts.Count >= i + 1)
               {
                  if (Int32.TryParse(actualVersionParts[i], out actualVal) && Int32.TryParse(baseVersionParts[i], out expectedVal))
                  {
                     if (actualVal > expectedVal)
                     {
                        compareSuccess = true;
                        break;
                     }
                     else if (actualVal < expectedVal)
                        break;
                     else if (i == actualVersionParts.Count - 1 && actualVersionParts.Count == baseVersionParts.Count)
                     {
                        compareSuccess = true;
                        break;
                     }
                  }
               }
               else
               {
                  compareSuccess = true;
                  break;
               }
            }
         }
         else if (String.Equals(compareOperator, "gt", StringComparison.OrdinalIgnoreCase))
         {
            for (int i = 0; i < actualVersionParts.Count; i++)
            {
               if (baseVersionParts.Count >= i + 1)
               {
                  if (Int32.TryParse(actualVersionParts[i], out actualVal) && Int32.TryParse(baseVersionParts[i], out expectedVal))
                  {
                     if (actualVal > expectedVal)
                     {
                        compareSuccess = true;
                        break;
                     }
                  }
               }
               else
               {
                  compareSuccess = true;
                  break;
               }
            }
         }
         else if (String.Equals(compareOperator, "lt", StringComparison.OrdinalIgnoreCase))
         {
            compareSuccess = true;
            bool isEqual = false;

            for (int i = 0; i < actualVersionParts.Count; i++)
            {
               if (baseVersionParts.Count >= i + 1)
               {
                  if (Int32.TryParse(actualVersionParts[i], out actualVal) && Int32.TryParse(baseVersionParts[i], out expectedVal))
                  {
                     if (actualVal > expectedVal)
                     {
                        isEqual = false;
                        compareSuccess = false;
                        break;
                     }
                     else if (actualVal < expectedVal)
                     {
                        isEqual = false;
                        break;
                     }
                     else
                        isEqual = true;
                  }
               }
            }

            if (isEqual && baseVersionParts.Count == actualVersionParts.Count)
               compareSuccess = false;
         }

         return compareSuccess;
      }

      private static bool actualAndBaseVersionsAreEqual(List<string> baseVersionParts, List<string> actualVersionParts)
      {
         bool compareSuccess = true;
         int actualVal = 0, baseVal = 0;

         if (baseVersionParts.Count != actualVersionParts.Count)
            compareSuccess = false;
         else
         {
            for (int i = 0; i < actualVersionParts.Count; i++)
            {
               if (baseVersionParts.Count >= i + 1)
               {
                  if (Int32.TryParse(actualVersionParts[i], out actualVal) && Int32.TryParse(baseVersionParts[i], out baseVal))
                  {
                     if (actualVal != baseVal)
                     {
                        compareSuccess = false;
                        break;
                     }
                  }
               }
            }
         }

         return compareSuccess;
      }

      public static string getStringEquivalentOfComparisonOperator(string compareOperator)
      {
         string description = "";

         if (String.Equals(compareOperator, "eq", StringComparison.OrdinalIgnoreCase))
         {
            description = "equal to";
         }
         else if (String.Equals(compareOperator, "lte", StringComparison.OrdinalIgnoreCase))
         {
            description = "less than or equal to";
         }
         else if (String.Equals(compareOperator, "gte", StringComparison.OrdinalIgnoreCase))
         {
            description = "greater than or equal to";
         }
         else if (String.Equals(compareOperator, "gt", StringComparison.OrdinalIgnoreCase))
         {
            description = "greater than";
         }
         else if (String.Equals(compareOperator, "lt", StringComparison.OrdinalIgnoreCase))
         {
            description = "less than";
         }
         else if (String.Equals(compareOperator, "ne", StringComparison.OrdinalIgnoreCase))
         {
            description = "not equal to"; ;
         }

         return description;
      }

      // get all the necessary info if user choose to verify software by looking for exe name
      public void getInfoUsingExeVerification(List<ConfigFileManager.Ini_KeyValue<string>> iniKeys, ref string setupFile, ref string exeFile, ref bool promptBeforeInstall, ref bool rebootComputerAtCompletionRequested, ref bool rebootComputerImmediatelyRequested)
      {
         setupFile = "";
         exeFile = "";
         // for every key specified for the app to be installed/checked
         foreach (ConfigFileManager.Ini_KeyValue<string> iniKey in iniKeys)
         {
            if (Regex.IsMatch(iniKey.iniKeyName, @"app\d+_setup_file", RegexOptions.IgnoreCase))
            {
               setupFile = iniKey.alternateValue;
            }
            else if (Regex.IsMatch(iniKey.iniKeyName, @"app\d+_exe_file", RegexOptions.IgnoreCase))
            {
               exeFile = iniKey.iniValue;
            }
            else if (Regex.IsMatch(iniKey.iniKeyName, @"app\d+_setup_confirm_prompt", RegexOptions.IgnoreCase))
            {
               if (String.Equals(iniKey.iniValue, "yes", StringComparison.OrdinalIgnoreCase))
                  promptBeforeInstall = true;
               else
                  promptBeforeInstall = false;
            }
            else if (Regex.IsMatch(iniKey.iniKeyName, @"app\d+_reboot_computer_at_completion", RegexOptions.IgnoreCase))
            {
               if (String.Equals(iniKey.iniValue, "yes", StringComparison.OrdinalIgnoreCase) || String.Equals(iniKey.iniValue, "true", StringComparison.OrdinalIgnoreCase))
                  rebootComputerAtCompletionRequested = true;
               else
                  rebootComputerAtCompletionRequested = false;
            }
            else if (Regex.IsMatch(iniKey.iniKeyName, @"app\d+_reboot_computer_immediately", RegexOptions.IgnoreCase))
            {
               if (String.Equals(iniKey.iniValue, "yes", StringComparison.OrdinalIgnoreCase) || String.Equals(iniKey.iniValue, "true", StringComparison.OrdinalIgnoreCase))
                  rebootComputerImmediatelyRequested = true;
               else
                  rebootComputerImmediatelyRequested = false;
            }
         }
      }

      // get all the necessary info if user choose to verify software by looking at the registry
      public void getInfoUsingRegistryVerification(List<ConfigFileManager.Ini_KeyValue<string>> iniKeys, ref string registryPath, ref string registryDisplayName,
       ref string registryType, ref string registryName, ref string registryValue, ref string registryCompareOperator, ref string installArgs, ref string exitCodesToIgnore, ref string spawnedProcessesToIgnore)
      {
         registryPath = "";
         registryDisplayName = "";
         registryType = "";
         registryName = "";
         registryValue = "";
         registryCompareOperator = "";
         installArgs = "";
         exitCodesToIgnore = "";

         // for every key specified for the app to be installed/checked
         foreach (ConfigFileManager.Ini_KeyValue<string> iniKey in iniKeys)
         {
            if (Regex.IsMatch(iniKey.iniKeyName, @"app\d+_reg_path$", RegexOptions.IgnoreCase))
            {
               registryPath = iniKey.iniValue;
            }
            if (Regex.IsMatch(iniKey.iniKeyName, @"app\d+_reg_display_name$", RegexOptions.IgnoreCase))
            {
               registryDisplayName = iniKey.iniValue;
            }
            else if (Regex.IsMatch(iniKey.iniKeyName, @"app\d+_reg_type", RegexOptions.IgnoreCase))
            {
               registryType = iniKey.iniValue;
            }
            else if (Regex.IsMatch(iniKey.iniKeyName, @"app\d+_reg_name", RegexOptions.IgnoreCase))
            {
               registryName = iniKey.iniValue;
            }
            else if (Regex.IsMatch(iniKey.iniKeyName, @"app\d+_reg_value", RegexOptions.IgnoreCase))
            {
               registryValue = iniKey.iniValue;
            }
            else if (Regex.IsMatch(iniKey.iniKeyName, @"app\d+_reg_compare", RegexOptions.IgnoreCase))
            {
               registryCompareOperator = iniKey.iniValue;
            }
            else if (Regex.IsMatch(iniKey.iniKeyName, @"app\d+_setup_argument", RegexOptions.IgnoreCase))
            {
               installArgs = iniKey.iniValue;
            }
            else if (Regex.IsMatch(iniKey.iniKeyName, @"app\d+_exit_codes_to_ignore", RegexOptions.IgnoreCase))
            {
               exitCodesToIgnore = iniKey.iniValue;
            }
            else if (Regex.IsMatch(iniKey.iniKeyName, @"app\d+_spawned_processes_to_ignore", RegexOptions.IgnoreCase))
            {
               spawnedProcessesToIgnore = iniKey.iniValue;
            }
         }
      }

      bool processConfigInfo(KeyValuePair<ConfigFileManager.Ini_KeyValue<string>, List<ConfigFileManager.Ini_KeyValue<string>>> entry,
         ref KeyValuePair<ConfigFileManager.Ini_KeyValue<string>, List<ConfigFileManager.Ini_KeyValue<string>>> processedEntry)
      {
         bool setupSuccessful = true;
         ConfigFileManager.Ini_KeyValue<string> iniVal = new ConfigFileManager.Ini_KeyValue<string>("");
         ConfigFileManager.Ini_KeyValue<string> iniVal2 = new ConfigFileManager.Ini_KeyValue<string>("");
         string indentation = String.Empty;
         string msg = "", msg2 = "";
         Match regExMatch;

         Dictionary<ConfigFileManager.Ini_KeyValue<string>, List<ConfigFileManager.Ini_KeyValue<string>>> tempOneToManyDict = new Dictionary<ConfigFileManager.Ini_KeyValue<string>, List<ConfigFileManager.Ini_KeyValue<string>>>();
         List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor> textPropList = new List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor>();

         bool appVerifiedByCheckingRegistry = false;
         bool appVerifiedByCheckingExe = false;
         bool appRegistryTypeSpecified = false;
         string appId = "";
         string registryKeyType = "";

         appVerifiedByCheckingRegistry = false;
         appVerifiedByCheckingExe = false;
         appRegistryTypeSpecified = false;

         Dictionary<string, string> idToPath = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

         // for every key specified for the app to be installed
         foreach (ConfigFileManager.Ini_KeyValue<string> iniKey in entry.Value)
         {
            regExMatch = Regex.Match(iniKey.iniKeyName, @"(app\d+)_.+$", RegexOptions.IgnoreCase);

            if (regExMatch.Success)
               appId = regExMatch.Groups[1].Value;

            // user has specified the verification method
            if (Regex.IsMatch(iniKey.iniKeyName, @"app\d+_exe_file", RegexOptions.IgnoreCase))
            {
               appVerifiedByCheckingExe = true;
               break;
            }
            else if (Regex.IsMatch(iniKey.iniKeyName, @"(app\d+)_reg_path$", RegexOptions.IgnoreCase)
                || Regex.IsMatch(iniKey.iniKeyName, @"(app\d+)_reg_display_name$", RegexOptions.IgnoreCase))
            {
               appVerifiedByCheckingRegistry = true;
            }
            else if (Regex.IsMatch(iniKey.iniKeyName, @"(app\d+)_reg_type$", RegexOptions.IgnoreCase))
            {
               regExMatch = Regex.Match(iniKey.iniKeyName, @"(app\d+)_reg_type$", RegexOptions.IgnoreCase);

               registryKeyType = regExMatch.Groups[1].Value;
               appRegistryTypeSpecified = true;

               if (!WindowsRegistryManager.registryTypeIsValid(iniKey.iniValue))
               {
                  indentation = Common.getIndentation(1);
                  msg = "\n" + indentation + "- FAILED. "
                     + "\n" + indentation + "File: " + ConfigFileManager.ms_configGeneralInfo.pathAndNameOfConfigFile
                     + "\n" + indentation + "Section: " + iniKey.iniSectionName
                     + "\n" + indentation + "Key: " + iniKey.iniKeyName
                     + "\n" + indentation + "Value: " + iniKey.iniValue
                     + "\n" + indentation + "Error Description: The value is invalid the INI file.";

                  Common.formatConfigIniFailureMessage(msg, textPropList);

                  UpdateStatusDisplayAndLogEvent(msg, msg, textPropList);

                  setupSuccessful = false;

                  break;
               }
            }
            else if (Regex.IsMatch(iniKey.iniKeyName, @"app\d+_reg_compare", RegexOptions.IgnoreCase))
            {
               string description = SoftwareInstallManager.getStringEquivalentOfComparisonOperator(iniKey.iniValue);

               if (description.Length == 0)
               {
                  indentation = Common.getIndentation(1);
                  msg = "\n" + indentation + "- FAILED. "
                     + "\n" + indentation + "File: " + ConfigFileManager.ms_configGeneralInfo.pathAndNameOfConfigFile
                     + "\n" + indentation + "Section: " + iniKey.iniSectionName
                     + "\n" + indentation + "Key: " + iniKey.iniKeyName
                     + "\n" + indentation + "Value: " + iniKey.iniValue
                     + "\n" + indentation + "Error Description: The value is invalid the INI file.";

                  Common.formatConfigIniFailureMessage(msg, textPropList);

                  UpdateStatusDisplayAndLogEvent(msg, msg, textPropList);

                  setupSuccessful = false;

                  break;
               }
            }
            else if (Regex.IsMatch(iniKey.iniKeyName, @"app\d+_setup_file", RegexOptions.IgnoreCase) && iniKey.iniValue.Length > 0)
            {
               iniVal = iniKey;

               if (!Common.pathContainsMacro(iniKey.iniValue))
               {
                  iniVal.alternateValue = Path.GetFullPath(iniKey.iniValue);

                  // check only relative path
                  if (!Path.IsPathRooted(iniKey.iniValue) && (!File.Exists(iniVal.alternateValue)))
                  {
                     indentation = Common.getIndentation(1);
                     msg = "\n" + indentation + "- FAILED. "
                        + "\n" + indentation + "File: " + ConfigFileManager.ms_configGeneralInfo.pathAndNameOfConfigFile
                        + "\n" + indentation + "Section: " + iniKey.iniSectionName
                        + "\n" + indentation + "Key: " + iniKey.iniKeyName
                        + "\n" + indentation + "Value: " + iniKey.iniValue
                        + "\n" + indentation + "Error Description: The path defined in the value above is invalid. Translated path " + iniVal.alternateValue;

                     Common.formatConfigIniFailureMessage(msg, textPropList);

                     UpdateStatusDisplayAndLogEvent(msg, msg, textPropList);

                     setupSuccessful = false;

                     break;
                  }
               }
               else  // the path specified contains macros, we have to resolve macros
               {
                  iniVal.alternateValue = Common.resolveMacroPath(iniKey.iniValue);

                  if (Common.pathContainsCdDriveMacro(iniKey.iniValue))
                  {
                     if (iniVal.alternateValue.Length == 0)
                     {
                        setupSuccessful = false;
                        msg2 = PathManip.enumWindowsDesignatedPaths.CD_DRIVE.ToString() + " macro was defined. But no CD drive is found in the system";
                     }
                     //else
                     //{
                     //   setupSuccessful = Common.validatePathInCdDrive(iniVal.alternateValue, m_parentForm);
                     //   msg2 = "\"" + iniVal.alternateValue + "\" does not exist. Please insert a CD with that path";
                     //}

                     if (!setupSuccessful)
                     {
                        indentation = Common.getIndentation(1);
                        msg = "\n" + indentation + "- FAILED. "
                           + "\n" + indentation + "File: " + ConfigFileManager.ms_configGeneralInfo.pathAndNameOfConfigFile
                           + "\n" + indentation + "Section: " + iniKey.iniSectionName
                           + "\n" + indentation + "Key: " + iniKey.iniKeyName
                           + "\n" + indentation + "Value: " + iniKey.iniValue
                           + "\n" + indentation + "Error Description: " + msg2;

                        Common.formatConfigIniFailureMessage(msg, textPropList);

                        UpdateStatusDisplayAndLogEvent(msg, msg, textPropList);
                     }
                  }
               }
            }
            else if (Regex.IsMatch(iniKey.iniKeyName, @"(app\d+)_setup_argument$", RegexOptions.IgnoreCase))
            {
               MatchCollection matches = Regex.Matches(iniKey.iniValue, @"\{([^\{\}]+)\}", RegexOptions.IgnoreCase);

               if (matches.Count > 0)
               {
                  // each match can have multiple parts i.e. $1 $2...$n
                  for (int i = 0; i < matches.Count; i++)
                  {
                     // matches[i].Groups[0].Value is the actual macro itself, i.e. {keyname}
                     // matches[i].Groups[1].Value is the string inside curly brackets, i.e. keyname
                     string pattern = Regex.Escape(matches[i].Groups[1].Value);

                     // looking for a key that defines the actual path to be used in the argument
                     int matchedPathIndex = entry.Value.FindIndex(x => Regex.IsMatch(x.iniKeyName, pattern, RegexOptions.IgnoreCase));

                     // if the macro in the argument refers to another key the defines a path
                     if (matchedPathIndex >= 0 && !idToPath.ContainsKey(matches[i].Groups[0].Value))
                     {
                        string resolvedPath = Common.resolveMacroPath(entry.Value[matchedPathIndex].iniValue);

                        if (Common.pathContainsCdDriveMacro(entry.Value[matchedPathIndex].iniValue))
                        {
                           if (resolvedPath.Length == 0)
                           {
                              setupSuccessful = false;
                              msg2 = matches[i].Groups[1].Value + " macro was defined. But no CD drive is found in the system";
                           }
                           else
                           {
                              setupSuccessful = Common.validatePathInCdDrive(resolvedPath, m_parentForm);
                              msg2 = "\"" + resolvedPath + "\" does not exist. Please insert a CD with that path";
                           }

                           if (!setupSuccessful)
                           {
                              indentation = Common.getIndentation(1);
                              msg = "\n" + indentation + "- FAILED. "
                                 + "\n" + indentation + "File: " + ConfigFileManager.ms_configGeneralInfo.pathAndNameOfConfigFile
                                 + "\n" + indentation + "Section: " + iniKey.iniSectionName
                                 + "\n" + indentation + "Key: " + iniKey.iniKeyName
                                 + "\n" + indentation + "Value: " + iniKey.iniValue
                                 + "\n" + indentation + "Error Description: " + msg2;

                              Common.formatConfigIniFailureMessage(msg, textPropList);

                              UpdateStatusDisplayAndLogEvent(msg, msg, textPropList);
                           }
                        }

                        if (PathManip.PathIsFile(resolvedPath))
                           idToPath[matches[i].Groups[0].Value] = Path.GetDirectoryName(Path.GetFullPath(resolvedPath));
                        else
                           idToPath[matches[i].Groups[0].Value] = Path.GetFullPath(resolvedPath);
                     }
                     // if the macro refers to user's desktop, temp folder, cd drive
                     else if (Common.pathContainsMacro(matches[i].Groups[0].Value) && !idToPath.ContainsKey(matches[i].Groups[0].Value))
                     {
                        string resolvedPath = Common.resolveMacroPath(matches[i].Groups[0].Value);

                        if (Common.pathContainsCdDriveMacro(matches[i].Groups[0].Value))
                        {
                           if (resolvedPath.Length == 0)
                           {
                              setupSuccessful = false;
                              msg2 = matches[i].Groups[1].Value + " macro was defined. But no CD drive is found in the system";
                           }

                           if (!setupSuccessful)
                           {
                              indentation = Common.getIndentation(1);
                              msg = "\n" + indentation + "- FAILED. "
                                 + "\n" + indentation + "File: " + ConfigFileManager.ms_configGeneralInfo.pathAndNameOfConfigFile
                                 + "\n" + indentation + "Section: " + iniKey.iniSectionName
                                 + "\n" + indentation + "Key: " + iniKey.iniKeyName
                                 + "\n" + indentation + "Value: " + iniKey.iniValue
                                 + "\n" + indentation + "Error Description: " + msg2;

                              Common.formatConfigIniFailureMessage(msg, textPropList);

                              UpdateStatusDisplayAndLogEvent(msg, msg, textPropList);
                           }
                        }

                        idToPath[matches[i].Groups[0].Value] = resolvedPath;
                     }
                     else
                     {
                        indentation = Common.getIndentation(1);
                        msg = "\n" + indentation + "- FAILED. "
                           + "\n" + indentation + "File: " + ConfigFileManager.ms_configGeneralInfo.pathAndNameOfConfigFile
                           + "\n" + indentation + "Section: " + iniKey.iniSectionName
                           + "\n" + indentation + "Key: " + iniKey.iniKeyName
                           + "\n" + indentation + "Value: " + iniKey.iniValue
                           + "\n" + indentation + "Error Description: The value above specifies a variable \"" + matches[i].Groups[0].Value + "\" in the argument."
                                                   + " Key \"" + iniKey.iniKeyName + "_" + matches[i].Groups[1].Value
                                                   + "\" must be defined to specify the actual path for variable \"" + matches[i].Groups[0].Value + "\"";

                        Common.formatConfigIniFailureMessage(msg, textPropList);

                        UpdateStatusDisplayAndLogEvent(msg, msg, textPropList);

                        setupSuccessful = false;

                        break;
                     }
                  }
               }

               if (!setupSuccessful)
                  break;
            }
         }

         if (setupSuccessful)
         {

            if (appVerifiedByCheckingExe)
            {
               tempOneToManyDict[entry.Key] = new List<ConfigFileManager.Ini_KeyValue<string>>(entry.Value);
            }
            else if (appVerifiedByCheckingRegistry)
            {
               if (!appRegistryTypeSpecified)
               {
                  indentation = Common.getIndentation(1);
                  msg = "\n" + indentation + "- FAILED. "
                     + "\n" + indentation + "File: " + ConfigFileManager.ms_configGeneralInfo.pathAndNameOfConfigFile
                     + "\n" + indentation + "Section: " + entry.Key.iniSectionName
                     + "\n" + indentation + "Error Description: The key name " + appId + "_reg_type is missing in the above section in the INI file.";

                  Common.formatConfigIniFailureMessage(msg, textPropList);

                  UpdateStatusDisplayAndLogEvent(msg, msg, textPropList);

                  setupSuccessful = false;

               }

               if (setupSuccessful)
               {
                  tempOneToManyDict[entry.Key] = new List<ConfigFileManager.Ini_KeyValue<string>>(entry.Value);
               }
            }
            else
            {
               indentation = Common.getIndentation(1);
               msg = "\n" + indentation + "- FAILED. "
                  + "\n" + indentation + "File: " + ConfigFileManager.ms_configGeneralInfo.pathAndNameOfConfigFile
                  + "\n" + indentation + "Section: " + entry.Key.iniSectionName
                  + "\n" + indentation + "Error Description: One of the following 3 key names must be defined: " + appId + "_exe_file, " + appId + "_reg_path, " + appId + "_reg_display_name, ";

               Common.formatConfigIniFailureMessage(msg, textPropList);

               UpdateStatusDisplayAndLogEvent(msg, msg, textPropList);

               setupSuccessful = false;
            }
         }

         if (setupSuccessful)
         {
            int matchedArgumentIndex = -1;

            matchedArgumentIndex = tempOneToManyDict[entry.Key].FindIndex(x => Regex.IsMatch(x.iniKeyName, @"app\d+_setup_file$", RegexOptions.IgnoreCase));

            if (matchedArgumentIndex >= 0)
            {
               tempOneToManyDict[entry.Key][matchedArgumentIndex] = iniVal;
            }

            if (idToPath.Count > 0)
            {
               matchedArgumentIndex = -1;
               foreach (KeyValuePair<string, string> item in idToPath)
               {
                  if (matchedArgumentIndex < 0)
                  {
                     matchedArgumentIndex = tempOneToManyDict[entry.Key].FindIndex(x => Regex.IsMatch(x.iniKeyName, @"app\d+_setup_argument$", RegexOptions.IgnoreCase));
                     iniVal2 = tempOneToManyDict[entry.Key][matchedArgumentIndex];
                  }

                  if (matchedArgumentIndex >= 0)
                  {
                     iniVal2.iniValue = Regex.Replace(iniVal2.iniValue, Regex.Escape(item.Key), item.Value, RegexOptions.IgnoreCase);
                  }
               }

               if (matchedArgumentIndex >= 0)
                  tempOneToManyDict[entry.Key][matchedArgumentIndex] = iniVal2;
            }

            processedEntry = new KeyValuePair<ConfigFileManager.Ini_KeyValue<string>, List<ConfigFileManager.Ini_KeyValue<string>>>(entry.Key, new List<ConfigFileManager.Ini_KeyValue<string>>(tempOneToManyDict[entry.Key]));
         }

         return setupSuccessful;
      }
   }
}
