// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Build.Construction;
using Microsoft.DotNet.Cli.Utils.CommandParsing;
using Microsoft.DotNet.ProjectJsonMigration.Transforms;
using Microsoft.DotNet.Internal.ProjectModel;

namespace Microsoft.DotNet.ProjectJsonMigration.Rules
{
    internal class MigrateScriptsRule : IMigrationRule
    {
        private readonly ITransformApplicator _transformApplicator;

        public MigrateScriptsRule(ITransformApplicator transformApplicator = null)
        {
            _transformApplicator = transformApplicator ?? new TransformApplicator();
        }

        public void Apply(MigrationSettings migrationSettings, MigrationRuleInputs migrationRuleInputs)
        {
            var csproj = migrationRuleInputs.OutputMSBuildProject;
            var commonPropertyGroup = migrationRuleInputs.CommonPropertyGroup;
            var projectContext = migrationRuleInputs.DefaultProjectContext;
            var scripts = projectContext.ProjectFile.Scripts;

            foreach (var scriptSet in scripts)
            {
                MigrateScriptSet(
                    csproj,
                    commonPropertyGroup,
                    scriptSet.Value,
                    scriptSet.Key,
                    migrationRuleInputs.IsMultiTFM);
            }
        }

        public ProjectTargetElement MigrateScriptSet(
            ProjectRootElement csproj,
            ProjectPropertyGroupElement commonPropertyGroup,
            IEnumerable<string> scriptCommands,
            string scriptSetName,
            bool isMultiTFM)
        {
            var target = CreateTarget(csproj, scriptSetName, isMultiTFM);
            foreach (var scriptCommand in scriptCommands)
            {
                if (CommandIsNotNeededInMSBuild(scriptCommand))
                {
                    continue;
                }
                else if (IsRazorPrecompilationCommand(scriptCommand))
                {
                    EnableRazorCompilationOnPublish(commonPropertyGroup);
                    continue;
                }

                AddExec(target, FormatScriptCommand(scriptCommand));
            }

            return target;
        }

        internal string FormatScriptCommand(string scriptCommandline)
        {
            return ReplaceScriptVariables(scriptCommandline);
        }

        internal string ReplaceScriptVariables(string scriptCommandline)
        {
            Func<string, string> scriptVariableReplacementDelegate = key =>
            {
                if (ScriptVariableToMSBuildMap.ContainsKey(key))
                {
                    if (ScriptVariableToMSBuildMap[key] == null)
                    {
                        MigrationErrorCodes.MIGRATE1016(
                                String.Format(LocalizableStrings.MIGRATE1016Arg, key))
                            .Throw();
                    }

                    return ScriptVariableToMSBuildMap[key];
                }
                return $"$({key})";
            };

            var scriptArguments = CommandGrammar.Process(
                scriptCommandline,
                scriptVariableReplacementDelegate,
                preserveSurroundingQuotes: true);

            scriptArguments = scriptArguments.Where(argument => !string.IsNullOrEmpty(argument)).ToArray();

            return string.Join(" ", scriptArguments);
        }

        private bool CommandIsNotNeededInMSBuild(string command)
        {
            return command.Contains("dotnet publish-iis");
        }

        private static bool IsRazorPrecompilationCommand(string command)
        {
            return command.Contains("dotnet razor-precompile");
        }

        private static void EnableRazorCompilationOnPublish(ProjectPropertyGroupElement commonPropertyGroup)
        {
            commonPropertyGroup.AddProperty("MvcRazorCompileOnPublish", "true");
        }

        private bool IsPathRootedForAnyOS(string path)
        {
            return path.StartsWith("/") || path.Substring(1).StartsWith(":\\");
        }

        private ProjectTargetElement CreateTarget(ProjectRootElement csproj, string scriptSetName, bool isMultiTFM)
        {
            var targetName = $"{scriptSetName[0].ToString().ToUpper()}{string.Concat(scriptSetName.Skip(1))}Script";

            TargetHookInfo targetHookInfo;
            if(!ScriptSetToMSBuildHookTargetMap.TryGetValue(scriptSetName, out targetHookInfo))
            {
                MigrationErrorCodes.MIGRATE1019(
                        String.Format(LocalizableStrings.MIGRATE1019Arg, scriptSetName))
                    .Throw();
            }

            var target = csproj.CreateTargetElement(targetName);
            csproj.InsertBeforeChild(target, csproj.LastChild);
            if (targetHookInfo.IsRunBefore)
            {
                target.BeforeTargets = targetHookInfo.TargetName;
            }
            else
            {
                target.AfterTargets = targetHookInfo.TargetName;
            }

            if (isMultiTFM)
            {
                ConditionTargetToRunScriptsAfterEachInnerBuild(target);
            }

            return target;
        }

        private void ConditionTargetToRunScriptsAfterEachInnerBuild(ProjectTargetElement target)
        {
            target.Condition = " '$(IsCrossTargetingBuild)' != 'true' ";
        }

        private void AddExec(ProjectTargetElement target, string command)
        {
            var task = target.AddTask("Exec");
            task.SetParameter("Command", command);
        }

        // ProjectJson Script Set Name to 
        private static Dictionary<string, TargetHookInfo> ScriptSetToMSBuildHookTargetMap => new Dictionary<string, TargetHookInfo>()
        {
            { "precompile",  new TargetHookInfo(true, "BeforeBuild") },
            { "postcompile", new TargetHookInfo(false, "Build") },
            { "prepublish",  new TargetHookInfo(true, "PrepareForPublish") },
            { "postpublish", new TargetHookInfo(false, "Publish") }
        };

        private static Dictionary<string, string> ScriptVariableToMSBuildMap => new Dictionary<string, string>
        {
            { "compile:ResponseFile", null },     // Not migrated
            { "compile:CompilerExitCode", null }, // Not migrated
            { "compile:RuntimeOutputDir", null }, // Not migrated
            { "compile:RuntimeIdentifier", null },// Not Migrated
            
            { "compile:TargetFramework", "$(TargetFramework)" },
            { "publish:TargetFramework", "$(TargetFramework)" },
            { "publish:Runtime", "$(RuntimeIdentifier)" },

            { "compile:FullTargetFramework", "$(TargetFrameworkIdentifier),Version=$(TargetFrameworkVersion)" },
            { "compile:Configuration", "$(Configuration)" },
            { "compile:OutputFile", "$(TargetPath)" },
            { "compile:OutputDir", "$(TargetDir)" },

            { "publish:ProjectPath", "$(MSBuildThisFileDirectory)" },
            { "publish:Configuration", "$(Configuration)" },
            { "publish:OutputPath", "$(TargetDir)" },
            { "publish:FullTargetFramework", "$(TargetFrameworkIdentifier),Version=$(TargetFrameworkVersion)" },

            { "project:Directory", "$(MSBuildProjectDirectory)" },
            { "project:Name", "$(AssemblyName)" },
            { "project:Version", "$(Version)" }
        };

        private class TargetHookInfo
        {
            public bool IsRunBefore { get; }
            public string TargetName { get; }

            public TargetHookInfo(bool isRunBefore, string targetName)
            {
                IsRunBefore = isRunBefore;
                TargetName = targetName;
            }
        }
    }
}
