// 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 System.Reflection;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Shared;
using Xunit;

#nullable disable

namespace Microsoft.Build.UnitTests.XamlDataDrivenToolTask_Tests
{
    /// <summary>
    /// Test fixture for testing the DataDrivenToolTask class with a fake task generated by XamlTestHelpers.cs
    /// Tests to see if certain switches are appended.
    /// </summary>
    public class GeneratedTask
    {
        private Assembly _fakeTaskDll;

        public GeneratedTask()
        {
            _fakeTaskDll = XamlTestHelpers.SetupGeneratedCode();
        }

        /// <summary>
        /// Test to see whether all of the correct boolean switches are appended.
        /// </summary>
        [Fact]
        public void TestDefaultFlags()
        {
            object fakeTaskInstance = CreateFakeTask();
            CheckCommandLine("/always /Cr:CT", XamlTestHelpers.GenerateCommandLine(fakeTaskInstance));
        }

        /// <summary>
        /// A test to see if all of the reversible flags are generated correctly
        /// This test case leaves the default flags the way they are
        /// </summary>
        [Fact]
        public void TestReversibleFlagsWithDefaults()
        {
            object fakeTaskInstance = CreateFakeTask();
            XamlTestHelpers.SetProperty(fakeTaskInstance, "BasicReversible", true);
            string expectedResult = "/always /Br /Cr:CT";
            CheckCommandLine(expectedResult, XamlTestHelpers.GenerateCommandLine(fakeTaskInstance));
        }

        /// <summary>
        /// A test to see if all of the reversible flags are generated correctly
        /// This test case explicitly sets the ComplexReversible to be false
        /// </summary>
        [Fact]
        public void TestReversibleFlagsWithoutDefaults()
        {
            object fakeTaskInstance = CreateFakeTask();
            XamlTestHelpers.SetProperty(fakeTaskInstance, "BasicReversible", true);
            XamlTestHelpers.SetProperty(fakeTaskInstance, "ComplexReversible", false);
            string expectedResult = "/always /Br /Cr:CF";
            CheckCommandLine(expectedResult, XamlTestHelpers.GenerateCommandLine(fakeTaskInstance));
        }

        /// <summary>
        /// Tests to make sure enums are working well.
        /// </summary>
        [Fact]
        public void TestBasicString()
        {
            object fakeTaskInstance = CreateFakeTask();
            XamlTestHelpers.SetProperty(fakeTaskInstance, "BasicString", "Enum1");
            string expectedResult = "/always /Bs1 /Cr:CT";
            CheckCommandLine(expectedResult, XamlTestHelpers.GenerateCommandLine(fakeTaskInstance));
        }

        [Fact]
        public void TestDynamicEnum()
        {
            object fakeTaskInstance = CreateFakeTask();
            XamlTestHelpers.SetProperty(fakeTaskInstance, "BasicDynamicEnum", "MyBeforeTarget");
            string expectedResult = "/always MyBeforeTarget /Cr:CT";
            CheckCommandLine(expectedResult, XamlTestHelpers.GenerateCommandLine(fakeTaskInstance));
        }

        /// <summary>
        /// Tests the basic string array type
        /// </summary>
        [Fact]
        public void TestBasicStringArray()
        {
            object fakeTaskInstance = CreateFakeTask();
            string[] fakeArray = new string[1];
            fakeArray[0] = "FakeStringArray";
            XamlTestHelpers.SetProperty(fakeTaskInstance, "BasicStringArray", new object[] { fakeArray });
            string expectedResult = "/always /BsaFakeStringArray /Cr:CT";
            CheckCommandLine(expectedResult, XamlTestHelpers.GenerateCommandLine(fakeTaskInstance));
        }

        /// <summary>
        /// Tests the basic string array type, with an array that contains multiple values.
        /// </summary>
        [Fact]
        public void TestBasicStringArray_MultipleValues()
        {
            object fakeTaskInstance = CreateFakeTask();
            string[] fakeArray = new string[3];
            fakeArray[0] = "Fake";
            fakeArray[1] = "String";
            fakeArray[2] = "Array";
            XamlTestHelpers.SetProperty(fakeTaskInstance, "BasicStringArray", new object[] { fakeArray });
            string expectedResult = "/always /BsaFake /BsaString /BsaArray /Cr:CT";
            CheckCommandLine(expectedResult, XamlTestHelpers.GenerateCommandLine(fakeTaskInstance));
        }

