using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using CommonLib.Misc;
using CommonLib.Windows.Misc;
using Microsoft.Win32;

namespace All_Purpose_Auto_Setup
{
   class WindowsRegistryManager
   {
      // 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;

      // this dictionary stores registry path and its associated properties
      // it would look like this:
      // [ reg1_path ]  ->  List[reg1_action,reg1_type, etc]
      // [ reg2_path ]  ->  List[reg2_action,reg2_type, etc]
      public Dictionary<ConfigFileManager.Ini_KeyValue<string>, List<ConfigFileManager.Ini_KeyValue<string>>> regPathToVariousThings = new Dictionary<ConfigFileManager.Ini_KeyValue<string>, List<ConfigFileManager.Ini_KeyValue<string>>>();

      Form m_parentForm;

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

      public bool ModifyWindowsRegistry(string sectionName)
      {
         bool setupSuccessful = true;
         string msg;
         string 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> textPropList = new List<CommonLib.Windows.Forms.TextFormat.TextFontAndColor>();

         string registryPath, registryType, registryAction;

         // 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 = 100;
         // 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-=Modifying Windows Registry=-";
         UpdateStatusDisplayAndLogEvent(msg, msg, textPropList);

         regPathToVariousThings.Clear();

         ConfigFileManager.readOneToManyDictionary(ConfigFileManager.ms_configGeneralInfo.iniFile,
                    sectionName, @"(reg\d+)_path$", @"(reg\d+)_.+", regPathToVariousThings);

         setupSuccessful = verifyConfigInfo();

         if (setupSuccessful)
         {

            if (regPathToVariousThings.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 set";
               UpdateStatusDisplayAndLogEvent(msg, msg, textPropList);
            }

            // for every app name to be installed/checked
            foreach (KeyValuePair<ConfigFileManager.Ini_KeyValue<string>, List<ConfigFileManager.Ini_KeyValue<string>>> entry in regPathToVariousThings)
            {
               registryPath = entry.Key.iniValue;
               registryType = "";
               registryAction = "";

               Dictionary<string, string> registryNameValuePairs = new Dictionary<string, string>();

               getRegistryInfo(entry.Value, ref registryAction, ref registryType, registryNameValuePairs);

               registryAction = registryAction.Trim();
               registryType = registryType.Trim();

               setupSuccessful = modifyRegistryKey(registryPath, registryType, registryAction, registryNameValuePairs);

               if (!setupSuccessful)
                  break;
            }
         }

         return setupSuccessful;
      }

