// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;

using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Tasks;
using Microsoft.Build.Utilities;
using Shouldly;
using Xunit;
using Xunit.Abstractions;

#nullable disable

namespace Microsoft.Build.UnitTests
{
    public sealed class CreateCSharpManifestResourceName_Tests
    {
        private readonly ITestOutputHelper _testOutput;

        public CreateCSharpManifestResourceName_Tests(ITestOutputHelper output)
        {
            _testOutput = output;
        }
        /// <summary>
        /// Test the basic functionality.
        /// </summary>
        [Fact]
        public void Basic()
        {
            string result =
            CreateCSharpManifestResourceName.CreateManifestNameImpl(
                    fileName: @"f:\myproject\SubFolder\MyForm.resx",
                    linkFileName: null,
                    prependCultureAsDirectory: true,
                    rootNamespace: null,    // Root namespace
                    dependentUponFileName: null,
                    culture: null,
                    binaryStream: StreamHelpers.StringToStream("namespace MyStuff.Namespace { class Class {} }"),
                    log: null);

            Assert.Equal("MyStuff.Namespace.Class", result);
        }

        /// <summary>
        /// Test for a namespace that has ANSI but non-ascii characters.
        ///
        /// NOTE: namespace dÃa {} get's compiled into different IL depending on the language of the OS
        /// that its running on. This is because 'Ã' is a high ANSI character which is interpreted differently
        /// for different codepages.
        /// </summary>
#if RUNTIME_TYPE_NETCORE
        [Fact(Skip = "https://github.com/dotnet/msbuild/issues/295")]
#else
        [Fact]
#endif
        public void Regress172107()
        {
            // Can't embed the 'Ã' directly because the string is Unicode already and the Unicode<-->ANSI transform
            // isn't bidirectional.
            MemoryStream sourcesStream = (MemoryStream)StreamHelpers.StringToStream("namespace d?a { class Class {} }");

            // Instead, directly write the ANSI character into the memory buffer.
            sourcesStream.Seek(11, SeekOrigin.Begin);
            sourcesStream.WriteByte(0xc3);    // Plug the 'Ã' in
            sourcesStream.Seek(0, SeekOrigin.Begin);

            string result =
            CreateCSharpManifestResourceName.CreateManifestNameImpl(
                    fileName: @"irrelevant",
                    linkFileName: null,
                    prependCultureAsDirectory: true,
                    rootNamespace: null,    // Root namespace
                    dependentUponFileName: null,
                    culture: null,
                    binaryStream: sourcesStream,
                    log: null);

            MemoryStream m = new MemoryStream();
            m.Write(new byte[] { 0x64, 0xc3, 0x61, 0x2e, 0x43, 0x6c, 0x61, 0x73, 0x73 }, 0, 9); // dÃa.Class in ANSI
            m.Flush();
            m.Seek(0, SeekOrigin.Begin);
#if FEATURE_ENCODING_DEFAULT
            StreamReader r = new StreamReader(m, System.Text.Encoding.Default, true); // HIGHCHAR: Test reads ANSI because that's the scenario.
#else
            StreamReader r = new StreamReader(m, System.Text.Encoding.ASCII, true); // HIGHCHAR: Test reads ANSI because that's the scenario.
#endif
            string className = r.ReadToEnd();

            Assert.Equal(className, result);
        }

        /// <summary>
        /// Test for a namespace that has UTF8 characters but there's no BOM at the start.
        ///
        /// </summary>
#if RUNTIME_TYPE_NETCORE
        [Fact(Skip = "https://github.com/dotnet/msbuild/issues/295")]
#else
        [Fact]
#endif
        public void Regress249540()
        {
            // Special character is 'Ä' in UTF8: 0xC3 84
            MemoryStream sourcesStream = (MemoryStream)StreamHelpers.StringToStream("namespace d??a { class Class {} }");

            // Instead, directly write the ANSI character into the memory buffer.
            sourcesStream.Seek(11, SeekOrigin.Begin);
            sourcesStream.WriteByte(0xc3);    // Plug the first byte of 'Ä' in.
            sourcesStream.WriteByte(0x84);    // Plug the second byte of 'Ä' in.
            sourcesStream.Seek(0, SeekOrigin.Begin);

            string result =
            CreateCSharpManifestResourceName.CreateManifestNameImpl(
                    fileName: @"irrelevant",
                    linkFileName: null,
                    prependCultureAsDirectory: true,
                    rootNamespace: null,    // Root namespace
                    dependentUponFileName: null,
                    culture: null,
                    binaryStream: sourcesStream,
                    log: null);

            Assert.Equal("d\u00C4a.Class", result);
        }