        /// <summary>
        /// Tests to see whether the integer appears correctly on the command line
        /// </summary>
        [Fact]
        public void TestInteger()
        {
            object fakeTaskInstance = CreateFakeTask();
            XamlTestHelpers.SetProperty(fakeTaskInstance, "BasicInteger", 2);
            string expectedResult = "/always /Bi2 /Cr:CT";
            CheckCommandLine(expectedResult, XamlTestHelpers.GenerateCommandLine(fakeTaskInstance));
        }

        // complex tests
        /// <summary>
        /// Tests the (full) functionality of a reversible property
        /// </summary>
        [Fact]
        public void TestComplexReversible()
        {
            // When flag is set to false
            object fakeTaskInstance = CreateFakeTask();
            XamlTestHelpers.SetProperty(fakeTaskInstance, "ComplexReversible", false);
            string expectedResult = "/always /Cr:CF";
            CheckCommandLine(expectedResult, XamlTestHelpers.GenerateCommandLine(fakeTaskInstance));

            // When flag is set to true
            fakeTaskInstance = CreateFakeTask();
            XamlTestHelpers.SetProperty(fakeTaskInstance, "ComplexReversible", true);
            expectedResult = "/always /Cr:CT";
            CheckCommandLine(expectedResult, XamlTestHelpers.GenerateCommandLine(fakeTaskInstance));
        }

        [Fact]
        public void TestComplexString()
        {
            // check to see that the resulting value is good
            object fakeTaskInstance = CreateFakeTask();
            XamlTestHelpers.SetProperty(fakeTaskInstance, "ComplexString", "LegalValue1");
            string expectedResult = "/always /Cr:CT /Lv1";
            CheckCommandLine(expectedResult, XamlTestHelpers.GenerateCommandLine(fakeTaskInstance));
        }

        /// <summary>
        /// Tests the functionality of a string type property
        /// </summary>
        [Fact]
        public void TestComplexStringArray()
        {
            object fakeTaskInstance = CreateFakeTask();
            string[] fakeArray = new string[] { "FakeFile1", "FakeFile2", "FakeFile3" };
            XamlTestHelpers.SetProperty(fakeTaskInstance, "ComplexStringArray", new object[] { fakeArray });
            string expectedResult = "/always /Cr:CT /CsaFakeFile1;FakeFile2;FakeFile3";
            CheckCommandLine(expectedResult, XamlTestHelpers.GenerateCommandLine(fakeTaskInstance));
        }

        [Fact]
        public void TestComplexIntegerLessThanMin()
        {
            Assert.Throws<InvalidOperationException>(() =>
            {
                object fakeTaskInstance = CreateFakeTask();
                XamlTestHelpers.SetProperty(fakeTaskInstance, "ComplexInteger", 2);
            });
        }

        [Fact]
        public void TestComplexIntegerGreaterThanMax()
        {
            Assert.Throws<InvalidOperationException>(() =>
            {
                object fakeTaskInstance = CreateFakeTask();
                XamlTestHelpers.SetProperty(fakeTaskInstance, "ComplexInteger", 256);
                string expectedResult = "/always /Ci256 /Cr:CT";
                CheckCommandLine(expectedResult, XamlTestHelpers.GenerateCommandLine(fakeTaskInstance));
            });
        }
        [Fact]
        public void TestComplexIntegerWithinRange()
        {
            object fakeTaskInstance = CreateFakeTask();
            XamlTestHelpers.SetProperty(fakeTaskInstance, "ComplexInteger", 128);
            string expectedResult = "/always /Cr:CT /Ci128";
            CheckCommandLine(expectedResult, XamlTestHelpers.GenerateCommandLine(fakeTaskInstance));
        }

        /// <summary>
        /// This method checks the generated command line against the expected command line
        /// </summary>
        /// <param name="expected"></param>
        /// <param name="actual"></param>
        /// <returns>true if the two are the same, false if they are different</returns>
        private void CheckCommandLine(string expected, string actual)
        {
            Assert.Equal(expected, actual);
        }

        /// <summary>
        /// XamlTaskFactory does not, in and of itself, support the idea of "always" switches or default values.  At least
        /// for Dev10, the workaround is to create a property as usual, and then specify the required values in the .props
        /// file.  Since these unit tests are just testing the task itself, this method serves as our ".props file".
        /// </summary>
        public object CreateFakeTask()
        {
            object fakeTaskInstance = _fakeTaskDll.CreateInstance("XamlTaskNamespace.FakeTask");