      public bool modifyRegistryKey(string registryPath, string registryType, string registryAction, Dictionary<string, string> registryNameValuePairs)
      {
         bool setupSuccessful = true;
         string errMsg = "", 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>();
         Match regExMatch;

         indentation = Common.getIndentation(1);

         if (registryNameValuePairs.Count > 0)
         {
            foreach (KeyValuePair<string, string> entry in registryNameValuePairs)
            {
               msg1 = "";

               if (String.Equals(registryAction, "delete", StringComparison.OrdinalIgnoreCase))
               {
                  setupSuccessful = WindowsRegistry.DeleteRegistryValueName(registryPath, getRegistryHive(registryType), entry.Key, ref errMsg);

                  msg1 = "\n" + indentation + "Deleting value name " + entry.Key + " in " + registryPath;
               }
               else  // add to registry
               {
                  // match single-value registry value name
                  regExMatch = Regex.Match(entry.Value, @"^\s*value\s*:\s*(.*[^\s])\s*,\s*type\s*:\s*(.*[a-zA-Z])\s*", RegexOptions.IgnoreCase);

                  if (regExMatch.Success)
                  {
                     List<string> pair = new List<string>() { regExMatch.Groups[1].Value, regExMatch.Groups[2].Value };

                     if (getRegistryValueKind(pair[1]) != RegistryValueKind.MultiString)
                        setupSuccessful = WindowsRegistry.AddOrModifyRegistryKeySingleValue(registryPath, getRegistryHive(registryType), entry.Key, getRegistryValueKind(pair[1]), pair[0], ref errMsg);
                     else
                     {
                        setupSuccessful = false;
                        errMsg = "Expected a value name of type single value (string, dword, qword). Actual type is multi-string";
                     }

                     msg1 = "\n" + indentation + "Setting value name " + entry.Key + "=" + pair[0] + " in " + registryPath;
                  }
                  else
                  {
                     // match multi-string registry value name
                     regExMatch = Regex.Match(entry.Value, @"^(\s*:\s*value\s*:\s*.*[^\s])\s*,\s*type\s*:\s*(.*[a-zA-Z])\s*", RegexOptions.IgnoreCase);

                     if (regExMatch.Success)
                     {
                        List<string> pair = new List<string>() { regExMatch.Groups[1].Value, regExMatch.Groups[2].Value };
                        List<string> values = new List<string>(regExMatch.Groups[1].Value.Split(new string[] { ":value:" }, StringSplitOptions.RemoveEmptyEntries));

                        if (getRegistryValueKind(pair[1]) == RegistryValueKind.MultiString)
                        {
                           setupSuccessful = WindowsRegistry.AddOrModifyRegistryKeyMultiStringValue(registryPath, getRegistryHive(registryType), entry.Key, values, ref errMsg);
                        }
                        else
                        {
                           setupSuccessful = false;
                           errMsg = "Expected a value name of type multi-string. Actual type is single value (string, qword, dword)";
                        }

                        msg1 = "\n" + indentation + "Setting value name " + entry.Key + "={";

                        string tempStr = "";
                        foreach (string val in values)
                        {
                           if (tempStr.Length > 0)
                              tempStr += ";";
                           tempStr += val;
                        }

                        msg1 += tempStr + "} in " + registryPath;
                     }
                  }
               }

               if (msg1.Length > 0)
               {

                  // set font to default text and color
                  textProp.textFont = defaultTextProp.textFont;
                  textProp.textColor = Color.FromArgb(233, 31, 195);
                  textProp.wordIndex = 3;
                  textProp.wordCount = StringManip.GetPhraseWordIndexInText(msg1, "in " + registryPath) - textProp.wordIndex;
                  // set font style
                  textProp.textFont = new Font(textProp.textFont, FontStyle.Bold);
                  textPropList1.Add(textProp);

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

                  if (errMsg.Length > 0 && !setupSuccessful)
                  {
                     // 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. " + errMsg;
                  }
                  else if (errMsg.Length > 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);
                     textPropList2.Add(textProp);

                     msg2 = " - SKIPPED. " + errMsg;
                  }
                  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);

                     msg2 = " - SUCCESS.";
                  }

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

               }