        /// <summary>
        /// Test a dependent with a relative path
        /// </summary>
        [Fact]
        public void RelativeDependentUpon()
        {
            string result =
            CreateCSharpManifestResourceName.CreateManifestNameImpl(
                    fileName: @"f:\myproject\SubFolder\MyForm.resx",
                    linkFileName: null,
                    prependCultureAsDirectory: true,
                    rootNamespace: null,    // Root namespace
                    dependentUponFileName: null,
                    culture: null,
                    binaryStream: StreamHelpers.StringToStream("namespace Namespace { class Class {} }"),
                    log: null);

            Assert.Equal("Namespace.Class", result);
        }

        /// <summary>
        /// Test a dependent with a relative path
        /// </summary>
        [Fact]
        public void AbsoluteDependentUpon()
        {
            string result =
            CreateCSharpManifestResourceName.CreateManifestNameImpl(
                    fileName: @"f:\myproject\SubFolder\MyForm.resx",
                    linkFileName: null,
                    prependCultureAsDirectory: true,
                    rootNamespace: "RootNamespace",    // Root namespace (will be ignored because it's dependent)
                    dependentUponFileName: null,
                    culture: null,
                    binaryStream: StreamHelpers.StringToStream("namespace MyStuff.Namespace { class Class {} }"),
                    log: null);

            Assert.Equal("MyStuff.Namespace.Class", result);
        }

        /// <summary>
        /// A dependent class plus there is a culture.
        /// </summary>
        [Fact]
        public void DependentWithCulture()
        {
            string result =
            CreateCSharpManifestResourceName.CreateManifestNameImpl(
                    fileName: @"f:\myproject\SubFolder\MyForm.en-GB.resx",
                    linkFileName: null,
                    prependCultureAsDirectory: true,
                    rootNamespace: "RootNamespace",    // Root namespace (will be ignored because it's dependent)
                    dependentUponFileName: null,
                    culture: null,
                    binaryStream: StreamHelpers.StringToStream("namespace MyStuff.Namespace { class Class {} }"),
                    log: null);

            Assert.Equal("MyStuff.Namespace.Class.en-GB", result);
        }

        /// <summary>
        /// A dependent class plus there is a culture that was expressed in the metadata of the
        /// item rather than the filename.
        /// </summary>
        [Fact]
        public void DependentWithCultureMetadata()
        {
            string result =
            CreateCSharpManifestResourceName.CreateManifestNameImpl(
                    fileName: @"f:\myproject\SubFolder\MyForm.resx",
                    linkFileName: null,
                    prependCultureAsDirectory: true,
                    rootNamespace: "RootNamespace",    // Root namespace (will be ignored because it's dependent)
                    dependentUponFileName: null,
                    culture: "en-GB",
                    binaryStream: StreamHelpers.StringToStream("namespace MyStuff.Namespace { class Class {} }"),
                    log: null);

            Assert.Equal("MyStuff.Namespace.Class.en-GB", result);
        }

        /// <summary>
        /// A dependent class plus there is a culture embedded in the .RESX filename.
        /// </summary>
        [Fact]
        public void DependentWithEmbeddedCulture()
        {
            string result =
            CreateCSharpManifestResourceName.CreateManifestNameImpl(
                    fileName: @"f:\myproject\SubFolder\MyForm.fr-fr.resx",
                    linkFileName: null,
                    prependCultureAsDirectory: true,
                    rootNamespace: "RootNamespace",    // Root namespace (will be ignored because it's dependent)
                    dependentUponFileName: null,
                    culture: null,
                    binaryStream: StreamHelpers.StringToStream("namespace MyStuff.Namespace { class Class {} }"),
                    log: null);

            Assert.Equal("MyStuff.Namespace.Class.fr-fr", result);
        }

        /// <summary>
        /// No dependent class, but there is a root namespace place.  Also, the .resx
        /// extension contains some upper-case characters.
        /// </summary>
        [Fact]
        public void RootnamespaceWithCulture()
        {
            string result =
            CreateCSharpManifestResourceName.CreateManifestNameImpl(
                    fileName: @"SubFolder\MyForm.en-GB.ResX",
                    linkFileName: null,
                    prependCultureAsDirectory: true,
                    rootNamespace: "RootNamespace",        // Root namespace
                    dependentUponFileName: null,
                    culture: null,
                    binaryStream: null,
                    log: null);

            Assert.Equal("RootNamespace.SubFolder.MyForm.en-GB", result);
        }