            XamlTestHelpers.SetProperty(fakeTaskInstance, "Always", true);
            XamlTestHelpers.SetProperty(fakeTaskInstance, "ComplexReversible", true);

            return fakeTaskInstance;
        }
    }

    /// <summary>
    /// Tests for XamlDataDrivenToolTask / XamlTaskFactory in the context of a project file.
    /// </summary>
    public class ProjectFileTests
    {
        /// <summary>
        /// Tests that when a call to a XamlDataDrivenTask fails, the commandline is reported in the error message.
        /// </summary>
        [Fact]
        public void CommandLineErrorsReportFullCommandlineAmpersandTemp()
        {
            string projectFile = @"
                      <Project ToolsVersion=`msbuilddefaulttoolsversion` DefaultTargets=`XamlTaskFactory`>
                        <UsingTask TaskName=`TestTask` TaskFactory=`XamlTaskFactory` AssemblyName=`Microsoft.Build.Tasks.v4.0, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`>
                          <Task>
                            <![CDATA[
                              <ProjectSchemaDefinitions xmlns=`clr-namespace:Microsoft.Build.Framework.XamlTypes;assembly=Microsoft.Build.Framework` xmlns:x=`http://schemas.microsoft.com/winfx/2006/xaml` xmlns:sys=`clr-namespace:System;assembly=mscorlib` xmlns:impl=`clr-namespace:Microsoft.VisualStudio.Project.Contracts.Implementation;assembly=Microsoft.VisualStudio.Project.Contracts.Implementation`>
                                <Rule Name=`TestTask` ToolName=`findstr.exe`>
                                  <StringProperty Name=`test` />
                                </Rule>
                              </ProjectSchemaDefinitions>
                            ]]>
                          </Task>
                        </UsingTask>
                        <Target Name=`XamlTaskFactory`>
                          <TestTask CommandLineTemplate='findstr'/>
                        </Target>
                      </Project>";


            string directoryWithAmpersand = "xaml&datadriven";
            string newTmp = Path.Combine(Path.GetTempPath(), directoryWithAmpersand);
            string oldTmp = Environment.GetEnvironmentVariable("TMP");

            try
            {
                Directory.CreateDirectory(newTmp);
                Environment.SetEnvironmentVariable("TMP", newTmp);
                Project p = ObjectModelHelpers.CreateInMemoryProject(projectFile);
                MockLogger logger = new MockLogger();

                bool success = p.Build(logger);
                Assert.False(success);
                logger.AssertLogContains("FINDSTR");

                // Should not be logging ToolTask.ToolCommandFailed, should be logging Xaml.CommandFailed
                logger.AssertLogDoesntContain("MSB6006");
                logger.AssertLogContains("MSB3721");
            }
            finally
            {
                Environment.SetEnvironmentVariable("TMP", oldTmp);
                ObjectModelHelpers.DeleteDirectory(newTmp);
                if (Directory.Exists(newTmp))
                {
                    FileUtilities.DeleteWithoutTrailingBackslash(newTmp);
                }
            }
        }


        /// <summary>
        /// Tests that when a call to a XamlDataDrivenTask fails, the commandline is reported in the error message.
        /// </summary>
        [Fact]
        public void CommandLineErrorsReportFullCommandline()
        {
            string projectFile = @"
                      <Project ToolsVersion=`msbuilddefaulttoolsversion` DefaultTargets=`XamlTaskFactory`>
                        <UsingTask TaskName=`TestTask` TaskFactory=`XamlTaskFactory` AssemblyName=`Microsoft.Build.Tasks.v4.0, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`>
                          <Task>
                            <![CDATA[
                              <ProjectSchemaDefinitions xmlns=`clr-namespace:Microsoft.Build.Framework.XamlTypes;assembly=Microsoft.Build.Framework` xmlns:x=`http://schemas.microsoft.com/winfx/2006/xaml` xmlns:sys=`clr-namespace:System;assembly=mscorlib` xmlns:impl=`clr-namespace:Microsoft.VisualStudio.Project.Contracts.Implementation;assembly=Microsoft.VisualStudio.Project.Contracts.Implementation`>
                                <Rule Name=`TestTask` ToolName=`echoparameters.exe`>
                                  <StringProperty Name=`test` />
                                </Rule>
                              </ProjectSchemaDefinitions>
                            ]]>
                          </Task>
                        </UsingTask>
                        <Target Name=`XamlTaskFactory`>
                          <TestTask CommandLineTemplate=`where /x` />
                        </Target>
                      </Project>";

            Project p = ObjectModelHelpers.CreateInMemoryProject(projectFile);
            MockLogger logger = new MockLogger();

            bool success = p.Build(logger);

            Assert.False(success); // "Build should have failed"

            // Should not be logging ToolTask.ToolCommandFailed, should be logging Xaml.CommandFailed
            logger.AssertLogDoesntContain("MSB6006");
            logger.AssertLogContains("MSB3721");
        }

