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

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

namespace All_Purpose_Auto_Setup
{
   class ConfigFileManager
   {
      // each of the enumeration in here should match a section name in the INI file
      public enum enum_INI_SECTION_NAMES
      {
         General,
         Global_Settings,
         GUTS_Info,
         Install_Software,
         Paths_Creation,
         Paths_Sharing,
         Env_Variables,
         Setup_Step_Manager,
         GUTS_Pre_Build_Assistance,
         Files_Transfer_Packages,
         GUTS_Config_File,
         Windows_Shortcuts,
         Files_Folders_Removal,
         Run_Applications,
         Windows_Features,
         Windows_Registry
      }

      // this is used to verify INI file and flag errors in the INI file for user to fix
      public struct Ini_KeyValue<T>
      {
         public string iniSectionName;
         public string iniKeyName;
         public string iniValue;
         public T alternateValue; // user-defined value, this could be a bool, int, anything

         public Ini_KeyValue(T tvar)
         {
            iniSectionName = String.Empty;
            iniKeyName = String.Empty;
            iniValue = String.Empty;
            alternateValue = tvar;
         }
      }

      public struct ConfigGeneralInfo
      {
         public IniFileManip iniFile;
         public string pathAndNameOfConfigFile;

         public bool is64BitPlatform;
         public bool is32BitPlatform;

         public Ini_KeyValue<string> programName;
         public Ini_KeyValue<string> applicationPlatform;
      }

      public struct ConfigGlobalSettings
      {
         public Ini_KeyValue<bool> suppressAllPrompts;
      }

      // 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;

      Form m_parentForm;
      public static ConfigGeneralInfo ms_configGeneralInfo;
      public static ConfigGlobalSettings ms_configGlobalSettings;

      public ConfigFileManager(Form parentForm, string configFile)
      {
         m_parentForm = parentForm;

         ms_configGeneralInfo.pathAndNameOfConfigFile = configFile;
         ms_configGeneralInfo.programName = new Ini_KeyValue<string>("");
         ms_configGeneralInfo.applicationPlatform = new Ini_KeyValue<string>("");

         ms_configGeneralInfo.is32BitPlatform = false;
         ms_configGeneralInfo.is64BitPlatform = false;

         ms_configGeneralInfo.iniFile = new IniFileManip(ms_configGeneralInfo.pathAndNameOfConfigFile);

         ms_configGlobalSettings.suppressAllPrompts = new Ini_KeyValue<bool>(false);

         parseConfigInfo();
      }

      private void parseConfigInfo()
      {
         readGeneralSection(ms_configGeneralInfo.iniFile);
         readGlobalSection(ms_configGeneralInfo.iniFile);
      }

      public static void parseConfigInfo(string iniSectionName, List<Ini_KeyValue<string>> configInfo)
      {
         string[] keyDataPair;
         string[] sectionData;
         Ini_KeyValue<string> iniVal = new Ini_KeyValue<string>("");

         if (configInfo == null)
            configInfo = new List<Ini_KeyValue<string>>();

         sectionData = ms_configGeneralInfo.iniFile.ReadSectionData(iniSectionName);
         foreach (string line in sectionData)
         {
            keyDataPair = line.Split('=');

            if (keyDataPair.Length == 2)
            {
               iniVal.iniSectionName = iniSectionName;
               iniVal.iniKeyName = keyDataPair[0];
               iniVal.iniValue = keyDataPair[1];

               configInfo.Add(iniVal) ;
            }
         }
      }

      public static void parseConfigInfo(string iniSectionName, Dictionary<string,string> configInfo)
      {
         string[] keyDataPair;
         string[] sectionData;

         if (configInfo == null)
            configInfo = new Dictionary<string,string>();

         sectionData = ms_configGeneralInfo.iniFile.ReadSectionData(iniSectionName);
         foreach (string line in sectionData)
         {
            keyDataPair = line.Split('=');

            if (keyDataPair.Length == 2)
               configInfo[keyDataPair[0]] = keyDataPair[1];
         }
      }