        /// <summary>
        /// Explicitly retain culture
        /// </summary>
        [Fact]
        public void RootnamespaceWithCulture_RetainCultureInFileName()
        {
            string result =
            CreateCSharpManifestResourceName.CreateManifestNameImpl(
                    fileName: @"Subfolder\File.cs.cshtml",
                    linkFileName: null,
                    prependCultureAsDirectory: true,
                    rootNamespace: "RootNamespace",        // Root namespace
                    dependentUponFileName: null,
                    culture: null,
                    binaryStream: null,
                    log: null,
                    treatAsCultureNeutral: true); // retain culture in name

            result.ShouldBe("RootNamespace.Subfolder.File.cs.cshtml");
        }

        /// <summary>
        /// If there is a link file name then it is preferred over the main file name.
        /// </summary>
        [Fact]
        public void Regress222308()
        {
            string result =
            CreateCSharpManifestResourceName.CreateManifestNameImpl(
                    fileName: @"..\..\XmlEditor\Setup\XmlEditor.rgs",
                    linkFileName: @"XmlEditor.rgs",
                    prependCultureAsDirectory: true,
                    rootNamespace: "RootNamespace",        // Root namespace
                    dependentUponFileName: null,
                    culture: null,
                    binaryStream: null,
                    log: null);

            Assert.Equal("RootNamespace.XmlEditor.rgs", result);
        }

        /// <summary>
        /// A non-resx file in a subfolder, with a root namespace.
        /// </summary>
        [Fact]
        public void BitmapWithRootNamespace()
        {
            string result =
            CreateCSharpManifestResourceName.CreateManifestNameImpl(
                    fileName: @"SubFolder\SplashScreen.bmp",
                    linkFileName: null,
                    prependCultureAsDirectory: true,
                    rootNamespace: "RootNamespace",        // Root namespace
                    dependentUponFileName: null,
                    culture: null,
                    binaryStream: null,
                    log: null);

            Assert.Equal("RootNamespace.SubFolder.SplashScreen.bmp", result);
        }

        /// <summary>
        /// A culture-specific non-resx file in a subfolder, with a root namespace.
        /// </summary>
        [Fact]
        public void CulturedBitmapWithRootNamespace()
        {
            string result =
            CreateCSharpManifestResourceName.CreateManifestNameImpl(
                    fileName: @"SubFolder\SplashScreen.fr.bmp",
                    linkFileName: null,
                    prependCultureAsDirectory: true,
                    rootNamespace: "RootNamespace",        // Root namespace
                    dependentUponFileName: null,
                    culture: null,
                    binaryStream: null,
                    log: null);

            Assert.Equal(FileUtilities.FixFilePath(@"fr\RootNamespace.SubFolder.SplashScreen.bmp"), result);
        }

        /// <summary>
        /// A culture-specific non-resx file in a subfolder, with a root namespace, but no culture directory prefix
        /// </summary>
        [Fact]
        public void CulturedBitmapWithRootNamespaceNoDirectoryPrefix()
        {
            string result =
            CreateCSharpManifestResourceName.CreateManifestNameImpl(
                    fileName: @"SubFolder\SplashScreen.fr.bmp",
                    linkFileName: null,    // Link file name
                    prependCultureAsDirectory: false,
                    rootNamespace: "RootNamespace",        // Root namespace
                    dependentUponFileName: null,
                    culture: null,
                    binaryStream: null,
                    log: null);

            Assert.Equal(@"RootNamespace.SubFolder.SplashScreen.bmp", result);
        }

        /// <summary>
        /// If the filename passed in as the "DependentUpon" file doesn't end in .cs then
        /// we want to fall back to the RootNamespace+FileName logic.
        /// </summary>
        [Fact]
        public void Regress188319()
        {
            CreateCSharpManifestResourceName t = new CreateCSharpManifestResourceName();

            t.BuildEngine = new MockEngine();
            ITaskItem i = new TaskItem("SR1.resx");
            i.SetMetadata("BuildAction", "EmbeddedResource");
            i.SetMetadata("DependentUpon", "SR1.strings");        // Normally, this would be a C# file.
            t.ResourceFiles = new ITaskItem[] { i };
            t.RootNamespace = "CustomToolTest";
            bool success = t.Execute(
                new Microsoft.Build.Tasks.CreateFileStream(CreateFileStream));

            Assert.True(success); // "Expected the task to succeed."

            ITaskItem[] resourceNames = t.ManifestResourceNames;

            Assert.Single(resourceNames);
            Assert.Equal(@"CustomToolTest.SR1", resourceNames[0].ItemSpec);
        }

