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.Diagnostics;
  using System.Drawing;
  using System.Globalization;
  using System.Threading.Tasks;
  using System.Windows.Forms;
  using System.Xml;
  using System.Xml.XPath;
  using NLog;
  
  namespace Chummer
  {
      /// <summary>
      /// A Metamagic or Echo.
      /// </summary>
      [HubClassTag("SourceID", true, "Name", null)]
      [DebuggerDisplay("{DisplayName(GlobalSettings.DefaultLanguage)}")]
      public class Metamagic : IHasInternalId, IHasName, IHasXmlDataNode, IHasNotes, ICanRemove, IHasSource
      {
          private static Logger Log { get; } = LogManager.GetCurrentClassLogger();
  
          private Guid _guiID;
          private Guid _guiSourceID;
          private string _strName = string.Empty;
          private string _strSource = string.Empty;
          private string _strPage = string.Empty;
          private bool _blnPaidWithKarma;
          private int _intGrade;
          private XmlNode _nodBonus;
          private Improvement.ImprovementSource _eImprovementSource = Improvement.ImprovementSource.Metamagic;
          private string _strNotes = string.Empty;
          private Color _colNotes = ColorManager.HasNotesColor;
  
          private readonly Character _objCharacter;
  
          #region Constructor, Create, Save, Load, and Print Methods
  
          public Metamagic(Character objCharacter)
          {
              // Create the GUID for the new piece of Cyberware.
              _guiID = Guid.NewGuid();
              _objCharacter = objCharacter;
          }
  
          /// Create a Metamagic from an XmlNode.
          /// <param name="objXmlMetamagicNode">XmlNode to create the object from.</param>
          /// <param name="objSource">Source of the Improvement.</param>
          /// <param name="strForcedValue">Value to forcefully select for any ImprovementManager prompts.</param>
          public void Create(XmlNode objXmlMetamagicNode, Improvement.ImprovementSource objSource, string strForcedValue = "")
          {
              if (!objXmlMetamagicNode.TryGetField("id", Guid.TryParse, out _guiSourceID))
              {
                  Log.Warn(new object[] { "Missing id field for xmlnode", objXmlMetamagicNode });
                  Utils.BreakIfDebug();
              }
  
              if (objXmlMetamagicNode.TryGetStringFieldQuickly("name", ref _strName))
              {
                  _objCachedMyXmlNode = null;
                  _objCachedMyXPathNode = null;
              }
  
              objXmlMetamagicNode.TryGetStringFieldQuickly("source", ref _strSource);
              objXmlMetamagicNode.TryGetStringFieldQuickly("page", ref _strPage);
              _eImprovementSource = objSource;
              objXmlMetamagicNode.TryGetInt32FieldQuickly("grade", ref _intGrade);
              if (!objXmlMetamagicNode.TryGetMultiLineStringFieldQuickly("altnotes", ref _strNotes))
                  objXmlMetamagicNode.TryGetMultiLineStringFieldQuickly("notes", ref _strNotes);
  
              string sNotesColor = ColorTranslator.ToHtml(ColorManager.HasNotesColor);
              objXmlMetamagicNode.TryGetStringFieldQuickly("notesColor", ref sNotesColor);
              _colNotes = ColorTranslator.FromHtml(sNotesColor);
  
              if (string.IsNullOrEmpty(Notes))
              {
                  Notes = CommonFunctions.GetBookNotes(objXmlMetamagicNode, Name, CurrentDisplayName, Source, Page,
                      DisplayPage(GlobalSettings.Language), _objCharacter);
              }
  
              _nodBonus = objXmlMetamagicNode["bonus"];
              if (_nodBonus != null)
              {
                  int intRating = _objCharacter.SubmersionGrade > 0 ? _objCharacter.SubmersionGrade : _objCharacter.InitiateGrade;
  
                  string strOldFocedValue = ImprovementManager.ForcedValue;
                  string strOldSelectedValue = ImprovementManager.SelectedValue;
                  ImprovementManager.ForcedValue = strForcedValue;
                  if (!ImprovementManager.CreateImprovements(_objCharacter, objSource, _guiID.ToString("D", GlobalSettings.InvariantCultureInfo), _nodBonus, intRating, DisplayNameShort(GlobalSettings.Language)))
                  {
                      _guiID = Guid.Empty;
                      ImprovementManager.ForcedValue = strOldFocedValue;
                      return;
                  }
                  if (!string.IsNullOrEmpty(ImprovementManager.SelectedValue))
                  {
                      _strName += LanguageManager.GetString("String_Space") + '(' + ImprovementManager.SelectedValue + ')';
                      _objCachedMyXmlNode = null;
                      _objCachedMyXPathNode = null;
                  }
                  ImprovementManager.ForcedValue = strOldFocedValue;
                  ImprovementManager.SelectedValue = strOldSelectedValue;
              }
              /*
              if (string.IsNullOrEmpty(_strNotes))
              {
                  _strNotes = CommonFunctions.GetTextFromPdf(_strSource + ' ' + _strPage, _strName);
                  if (string.IsNullOrEmpty(_strNotes))
                  {
                      _strNotes = CommonFunctions.GetTextFromPdf(Source + ' ' + DisplayPage(GlobalSettings.Language), CurrentDisplayName);
                  }
              }
              */
          }
  
          private SourceString _objCachedSourceDetail;
  
          public SourceString SourceDetail
          {
              get
              {
                  if (_objCachedSourceDetail == default)
                      _objCachedSourceDetail = SourceString.GetSourceString(Source,
                          DisplayPage(GlobalSettings.Language), GlobalSettings.Language, GlobalSettings.CultureInfo,
                          _objCharacter);
                  return _objCachedSourceDetail;
              }
          }
  
          /// <summary>
          /// Save the object's XML to the XmlWriter.
          /// </summary>
          /// <param name="objWriter">XmlTextWriter to write with.</param>
          public void Save(XmlWriter objWriter)
          {
              if (objWriter == null)
                  return;
              objWriter.WriteStartElement("metamagic");
              objWriter.WriteElementString("sourceid", SourceIDString);
              objWriter.WriteElementString("guid", InternalId);
              objWriter.WriteElementString("name", _strName);
              objWriter.WriteElementString("source", _strSource);
              objWriter.WriteElementString("paidwithkarma", _blnPaidWithKarma.ToString(GlobalSettings.InvariantCultureInfo));
              objWriter.WriteElementString("page", _strPage);
              objWriter.WriteElementString("grade", _intGrade.ToString(GlobalSettings.InvariantCultureInfo));
              if (_nodBonus != null)
                  objWriter.WriteRaw(_nodBonus.OuterXml);
              else
                  objWriter.WriteElementString("bonus", string.Empty);
              objWriter.WriteElementString("improvementsource", _eImprovementSource.ToString());
              objWriter.WriteElementString("notes", System.Text.RegularExpressions.Regex.Replace(_strNotes, @"[\u0000-\u0008\u000B\u000C\u000E-\u001F]", ""));
              objWriter.WriteElementString("notesColor", ColorTranslator.ToHtml(_colNotes));
              objWriter.WriteEndElement();
          }
  
          /// <summary>
          /// Load the Metamagic from the XmlNode.
          /// </summary>
          /// <param name="objNode">XmlNode to load.</param>
          public void Load(XmlNode objNode)
          {
              if (!objNode.TryGetField("guid", Guid.TryParse, out _guiID))
              {
                  _guiID = Guid.NewGuid();
              }
  
              objNode.TryGetStringFieldQuickly("name", ref _strName);
              _objCachedMyXmlNode = null;
              _objCachedMyXPathNode = null;
  
              if (!objNode.TryGetGuidFieldQuickly("sourceid", ref _guiSourceID))
              {
                  this.GetNodeXPath()?.TryGetGuidFieldQuickly("id", ref _guiSourceID);
              }
              objNode.TryGetStringFieldQuickly("source", ref _strSource);
              objNode.TryGetStringFieldQuickly("page", ref _strPage);
              objNode.TryGetBoolFieldQuickly("paidwithkarma", ref _blnPaidWithKarma);
              objNode.TryGetInt32FieldQuickly("grade", ref _intGrade);
  
              _nodBonus = objNode["bonus"];
              if (objNode["improvementsource"] != null)
                  SourceType = Improvement.ConvertToImprovementSource(objNode["improvementsource"].InnerText);
  
              objNode.TryGetMultiLineStringFieldQuickly("notes", ref _strNotes);
  
              string sNotesColor = ColorTranslator.ToHtml(ColorManager.HasNotesColor);
              objNode.TryGetStringFieldQuickly("notesColor", ref sNotesColor);
              _colNotes = ColorTranslator.FromHtml(sNotesColor);
          }
  
          /// <summary>
          /// Print the object's XML to the XmlWriter.
          /// </summary>
          /// <param name="objWriter">XmlTextWriter to write with.</param>
          /// <param name="objCulture">Culture in which to print.</param>
          /// <param name="strLanguageToPrint">Language in which to print</param>
          public async ValueTask Print(XmlWriter objWriter, CultureInfo objCulture, string strLanguageToPrint)
          {
              if (objWriter == null)
                  return;
              // <metamagic>
              XmlElementWriteHelper objBaseElement = await objWriter.StartElementAsync("metamagic");
              try
              {
                  await objWriter.WriteElementStringAsync("guid", InternalId);
                  await objWriter.WriteElementStringAsync("sourceid", SourceIDString);
                  await objWriter.WriteElementStringAsync("name", await DisplayNameShortAsync(strLanguageToPrint));
                  await objWriter.WriteElementStringAsync("fullname", await DisplayNameAsync(strLanguageToPrint));
                  await objWriter.WriteElementStringAsync("name_english", Name);
                  await objWriter.WriteElementStringAsync("source", await _objCharacter.LanguageBookShortAsync(Source, strLanguageToPrint));
                  await objWriter.WriteElementStringAsync("page", await DisplayPageAsync(strLanguageToPrint));
                  await objWriter.WriteElementStringAsync("grade", Grade.ToString(objCulture));
                  await objWriter.WriteElementStringAsync("improvementsource", _eImprovementSource.ToString());
                  if (GlobalSettings.PrintNotes)
                      await objWriter.WriteElementStringAsync("notes", Notes);
              }
              finally
              {
                  // </metamagic>
                  await objBaseElement.DisposeAsync();
              }
          }
  
          #endregion Constructor, Create, Save, Load, and Print Methods
  
          #region Properties
  
          /// <summary>
          /// Identifier of the object within data files.
          /// </summary>
          public Guid SourceID => _guiSourceID;
  
          /// <summary>
          /// String-formatted identifier of the <inheritdoc cref="SourceID"/> from the data files.
          /// </summary>
          public string SourceIDString => _guiSourceID.ToString("D", GlobalSettings.InvariantCultureInfo);
  
          /// <summary>
          /// Internal identifier which will be used to identify this Metamagic in the Improvement system.
          /// </summary>
          public string InternalId => _guiID.ToString("D", GlobalSettings.InvariantCultureInfo);
  
          /// <summary>
          /// Bonus node from the XML file.
          /// </summary>
          public XmlNode Bonus
          {
              get => _nodBonus;
              set => _nodBonus = value;
          }
  
          /// <summary>
          /// ImprovementSource Type.
          /// </summary>
          public Improvement.ImprovementSource SourceType
          {
              get => _eImprovementSource;
              set
              {
                  if (_eImprovementSource == value)
                      return;
                  _objCachedMyXmlNode = null;
                  _objCachedMyXPathNode = null;
                  _eImprovementSource = value;
              }
          }
  
          /// <summary>
          /// Metamagic name.
          /// </summary>
          public string Name
          {
              get => _strName;
              set
              {
                  if (_strName == value)
                      return;
                  if (SourceID == Guid.Empty)
                  {
                      _objCachedMyXmlNode = null;
                      _objCachedMyXPathNode = null;
                  }
  
                  _strName = value;
              }
          }
  
          /// <summary>
          /// The name of the object as it should be displayed on printouts (translated name only).
          /// </summary>
          public string DisplayNameShort(string strLanguage)
          {
              // Get the translated name if applicable.
              if (strLanguage.Equals(GlobalSettings.DefaultLanguage, StringComparison.OrdinalIgnoreCase))
                  return Name;
  
              return this.GetNodeXPath(strLanguage)?.SelectSingleNodeAndCacheExpression("translate")?.Value ?? Name;
          }
  
          /// <summary>
          /// The name of the object as it should be displayed on printouts (translated name only).
          /// </summary>
          public async ValueTask<string> DisplayNameShortAsync(string strLanguage)
          {
              // Get the translated name if applicable.
              if (strLanguage.Equals(GlobalSettings.DefaultLanguage, StringComparison.OrdinalIgnoreCase))
                  return Name;
  
              XPathNavigator objNode = await this.GetNodeXPathAsync(strLanguage);
              return objNode != null ? (await objNode.SelectSingleNodeAndCacheExpressionAsync("translate"))?.Value ?? Name : Name;
          }
  
          /// <summary>
          /// The name of the object as it should be displayed on printouts in the program's current language.
          /// </summary>
          public string CurrentDisplayNameShort => DisplayNameShort(GlobalSettings.Language);
  
          /// <summary>
          /// The name of the object as it should be displayed in lists. Name (Extra).
          /// </summary>
          public string DisplayName(string strLanguage)
          {
              string strReturn = DisplayNameShort(strLanguage);
  
              return strReturn;
          }
  
          /// <summary>
          /// The name of the object as it should be displayed in lists. Name (Extra).
          /// </summary>
          public ValueTask<string> DisplayNameAsync(string strLanguage)
          {
              return DisplayNameShortAsync(strLanguage);
          }
  
          /// <summary>
          /// The name of the object as it should be displayed in lists in the program's current language.
          /// </summary>
          public string CurrentDisplayName => DisplayName(GlobalSettings.Language);
  
          /// <summary>
          /// Grade to which the Metamagic is tied. Negative if the Metamagic was added by an Improvement and not by an Initiation/Submersion.
          /// </summary>
          public int Grade
          {
              get => _intGrade;
              set => _intGrade = value;
          }
  
          /// <summary>
          /// Sourcebook.
          /// </summary>
          public string Source
          {
              get => _strSource;
              set => _strSource = value;
          }
  
          /// <summary>
          /// Sourcebook Page Number.
          /// </summary>
          public string Page
          {
              get => _strPage;
              set => _strPage = value;
          }
  
          /// <summary>
          /// Sourcebook Page Number using a given language file.
          /// Returns Page if not found or the string is empty.
          /// </summary>
          /// <param name="strLanguage">Language file keyword to use.</param>
          /// <returns></returns>
          public string DisplayPage(string strLanguage)
          {
              if (strLanguage.Equals(GlobalSettings.DefaultLanguage, StringComparison.OrdinalIgnoreCase))
                  return Page;
              string s = this.GetNodeXPath(strLanguage)?.SelectSingleNodeAndCacheExpression("altpage")?.Value ?? Page;
              return !string.IsNullOrWhiteSpace(s) ? s : Page;
          }
  
          /// <summary>
          /// Sourcebook Page Number using a given language file.
          /// Returns Page if not found or the string is empty.
          /// </summary>
          /// <param name="strLanguage">Language file keyword to use.</param>
          /// <returns></returns>
          public async ValueTask<string> DisplayPageAsync(string strLanguage)
          {
              if (strLanguage.Equals(GlobalSettings.DefaultLanguage, StringComparison.OrdinalIgnoreCase))
                  return Page;
              XPathNavigator objNode = await this.GetNodeXPathAsync(strLanguage);
              string s = objNode != null
                  ? (await objNode.SelectSingleNodeAndCacheExpressionAsync("altpage"))?.Value ?? Page
                  : Page;
              return !string.IsNullOrWhiteSpace(s) ? s : Page;
          }
  
          /// <summary>
          /// Whether or not the Metamagic was paid for with Karma.
          /// </summary>
          public bool PaidWithKarma
          {
              get => _blnPaidWithKarma;
              set => _blnPaidWithKarma = value;
          }
  
          /// <summary>
          /// Notes.
          /// </summary>
          public string Notes
          {
              get => _strNotes;
              set => _strNotes = value;
          }
  
          /// <summary>
          /// Forecolor to use for Notes in treeviews.
          /// </summary>
          public Color NotesColor
          {
              get => _colNotes;
              set => _colNotes = value;
          }
  
          private XmlNode _objCachedMyXmlNode;
          private string _strCachedXmlNodeLanguage = string.Empty;
  
          public async Task<XmlNode> GetNodeCoreAsync(bool blnSync, string strLanguage)
          {
              if (_objCachedMyXmlNode != null && strLanguage == _strCachedXmlNodeLanguage
                                              && !GlobalSettings.LiveCustomData)
                  return _objCachedMyXmlNode;
              string strDoc = "metamagic.xml";
              string strPath = "/chummer/metamagics/metamagic";
              if (_eImprovementSource == Improvement.ImprovementSource.Echo)
              {
                  strDoc = "echoes.xml";
                  strPath = "/chummer/echoes/echo";
              }
  
              _objCachedMyXmlNode = (blnSync
                      // ReSharper disable once MethodHasAsyncOverload
                      ? _objCharacter.LoadData(strDoc, strLanguage)
                      : await _objCharacter.LoadDataAsync(strDoc, strLanguage))
                  .SelectSingleNode(strPath + (SourceID == Guid.Empty
                                        ? "[name = " + Name.CleanXPath() + ']'
                                        : "[id = " + SourceIDString.CleanXPath()
                                                   + " or id = " + SourceIDString
                                                                   .ToUpperInvariant().CleanXPath()
                                                   + ']'));
              _strCachedXmlNodeLanguage = strLanguage;
              return _objCachedMyXmlNode;
          }
  
          private XPathNavigator _objCachedMyXPathNode;
          private string _strCachedXPathNodeLanguage = string.Empty;
  
          public async Task<XPathNavigator> GetNodeXPathCoreAsync(bool blnSync, string strLanguage)
          {
              if (_objCachedMyXPathNode != null && strLanguage == _strCachedXPathNodeLanguage
                                                && !GlobalSettings.LiveCustomData)
                  return _objCachedMyXPathNode;
              string strDoc = "metamagic.xml";
              string strPath = "/chummer/metamagics/metamagic";
              if (_eImprovementSource == Improvement.ImprovementSource.Echo)
              {
                  strDoc = "echoes.xml";
                  strPath = "/chummer/echoes/echo";
              }
  
              _objCachedMyXPathNode = (blnSync
                      // ReSharper disable once MethodHasAsyncOverload
                      ? _objCharacter.LoadDataXPath(strDoc, strLanguage)
                      : await _objCharacter.LoadDataXPathAsync(strDoc, strLanguage))
                  .SelectSingleNode(strPath + (SourceID == Guid.Empty
  
gt:                                       ? "[name = " + Name.CleanXPath() + ']'