// 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.Buffers.Binary;

namespace Microsoft.NET.HostModel.ComHost
{
    /// <summary>
    /// Reads data from a COM Type Library file based on the official implementation in the Win32 function LoadTypeLib.
    /// We do the reading ourselves instead of calling into the OS so we don't have to worry about the type library's
    /// dependencies being discoverable on disk.
    /// </summary>
    internal sealed class TypeLibReader
    {
        private readonly byte[] tlbBytes;

        public TypeLibReader(byte[] tlbBytes)
        {
            this.tlbBytes = tlbBytes;
        }

        private const int OffsetOfGuidOffset = sizeof(int) * 2;
        private const int SizeOfGuidOffset = sizeof(int);

        private const int OffsetOfMajorVersion = OffsetOfGuidOffset + SizeOfGuidOffset + sizeof(int) * 2 + sizeof(ushort) * 2;
        private const int SizeOfMajorVersion = sizeof(ushort);
        private const int OffsetOfMinorVersion = OffsetOfMajorVersion + SizeOfMajorVersion;
        private const int SizeOfMinorVersion = sizeof(ushort);

        private const int OffsetOfTypeInfosCount = OffsetOfMinorVersion + SizeOfMinorVersion + sizeof(int);
        private const int SizeOfTypeInfosCount = sizeof(int);

        private const int OffsetOfTablesStart = OffsetOfTypeInfosCount + SizeOfTypeInfosCount + sizeof(int) * 12;
        private const int NumTablesToSkip = 5;
        private const int SizeOfTableHeader = sizeof(int) * 4;

        private static Guid FindGuid(ReadOnlySpan<byte> fileContents)
        {
            checked
            {
                int typelibGuidEntryOffset = (int)BinaryPrimitives.ReadUInt32LittleEndian(fileContents.Slice(OffsetOfGuidOffset));
                int infoRefsOffsetCount = (int)BinaryPrimitives.ReadUInt32LittleEndian(fileContents.Slice(OffsetOfTypeInfosCount));
                int infoBytes = infoRefsOffsetCount * SizeOfTypeInfosCount;
                int guidTableOffset = OffsetOfTablesStart + infoBytes + SizeOfTableHeader * NumTablesToSkip;
                int fileOffset = (int)BinaryPrimitives.ReadUInt32LittleEndian(fileContents.Slice(guidTableOffset));
                return new Guid(fileContents.Slice(fileOffset + typelibGuidEntryOffset, 16).ToArray());
            }
        }

        public bool TryReadTypeLibGuidAndVersion(out Guid typelibId, out Version version)
        {
            typelibId = default;
            version = default;
            try
            {
                var span = new ReadOnlySpan<byte>(tlbBytes);
                typelibId = FindGuid(span);
                ushort majorVer = BinaryPrimitives.ReadUInt16LittleEndian(span.Slice(OffsetOfMajorVersion));
                ushort minorVer = BinaryPrimitives.ReadUInt16LittleEndian(span.Slice(OffsetOfMinorVersion));
                version = new Version(majorVer, minorVer);
                return true;
            }
            catch (System.OverflowException)
            {
                return false;
            }
            catch (System.IndexOutOfRangeException)
            {
                return false;
            }
        }
    }
}