        /// <summary>
        /// Opt into DependentUpon convention and load the expected file properly.
        /// </summary>
        [Fact]
        public void DependentUponConvention_FindsMatch()
        {
            using (var env = TestEnvironment.Create(_testOutput))
            {
                var csFile = env.CreateFile("SR1.cs", "namespace MyStuff.Namespace { class Class { } }");
                var resXFile = env.CreateFile("SR1.resx", "");

                ITaskItem i = new TaskItem(resXFile.Path);
                i.SetMetadata("BuildAction", "EmbeddedResource");
                // Don't set DependentUpon so it goes by convention

                CreateCSharpManifestResourceName t = new CreateCSharpManifestResourceName
                {
                    BuildEngine = new MockEngine(_testOutput),
                    UseDependentUponConvention = true,
                    ResourceFiles = new ITaskItem[] { i }
                };

                t.Execute().ShouldBeTrue("Expected the task to succeed.");

                t.ManifestResourceNames.ShouldHaveSingleItem();

                t.ManifestResourceNames[0].ItemSpec.ShouldBe("MyStuff.Namespace.Class", "Expecting to find the namespace & class name from SR1.cs");
            }
        }

        /// <summary>
        /// Opt into DependentUpon convention but don't expect it to be used for this file.
        /// </summary>
        [Theory]
        [InlineData(true)]
        [InlineData(false)]
        public void DependentUponConvention_DoesNotApplyToNonResx(bool explicitlySpecifyType)
        {
            using (var env = TestEnvironment.Create())
            {
                var csFile = env.CreateFile("SR1.cs", "namespace MyStuff.Namespace { class Class { } }");
                const string ResourceFileName = "SR1.txt";
                var resourceFile = env.CreateFile(ResourceFileName, "");

                // Default resource naming is based on the item include, so use a relative
                // path here instead of a full path.
                env.SetCurrentDirectory(Path.GetDirectoryName(resourceFile.Path));
                ITaskItem i = new TaskItem(ResourceFileName);
                i.SetMetadata("BuildAction", "EmbeddedResource");
                if (explicitlySpecifyType)
                {
                    i.SetMetadata("Type", "Non-Resx");
                }
                // Don't set DependentUpon so it goes by convention

                CreateCSharpManifestResourceName t = new CreateCSharpManifestResourceName
                {
                    BuildEngine = new MockEngine(_testOutput),
                    UseDependentUponConvention = true,
                    ResourceFiles = new ITaskItem[] { i }
                };

                t.Execute().ShouldBeTrue("Expected the task to succeed.");

                t.ManifestResourceNames.ShouldHaveSingleItem();

                t.ManifestResourceNames[0].ItemSpec.ShouldBe(ResourceFileName, "Expecting to find the namespace & class name from SR1.cs");
            }
        }

        /// <summary>
        /// Opt into DependentUpon convention and load the expected file properly when the file is in a subfolder.
        /// </summary>
        [Fact]
        public void DependentUponConvention_FindsMatchInSubfolder()
        {
            using (var env = TestEnvironment.Create())
            {
                var subfolder = env.DefaultTestDirectory.CreateDirectory("SR1");
                var csFile = subfolder.CreateFile("SR1.cs", "namespace MyStuff.Namespace { class Class { } }");
                var resXFile = subfolder.CreateFile("SR1.resx", "");

                env.SetCurrentDirectory(env.DefaultTestDirectory.Path);

                ITaskItem i = new TaskItem(@"SR1\SR1.resx");
                i.SetMetadata("BuildAction", "EmbeddedResource");
                // Don't set DependentUpon so it goes by convention

                CreateCSharpManifestResourceName t = new CreateCSharpManifestResourceName
                {
                    BuildEngine = new MockEngine(_testOutput),
                    UseDependentUponConvention = true,
                    ResourceFiles = new ITaskItem[] { i }
                };

                t.Execute().ShouldBeTrue("Expected the task to succeed.");

                t.ManifestResourceNames.ShouldHaveSingleItem();

                t.ManifestResourceNames[0].ItemSpec.ShouldBe("MyStuff.Namespace.Class", "Expecting to find the namespace & class name from SR1.cs");
            }
        }