               if (!setupSuccessful)
                  break;
            }
         }
         else  // add/delete registry path
         {
            if (String.Equals(registryAction, "delete", StringComparison.OrdinalIgnoreCase))
            {
               setupSuccessful = WindowsRegistry.DeleteRegistryKey(registryPath, getRegistryHive(registryType), ref errMsg);

               msg1 = "\n" + indentation + "Deleting key " + registryPath;
            }
            else // add name/value to registry key
            {
               // only creating path
               setupSuccessful = WindowsRegistry.AddOrModifyRegistryKeySingleValue(registryPath, getRegistryHive(registryType), "", RegistryValueKind.String, "", ref errMsg);

               msg1 = "\n" + indentation + "Creating key " + registryPath;
            }

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

            if (errMsg.Length > 0 && !setupSuccessful)
            {
               // 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. " + errMsg;
            }
            else if (errMsg.Length > 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);
               textPropList2.Add(textProp);

               msg2 = " - SKIPPED. " + errMsg;
            }
            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);

               msg2 = " - SUCCESS.";
            }

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

         return setupSuccessful;
      }

      public RegistryValueKind getRegistryValueKind(string valueKind)
      {
         RegistryValueKind regValueKind = RegistryValueKind.String;

         if (String.Equals(valueKind, WindowsRegistry.valueKind.DWORD.ToString(), StringComparison.OrdinalIgnoreCase))
         {
            regValueKind = RegistryValueKind.DWord;
         }
         else if (String.Equals(valueKind, WindowsRegistry.valueKind.QWORD.ToString(), StringComparison.OrdinalIgnoreCase))
         {
            regValueKind = RegistryValueKind.QWord;
         }
         else if (String.Equals(valueKind, WindowsRegistry.valueKind.MULTI_STRING.ToString(), StringComparison.OrdinalIgnoreCase))
         {
            regValueKind = RegistryValueKind.MultiString;
         }

         return regValueKind;
      }

      public void getRegistryInfo(List<ConfigFileManager.Ini_KeyValue<string>> iniKeys, ref string action,
         ref string registryType, Dictionary<string, string> nameValuePairs)
      {
         Match regExMatch;
         registryType = "";
         action = "";

         // for every key specified for the app to be installed/checked
         foreach (ConfigFileManager.Ini_KeyValue<string> iniKey in iniKeys)
         {
            if (Regex.IsMatch(iniKey.iniKeyName, @"reg\d+_action$", RegexOptions.IgnoreCase))
            {
               action = iniKey.iniValue;
            }
            else if (Regex.IsMatch(iniKey.iniKeyName, @"reg\d+_type", RegexOptions.IgnoreCase))
            {
               registryType = iniKey.iniValue;
            }
            else if (Regex.IsMatch(iniKey.iniKeyName, @"reg\d+_name_value_pair", RegexOptions.IgnoreCase))
            {
               regExMatch = Regex.Match(iniKey.iniValue, @"\[\s*name\s*:\s*(.*[^\s])\s*,\s*value\s*:\s*(.*[^\s])\s*,\s*type\s*:\s*(.*[a-zA-Z])\s*\]", RegexOptions.IgnoreCase);

               if (regExMatch.Success)
               {
                  if (!nameValuePairs.ContainsKey(regExMatch.Groups[1].Value))
                  {
                     nameValuePairs[regExMatch.Groups[1].Value] = "value:" + regExMatch.Groups[2].Value + ",type:" + regExMatch.Groups[3].Value;
                  }
               }
               else
               {
                  regExMatch = Regex.Match(iniKey.iniValue, @"\[\s*name\s*:\s*([^,]*[^\s,])\s*\]", RegexOptions.IgnoreCase);

                  if (regExMatch.Success)
                  {
                     if (!nameValuePairs.ContainsKey(regExMatch.Groups[1].Value))
                     {
                        nameValuePairs[regExMatch.Groups[1].Value] = "";
                     }
                  }
               }

               if (!regExMatch.Success)
               {
                  regExMatch = Regex.Match(iniKey.iniValue, @"\[\s*name\s*:\s*(.*[^\s])\s*,\s*:\s*value\s*:\s*(.*[^\s])\s*,\s*type\s*:\s*(.*[a-zA-Z])\s*\]", RegexOptions.IgnoreCase);

                  if (regExMatch.Success)
                  {
                     if (!nameValuePairs.ContainsKey(regExMatch.Groups[1].Value))
                     {
                        nameValuePairs[regExMatch.Groups[1].Value] = ":value:" + regExMatch.Groups[2].Value + ",type:" + regExMatch.Groups[3].Value;
                     }
                  }
               }
            }
         }
      }

      public static bool registryTypeIsValid(string regType)
      {
         bool isValid = false;

         if (String.Equals(regType, "local_machine", StringComparison.OrdinalIgnoreCase)
             ||
            String.Equals(regType, "classes_root", StringComparison.OrdinalIgnoreCase)
             ||
            String.Equals(regType, "current_user", StringComparison.OrdinalIgnoreCase))
         {
            isValid = true;
         }

         return isValid;
      }

      public static RegistryHive getRegistryHive(string regType)
      {
         RegistryHive regHive = RegistryHive.LocalMachine;

         if (String.Equals(regType, "local_machine", StringComparison.OrdinalIgnoreCase))
            regHive = RegistryHive.LocalMachine;
         else if (String.Equals(regType, "classes_root", StringComparison.OrdinalIgnoreCase))
            regHive = RegistryHive.ClassesRoot;
         else if (String.Equals(regType, "current_user", StringComparison.OrdinalIgnoreCase))
            regHive = RegistryHive.CurrentUser;
         else
            regHive = RegistryHive.LocalMachine;

         return regHive;
      }

      bool verifyConfigInfo()
      {
         bool setupSuccessful = true;
         ConfigFileManager.Ini_KeyValue<string> iniVal = new ConfigFileManager.Ini_KeyValue<string>("");
         string indentation = String.Empty;
         string msg;
         Match regExMatch;

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

         string registryAction = "";
         string regId = "";
         bool registryActionDefined = false;
         bool registryTypeDefined = false;

         // for every registry path defined
         foreach (KeyValuePair<ConfigFileManager.Ini_KeyValue<string>, List<ConfigFileManager.Ini_KeyValue<string>>> entry in regPathToVariousThings)
         {
            registryActionDefined = false;
            registryTypeDefined = false;

            // for every key specified for this registry path
            foreach (ConfigFileManager.Ini_KeyValue<string> iniKey in entry.Value)
            {
               regExMatch = Regex.Match(iniKey.iniKeyName, @"(reg\d+)_.+$", RegexOptions.IgnoreCase);

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

               if (Regex.IsMatch(iniKey.iniKeyName, @"reg\d+_action", RegexOptions.IgnoreCase))
               {
                  registryActionDefined = true;
                  registryAction = iniKey.iniValue;
               }
               else if (Regex.IsMatch(iniKey.iniKeyName, @"reg\d+_type", RegexOptions.IgnoreCase))
               {
                  registryTypeDefined = true;

                  if (!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;
                  }
               }
            }

            if (registryActionDefined)
            {
               // for every key specified to be added/modified/delete
               foreach (ConfigFileManager.Ini_KeyValue<string> iniKey in entry.Value)
               {
                  if (Regex.IsMatch(iniKey.iniKeyName, @"reg\d+_name_value_pair", RegexOptions.IgnoreCase))
                  {
                     if (String.Equals(registryAction, "delete", StringComparison.OrdinalIgnoreCase))
                     {
                        regExMatch = Regex.Match(iniKey.iniValue, @"\[\s*name\s*:\s*([^,]*[^\s,])\s*\]", RegexOptions.IgnoreCase);
                     }
                     else
                     {
                        // match single value
                        regExMatch = Regex.Match(iniKey.iniValue, @"\[\s*name\s*:\s*(.*[^\s])\s*,\s*value\s*:\s*(.*[^\s])\s*,\s*type\s*:\s*(.*[a-zA-Z])\s*\]", RegexOptions.IgnoreCase);

                        // match multi value
                        if (!regExMatch.Success)
                           regExMatch = Regex.Match(iniKey.iniValue, @"\[\s*name\s*:\s*(.*[^\s])\s*,\s*:\s*value\s*:\s*(.*[^\s])\s*,\s*type\s*:\s*(.*[a-zA-Z])\s*\]", RegexOptions.IgnoreCase);
                     }

                     if (!regExMatch.Success)
                     {
                        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;
                     }
                  }
               }
            }

            if (!setupSuccessful)
               break;

            if (!registryActionDefined)
            {
               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 " + regId + "_action is missing in the above section in the INI file.";

               Common.formatConfigIniFailureMessage(msg, textPropList);

               UpdateStatusDisplayAndLogEvent(msg, msg, textPropList);

               setupSuccessful = false;

               break;
            }
            else if (!registryTypeDefined)
            {
               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 " + regId + "_type is missing in the above section in the INI file.";

               Common.formatConfigIniFailureMessage(msg, textPropList);

               UpdateStatusDisplayAndLogEvent(msg, msg, textPropList);

               setupSuccessful = false;

               break;
            }
         }

         return setupSuccessful;
      }
   }
}
