context: using System.Collections.Generic;
  using System.IO;
  using System.Linq;
  using System.Text;
  using System.Windows.Forms;
  using System.Xml.Serialization;
  using GitCommands;
  using GitUI.CommandsDialogs;
  using GitUI.Editor;
  using GitUI.Script;
  using Microsoft;
  using ResourceManager;
  
  namespace GitUI.Hotkey
  {
      internal static class HotkeySettingsManager
      {
          #region Serializer
          private static XmlSerializer? _serializer;
  
          /// <summary>Lazy-loaded Serializer for HotkeySettings[].</summary>
          private static XmlSerializer Serializer => _serializer ??= new XmlSerializer(typeof(HotkeySettings[]), new[] { typeof(HotkeyCommand) });
  
          #endregion
  
          private static readonly HashSet<Keys> _usedKeys = new();
  
          /// <summary>
          /// Returns whether the hotkey is already assigned.
          /// </summary>
          public static bool IsUniqueKey(Keys keyData)
          {
              return _usedKeys.Contains(keyData);
          }
  
          public static HotkeyCommand[] LoadHotkeys(string name)
          {
              HotkeySettings settings = new();
              HotkeySettings scriptKeys = new();
              var allSettings = LoadSettings();
  
              UpdateUsedKeys(allSettings);
  
              foreach (var setting in allSettings)
              {
                  if (setting.Name == name)
                  {
                      settings = setting;
                  }
  
                  if (setting.Name == "Scripts")
                  {
                      scriptKeys = setting;
                  }
              }
  
              // append general hotkeys to every form
              Validates.NotNull(settings.Commands);
              Validates.NotNull(scriptKeys.Commands);
              var allKeys = new HotkeyCommand[settings.Commands.Length + scriptKeys.Commands.Length];
              settings.Commands.CopyTo(allKeys, 0);
              scriptKeys.Commands.CopyTo(allKeys, settings.Commands.Length);
  
              return allKeys;
          }
  
          public static HotkeySettings[] LoadSettings()
          {
              // Get the default settings
              var defaultSettings = CreateDefaultSettings();
              var loadedSettings = LoadSerializedSettings();
  
              MergeIntoDefaultSettings(defaultSettings, loadedSettings);
  
              return defaultSettings;
          }
  
          private static void UpdateUsedKeys(HotkeySettings[] settings)
          {
              _usedKeys.Clear();
  
              foreach (var setting in settings)
              {
                  if (setting.Commands is not null)
                  {
                      foreach (var command in setting.Commands)
                      {
                          _usedKeys.Add(command.KeyData);
                      }
                  }
              }
          }
  
          /// <summary>Serializes and saves the supplied settings.</summary>
          public static void SaveSettings(HotkeySettings[] settings)
          {
              try
              {
                  UpdateUsedKeys(settings);
  
                  StringBuilder str = new();
                  using StringWriter writer = new(str);
                  Serializer.Serialize(writer, settings);
                  AppSettings.SerializedHotkeys = str.ToString();
              }
              catch
              {
                  // ignore
              }
          }
  
          internal static void MergeIntoDefaultSettings(HotkeySettings[] defaultSettings, HotkeySettings[]? loadedSettings)
          {
              if (loadedSettings is null)
              {
                  return;
              }
  
              Dictionary<string, HotkeyCommand> defaultCommands = new();
  
              FillDictionaryWithCommands();
              AssignHotkeysFromLoaded();
  
              void AssignHotkeysFromLoaded()
              {
                  foreach (var setting in loadedSettings)
                  {
                      if (setting.Commands is not null && setting.Name is not null)
                      {
                          foreach (var command in setting.Commands)
                          {
                              string dictKey = CalcDictionaryKey(setting.Name, command.CommandCode);
                              if (defaultCommands.TryGetValue(dictKey, out var defaultCommand))
                              {
                                  defaultCommand.KeyData = command.KeyData;
                              }
                          }
                      }
                  }
              }
  
              void FillDictionaryWithCommands()
              {
                  foreach (var setting in defaultSettings)
                  {
                      if (setting.Commands is not null && setting.Name is not null)
                      {
                          foreach (var command in setting.Commands)
                          {
                              string dictKey = CalcDictionaryKey(setting.Name, command.CommandCode);
                              defaultCommands.Add(dictKey, command);
                          }
                      }
                  }
              }
  
              string CalcDictionaryKey(string settingName, int commandCode) => settingName + ":" + commandCode;
          }
  
          private static HotkeySettings[]? LoadSerializedSettings()
          {
              MigrateSettings();
  
              if (!string.IsNullOrWhiteSpace(AppSettings.SerializedHotkeys))
              {
                  return LoadSerializedSettings(AppSettings.SerializedHotkeys);
              }
  
              return null;
          }
  
          private static HotkeySettings[]? LoadSerializedSettings(string serializedHotkeys)
          {
              try
              {
                  using StringReader reader = new(serializedHotkeys);
                  return (HotkeySettings[])Serializer.Deserialize(reader);
              }
              catch
              {
                  return null;
              }
          }
  
          private static void MigrateSettings()
          {
              if (AppSettings.SerializedHotkeys is null)
              {
                  Properties.Settings.Default.Upgrade();
                  if (!string.IsNullOrWhiteSpace(Properties.Settings.Default.Hotkeys))
                  {
                      HotkeySettings[]? settings = LoadSerializedSettings(Properties.Settings.Default.Hotkeys);
                      if (settings is null)
                      {
                          AppSettings.SerializedHotkeys = " "; // mark settings as migrated
                      }
                      else
                      {
                          SaveSettings(settings);
                      }
                  }
                  else
                  {
                      AppSettings.SerializedHotkeys = " "; // mark settings as migrated
                  }
              }
          }
  
          public static HotkeySettings[] CreateDefaultSettings()
          {
              HotkeyCommand Hk(object en, Keys k) => new((int)en, en.ToString()) { KeyData = k };
  
              const Keys OpenWithDifftoolHotkey = Keys.F3;
              const Keys OpenWithDifftoolFirstToLocalHotkey = Keys.Alt | Keys.F3;
              const Keys OpenWithDifftoolSelectedToLocalHotkey = Keys.Shift | Keys.Alt | Keys.F3;
              const Keys OpenAsTempFileHotkey = Keys.Control | Keys.F3;
              const Keys OpenAsTempFileWithHotkey = Keys.Shift | Keys.Control | Keys.F3;
              const Keys EditFileHotkey = Keys.F4;
  
gt:             const Keys OpenFileHotkey = Keys.Shift | Keys.F4;