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

using Microsoft.Build.Framework;
using System;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using Microsoft.Extensions.DependencyModel;
using System.Collections.Generic;
using NuGet.RuntimeModel;

namespace Microsoft.DotNet.Build.Tasks
{
    public partial class GenerateTestSharedFrameworkDepsFile : BuildTask
    {
        // we don't care about these values in the deps file
        const string rid = "rid";
        const string tfm = "netcoreapp0.0";
        const string fullTfm = ".NETCoreApp,Version=v0.0";

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

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

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

        public override bool Execute()
        {
            var sharedFxDir = new DirectoryInfo(SharedFrameworkDirectory);
            if (!sharedFxDir.Exists)
            {
                Log.LogError($"{nameof(SharedFrameworkDirectory)} '{SharedFrameworkDirectory}' does not exist.");
                return false;
            }

            // directory is the version folder, parent is the shared framework name.
            string sharedFxVersion = sharedFxDir.Name;
            string sharedFxName = sharedFxDir.Parent.Name;

            var ignoredExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
            {
                ".pdb",
                ".json",
                ".config",
                ".xml"
            };

            var isAssemblyTofileNames = Directory.EnumerateFiles(SharedFrameworkDirectory)
                .Where(file => !ignoredExtensions.Contains(Path.GetExtension(file)))
                .ToLookup(file => IsManagedAssembly(file), file => Path.GetFileName(file));

            var managedFileNames = isAssemblyTofileNames[true];
            var nativeFileNames = isAssemblyTofileNames[false];

            var runtimeLibraries = new[]
            {
                new RuntimeLibrary(
                    type:"package",
                    name: sharedFxName,
                    version: sharedFxVersion,
                    hash: "hash",
                    runtimeAssemblyGroups: new[] { new RuntimeAssetGroup(string.Empty, managedFileNames.Select(f => $"runtimes/{rid}/lib/{tfm}/{f}")) },
                    nativeLibraryGroups: new[] { new RuntimeAssetGroup(string.Empty, nativeFileNames.Select(f => $"runtimes/{rid}/native/{f}")) },
                    resourceAssemblies: Enumerable.Empty<ResourceAssembly>(),
                    dependencies: Enumerable.Empty<Dependency>(),
                    serviceable: true)
            };

            var targetInfo = new TargetInfo(fullTfm, rid, "runtimeSignature", isPortable: false);

            var runtimeFallbacks = GetRuntimeFallbacks(RuntimeGraphFiles, TargetRuntimeIdentifier);

            var dependencyContext = new DependencyContext(
                targetInfo,
                CompilationOptions.Default,
                Enumerable.Empty<CompilationLibrary>(),
                runtimeLibraries,
                runtimeFallbacks);

            using (var depsFileStream = File.Create(Path.Combine(SharedFrameworkDirectory, $"{sharedFxName}.deps.json")))
            {
                new DependencyContextWriter().Write(dependencyContext, depsFileStream);
            }

            return !Log.HasLoggedErrors;
        }

        private static bool IsManagedAssembly(string file)
        {
            bool result = false;
            try
            {
                using (var peReader = new PEReader(File.OpenRead(file)))
                {
                    result = peReader.HasMetadata && peReader.GetMetadataReader().IsAssembly;
                }
            }
            catch (BadImageFormatException)
            { }

            return result;
        }

        private static IEnumerable<RuntimeFallbacks> GetRuntimeFallbacks(string[] runtimeGraphFiles, string runtime)
        {
            RuntimeGraph runtimeGraph = RuntimeGraph.Empty;

            foreach (string runtimeGraphFile in runtimeGraphFiles)
            {
                runtimeGraph = RuntimeGraph.Merge(runtimeGraph, JsonRuntimeFormat.ReadRuntimeGraph(runtimeGraphFile));
            }

            foreach (string rid in runtimeGraph.Runtimes.Select(p => p.Key))
            {
                IEnumerable<string> ridFallback = runtimeGraph.ExpandRuntime(rid);

                if (ridFallback.Contains(runtime))
                {
                    // ExpandRuntime return runtime itself as first item so we are skiping it
                    yield return new RuntimeFallbacks(rid, ridFallback.Skip(1));
                }
            }
        }

    }
}