      public void readGeneralSection(IniFileManip iniFile)
      {
         string[] keyDataPair;
         Dictionary<string, string> keyAndValuePair = new Dictionary<string, string>();
         Dictionary<string, string> keyAndValuePair2 = new Dictionary<string, string>();
         Ini_KeyValue<bool> bIniVal = new Ini_KeyValue<bool>(false);
         Ini_KeyValue<string> iniVal = new Ini_KeyValue<string>("");
         Ini_KeyValue<string> iniVal2 = new Ini_KeyValue<string>("");

         string sectionName;

         string[] sectionData;

         sectionName = enum_INI_SECTION_NAMES.General.ToString();
         sectionData = iniFile.ReadSectionData(sectionName);

         ms_configGeneralInfo.programName.iniKeyName = "program_name";
         ms_configGeneralInfo.programName.iniSectionName = sectionName;

         ms_configGeneralInfo.applicationPlatform.iniKeyName = "Application_Platform";
         ms_configGeneralInfo.applicationPlatform.iniSectionName = sectionName;

         foreach (string line in sectionData)
         {
            keyDataPair = line.Split('=');

            if (String.Equals(keyDataPair[0], ms_configGeneralInfo.programName.iniKeyName, StringComparison.OrdinalIgnoreCase))
            {
               if (keyDataPair.Length == 2)
               {
                  ms_configGeneralInfo.programName.iniValue = keyDataPair[1];
               }
            }
            else if (String.Equals(keyDataPair[0], ms_configGeneralInfo.applicationPlatform.iniKeyName, StringComparison.OrdinalIgnoreCase))
            {
               if (keyDataPair.Length == 2)
               {
                  ms_configGeneralInfo.applicationPlatform.iniValue = keyDataPair[1];
               }
               if (String.Equals(ms_configGeneralInfo.applicationPlatform.iniValue, "32", StringComparison.OrdinalIgnoreCase))
                  ms_configGeneralInfo.is32BitPlatform = true;
               else if (String.Equals(ms_configGeneralInfo.applicationPlatform.iniValue, "64", StringComparison.OrdinalIgnoreCase))
                  ms_configGeneralInfo.is64BitPlatform = true;
               else
                  ms_configGeneralInfo.is64BitPlatform = true;
            }
         }
      }

      public void readGlobalSection(IniFileManip iniFile)
      {
         string[] keyDataPair;
         Dictionary<string, string> keyAndValuePair = new Dictionary<string, string>();
         Dictionary<string, string> keyAndValuePair2 = new Dictionary<string, string>();
         Ini_KeyValue<bool> bIniVal = new Ini_KeyValue<bool>(false);
         Ini_KeyValue<string> iniVal = new Ini_KeyValue<string>("");
         Ini_KeyValue<string> iniVal2 = new Ini_KeyValue<string>("");

         string sectionName;

         string[] sectionData;

         sectionName = enum_INI_SECTION_NAMES.Global_Settings.ToString();
         sectionData = iniFile.ReadSectionData(sectionName);

         ms_configGlobalSettings.suppressAllPrompts.iniKeyName = "Suppress_All_Prompts";
         ms_configGlobalSettings.suppressAllPrompts.iniSectionName = sectionName;

         foreach (string line in sectionData)
         {
            keyDataPair = line.Split('=');

            if (String.Equals(keyDataPair[0], ms_configGlobalSettings.suppressAllPrompts.iniKeyName, StringComparison.OrdinalIgnoreCase))
            {
               if (keyDataPair.Length == 2)
               {
                  ms_configGlobalSettings.suppressAllPrompts.iniValue = keyDataPair[1];
               }

               if (String.Equals(ms_configGlobalSettings.suppressAllPrompts.iniValue, "true", StringComparison.OrdinalIgnoreCase)
                  ||
                  String.Equals(ms_configGlobalSettings.suppressAllPrompts.iniValue, "yes", StringComparison.OrdinalIgnoreCase))
                  ms_configGlobalSettings.suppressAllPrompts.alternateValue = true;
               else
                  ms_configGlobalSettings.suppressAllPrompts.alternateValue = false;
            }
         }
      }

