// 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.Runtime.InteropServices;

#nullable disable

namespace Microsoft.Build.Tasks.Deployment.ManifestUtilities
{
    internal class EmbeddedManifestReader
    {
        private static readonly IntPtr s_id1 = new IntPtr(1);
        private Stream _manifest;

        private EmbeddedManifestReader(string path)
        {
            IntPtr hModule = IntPtr.Zero;
            try
            {
                hModule = NativeMethods.LoadLibraryExW(path, IntPtr.Zero, NativeMethods.LOAD_LIBRARY_AS_DATAFILE);
                if (hModule == IntPtr.Zero)
                {
                    return;
                }
                NativeMethods.EnumResNameProc callback = EnumResNameCallback;
                NativeMethods.EnumResourceNames(hModule, NativeMethods.RT_MANIFEST, callback, IntPtr.Zero);
            }
            finally
            {
                if (hModule != IntPtr.Zero)
                {
                    NativeMethods.FreeLibrary(hModule);
                }
            }
        }

        private bool EnumResNameCallback(IntPtr hModule, IntPtr pType, IntPtr pName, IntPtr param)
        {
            if (pName != s_id1)
            {
                return false; // only look for resources with ID=1
            }
            IntPtr hResInfo = NativeMethods.FindResource(hModule, pName, NativeMethods.RT_MANIFEST);
            if (hResInfo == IntPtr.Zero)
            {
                return false; // continue looking
            }
            IntPtr hResource = NativeMethods.LoadResource(hModule, hResInfo);
            NativeMethods.LockResource(hResource);
            uint bufsize = NativeMethods.SizeofResource(hModule, hResInfo);
            var buffer = new byte[bufsize];

            Marshal.Copy(hResource, buffer, 0, buffer.Length);
            _manifest = new MemoryStream(buffer, false);
            return false; // found what we are looking for
        }

        public static Stream Read(string path)
        {
            if (path == null)
            {
                throw new ArgumentNullException(nameof(path));
            }

            if (!path.EndsWith(".manifest", StringComparison.Ordinal) && !path.EndsWith(".dll", StringComparison.Ordinal))
            {
                // Everything that does not end with .dll or .manifest is not a valid native assembly (this includes
                //    EXEs with ID1 manifest)
                return null;
            }

            int t1 = Environment.TickCount;
            EmbeddedManifestReader r = new EmbeddedManifestReader(path);
            Util.WriteLog($"EmbeddedManifestReader.Read t={Environment.TickCount - t1}");
            return r._manifest;
        }
    }
}