        /// <summary>
        /// Tests that when a call to a XamlDataDrivenTask fails, the commandline is reported in the error message.
        /// </summary>
        [Fact]
        public void SquareBracketEscaping()
        {
            string projectFile = @"
                      <Project ToolsVersion=`msbuilddefaulttoolsversion` DefaultTargets=`XamlTaskFactory`>
                        <UsingTask TaskName=`TestTask` TaskFactory=`XamlTaskFactory` AssemblyName=`Microsoft.Build.Tasks.v4.0, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a`>
                          <Task>
                            <![CDATA[
                              <ProjectSchemaDefinitions xmlns=`clr-namespace:Microsoft.Build.Framework.XamlTypes;assembly=Microsoft.Build.Framework` xmlns:x=`http://schemas.microsoft.com/winfx/2006/xaml` xmlns:sys=`clr-namespace:System;assembly=mscorlib` xmlns:impl=`clr-namespace:Microsoft.VisualStudio.Project.Contracts.Implementation;assembly=Microsoft.VisualStudio.Project.Contracts.Implementation`>
                                <Rule Name=`TestTask` ToolName=`echoparameters.exe`>
                                  <StringProperty Name=`test` />
                                </Rule>
                              </ProjectSchemaDefinitions>
                            ]]>
                          </Task>
                        </UsingTask>
                        <Target Name=`XamlTaskFactory`>
                          <TestTask CommandLineTemplate=`echo  1) [test]            end` test=`value` />
                          <TestTask CommandLineTemplate=`echo  2) [[[test]           end` test=`value` />
                          <TestTask CommandLineTemplate=`echo  3) [ [test]          end` test=`value` />
                          <TestTask CommandLineTemplate=`echo  4) [ [test] [test]    end` test=`value` />
                          <TestTask CommandLineTemplate=`echo  5) [test]]           end` test=`value` />
                          <TestTask CommandLineTemplate=`echo  6) [[test]]          end` test=`value` />
                          <TestTask CommandLineTemplate=`echo  7) [[[test]]          end` test=`value` />
                          <TestTask CommandLineTemplate=`echo  8) [notaproperty]   end` test=`value` />
                          <TestTask CommandLineTemplate=`echo  9) [[[notaproperty]  end` test=`value` />
                          <TestTask CommandLineTemplate=`echo 10) [ [notaproperty] end` test=`value` />
                          <TestTask CommandLineTemplate=`echo 11) [ [nap] [nap]    end` test=`value` />
                          <TestTask CommandLineTemplate=`echo 12) [notaproperty]]  end` test=`value` />
                          <TestTask CommandLineTemplate=`echo 13) [[notaproperty]]  end` test=`value` />
                          <TestTask CommandLineTemplate=`echo 14) [[[notaproperty]] end` test=`value` />
                        </Target>
                      </Project>";

            Project p = ObjectModelHelpers.CreateInMemoryProject(projectFile);
            MockLogger logger = new MockLogger();

            bool success = p.Build(logger);

            Assert.True(success); // "Build should have succeeded"

            logger.AssertLogContains("echo  1) value            end");
            logger.AssertLogContains("echo  2) [value           end");
            logger.AssertLogContains("echo  3) [ value          end");
            logger.AssertLogContains("echo  4) [ value value    end");
            logger.AssertLogContains("echo  5) value]           end");
            logger.AssertLogContains("echo  6) [test]]          end");
            logger.AssertLogContains("echo  7) [value]          end");
            logger.AssertLogContains("echo  8) [notaproperty]   end");
            logger.AssertLogContains("echo  9) [[notaproperty]  end");
            logger.AssertLogContains("echo 10) [ [notaproperty] end");
            logger.AssertLogContains("echo 11) [ [nap] [nap]    end");
            logger.AssertLogContains("echo 12) [notaproperty]]  end");
            logger.AssertLogContains("echo 13) [notaproperty]]  end");
            logger.AssertLogContains("echo 14) [[notaproperty]] end");
        }
    }
}