      public static void readOneToManyDictionary(IniFileManip iniFile, string sectionName, string leftDictPattern, string rightDictPattern, Dictionary<Ini_KeyValue<string>, List<Ini_KeyValue<string>>> dict)
      {
         string[] keyDataPair, splitStrings, splitStrings2;
         Match regExMatch;
         Dictionary<string, string> keyAndValuePair = new Dictionary<string, string>();
         Dictionary<string, string> keyAndValuePair2 = new Dictionary<string, string>();
         Ini_KeyValue<bool> bIniVal = new Ini_KeyValue<bool>(false);
         Ini_KeyValue<string> iniVal = new Ini_KeyValue<string>("");
         Ini_KeyValue<string> iniVal2 = new Ini_KeyValue<string>("");

         string[] sectionData;

         string iniEntryDelimiter = "[ini_entry_separator]";
         string iniKeyValueDelimiter = "[key_value_separator]";

         sectionData = iniFile.ReadSectionData(sectionName);
         foreach (string line in sectionData)
         {
            keyDataPair = line.Split('=');

            if (keyDataPair.Length == 2)
               keyDataPair[1] = Regex.Replace(keyDataPair[1], @"\{equal_sign\}", "=");

            regExMatch = Regex.Match(keyDataPair[0], leftDictPattern, RegexOptions.IgnoreCase);

            // if item belongs to the left side of the dictionary
            if (regExMatch.Success && keyDataPair.Length == 2)
            {
               // if there's not already an item that exists in the dictionary
               if (!keyAndValuePair.ContainsKey(regExMatch.Groups[1].Value) && keyDataPair[1].Length > 0)
               {
                  // generate the key information
                  iniVal.iniSectionName = sectionName;
                  iniVal.iniKeyName = keyDataPair[0];
                  iniVal.iniValue = keyDataPair[1];

                  // if there are items on the right of the dictionary that's associated with the item on the left side
                  if (keyAndValuePair2.ContainsKey(regExMatch.Groups[1].Value))
                  {
                     splitStrings = keyAndValuePair2[regExMatch.Groups[1].Value].Split(new string[] { iniEntryDelimiter }, StringSplitOptions.RemoveEmptyEntries);

                     foreach (string keyValuePairItem in splitStrings)
                     {
                        splitStrings2 = keyValuePairItem.Split(new string[] { iniKeyValueDelimiter }, StringSplitOptions.None);

                        // generate value information
                        iniVal2.iniSectionName = sectionName;
                        iniVal2.iniKeyName = splitStrings2[0];
                        iniVal2.iniValue = splitStrings2[1];

                        if (dict.ContainsKey(iniVal))
                           dict[iniVal].Add(iniVal2);
                        else
                           dict[iniVal] = new List<Ini_KeyValue<string>>() { iniVal2 };

                        if (!keyAndValuePair.ContainsKey(regExMatch.Groups[1].Value))
                        {
                           keyAndValuePair[regExMatch.Groups[1].Value] = keyDataPair[0] + iniKeyValueDelimiter + keyDataPair[1];
                        }
                     }

                     keyAndValuePair2.Remove(regExMatch.Groups[1].Value);
                  }
                  else  // store the Ini struct of the left hand side of the dictionary
                     keyAndValuePair[regExMatch.Groups[1].Value] = keyDataPair[0] + iniKeyValueDelimiter + keyDataPair[1];
               }
            }
            else
            {
               regExMatch = Regex.Match(keyDataPair[0], rightDictPattern, RegexOptions.IgnoreCase);

               // if the item belong to the right side of the dictionary
               if (regExMatch.Success && keyDataPair.Length == 2)
               {
                  // is this key/value is associated with the corresponding value of the left hand side of the dictionary
                  if (keyAndValuePair.ContainsKey(regExMatch.Groups[1].Value))
                  {
                     // get the ini properties of the left side of the dictionary
                     splitStrings2 = keyAndValuePair[regExMatch.Groups[1].Value].Split(new string[] { iniKeyValueDelimiter }, StringSplitOptions.RemoveEmptyEntries);

                     // generate the key information
                     iniVal.iniSectionName = sectionName;
                     iniVal.iniKeyName = splitStrings2[0];
                     iniVal.iniValue = splitStrings2[1];

                     // generate value information
                     iniVal2.iniSectionName = sectionName;
                     iniVal2.iniKeyName = keyDataPair[0];
                     iniVal2.iniValue = keyDataPair[1];

                     // save the key/value for this app name
                     if (dict.ContainsKey(iniVal))
                        dict[iniVal].Add(iniVal2);
                     else
                        dict[iniVal] = new List<Ini_KeyValue<string>>() { iniVal2 };
                  }
                  else
                  {
                     if (keyAndValuePair2.ContainsKey(regExMatch.Groups[1].Value))
                     {
                        keyAndValuePair2[regExMatch.Groups[1].Value] += iniEntryDelimiter + keyDataPair[0] + iniKeyValueDelimiter + keyDataPair[1];
                     }
                     else
                        keyAndValuePair2[regExMatch.Groups[1].Value] = keyDataPair[0] + iniKeyValueDelimiter + keyDataPair[1];
                  }
               }
            }
         }

         if (keyAndValuePair.Count > 0)
         {
            // remove  entries in keyAndValuePair that have already been saved in the dictionary
            foreach (KeyValuePair<ConfigFileManager.Ini_KeyValue<string>, List<ConfigFileManager.Ini_KeyValue<string>>> entry in dict)
            {
               regExMatch = Regex.Match(entry.Key.iniKeyName, leftDictPattern, RegexOptions.IgnoreCase);
               // if item belongs to the left side of the dictionary
               if (regExMatch.Success)
               {
                  if (keyAndValuePair.ContainsKey(regExMatch.Groups[1].Value))
                     keyAndValuePair.Remove(regExMatch.Groups[1].Value);
               }
            }

            // for each group of items, if only the left side of the dictionary is defined, add it to the dictionary anyway
            foreach (KeyValuePair<string, string> entry in keyAndValuePair)
            {
               // break down the elements of the left side of dictionary
               splitStrings2 = entry.Value.Split(new string[] { iniKeyValueDelimiter }, StringSplitOptions.RemoveEmptyEntries);

               // generate the key information
               iniVal.iniSectionName = sectionName;
               iniVal.iniKeyName = splitStrings2[0];
               iniVal.iniValue = splitStrings2[1];

               if (!dict.ContainsKey(iniVal))
                  dict[iniVal] = new List<Ini_KeyValue<string>>();
            }
         }
      }