        /// <summary>
        /// Opt into DependentUpon convention without creating the equivalent .cs file for our resource file.
        /// </summary>
        [Fact]
        public void DependentUpon_UseConventionFileDoesNotExist()
        {
            using (var env = TestEnvironment.Create())
            {
                // cs file doesn't exist for this case.
                var resXFile = env.CreateFile("SR1.resx", "");

                ITaskItem i = new TaskItem(Path.GetFileName(resXFile.Path));
                i.SetMetadata("BuildAction", "EmbeddedResource");
                // Don't set DependentUpon so it goes by convention

                // Use relative paths to ensure short manifest name based on the path to the resx.
                // See CreateManifestNameImpl
                env.SetCurrentDirectory(Path.GetDirectoryName(resXFile.Path));

                CreateCSharpManifestResourceName t = new CreateCSharpManifestResourceName
                {
                    BuildEngine = new MockEngine(_testOutput),
                    UseDependentUponConvention = true,
                    ResourceFiles = new ITaskItem[] { i }
                };

                t.Execute().ShouldBeTrue("Expected the task to succeed.");

                t.ManifestResourceNames.ShouldHaveSingleItem();

                t.ManifestResourceNames[0].ItemSpec.ShouldBe("SR1", "Expected only the file name.");
            }
        }

        /// <summary>
        /// Opt into DependentUponConvention, but include DependentUpon metadata with different name.
        /// </summary>
        [Fact]
        public void DependentUpon_SpecifyNewFile()
        {
            using (var env = TestEnvironment.Create())
            {
                var conventionCSFile = env.CreateFile("SR1.cs", "namespace MyStuff.Namespace { class Class { } }");
                var nonConventionCSFile = env.CreateFile("SR2.cs", "namespace MyStuff2.Namespace { class Class2 { } }");
                var resXFile = env.CreateFile("SR1.resx", "");

                ITaskItem i = new TaskItem(resXFile.Path);
                i.SetMetadata("BuildAction", "EmbeddedResource");
                i.SetMetadata("DependentUpon", "SR2.cs");

                CreateCSharpManifestResourceName t = new CreateCSharpManifestResourceName
                {
                    BuildEngine = new MockEngine(_testOutput),
                    UseDependentUponConvention = true,
                    ResourceFiles = new ITaskItem[] { i }
                };

                t.Execute().ShouldBeTrue("Expected the task to succeed.");

                t.ManifestResourceNames.ShouldHaveSingleItem();

                t.ManifestResourceNames[0].ItemSpec.ShouldBe("MyStuff2.Namespace.Class2", "Expected the namespace & class of SR2.");
            }
        }

        /// <summary>
        /// When disabling UseDependentUponConvention it will find no .cs file and default to filename.
        /// </summary>
        [Fact]
        public void DependentUponConvention_ConventionDisabledDoesNotReadConventionFile()
        {
            using (var env = TestEnvironment.Create())
            {
                var csFile = env.CreateFile("SR1.cs", "namespace MyStuff.Namespace { class Class { } }");
                var resXFile = env.CreateFile("SR1.resx", "");

                ITaskItem i = new TaskItem(Path.GetFileName(resXFile.Path));
                i.SetMetadata("BuildAction", "EmbeddedResource");
                // No need to set DependentUpon

                // Use relative paths to ensure short manifest name based on the path to the resx.
                // See CreateManifestNameImpl
                env.SetCurrentDirectory(Path.GetDirectoryName(resXFile.Path));

                CreateCSharpManifestResourceName t = new CreateCSharpManifestResourceName
                {
                    BuildEngine = new MockEngine(_testOutput),
                    UseDependentUponConvention = false,
                    ResourceFiles = new ITaskItem[] { i }
                };

                t.Execute().ShouldBeTrue("Expected the task to succeed.");

                t.ManifestResourceNames.ShouldHaveSingleItem();

                t.ManifestResourceNames[0].ItemSpec.ShouldBe("SR1", "Expected only the file name.");
            }
        }

        /// <summary>
        /// If we have a resource file that has a culture within it's name (resourceFile.de.cs), find it by convention.
        /// </summary>
        [Fact]
        public void CulturedResourceFileFindByConvention()
        {
            using (var env = TestEnvironment.Create(_testOutput))
            {
                var csFile = env.CreateFile("SR1.cs", "namespace MyStuff.Namespace { class Class { } }");
                var resXFile = env.CreateFile("SR1.de.resx", "");

                ITaskItem i = new TaskItem(resXFile.Path);

                i.SetMetadata("BuildAction", "EmbeddedResource");

                // this data is set automatically through the AssignCulture task, so we manually set it here
                i.SetMetadata("WithCulture", "true");
                i.SetMetadata("Culture", "de");

                env.SetCurrentDirectory(Path.GetDirectoryName(resXFile.Path));

                CreateCSharpManifestResourceName t = new CreateCSharpManifestResourceName
                {
                    BuildEngine = new MockEngine(),
                    UseDependentUponConvention = true,
                    ResourceFiles = new ITaskItem[] { i },
                };

                t.Execute().ShouldBeTrue("Expected the task to succeed");

                t.ManifestResourceNames.ShouldHaveSingleItem();

                // CreateManifestNameImpl appends culture to the end of the convention
                t.ManifestResourceNames[0].ItemSpec.ShouldBe("MyStuff.Namespace.Class.de", "Expected Namespace.Class.Culture");
            }
        }

