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

using System.Collections.Generic;
using System.IO;
using System.Text;
using Microsoft.Build.Tasks.ResourceHandling;
using Microsoft.Build.Tasks.UnitTests.ResourceHandling;
using Microsoft.Build.UnitTests;
using Shouldly;
using Xunit;
using Xunit.Abstractions;

#nullable disable

namespace Microsoft.Build.Tasks.UnitTests.GenerateResource
{
    public class MSBuildResXReader_Tests
    {
        private readonly ITestOutputHelper _output;

        public MSBuildResXReader_Tests(ITestOutputHelper output)
        {
            _output = output;
        }

        [Fact]
        public void ParsesSingleStringAsString()
        {
            var resxWithSingleString = MSBuildResXReader.GetResourcesFromString(
                ResXHelper.SurroundWithBoilerplate(
                    @"<data name=""StringResource"" xml:space=""preserve"">
    <value>StringValue</value>
    <comment>Comment</comment>
  </data>"), null, false);

            AssertSingleStringResource(resxWithSingleString, "StringResource", "StringValue");
        }

        [Fact]
        public void ParsesSingleStringWithoutPreserveAsString()
        {
            var resxWithSingleString = MSBuildResXReader.GetResourcesFromString(
                ResXHelper.SurroundWithBoilerplate(
                    @"<data name=""StringResource"">
    <value> StringValue </value>
    <comment>Comment</comment>
  </data>"), null, false);

            AssertSingleStringResource(resxWithSingleString, "StringResource", " StringValue ");
        }

        [Fact]
        public void ParsesSingleWhitespaceStringAsString()
        {
            var resxWithSingleString = MSBuildResXReader.GetResourcesFromString(
                ResXHelper.SurroundWithBoilerplate(
                    @"<data name=""StringResource"" xml:space=""preserve"">
    <value> </value>
    <comment>Comment</comment>
  </data>"), null, false);

            AssertSingleStringResource(resxWithSingleString, "StringResource", " ");
        }

        [Fact]
        public void ParsesSingleWhitespaceStringWithNoPreserveAsEmptyString()
        {
            var resxWithSingleString = MSBuildResXReader.GetResourcesFromString(
                ResXHelper.SurroundWithBoilerplate(
                    @"<data name=""StringResource"">
    <value> </value>
    <comment>Comment</comment>
  </data>"), null, false);

            AssertSingleStringResource(resxWithSingleString, "StringResource", "");
        }

        [Fact]
        public void ParsesSingleStringWithPartialTypeName()
        {
            var resxWithSingleString = MSBuildResXReader.GetResourcesFromString(
                ResXHelper.SurroundWithBoilerplate(
                    @"<data name=""StringResource"" type=""System.String"">
    <value>StringValue</value>
  </data>"), null, false);

            AssertSingleStringResource(resxWithSingleString, "StringResource", "StringValue");
        }


        [Fact]
        public void LoadsMultipleStringsPreservingOrder()
        {
            var resxWithTwoStrings = MSBuildResXReader.GetResourcesFromString(
    ResXHelper.SurroundWithBoilerplate(
        @"<data name=""StringResource"" xml:space=""preserve"">
    <value>StringValue</value>
    <comment>Comment</comment>
  </data>
  <data name=""2StringResource2"" xml:space=""preserve"">
    <value>2StringValue2</value>
  </data>"), null, false);

            resxWithTwoStrings.Count.ShouldBe(2);

            resxWithTwoStrings[0].Name.ShouldBe("StringResource");
            resxWithTwoStrings[0].ShouldBeOfType<StringResource>()
                .Value.ShouldBe("StringValue");

            resxWithTwoStrings[1].Name.ShouldBe("2StringResource2");
            resxWithTwoStrings[1].ShouldBeOfType<StringResource>()
                .Value.ShouldBe("2StringValue2");
        }

        [Fact]
        public void ResXNullRefProducesNullLiveObject()
        {
            var resxWithNullRef = MSBuildResXReader.GetResourcesFromString(
                ResXHelper.SurroundWithBoilerplate(
@"  <assembly alias=""System.Windows.Forms"" name=""System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"" />
  <data name=""$this.AccessibleDescription"" type=""System.Resources.ResXNullRef, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"">
    <value />
  </data>"), null, false);

            resxWithNullRef.ShouldHaveSingleItem();

            resxWithNullRef[0].Name.ShouldBe("$this.AccessibleDescription");

            resxWithNullRef[0].ShouldBeOfType<LiveObjectResource>()
                .Value.ShouldBeNull();
        }

        [Theory]
        [InlineData("System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
        [InlineData("System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
        public void LoadsStringFromFileRefAsString(string stringType)
        {
            File.Exists(Path.Combine("ResourceHandling", "TextFile1.txt")).ShouldBeTrue("Test deployment is missing None files");

            var resxWithLinkedString = MSBuildResXReader.GetResourcesFromString(
                ResXHelper.SurroundWithBoilerplate(
$@"  <assembly alias=""System.Windows.Forms"" name=""System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"" />
  <data name=""TextFile1"" type=""System.Resources.ResXFileRef, System.Windows.Forms"">
    <value>ResourceHandling\TextFile1.txt;{stringType};utf-8</value>
  </data>"), null, false);

            AssertSingleStringResource(resxWithLinkedString, "TextFile1", "Contents of TextFile1");
        }

        [Fact]
        public void LoadsStringFromFileRefAsStringWithShiftJISEncoding()
        {
            using (var env = TestEnvironment.Create(_output))
            {
                const string JapaneseString = "ハローワールド！";

                var baseDir = env.CreateFolder(createFolder: true);
                var resourceHandlingFolder = baseDir.CreateDirectory("ResourceHandling");

                var linkedTextFile = resourceHandlingFolder.CreateFile("TextFileInShiftJIS.txt");

#if RUNTIME_TYPE_NETCORE
                Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
#endif

                File.WriteAllText(linkedTextFile.Path,
                                  JapaneseString,
                                  Encoding.GetEncoding("shift_jis"));

                var resxWithLinkedString = MSBuildResXReader.GetResourcesFromString(
                    ResXHelper.SurroundWithBoilerplate(
    @"  <assembly alias=""System.Windows.Forms"" name=""System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"" />
  <data name=""TextFile1"" type=""System.Resources.ResXFileRef, System.Windows.Forms"">
    <value>ResourceHandling\TextFileInShiftJIS.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;shift_jis</value>
  </data>"),
                    null,
                    false,
                    Path.Combine(baseDir.Path, nameof(LoadsStringFromFileRefAsStringWithShiftJISEncoding) + ".resx"),
                    useRelativePath: true);

                AssertSingleStringResource(resxWithLinkedString, "TextFile1", JapaneseString);
            }
        }

        private static void AssertSingleStringResource(IReadOnlyList<IResource> resources, string name, string value)
        {
            resources.ShouldHaveSingleItem();

            resources[0].Name.ShouldBe(name);

            resources[0].ShouldBeOfType<StringResource>()
                .Value.ShouldBe(value);
        }

        [Fact]
        public void PassesThroughBitmapInResx()
        {
            var resxWithEmbeddedBitmap = MSBuildResXReader.GetResourcesFromString(
                ResXHelper.SurroundWithBoilerplate(
@"  <assembly alias=""System.Drawing"" name=""System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"" />
  <data name=""pictureBox1.Image"" type=""System.Drawing.Bitmap, System.Drawing"" mimetype=""application/x-microsoft.net.object.bytearray.base64"">
    <value>
        iVBORw0KGgoAAAANSUhEUgAAACgAAAAeCAIAAADRv8uKAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
        wwAADsMBx2+oZAAAABh0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMS41ZEdYUgAAAZNJREFUSEvtk6GL
        wmAYxlfMFoNpQWe4Nqt/gIjBJAgiVoMIhomCiElMphUZGtRm1jTErSlcEAwaxei6eCDbPe4dY8g4dnc4
        y37p+R5e9tu37xtjvIlA7BuB2DcCsW8E4lqt1ul0ut3ubDbTdZ3KarXabDYpHw6HSqVCGUyn01arhabX
        61mVN57FDMMUi8V2u83zPMuyp9OJSnA+n5E3mw2yOWuUy2VkQRD6/X6pVFoul9R7wUWMR1POZrPYOpUc
        xzUaDeTtdkvi8XgciUQul8tj9Pf8JC4UCvV6ncrhcBgKha7X6263I3E6nabX+hsuYlmWj8ejKIrI6/Wa
        yv1+n8lkBoOBLY7FYpPJBCGXy32YrFarxyO84SIGON1UKmWfGRqIF4tFPB63xbgEo9EI4dMEpaIo5rgn
        XMT2p7ZBCR9CMpnEJSJxPp93Xu9XibEnBPxjyABZVVUESZLu9zvNvFAMotEoicF8Pk8kEliGw2Fc+3+J
        b7eblRw4yy8Ta2GCpaZp1sIzz2LfCMS+EYh9401iw/gG1gYfvzjQIXcAAAAASUVORK5CYII=
</value>
  </data>
"), null, false);
            resxWithEmbeddedBitmap.ShouldHaveSingleItem();
            resxWithEmbeddedBitmap[0].ShouldBeOfType(typeof(TypeConverterByteArrayResource));

            var resource = (TypeConverterByteArrayResource)resxWithEmbeddedBitmap[0];
            resource.Name.ShouldBe("pictureBox1.Image");
            resource.TypeAssemblyQualifiedName.ShouldBe("System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
        }

        [Fact]
        public void TypeConverterStringWellFormatted()
        {
            var resxWithEmbeddedBitmap = MSBuildResXReader.GetResourcesFromString(
                ResXHelper.SurroundWithBoilerplate(
@"  <assembly alias=""System.Drawing"" name=""System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"" />
    <data name=""color"" type=""System.Drawing.Color, System.Drawing"">
      <value>Blue</value>
    </data>
"), null, false);
            resxWithEmbeddedBitmap.ShouldHaveSingleItem();
            resxWithEmbeddedBitmap[0].ShouldBeOfType(typeof(TypeConverterStringResource));

            var resource = (TypeConverterStringResource)resxWithEmbeddedBitmap[0];
            resource.Name.ShouldBe("color");
            resource.TypeAssemblyQualifiedName.ShouldBe("System.Drawing.Color, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
            resource.StringRepresentation.ShouldBe("Blue");
        }

        /// <summary>
        /// Test a string-based TypeConverter resource shaped like the one in the old
        /// ResXResourceWriter block comment without a "data" element.
        /// </summary>
        /// <remarks>
        /// https://github.com/dotnet/winforms/blob/195f89af79d550c2da1711c45c379efd63519ac1/src/System.Windows.Forms/src/System/Resources/ResXResourceWriter.cs#L141
        /// </remarks>
        [Fact]
        public void TypeConverterStringDirectValue()
        {
            var resxWithEmbeddedBitmap = MSBuildResXReader.GetResourcesFromString(
                ResXHelper.SurroundWithBoilerplate(
@"  <assembly alias=""System.Drawing"" name=""System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"" />
    <data name=""Color1"" type=""System.Drawing.Color, System.Drawing"">Blue</data>
"), null, false);
            resxWithEmbeddedBitmap.ShouldHaveSingleItem();
            resxWithEmbeddedBitmap[0].ShouldBeOfType(typeof(TypeConverterStringResource));

            var resource = (TypeConverterStringResource)resxWithEmbeddedBitmap[0];
            resource.Name.ShouldBe("Color1");
            resource.TypeAssemblyQualifiedName.ShouldBe("System.Drawing.Color, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
            resource.StringRepresentation.ShouldBe("Blue");
        }

        [Fact]
        public void ResXFileRefToBitmap()
        {
            string bitmapPath = Build.UnitTests.GenerateResource_Tests.Utilities.CreateWorldsSmallestBitmap();

            var resxWithLinkedBitmap = MSBuildResXReader.GetResourcesFromString(
                ResXHelper.SurroundWithBoilerplate(
$@"  <data name='Image1' type='System.Resources.ResXFileRef, System.Windows.Forms'>
    <value>{bitmapPath};System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
  </data>
"), null, false);
            resxWithLinkedBitmap.ShouldHaveSingleItem();
            resxWithLinkedBitmap[0].ShouldBeOfType(typeof(FileStreamResource));

            var resource = (FileStreamResource)resxWithLinkedBitmap[0];
            resource.Name.ShouldBe("Image1");
            resource.TypeAssemblyQualifiedName.ShouldBe("System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
        }

        [Theory]
        [InlineData("System.IO.MemoryStream, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
        [InlineData("System.IO.MemoryStream, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
        public void ResXFileRefToMemoryStream(string typeNameInResx)
        {
            using var env = TestEnvironment.Create(_output);

            var baseDir = env.CreateFolder(createFolder: true);
            var resourceHandlingFolder = baseDir.CreateDirectory("ResourceHandling");

            var linkedTextFile = resourceHandlingFolder.CreateFile("FileToBeIncluded.txt");

            File.WriteAllText(linkedTextFile.Path,
                  "Test data");

            var resources = MSBuildResXReader.GetResourcesFromString(
                ResXHelper.SurroundWithBoilerplate(
$@"  <data name='Image1' type='System.Resources.ResXFileRef, System.Windows.Forms'>
    <value>{linkedTextFile.Path};{typeNameInResx}</value>
  </data>
"), null, false);

            var resource = resources.ShouldHaveSingleItem()
                .ShouldBeOfType<LiveObjectResource>();
            resource.Name.ShouldBe("Image1");

            byte[] bytes = new byte[4];
            resource.Value
                .ShouldBeOfType<MemoryStream>()
                .Read(bytes, 0, 4);
            bytes.ShouldBe(new byte[] { 84, 101, 115, 116 }, "Expected the bytes of 'Test' to start the stream");
        }

        [Fact]
        public void AssemblyElementWithNoAliasInfersSimpleName()
        {
            var resxWithEmbeddedBitmap = MSBuildResXReader.GetResourcesFromString(
                ResXHelper.SurroundWithBoilerplate(
@"  <assembly name=""System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"" />
    <data name=""Color1"" type=""System.Drawing.Color, System.Drawing""><value>Blue</value></data>
"), null, false);
            resxWithEmbeddedBitmap.ShouldHaveSingleItem();
            resxWithEmbeddedBitmap[0].ShouldBeOfType(typeof(TypeConverterStringResource));

            var resource = (TypeConverterStringResource)resxWithEmbeddedBitmap[0];
            resource.Name.ShouldBe("Color1");
            resource.TypeAssemblyQualifiedName.ShouldBe("System.Drawing.Color, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
            resource.StringRepresentation.ShouldBe("Blue");
        }

        // TODO: invalid resx xml

        // TODO: valid xml, but invalid resx-specific data

        // TODO: aliased entry but no defined alias (failure)
        // TODO: alias but with reversed order (ResXResourceReader fails)

        // TODO: not-well-qualified types: ResXFileRef with no alias
        // TODO: not-well-qualified types: System.String with no assembly qualification
    }
}