      public void readOneToOneDictionary(IniFileManip iniFile, string sectionName, string leftDictPattern, string rightDictPattern, Dictionary<Ini_KeyValue<string>, Ini_KeyValue<string>> dict)
      {
         string[] keyDataPair, splitStrings2;
         Match regExMatch;
         Dictionary<string, string> keyAndValuePair = new Dictionary<string, string>();
         Dictionary<string, string> keyAndValuePair2 = new Dictionary<string, string>();
         Ini_KeyValue<bool> bIniVal = new Ini_KeyValue<bool>(false);
         Ini_KeyValue<string> iniVal = new Ini_KeyValue<string>("");
         Ini_KeyValue<string> iniVal2 = new Ini_KeyValue<string>("");

         string[] sectionData;

         sectionData = iniFile.ReadSectionData(sectionName);
         foreach (string path in sectionData)
         {
            keyDataPair = path.Split('=');

            if (keyDataPair.Length == 2)
               keyDataPair[1] = Regex.Replace(keyDataPair[1], @"\{equal_sign\}", "=");

            regExMatch = Regex.Match(keyDataPair[0], leftDictPattern, RegexOptions.IgnoreCase);

            // if item belongs to the left side of the dictionary
            if (regExMatch.Success && keyDataPair.Length == 2)
            {
               // if there's not an existing item already in the dictionary
               if (!keyAndValuePair.ContainsKey(regExMatch.Groups[1].Value))
               {
                  // if the item on the right side is associated to the item on the left side of the dictionary
                  if (keyAndValuePair2.ContainsKey(regExMatch.Groups[1].Value))
                  {
                     // generate the key information
                     iniVal.iniSectionName = sectionName;
                     iniVal.iniKeyName = keyDataPair[0];
                     iniVal.iniValue = keyDataPair[1];

                     splitStrings2 = keyAndValuePair2[regExMatch.Groups[1].Value].Split('|');

                     // generate the value information
                     iniVal2.iniSectionName = sectionName;
                     iniVal2.iniKeyName = splitStrings2[0];
                     iniVal2.iniValue = splitStrings2[1];

                     dict[iniVal] = iniVal2;
                     keyAndValuePair2.Remove(regExMatch.Groups[1].Value);
                  }
                  else
                     keyAndValuePair[regExMatch.Groups[1].Value] = keyDataPair[0] + "|" + keyDataPair[1];
               }
            }

            regExMatch = Regex.Match(keyDataPair[0], rightDictPattern, RegexOptions.IgnoreCase);

            // if item belongs to the right side of the dictionary
            if (regExMatch.Success && keyDataPair.Length == 2)
            {
               // if item on the left is associated with the item on the right side of the dictionary
               if (keyAndValuePair.ContainsKey(regExMatch.Groups[1].Value))
               {
                  // get the ini properties of the left side of the dictionary
                  splitStrings2 = keyAndValuePair[regExMatch.Groups[1].Value].Split('|');

                  // generate the key information
                  iniVal.iniSectionName = sectionName;
                  iniVal.iniKeyName = splitStrings2[0];
                  iniVal.iniValue = splitStrings2[1];

                  // generate the value information
                  iniVal2.iniSectionName = sectionName;
                  iniVal2.iniKeyName = keyDataPair[0];
                  iniVal2.iniValue = keyDataPair[1];

                  // create a new entry in the dictionary
                  dict[iniVal] = iniVal2;
               }
               else
               {
                  keyAndValuePair2[regExMatch.Groups[1].Value] = keyDataPair[0] + "|" + keyDataPair[1];
               }
            }
         }

         if (keyAndValuePair.Count > 0)
         {
            // remove  entries in keyAndValuePair that have already been saved in the dictionary
            foreach (KeyValuePair<ConfigFileManager.Ini_KeyValue<string>, ConfigFileManager.Ini_KeyValue<string>> entry in dict)
            {
               regExMatch = Regex.Match(entry.Key.iniKeyName, leftDictPattern, RegexOptions.IgnoreCase);
               // if item belongs to the left side of the dictionary
               if (regExMatch.Success)
               {
                  if (keyAndValuePair.ContainsKey(regExMatch.Groups[1].Value))
                     keyAndValuePair.Remove(regExMatch.Groups[1].Value);
               }
            }

            // for each group of items, if only the left side of the dictionary is defined, add it to the dictionary anyway
            foreach (KeyValuePair<string, string> entry in keyAndValuePair)
            {
               // break down the elements of the left side of dictionary
               splitStrings2 = entry.Value.Split('|');

               // generate the key information
               iniVal.iniSectionName = sectionName;
               iniVal.iniKeyName = splitStrings2[0];
               iniVal.iniValue = splitStrings2[1];

               iniVal2.iniSectionName = "";
               iniVal2.iniKeyName = "";
               iniVal2.iniValue = "";

               if (!dict.ContainsKey(iniVal))
                  dict[iniVal] = iniVal2;
            }
         }
      }      
   }
}
