context: /*  This file is part of Chummer5a.
   *
   *  Chummer5a is free software: you can redistribute it and/or modify
   *  it under the terms of the GNU General Public License as published by
   *  the Free Software Foundation, either version 3 of the License, or
   *  (at your option) any later version.
   *
   *  Chummer5a is distributed in the hope that it will be useful,
   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *  GNU General Public License for more details.
   *
   *  You should have received a copy of the GNU General Public License
   *  along with Chummer5a.  If not, see <http://www.gnu.org/licenses/>.
   *
   *  You can obtain the full source code for Chummer5a at
   *  https://github.com/chummer5a/chummer5a
   */
  
  using System;
  using System.Collections.Generic;
  using System.Collections.Specialized;
  using System.ComponentModel;
  using System.Diagnostics;
  using System.Drawing;
  using System.Globalization;
  using System.Linq;
  using System.Runtime.CompilerServices;
  using System.Text;
  using System.Threading.Tasks;
  using System.Windows.Forms;
  using System.Xml;
  using System.Xml.XPath;
  using Chummer.Annotations;
  using Chummer.Backend.Attributes;
  using NLog;
  
  namespace Chummer.Backend.Equipment
  {
      /// <summary>
      /// Standard Character Gear.
      /// </summary>
      [HubClassTag("SourceID", true, "Name", "Extra")]
      [DebuggerDisplay("{DisplayName(GlobalSettings.InvariantCultureInfo, GlobalSettings.DefaultLanguage)}")]
      public sealed class Gear : IHasChildrenAndCost<Gear>, IHasName, IHasInternalId, IHasXmlDataNode, IHasMatrixAttributes,
          IHasNotes, ICanSell, IHasLocation, ICanEquip, IHasSource, IHasRating, INotifyMultiplePropertyChanged, ICanSort,
          IHasStolenProperty, ICanPaste, IHasWirelessBonus, IHasGear, ICanBlackMarketDiscount, IDisposable
      {
          private static Logger Log { get; } = LogManager.GetCurrentClassLogger();
          private Guid _guiID;
          private Guid _guiSourceID;
          private string _strName = string.Empty;
          private string _strCategory = string.Empty;
          private string _strMaxRating = string.Empty;
          private string _strMinRating = string.Empty;
          private string _strRatingLabel = "String_Rating";
          private int _intRating;
          private decimal _decQty = 1.0m;
          private string _strCapacity = string.Empty;
          private string _strArmorCapacity = string.Empty;
          private string _strAvail = string.Empty;
          private decimal _decCostFor = 1.0m;
          private string _strDeviceRating = string.Empty;
          private string _strCost = string.Empty;
          private string _strWeight = string.Empty;
          private string _strSource = string.Empty;
          private string _strPage = string.Empty;
          private string _strExtra = string.Empty;
          private string _strCanFormPersona = string.Empty;
          private string _strAmmoForWeaponType = string.Empty;
          private bool _blnBonded;
          private bool _blnEquipped = true;
          private bool _blnWirelessOn = true;
          private XmlNode _nodBonus;
          private XmlNode _nodWirelessBonus;
          private XmlNode _nodWeaponBonus;
          private XmlNode _nodFlechetteWeaponBonus;
          private Guid _guiWeaponID = Guid.Empty;
          private readonly TaggedObservableCollection<Gear> _lstChildren = new TaggedObservableCollection<Gear>();
          private string _strNotes = string.Empty;
          private Color _colNotes = ColorManager.HasNotesColor;
          private Location _objLocation;
          private readonly Character _objCharacter;
          private int _intChildCostMultiplier = 1;
          private int _intChildAvailModifier;
          private bool _blnDiscountCost;
          private string _strGearName = string.Empty;
          private string _strParentID = string.Empty;
          private int _intMatrixCMBonus;
          private int _intMatrixCMFilled;
          private string _strForcedValue = string.Empty;
          private bool _blnAllowRename;
          private string _strAttack = string.Empty;
          private string _strSleaze = string.Empty;
          private string _strDataProcessing = string.Empty;
          private string _strFirewall = string.Empty;
          private string _strAttributeArray = string.Empty;
          private string _strModAttack = string.Empty;
          private string _strModSleaze = string.Empty;
          private string _strModDataProcessing = string.Empty;
          private string _strModFirewall = string.Empty;
          private string _strModAttributeArray = string.Empty;
          private string _strProgramLimit = string.Empty;
          private string _strOverclocked = "None";
          private bool _blnCanSwapAttributes;
          private int _intSortOrder;
          private bool _blnStolen;
          private bool _blnIsFlechetteAmmo;
  
          #region Constructor, Create, Save, Load, and Print Methods
  
          public Gear(Character objCharacter)
          {
              // Create the GUID for the new piece of Gear.
              _guiID = Guid.NewGuid();
              _objCharacter = objCharacter;
  
              _lstChildren.AddTaggedCollectionChanged(this, ChildrenOnCollectionChanged);
          }
  
          private void ChildrenOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
          {
              switch (e.Action)
              {
                  case NotifyCollectionChangedAction.Add:
                      foreach (Gear objNewItem in e.NewItems)
                          objNewItem.Parent = this;
                      this.RefreshMatrixAttributeArray();
                      break;
  
                  case NotifyCollectionChangedAction.Remove:
                      foreach (Gear objOldItem in e.OldItems)
                          objOldItem.Parent = null;
                      this.RefreshMatrixAttributeArray();
                      break;
  
                  case NotifyCollectionChangedAction.Replace:
                      foreach (Gear objOldItem in e.OldItems)
                          objOldItem.Parent = null;
                      foreach (Gear objNewItem in e.NewItems)
                          objNewItem.Parent = this;
                      this.RefreshMatrixAttributeArray();
                      break;
  
                  case NotifyCollectionChangedAction.Reset:
                      this.RefreshMatrixAttributeArray();
                      break;
              }
          }
  
          /// Create a Gear from an XmlNode and return the TreeNodes for it.
          /// <param name="objXmlGear">XmlNode to create the object from.</param>
          /// <param name="intRating">Selected Rating for the Gear.</param>
          /// <param name="lstWeapons">List of Weapons that should be added to the character.</param>
          /// <param name="strForceValue">Value to forcefully select for any ImprovementManager prompts.</param>
          /// <param name="blnAddImprovements">Whether or not Improvements should be added to the character.</param>
          /// <param name="blnCreateChildren">Whether or not child Gear should be created.</param>
          /// <param name="blnSkipSelectForms">Whether or not to skip forms that are created for bonuses. Use only when creating Gear for previews in selection forms.</param>
          public void Create(XmlNode objXmlGear, int intRating, ICollection<Weapon> lstWeapons, string strForceValue = "",
              bool blnAddImprovements = true, bool blnCreateChildren = true, bool blnSkipSelectForms = false)
          {
              if (objXmlGear == null)
                  return;
              _strForcedValue = strForceValue;
              if (!objXmlGear.TryGetField("id", Guid.TryParse, out _guiSourceID))
              {
                  Log.Warn(new object[] { "Missing id field for armor xmlnode", objXmlGear });
                  Utils.BreakIfDebug();
              }
              else
              {
                  _objCachedMyXmlNode = null;
                  _objCachedMyXPathNode = null;
              }
  
              if (objXmlGear.TryGetStringFieldQuickly("name", ref _strName))
              {
                  _objCachedMyXmlNode = null;
                  _objCachedMyXPathNode = null;
              }
  
              if (objXmlGear.TryGetStringFieldQuickly("category", ref _strCategory))
              {
                  _objCachedMyXmlNode = null;
                  _objCachedMyXPathNode = null;
              }
  
              objXmlGear.TryGetStringFieldQuickly("avail", ref _strAvail);
              objXmlGear.TryGetStringFieldQuickly("capacity", ref _strCapacity);
              objXmlGear.TryGetStringFieldQuickly("armorcapacity", ref _strArmorCapacity);
              objXmlGear.TryGetDecFieldQuickly("costfor", ref _decCostFor);
              _decQty = _decCostFor;
              if (!objXmlGear.TryGetStringFieldQuickly("cost", ref _strCost))
                  _strCost = string.Empty;
              if (!objXmlGear.TryGetStringFieldQuickly("weight", ref _strWeight))
                  _strWeight = string.Empty;
              _nodBonus = objXmlGear["bonus"];
              _nodWirelessBonus = objXmlGear["wirelessbonus"];
              _blnWirelessOn = false;
              objXmlGear.TryGetStringFieldQuickly("ratinglabel", ref _strRatingLabel);
              objXmlGear.TryGetStringFieldQuickly("rating", ref _strMaxRating);
              if (_strMaxRating == "0")
                  _strMaxRating = string.Empty;
              objXmlGear.TryGetStringFieldQuickly("minrating", ref _strMinRating);
              if (!objXmlGear.TryGetMultiLineStringFieldQuickly("altnotes", ref _strNotes))
                  objXmlGear.TryGetMultiLineStringFieldQuickly("notes", ref _strNotes);
  
              string sNotesColor = ColorTranslator.ToHtml(ColorManager.HasNotesColor);
              objXmlGear.TryGetStringFieldQuickly("notesColor", ref sNotesColor);
              _colNotes = ColorTranslator.FromHtml(sNotesColor);
              _intRating = Math.Max(Math.Min(intRating, MaxRatingValue), MinRatingValue);
              objXmlGear.TryGetStringFieldQuickly("devicerating", ref _strDeviceRating);
              objXmlGear.TryGetInt32FieldQuickly("matrixcmbonus", ref _intMatrixCMBonus);
              objXmlGear.TryGetStringFieldQuickly("source", ref _strSource);
              objXmlGear.TryGetStringFieldQuickly("page", ref _strPage);
              objXmlGear.TryGetStringFieldQuickly("canformpersona", ref _strCanFormPersona);
              objXmlGear.TryGetStringFieldQuickly("ammoforweapontype", ref _strAmmoForWeaponType);
              objXmlGear.TryGetInt32FieldQuickly("childcostmultiplier", ref _intChildCostMultiplier);
              objXmlGear.TryGetInt32FieldQuickly("childavailmodifier", ref _intChildAvailModifier);
              objXmlGear.TryGetBoolFieldQuickly("allowrename", ref _blnAllowRename);
              objXmlGear.TryGetBoolFieldQuickly("stolen", ref _blnStolen);
              objXmlGear.TryGetBoolFieldQuickly("isflechetteammo", ref _blnIsFlechetteAmmo);
  
              if (string.IsNullOrEmpty(Notes))
              {
                  Notes = CommonFunctions.GetBookNotes(objXmlGear, Name, CurrentDisplayName, Source, Page,
                      DisplayPage(GlobalSettings.Language), _objCharacter);
              }
  
              // Check for a Custom name
              if (_strName == "Custom Item")
              {
                  if (!string.IsNullOrEmpty(_strForcedValue))
                  {
                      string strCustomName =
                          LanguageManager.GetString(_strForcedValue, GlobalSettings.DefaultLanguage, false);
                      if (string.IsNullOrEmpty(strCustomName))
                          strCustomName = _strForcedValue;
                      _strName = strCustomName;
                      _objCachedMyXmlNode = null;
                      _objCachedMyXPathNode = null;
                  }
                  else if (!blnSkipSelectForms)
                  {
                      using (SelectText frmPickText = new SelectText
                             {
                                 PreventXPathErrors = true,
                                 Description = LanguageManager.GetString("String_CustomItem_SelectText")
                             })
                      {
                          // Make sure the dialogue window was not canceled.
                          if (frmPickText.ShowDialogSafe(_objCharacter) == DialogResult.Cancel)
                          {
                              _guiID = Guid.Empty;
                              return;
                          }
  
                          string strCustomName = LanguageManager.GetString(frmPickText.SelectedValue,
                                                                           GlobalSettings.DefaultLanguage, false);
                          if (string.IsNullOrEmpty(strCustomName))
                              strCustomName =
                                  _objCharacter.ReverseTranslateExtra(frmPickText.SelectedValue);
                          _strName = strCustomName;
                          _objCachedMyXmlNode = null;
                          _objCachedMyXPathNode = null;
                      }
                  }
              }
  
              // Check for a Variable Cost.
              if (!string.IsNullOrEmpty(_strCost) && _strCost.StartsWith("Variable(", StringComparison.Ordinal) &&
                  string.IsNullOrEmpty(_strForcedValue))
              {
                  string strFirstHalf = _strCost.TrimStartOnce("Variable(", true).TrimEndOnce(')');
                  string strSecondHalf = string.Empty;
                  int intHyphenIndex = strFirstHalf.IndexOf('-');
                  if (intHyphenIndex != -1)
                  {
                      if (intHyphenIndex + 1 < strFirstHalf.Length)
                          strSecondHalf = strFirstHalf.Substring(intHyphenIndex + 1);
                      strFirstHalf = strFirstHalf.Substring(0, intHyphenIndex);
                  }
  
                  if (!blnSkipSelectForms)
                  {
                      decimal decMin;
                      decimal decMax = decimal.MaxValue;
                      if (intHyphenIndex != -1)
                      {
                          decMin = Convert.ToDecimal(strFirstHalf, GlobalSettings.InvariantCultureInfo);
                          decMax = Convert.ToDecimal(strSecondHalf, GlobalSettings.InvariantCultureInfo);
                      }
                      else
                          decMin = Convert.ToDecimal(strFirstHalf.FastEscape('+'), GlobalSettings.InvariantCultureInfo);
  
                      if (decMin != 0 || decMax != decimal.MaxValue)
                      {
                          if (decMax > 1000000)
                              decMax = 1000000;
                          DialogResult eResult = Program.GetFormForDialog(_objCharacter).DoThreadSafeFunc(x =>
                          {
                              using (SelectNumber frmPickNumber
                                     = new SelectNumber(_objCharacter.Settings.MaxNuyenDecimals)
                                     {
                                         Minimum = decMin,
                                         Maximum = decMax,
                                         Description = string.Format(
                                             GlobalSettings.CultureInfo,
                                             LanguageManager.GetString("String_SelectVariableCost"),
                                             DisplayNameShort(GlobalSettings.Language)),
                                         AllowCancel = false
                                     })
                              {
                                  if (frmPickNumber.ShowDialogSafe(x) != DialogResult.Cancel)
                                      _strCost = frmPickNumber.SelectedValue.ToString(
                                          GlobalSettings.InvariantCultureInfo);
                                  return frmPickNumber.DialogResult;
                              }
                          });
                          if (eResult == DialogResult.Cancel)
                          {
                              _guiID = Guid.Empty;
                              return;
                          }
                      }
                      else
                          _strCost = strFirstHalf;
                  }
                  else
                      _strCost = strFirstHalf;
              }
  
              if (!blnSkipSelectForms)
              {
                  // If the Gear is Ammunition, ask the user to select a Weapon Category for it to be limited to.
                  string strAmmoWeaponType = string.Empty;
                  bool blnDoExtra = false;
                  if (objXmlGear.TryGetStringFieldQuickly("ammoforweapontype", ref strAmmoWeaponType))
                      blnDoExtra = objXmlGear.SelectSingleNode("ammoforweapontype/@noextra")?.Value != bool.TrueString;
                  if (!string.IsNullOrEmpty(strAmmoWeaponType) && blnDoExtra)
                  {
                      SelectWeaponCategory frmPickWeaponCategory = new SelectWeaponCategory(_objCharacter)
                      {
                          Description = LanguageManager.GetString("String_SelectWeaponCategoryAmmo"),
                          WeaponType = strAmmoWeaponType
                      };
                      if (!string.IsNullOrEmpty(_strForcedValue) &&
                          !_strForcedValue.Equals(_strName, StringComparison.Ordinal))
                          frmPickWeaponCategory.OnlyCategory = _strForcedValue;
  
                      if (frmPickWeaponCategory.ShowDialogSafe(_objCharacter)
                          != DialogResult.OK)
                      {
                          _guiID = Guid.Empty;
                          return;
                      }
  
                      if (!string.IsNullOrEmpty(frmPickWeaponCategory.SelectedCategory))
                          _strExtra = frmPickWeaponCategory.SelectedCategory;
                  }
              }
  
              // Add Gear Weapons if applicable.
              using (XmlNodeList xmlWeaponList = objXmlGear.SelectNodes("addweapon"))
              {
                  if (xmlWeaponList != null)
                  {
                      XmlDocument objXmlWeaponDocument = _objCharacter.LoadData("weapons.xml");
  
                      // More than one Weapon can be added, so loop through all occurrences.
                      foreach (XmlNode objXmlAddWeapon in xmlWeaponList)
                      {
                          string strLoopID = objXmlAddWeapon.InnerText;
                          XmlNode objXmlWeapon = strLoopID.IsGuid()
                              ? objXmlWeaponDocument.SelectSingleNode(
                                  "/chummer/weapons/weapon[id = " + strLoopID.CleanXPath() + ']')
                              : objXmlWeaponDocument.SelectSingleNode(
                                  "/chummer/weapons/weapon[name = " + strLoopID.CleanXPath() + ']');
  
                          if (objXmlWeapon != null)
                          {
                              int intAddWeaponRating = 0;
                              string strLoopRating = objXmlAddWeapon.Attributes["rating"]?.InnerText;
                              if (!string.IsNullOrEmpty(strLoopRating))
                              {
                                  strLoopRating = strLoopRating.CheapReplace("{Rating}",
                                      () => Rating.ToString(GlobalSettings.InvariantCultureInfo));
                                  int.TryParse(strLoopRating, NumberStyles.Any, GlobalSettings.InvariantCultureInfo, out intAddWeaponRating);
                              }
  
                              Weapon objGearWeapon = new Weapon(_objCharacter);
                              objGearWeapon.Create(objXmlWeapon, lstWeapons, true, blnAddImprovements,
                                                   blnSkipSelectForms, intAddWeaponRating);
                              objGearWeapon.ParentID = InternalId;
                              objGearWeapon.Cost = "0";
                              if (Guid.TryParse(objGearWeapon.InternalId, out _guiWeaponID))
                                  lstWeapons.Add(objGearWeapon);
                              else
                                  _guiWeaponID = Guid.Empty;
                          }
                      }
                  }
              }
  
              // If the item grants a bonus, pass the information to the Improvement Manager.
              if (Bonus != null && !blnSkipSelectForms)
              {
                  // Do not apply the Improvements if this is a Focus, unless we're specifically creating a Weapon Focus. This is to avoid creating the Foci's Improvements twice (once when it's first added
                  // to the character which is incorrect, and once when the Focus is actually Bonded).
                  bool blnApply = !((_strCategory == "Foci" || _strCategory == "Metamagic Foci") &&
                                    !_nodBonus.InnerXml.Contains("selectweapon"));
  
                  if (blnApply)
                  {
                      string strSource = _guiID.ToString("D", GlobalSettings.InvariantCultureInfo);
                      ImprovementManager.ForcedValue = _strForcedValue;
                      if (!ImprovementManager.CreateImprovements(_objCharacter, Improvement.ImprovementSource.Gear,
                          strSource, Bonus, intRating, DisplayNameShort(GlobalSettings.Language), blnAddImprovements))
                      {
                          _guiID = Guid.Empty;
                          return;
                      }
  
                      if (!string.IsNullOrEmpty(ImprovementManager.SelectedValue))
                      {
                          _strExtra = ImprovementManager.SelectedValue;
                      }
                  }
              }
              else if (!string.IsNullOrEmpty(_strForcedValue) && string.IsNullOrEmpty(_strExtra))
              {
                  _strExtra = _strForcedValue;
              }
  
              // Add the Copy Protection and Registration plugins to the Matrix program. This does not apply if Unwired is not enabled, Hacked is selected, or this is a Suite being added (individual programs will add it to themselves).
              if (blnCreateChildren)
              {
                  // Check to see if there are any child elements.
                  CreateChildren(objXmlGear, blnAddImprovements, blnSkipSelectForms);
              }
  
              // If the item grants a Weapon bonus (Ammunition), just fill the WeaponBonus XmlNode.
              _nodWeaponBonus = objXmlGear["weaponbonus"];
              _nodFlechetteWeaponBonus = objXmlGear["flechetteweaponbonus"];
  
              if (!objXmlGear.TryGetStringFieldQuickly("attributearray", ref _strAttributeArray))
              {
                  objXmlGear.TryGetStringFieldQuickly("attack", ref _strAttack);
                  objXmlGear.TryGetStringFieldQuickly("sleaze", ref _strSleaze);
                  objXmlGear.TryGetStringFieldQuickly("dataprocessing", ref _strDataProcessing);
                  objXmlGear.TryGetStringFieldQuickly("firewall", ref _strFirewall);
              }
              else
              {
                  _blnCanSwapAttributes = true;
                  string[] strArray = _strAttributeArray.Split(',');
                  _strAttack = strArray[0];
                  _strSleaze = strArray[1];
                  _strDataProcessing = strArray[2];
                  _strFirewall = strArray[3];
              }
  
              objXmlGear.TryGetStringFieldQuickly("modattack", ref _strModAttack);
              objXmlGear.TryGetStringFieldQuickly("modsleaze", ref _strModSleaze);
              objXmlGear.TryGetStringFieldQuickly("moddataprocessing", ref _strModDataProcessing);
              objXmlGear.TryGetStringFieldQuickly("modfirewall", ref _strModFirewall);
              objXmlGear.TryGetStringFieldQuickly("modattributearray", ref _strModAttributeArray);
  
              objXmlGear.TryGetStringFieldQuickly("programs", ref _strProgramLimit);
  
              if (blnAddImprovements && !blnSkipSelectForms)
                  RefreshWirelessBonuses();
          }
  
          private SourceString _objCachedSourceDetail;
  
          public SourceString SourceDetail
          {
              get
              {
                  if (_objCachedSourceDetail == default)
                      _objCachedSourceDetail = SourceString.GetSourceString(Source, DisplayPage(GlobalSettings.Language),
                          GlobalSettings.Language, GlobalSettings.CultureInfo, _objCharacter);
                  return _objCachedSourceDetail;
              }
          }
  
          private void CreateChildren(XmlNode xmlParentGearNode, bool blnAddImprovements, bool blnSkipSelectForms)
          {
  
gt:             XmlNode objGearsNode = xmlParentGearNode?["gears"];