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

#nullable disable

namespace Microsoft.Build.UnitTests
{
    public class ToolTaskExtensionTests
    {
        /// <summary>
        /// Verify that the resources in ToolTask/derived classes work correctly (are accessible with correct resource managers)
        /// With moving ToolTask into Utilities, tasks inheriting from it now have to deal with 3 (THREE!) resource streams,
        /// which has a lot of potential for breaking. Make sure that tasks can access all of them using the correct logger helpers.
        /// </summary>
        [Fact]
        public void TestResourceAccess()
        {
            MyToolTaskExtension t = new MyToolTaskExtension();
            MockEngine engine = new MockEngine();

            t.BuildEngine = engine;

            // No need to actually check the outputted strings. We only care that this doesn't throw, which means that 
            // the resource strings were reachable.

            // Normal CSC messages first, from private XMakeTasks resources. They should be accessible with t.Log
            t.Log.LogErrorWithCodeFromResources("Csc.AssemblyAliasContainsIllegalCharacters", "PlanetSide", "Knights of the Old Republic");
            t.Log.LogWarningWithCodeFromResources("Csc.InvalidParameter");
            t.Log.LogMessageFromResources("Vbc.ParameterHasInvalidValue", "Rome Total War", "Need for Speed Underground");

            // Now shared messages. Should be accessible with the private LogShared property
            PropertyInfo logShared = typeof(ToolTask).GetProperty("LogShared", BindingFlags.Instance | BindingFlags.NonPublic);
            TaskLoggingHelper log = (TaskLoggingHelper)logShared.GetValue(t, null);
            log.LogWarningWithCodeFromResources("Shared.FailedCreatingTempFile", "Gothic II");
            log.LogMessageFromResources("Shared.CannotConvertStringToBool", "foo");

            // Now private Utilities messages. Should be accessible with the private LogPrivate property
            PropertyInfo logPrivate = typeof(ToolTask).GetProperty("LogPrivate", BindingFlags.Instance | BindingFlags.NonPublic);
            log = (TaskLoggingHelper)logPrivate.GetValue(t, null);
            log.LogErrorWithCodeFromResources("ToolTask.CommandTooLong", "Painkiller");
            log.LogWarningWithCodeFromResources("ToolTask.CouldNotStartToolExecutable", "Fallout Tactics", "Fallout 2");
            log.LogMessageFromResources("ToolsLocationHelper.InvalidRedistFile", "Deus Ex", "Fallout");
        }

        /// <summary>
        /// Verify that the above method actually tests something, that is make sure that non-existent resources throw
        /// </summary>
        [Fact]
        public void ResourceAccessSanityCheck()
        {
            Assert.Throws<ArgumentException>(() =>
            {
                MyToolTaskExtension t = new MyToolTaskExtension();
                MockEngine engine = new MockEngine();

                t.BuildEngine = engine;
                t.Log.LogErrorFromResources("Beyond Good and Evil");
            });
        }
        /// <summary>
        /// Retrieve a non-existent value but ask for a default.
        /// </summary>
        [Fact]
        public void GetNonExistentBoolWithDefault()
        {
            MyToolTaskExtension t = new MyToolTaskExtension();
            Assert.Equal(5, t.GetIntParameterWithDefault("Key", 5));
        }

        /// <summary>
        /// Retrieve a value that exists, but ask for a default. We expect the
        /// real value to win.
        /// </summary>
        [Fact]
        public void GetBoolWithDefault()
        {
            MyToolTaskExtension t = new MyToolTaskExtension();
            t.Bag["Key"] = true;

            Assert.True(t.GetBoolParameterWithDefault("Key", false));
        }

        /// <summary>
        /// Retrieve a value that exists, but ask for a default. We expect the
        /// real value to win.
        /// </summary>
        [Fact]
        public void GetIntWithDefault()
        {
            MyToolTaskExtension t = new MyToolTaskExtension();
            t.Bag["Key"] = 5;

            Assert.Equal(5, t.GetIntParameterWithDefault("Key", 9));
        }

        [Fact]
        public void UseNewLineSeparatorseInResponseFile()
        {
            Action<CommandLineBuilderExtension> addResponseFileCommands = (commandLineBuilder) =>
            {
                commandLineBuilder.AppendSwitchIfNotNull("/A:", "D66B977148114482A88B0EFC1E531F02");
                commandLineBuilder.AppendSwitchIfNotNull("/B:", "F9E03765A87543F4B385664B8DB7619D");
            };

            MyToolTaskExtension t = new MyToolTaskExtension(useNewLineSeparators: true, addResponseFileCommands: addResponseFileCommands);

            string[] actual = t.GetResponseFileCommands().Split(MSBuildConstants.EnvironmentNewLine, StringSplitOptions.None);
            string[] expected =
            {
                "/A:D66B977148114482A88B0EFC1E531F02",
                "/B:F9E03765A87543F4B385664B8DB7619D"
            };

            Assert.Equal(expected, actual);
        }

        private sealed class MyToolTaskExtension : ToolTaskExtension
        {
            private readonly Action<CommandLineBuilderExtension> _addResponseFileCommands;

            public MyToolTaskExtension(bool useNewLineSeparators = false, Action<CommandLineBuilderExtension> addResponseFileCommands = null)
            {
                _addResponseFileCommands = addResponseFileCommands;
                UseNewLineSeparatorInResponseFile = useNewLineSeparators;
            }
            protected override string ToolName
            {
                get { return "toolname"; }
            }

            protected override string GenerateFullPathToTool()
            {
                return "fullpath";
            }

            protected override bool UseNewLineSeparatorInResponseFile { get; }

            public string GetResponseFileCommands()
            {
                return GenerateResponseFileCommands();
            }

            protected internal override void AddResponseFileCommands(CommandLineBuilderExtension commandLine)
            {
                _addResponseFileCommands?.Invoke(commandLine);

                base.AddResponseFileCommands(commandLine);
            }
        }
    }
}