        /// <summary>
        /// helper method for verifying manifest resource names
        /// </summary>
        private void VerifyExpectedManifestResourceName(string resourcePath, string expectedName)
        {
            string result = CreateCSharpManifestResourceName.CreateManifestNameImpl(resourcePath, null, true, "Root", null, null, null, null);
            string expected = "Root." + expectedName;

            Assert.Equal(result, expected);
        }

        /// <summary>
        /// Need to convert any spaces in the directory name of embedded resource files to underscores.
        /// Leave spaces in the file name itself alone. That's how Everett did it.
        /// </summary>
        [Fact]
        public void Regress309027()
        {
            VerifyExpectedManifestResourceName(
                @"SubFolder With Spaces\Splash Screen.bmp", "SubFolder_With_Spaces.Splash Screen.bmp");
        }

        /// <summary>
        /// The folder part of embedded resource names (not the file name though) needs to be a proper identifier,
        /// since that's how Everett used to do this
        /// </summary>
        [Fact]
        public void Regress311473()
        {
            // First char must be a letter or a connector (underscore), others must be a letter/digit/connector or a combining mark
            // If the first character is not a valid first character but valid subsequent character, the name is prepended
            // with an underscore. Invalid subsequent characters are replaced with an underscore.
            VerifyExpectedManifestResourceName(@"1abc()\pic.bmp", "_1abc__.pic.bmp");

            // if the first character is not a valid id character at all, it's replaced with an underscore instead of
            // prepending an underscore to it
            VerifyExpectedManifestResourceName(@"@abc\pic.bmp", "_abc.pic.bmp");

            // Each folder name is processed independently
            VerifyExpectedManifestResourceName(@"1234\1abc\pic.bmp", "_1234._1abc.pic.bmp");

            // Each part of folder name separated by dots is processed independently as well
            VerifyExpectedManifestResourceName(@"1abc.@abc@._1234()\pic.bmp", "_1abc._abc_._1234__.pic.bmp");
            VerifyExpectedManifestResourceName(@"1abc\@abc@\_1234()\pic.bmp", "_1abc._abc_._1234__.pic.bmp");

            // Combination of dots and folders
            VerifyExpectedManifestResourceName(@"@Ab2.=gh\1hl.l=a1\pic.bmp", "_Ab2._gh._1hl.l_a1.pic.bmp");

            // A single underscore folder name is expanded to two underscores
            VerifyExpectedManifestResourceName(@"_\pic.bmp", "__.pic.bmp");

            // A more complex example of the last rule
            VerifyExpectedManifestResourceName(@"_\__\_.__\_\pic.bmp", "__.__._.__.__.pic.bmp");
        }

        /// <summary>
        /// If the dependent upon filename and the resource filename both contain what looks like
        /// a culture, do not treat it as a culture identifier.  E.g.:
        ///
        ///     Form1.ro.resx == DependentUpon ==> Form1.ro.vb
        ///
        /// In this case, we don't include "ro" as the culture because it's in both filenames.  In
        /// the case of:
        ///
        ///     Form1.ro.resx == DependentUpon ==> Form1.vb
        ///
        /// we continue to treat "ro" as the culture.
        /// </summary>
        [Fact]
        public void Regress419591()
        {
            string result =
            CreateCSharpManifestResourceName.CreateManifestNameImpl(
                    fileName: "MyForm.en-GB.resx",
                    linkFileName: null,
                    prependCultureAsDirectory: true,
                    rootNamespace: "RootNamespace",    // Root namespace (will be ignored because it's dependent)
                    dependentUponFileName: "MyForm.en-GB.cs",
                    culture: null,
                    binaryStream: StreamHelpers.StringToStream("namespace ClassLibrary1 { class MyForm {} }"),
                    log: null);

            Assert.Equal("ClassLibrary1.MyForm", result);
        }

