// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace Microsoft.DotNet.Build.Tasks
{
    public sealed class Crossgen : ToolTask
    {
        public Crossgen()
        {
            // Disable partial NGEN to avoid excess JIT-compilation.
            // The intention is to pre-compile as much as possible.
            EnvironmentVariables = new string[] { "COMPlus_PartialNGen=0" };
        }

        [Required]
        public string SourceAssembly { get;set; }

        [Required]
        public string DestinationPath { get; set; }

        [Required]
        public string JITPath { get; set; }

        public string CrossgenPath { get; set; }

        public bool CreateSymbols { get; set; }

        public string DiasymReaderPath { get; set; }

        public bool ReadyToRun { get; set; }

        public ITaskItem[] PlatformAssemblyPaths { get; set; }

        private string TempOutputPath { get; set; }

        private bool _secondInvocationToCreateSymbols;

        protected override bool ValidateParameters()
        {
            base.ValidateParameters();

            if (!File.Exists(SourceAssembly))
            {
                Log.LogError($"SourceAssembly '{SourceAssembly}' does not exist.");

                return false;
            }

            return true;
        }

        public override bool Execute()
        {
            string tempDirPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
            Directory.CreateDirectory(tempDirPath);
            TempOutputPath = Path.Combine(tempDirPath, Path.GetFileName(DestinationPath));

            var toolResult = base.Execute();

            if (toolResult)
            {
                File.Copy(TempOutputPath, DestinationPath, overwrite: true);
            }

            if (File.Exists(TempOutputPath))
            {
                File.Delete(TempOutputPath);
            }
            Directory.Delete(tempDirPath);

            if (toolResult && CreateSymbols)
            {
                _secondInvocationToCreateSymbols = true;
                toolResult = base.Execute();
            }

            return toolResult;
        }

        protected override string ToolName
        {
            get { return "crossgen"; }
        }

        protected override MessageImportance StandardOutputLoggingImportance
        {
            // Default is low, but we want to see output at normal verbosity.
            get { return MessageImportance.Normal; }
        }

        protected override MessageImportance StandardErrorLoggingImportance
        {
            // This turns stderr messages into msbuild errors below.
            get { return MessageImportance.High; }
        }

        protected override void LogEventsFromTextOutput(string singleLine, MessageImportance messageImportance)
        {
            // Crossgen's error/warning formatting is inconsistent and so we do
            // not use the "canonical error format" handling of base.
            //
            // Furthermore, we don't want to log crossgen warnings as msbuild
            // warnings because we cannot prevent them and they are only
            // occasionally formatted as something that base would recognize as
            // a canonically formatted warning anyway.
            //
            // One thing that is consistent is that crossgne errors go to stderr
            // and everything else goes to stdout. Above, we set stderr to high
            // importance above, and stdout to normal. So we can use that here
            // to distinguish between errors and messages.
            if (messageImportance == MessageImportance.High)
            {
                Log.LogError(singleLine);
            }
            else
            {
                Log.LogMessage(messageImportance, singleLine);
            }
        }

        protected override string GenerateFullPathToTool()
        {
            if (CrossgenPath != null)
            {
                return CrossgenPath;
            }

            return "crossgen";
        }

        protected override string GenerateCommandLineCommands()
        {
            if (_secondInvocationToCreateSymbols)
            {
                return $"{GetReadyToRun()} {GetPlatformAssemblyPaths()} {GetDiasymReaderPath()} {GetCreateSymbols()}";
            }

            return $"{GetReadyToRun()} {GetMissingDependenciesOk()} {GetInPath()} {GetOutPath()} {GetPlatformAssemblyPaths()} {GetJitPath()}";
        }

        private string GetCreateSymbols()
        {
            var option = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "-createpdb" : "-createperfmap";
            return $"{option} \"{Path.GetDirectoryName(DestinationPath)}\" \"{DestinationPath}\"";
        }

        private string GetDiasymReaderPath()
        {
            if (string.IsNullOrEmpty(DiasymReaderPath))
            {
                return null;
            }

            return $"-diasymreaderpath \"{DiasymReaderPath}\"";
        }

        private string GetReadyToRun()
        {
            if (ReadyToRun)
            {
                return "-readytorun";
            }

            return null;
        }

        private string GetInPath()
        {
            return $"-in \"{SourceAssembly}\"";
        }
        
        private string GetOutPath()
        {
            return $"-out \"{TempOutputPath}\"";
        }

        private string GetPlatformAssemblyPaths()
        {
            var platformAssemblyPaths = String.Empty;

            if (PlatformAssemblyPaths != null)
            {
                foreach (var excludeTaskItem in PlatformAssemblyPaths)
                {
                    platformAssemblyPaths += $"{excludeTaskItem.ItemSpec}{Path.PathSeparator}";
                }
            }
            
            return $" -platform_assemblies_paths {platformAssemblyPaths.Trim(':')}";
        }
        
        private string GetJitPath()
        {
            return $"-JITPath {JITPath}";
        }

        private string GetMissingDependenciesOk()
        {
            return "-MissingDependenciesOK";
        }

        protected override void LogToolCommand(string message)
        {
            base.LogToolCommand($"{base.GetWorkingDirectory()}> {message}");
        }
    }
}
