context: using System;
  using System.Collections.Generic;
  using FluentAssertions;
  using NUnit.Framework;
  using StardewModdingAPI;
  using StardewModdingAPI.Framework.Content;
  using StardewModdingAPI.Toolkit.Utilities;
  using StardewValley;
  
  namespace SMAPI.Tests.Core
  {
      /// <summary>Unit tests for <see cref="AssetName"/>.</summary>
      [TestFixture]
      internal class AssetNameTests
      {
          /*********
          ** Unit tests
          *********/
          /****
          ** Constructor
          ****/
          [Test(Description = $"Assert that the {nameof(AssetName)} constructor creates an instance with the expected values.")]
          [TestCase("SimpleName", "SimpleName", null, null)]
          [TestCase("Data/Achievements", "Data/Achievements", null, null)]
          [TestCase("Characters/Dialogue/Abigail", "Characters/Dialogue/Abigail", null, null)]
          [TestCase("Characters/Dialogue/Abigail.fr-FR", "Characters/Dialogue/Abigail", "fr-FR", LocalizedContentManager.LanguageCode.fr)]
          [TestCase("Characters/Dialogue\\Abigail.fr-FR", "Characters/Dialogue/Abigail.fr-FR", null, null)]
          [TestCase("Characters/Dialogue/Abigail.fr-FR", "Characters/Dialogue/Abigail", "fr-FR", LocalizedContentManager.LanguageCode.fr)]
          public void Constructor_Valid(string name, string expectedBaseName, string expectedLocale, LocalizedContentManager.LanguageCode? expectedLanguageCode)
          {
              // arrange
              name = PathUtilities.NormalizeAssetName(name);
  
              // act
              string calledWithLocale = null;
              IAssetName assetName = AssetName.Parse(name, parseLocale: locale => expectedLanguageCode);
  
              // assert
              assetName.Name.Should()
                  .NotBeNull()
                  .And.Be(name.Replace("\\", "/"));
              assetName.BaseName.Should()
                  .NotBeNull()
                  .And.Be(expectedBaseName);
              assetName.LocaleCode.Should()
                  .Be(expectedLocale);
              assetName.LanguageCode.Should()
                  .Be(expectedLanguageCode);
          }
  
          [Test(Description = $"Assert that the {nameof(AssetName)} constructor throws an exception if the value is invalid.")]
          [TestCase(null)]
          [TestCase("")]
          [TestCase(" ")]
          [TestCase("\t")]
          [TestCase("  \t   ")]
          public void Constructor_NullOrWhitespace(string name)
          {
              // act
              ArgumentException exception = Assert.Throws<ArgumentException>(() => _ = AssetName.Parse(name, null));
  
              // assert
              exception!.ParamName.Should().Be("rawName");
              exception.Message.Should().Be("The asset name can't be null or empty. (Parameter 'rawName')");
          }
  
  
          /****
          ** IsEquivalentTo
          ****/
          [Test(Description = $"Assert that {nameof(AssetName.IsEquivalentTo)} compares names as expected when the locale is included.")]
  
          // exact match (ignore case)
          [TestCase("Data/Achievements", "Data/Achievements", ExpectedResult = true)]
          [TestCase("DATA/achievements", "data/ACHIEVEMENTS", ExpectedResult = true)]
  
          // exact match (ignore formatting)
          [TestCase("Data/Achievements", "Data\\Achievements", ExpectedResult = true)]
          [TestCase("DATA\\achievements", "data/ACHIEVEMENTS", ExpectedResult = true)]
          [TestCase("DATA\\\\achievements", "data////ACHIEVEMENTS", ExpectedResult = true)]
  
          // whitespace-sensitive
          [TestCase("Data/Achievements", " Data/Achievements ", ExpectedResult = false)]
          [TestCase(" Data/Achievements ", "Data/Achievements", ExpectedResult = false)]
  
          // other is null or whitespace
          [TestCase("Data/Achievements", null, ExpectedResult = false)]
          [TestCase("Data/Achievements", "", ExpectedResult = false)]
          [TestCase("Data/Achievements", "   ", ExpectedResult = false)]
  
          // with locale codes
          [TestCase("Data/Achievements", "Data/Achievements.fr-FR", ExpectedResult = false)]
          [TestCase("Data/Achievements.fr-FR", "Data/Achievements", ExpectedResult = false)]
          [TestCase("Data/Achievements.fr-FR", "Data/Achievements.fr-FR", ExpectedResult = true)]
          public bool IsEquivalentTo_Name(string mainAssetName, string otherAssetName)
          {
              // arrange
              mainAssetName = PathUtilities.NormalizeAssetName(mainAssetName);
  
              // act
              AssetName name = AssetName.Parse(mainAssetName, _ => LocalizedContentManager.LanguageCode.fr);
  
              // assert
              return name.IsEquivalentTo(otherAssetName);
          }
  
          [Test(Description = $"Assert that {nameof(AssetName.IsEquivalentTo)} compares names as expected when the locale is excluded.")]
  
          // a few samples from previous test to make sure
          [TestCase("Data/Achievements", "Data/Achievements", ExpectedResult = true)]
          [TestCase("DATA/achievements", "data/ACHIEVEMENTS", ExpectedResult = true)]
          [TestCase("DATA\\\\achievements", "data////ACHIEVEMENTS", ExpectedResult = true)]
          [TestCase(" Data/Achievements ", "Data/Achievements", ExpectedResult = false)]
          [TestCase("Data/Achievements", "   ", ExpectedResult = false)]
  
          // with locale codes
          [TestCase("Data/Achievements", "Data/Achievements.fr-FR", ExpectedResult = false)]
          [TestCase("Data/Achievements.fr-FR", "Data/Achievements", ExpectedResult = true)]
          [TestCase("Data/Achievements.fr-FR", "Data/Achievements.fr-FR", ExpectedResult = false)]
          public bool IsEquivalentTo_BaseName(string mainAssetName, string otherAssetName)
          {
              // arrange
              mainAssetName = PathUtilities.NormalizeAssetName(mainAssetName);
  
              // act
              AssetName name = AssetName.Parse(mainAssetName, _ => LocalizedContentManager.LanguageCode.fr);
  
              // assert
              return name.IsEquivalentTo(otherAssetName, useBaseName: true);
          }
  
  
          /****
          ** StartsWith
          ****/
          [Test(Description = $"Assert that {nameof(AssetName.StartsWith)} compares names as expected for inputs that aren't affected by the input options.")]
  
          // exact match (ignore case and formatting)
          [TestCase("Data/Achievements", "Data/Achievements", ExpectedResult = true)]
          [TestCase("DATA/achievements", "data/ACHIEVEMENTS", ExpectedResult = true)]
          [TestCase("Data/Achievements", "Data\\Achievements", ExpectedResult = true)]
          [TestCase("DATA\\achievements", "data/ACHIEVEMENTS", ExpectedResult = true)]
          [TestCase("DATA\\\\achievements", "data////ACHIEVEMENTS", ExpectedResult = true)]
  
          // leading-whitespace-sensitive
          [TestCase("Data/Achievements", " Data/Achievements", ExpectedResult = false)]
          [TestCase(" Data/Achievements ", "Data/Achievements", ExpectedResult = false)]
  
          // invalid prefixes
          [TestCase("Data/Achievements", null, ExpectedResult = false)]
          [TestCase("Data/Achievements", "   ", ExpectedResult = false)]
  
          // with locale codes
          [TestCase("Data/Achievements.fr-FR", "Data/Achievements", ExpectedResult = true)]
          public bool StartsWith_SimpleCases(string mainAssetName, string prefix)
          {
              // arrange
              mainAssetName = PathUtilities.NormalizeAssetName(mainAssetName);
  
              // act
              AssetName name = AssetName.Parse(mainAssetName, _ => null);
  
              // assert value is the same for any combination of options
              bool result = name.StartsWith(prefix, true, true);
              foreach (bool allowPartialWord in new[] { true, false })
              {
                  foreach (bool allowSubfolder in new[] { true, true })
                  {
                      if (allowPartialWord && allowSubfolder)
                          continue;
  
                      name.StartsWith(prefix, allowPartialWord, allowSubfolder)
                          .Should().Be(result, $"the value returned for options ({nameof(allowPartialWord)}: {allowPartialWord}, {nameof(allowSubfolder)}: {allowSubfolder}) should match the base case");
                  }
              }
  
              // assert value
              return result;
          }
  
          [Test(Description = $"Assert that {nameof(AssetName.StartsWith)} compares names as expected for the 'allowPartialWord' option.")]
          [TestCase("Data/AchievementsToIgnore", "Data/Achievements", true, ExpectedResult = true)]
          [TestCase("Data/AchievementsToIgnore", "Data/Achievements", false, ExpectedResult = false)]
          [TestCase("Data/Achievements X", "Data/Achievements", true, ExpectedResult = true)]
          [TestCase("Data/Achievements X", "Data/Achievements", false, ExpectedResult = true)]
          [TestCase("Data/Achievements.X", "Data/Achievements", true, ExpectedResult = true)]
          [TestCase("Data/Achievements.X", "Data/Achievements", false, ExpectedResult = true)]
  
          // with locale codes
          [TestCase("Data/Achievements.fr-FR", "Data/Achievements", true, ExpectedResult = true)]
          [TestCase("Data/Achievements.fr-FR", "Data/Achievements", false, ExpectedResult = true)]
          public bool StartsWith_PartialWord(string mainAssetName, string prefix, bool allowPartialWord)
          {
              // arrange
              mainAssetName = PathUtilities.NormalizeAssetName(mainAssetName);
  
              // act
              AssetName name = AssetName.Parse(mainAssetName, _ => null);
  
              // assert value is the same for any combination of options
              bool result = name.StartsWith(prefix, allowPartialWord: allowPartialWord, allowSubfolder: true);
              name.StartsWith(prefix, allowPartialWord, allowSubfolder: false)
                  .Should().Be(result, "specifying allowSubfolder should have no effect for these inputs");
  
              // assert value
              return result;
          }
  
          [Test(Description = $"Assert that {nameof(AssetName.StartsWith)} compares names as expected for the 'allowSubfolder' option.")]
  
          // simple cases
          [TestCase("Data/Achievements/Path", "Data/Achievements", true, ExpectedResult = true)]
          [TestCase("Data/Achievements/Path", "Data/Achievements", false, ExpectedResult = false)]
          [TestCase("Data/Achievements/Path", "Data\\Achievements", true, ExpectedResult = true)]
          [TestCase("Data/Achievements/Path", "Data\\Achievements", false, ExpectedResult = false)]
  
          // trailing slash
          [TestCase("Data/Achievements/Path", "Data/", true, ExpectedResult = true)]
          [TestCase("Data/Achievements/Path", "Data/", false, ExpectedResult = false)]
  
          // normalize slash style
          [TestCase("Data/Achievements/Path", "Data\\", true, ExpectedResult = true)]
          [TestCase("Data/Achievements/Path", "Data\\", false, ExpectedResult = false)]
          [TestCase("Data/Achievements/Path", "Data/\\/", true, ExpectedResult = true)]
          [TestCase("Data/Achievements/Path", "Data/\\/", false, ExpectedResult = false)]
  
          // with locale code
          [TestCase("Data/Achievements/Path.fr-FR", "Data/Achievements", true, ExpectedResult = true)]
          [TestCase("Data/Achievements/Path.fr-FR", "Data/Achievements", false, ExpectedResult = false)]
          public bool StartsWith_Subfolder(string mainAssetName, string otherAssetName, bool allowSubfolder)
          {
              // arrange
              mainAssetName = PathUtilities.NormalizeAssetName(mainAssetName);
  
              // act
              AssetName name = AssetName.Parse(mainAssetName, _ => null);
  
              // assert value is the same for any combination of options
              bool result = name.StartsWith(otherAssetName, allowPartialWord: true, allowSubfolder: allowSubfolder);
              name.StartsWith(otherAssetName, allowPartialWord: false, allowSubfolder: allowSubfolder)
                  .Should().Be(result, "specifying allowPartialWord should have no effect for these inputs");
  
              // assert value
              return result;
          }
  
  
          /****
          ** GetHashCode
          ****/
          [Test(Description = $"Assert that {nameof(AssetName.GetHashCode)} generates the same hash code for two asset names which differ only by capitalization.")]
          public void GetHashCode_IsCaseInsensitive()
          {
              // arrange
              string left = "data/ACHIEVEMENTS";
              string right = "DATA/achievements";
  
              // act
              int leftHash = AssetName.Parse(left, _ => null).GetHashCode();
              int rightHash = AssetName.Parse(right, _ => null).GetHashCode();
  
              // assert
              leftHash.Should().Be(rightHash, "two asset names which differ only by capitalization should produce the same hash code");
          }
  
          [Test(Description = $"Assert that {nameof(AssetName.GetHashCode)} generates few hash code collisions for an arbitrary set of asset names.")]
          public void GetHashCode_HasFewCollisions()
          {
              // generate list of names
              List<string> names = new();
              {
                  Random random = new();
                  string characters = "abcdefghijklmnopqrstuvwxyz1234567890/";
  
                  while (names.Count < 1000)
                  {
                      char[] name = new char[random.Next(5, 20)];
  
gt:                     for (int i = 0; i < name.Length; i++)