        /// <summary>
        /// If the dependent upon filename and the resource filename both contain what looks like
        /// a culture, do not treat it as a culture identifier.  E.g.:
        ///
        ///     Form1.ro.resx == DependentUpon ==> Form1.ro.vb
        ///
        /// In this case, we don't include "ro" as the culture because it's in both filenames.  If
        /// The parent source file doesn't have a class name in it, we just use the culture neutral
        /// filename of the resource file.
        /// </summary>
        [Fact]
        public void Regress419591_EmptySource()
        {
            string result =
            CreateCSharpManifestResourceName.CreateManifestNameImpl(
                    fileName: "MyForm.en-GB.resx",
                    linkFileName: null,
                    prependCultureAsDirectory: true,
                    rootNamespace: "RootNamespace",
                    dependentUponFileName: "MyForm.en-GB.cs",
                    culture: null,
                    binaryStream: StreamHelpers.StringToStream(""),
                    log: null);

            Assert.Equal("RootNamespace.MyForm.en-GB", result);
        }

        /// <summary>
        /// If we encounter a class or namespace name within a conditional compilation directive,
        /// we need to warn because we do not try to resolve the correct manifest name depending
        /// on conditional compilation of code.
        /// </summary>
        [Fact]
        public void Regress459265()
        {
            MockEngine m = new MockEngine();
            CreateCSharpManifestResourceName c = new CreateCSharpManifestResourceName();
            c.BuildEngine = m;

            CreateCSharpManifestResourceName.CreateManifestNameImpl(
                    fileName: "MyForm.resx",
                    linkFileName: null,
                    prependCultureAsDirectory: true,
                    rootNamespace: "RootNamespace",    // Root namespace (will be ignored because it's dependent)
                    dependentUponFileName: "MyForm.cs",
                    culture: null,
                    binaryStream: StreamHelpers.StringToStream(
@"using System;
#if false
namespace ClassLibrary1
#endif
#if Debug
namespace ClassLibrary2
#else
namespace ClassLibrary3
#endif
{
    class MyForm 
    {
    }
}"),
                    log: c.Log);

            Assert.Contains(
                String.Format(AssemblyResources.GetString("CreateManifestResourceName.DefinitionFoundWithinConditionalDirective"), "MyForm.cs", "MyForm.resx"),
                m.Log);
        }

        /// <summary>
        /// Given a file path, return a stream on top of that path.
        /// </summary>
        /// <param name="path">Path to the file</param>
        /// <param name="mode">File mode</param>
        /// <param name="access">Access type</param>
        /// <returns>The Stream</returns>
        private Stream CreateFileStream(string path, FileMode mode, FileAccess access)
        {
            if (String.Equals(path, "SR1.strings", StringComparison.OrdinalIgnoreCase))
            {
                return StreamHelpers.StringToStream("namespace MyStuff.Namespace { class Class {} }");
            }
            Assert.True(false, String.Format("Encountered a new path {0}, needs unittesting support", path));
            return null;
        }

        /// <summary>
        /// Tests to ensure that the ResourceFilesWithManifestResourceNames contains everything that
        /// the ResourceFiles property on the task contains, but with additional metadata called ManifestResourceName
        /// </summary>
        [Fact]
        public void ResourceFilesWithManifestResourceNamesContainsAdditionalMetadata()
        {
            CreateCSharpManifestResourceName t = new CreateCSharpManifestResourceName();

            t.BuildEngine = new MockEngine();
            ITaskItem i = new TaskItem("strings.resx");

            t.ResourceFiles = new ITaskItem[] { i };
            t.RootNamespace = "ResourceRoot";
            bool success = t.Execute();

            Assert.True(success); // "Expected the task to succeed."

            ITaskItem[] resourceFiles = t.ResourceFilesWithManifestResourceNames;

            Assert.Single(resourceFiles);
            Assert.Equal(@"strings.resx", resourceFiles[0].ItemSpec);
            Assert.Equal(@"ResourceRoot.strings", resourceFiles[0].GetMetadata("ManifestResourceName"));
        }

        /// <summary>
        /// Ensure that if no LogicalName is specified, that the same ManifestResourceName metadata
        /// gets applied as LogicalName
        /// </summary>
        [Fact]
        public void AddLogicalNameForNonResx()
        {
            CreateCSharpManifestResourceName t = new CreateCSharpManifestResourceName();

            t.BuildEngine = new MockEngine();
            ITaskItem i = new TaskItem("pic.bmp");
            i.SetMetadata("Type", "Non-Resx");

            t.ResourceFiles = new ITaskItem[] { i };
            t.RootNamespace = "ResourceRoot";
            bool success = t.Execute();

            Assert.True(success); // "Expected the task to succeed."

            ITaskItem[] resourceFiles = t.ResourceFilesWithManifestResourceNames;

            Assert.Single(resourceFiles);
            Assert.Equal(@"pic.bmp", resourceFiles[0].ItemSpec);
            Assert.Equal(@"ResourceRoot.pic.bmp", resourceFiles[0].GetMetadata("LogicalName"));
        }

        /// <summary>
        /// Ensure that a LogicalName that is already present is preserved during manifest name generation
        /// </summary>
        [Fact]
        public void PreserveLogicalNameForNonResx()
        {
            CreateCSharpManifestResourceName t = new CreateCSharpManifestResourceName();

            t.BuildEngine = new MockEngine();
            ITaskItem i = new TaskItem("pic.bmp");
            i.SetMetadata("LogicalName", "foo");
            i.SetMetadata("Type", "Non-Resx");

            t.ResourceFiles = new ITaskItem[] { i };
            t.RootNamespace = "ResourceRoot";
            bool success = t.Execute();

            Assert.True(success); // "Expected the task to succeed."

            ITaskItem[] resourceFiles = t.ResourceFilesWithManifestResourceNames;

            Assert.Single(resourceFiles);
            Assert.Equal(@"pic.bmp", resourceFiles[0].ItemSpec);
            Assert.Equal(@"foo", resourceFiles[0].GetMetadata("LogicalName"));
        }

        /// <summary>
        /// Resx resources should not get ManifestResourceName metadata copied to the LogicalName value
        /// </summary>
        [Fact]
        public void NoLogicalNameAddedForResx()
        {
            CreateCSharpManifestResourceName t = new CreateCSharpManifestResourceName();

            t.BuildEngine = new MockEngine();
            ITaskItem i = new TaskItem("strings.resx");
            i.SetMetadata("Type", "Resx");

            t.ResourceFiles = new ITaskItem[] { i };
            t.RootNamespace = "ResourceRoot";
            bool success = t.Execute();

            Assert.True(success); // "Expected the task to succeed."

            ITaskItem[] resourceFiles = t.ResourceFilesWithManifestResourceNames;

            Assert.Single(resourceFiles);
            Assert.Equal(@"strings.resx", resourceFiles[0].ItemSpec);
            Assert.Equal(String.Empty, resourceFiles[0].GetMetadata("LogicalName"));
        }

        /// <summary>
        /// A culture-specific resources file in a subfolder, with a root namespace
        /// </summary>
        [Fact]
        public void CulturedResourcesFileWithRootNamespaceWithinSubfolder()
        {
            string result =
            CreateCSharpManifestResourceName.CreateManifestNameImpl(
                    fileName: @"SubFolder\MyResource.fr.resources",
                    linkFileName: null,    // Link file name
                    prependCultureAsDirectory: false,
                    rootNamespace: "RootNamespace",        // Root namespace
                    dependentUponFileName: null,
                    culture: null,
                    binaryStream: null,
                    log: null);

            Assert.Equal(@"RootNamespace.SubFolder.MyResource.fr.resources", result);
        }

        /// <summary>
        /// A culture-specific resources file with a root namespace
        /// </summary>
        [Fact]
        public void CulturedResourcesFileWithRootNamespace()
        {
            string result =
            CreateCSharpManifestResourceName.CreateManifestNameImpl(
                    fileName: @"MyResource.fr.resources",
                    linkFileName: null,    // Link file name
                    prependCultureAsDirectory: false,
                    rootNamespace: "RootNamespace",        // Root namespace
                    dependentUponFileName: null,
                    culture: null,
                    binaryStream: null,
                    log: null);

            Assert.Equal(@"RootNamespace.MyResource.fr.resources", result);
        }

        /// <summary>
        /// A non-culture-specific resources file with a root namespace
        /// </summary>
        [Fact]
        public void ResourcesFileWithRootNamespace()
        {
            string result =
            CreateCSharpManifestResourceName.CreateManifestNameImpl(
                    fileName: @"MyResource.resources",
                    linkFileName: null,    // Link file name
                    prependCultureAsDirectory: false,
                    rootNamespace: "RootNamespace",        // Root namespace
                    dependentUponFileName: null,
                    culture: null,
                    binaryStream: null,
                    log: null);

            Assert.Equal(@"RootNamespace.MyResource.resources", result);
        }
    }
}
