﻿// 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.Runtime.InteropServices;
using Roslyn.Utilities;

namespace Microsoft.VisualStudio.LanguageServices.Implementation.Interop
{
    internal static class ComAggregate
    {
        /// <summary>
        /// This method creates a native COM object that aggregates the passed in managed object.
        /// The reason we need to do this is to enable legacy managed code that expects managed casts
        /// expressions to perform a QI on the COM object wrapped by an RCW. These clients are relying
        /// on the fact that COM type equality is based on GUID, whereas type equality is identity in 
        /// the managed world.
        /// Example: IMethodXML is defined many times throughout VS and used by many managed clients
        ///          dealing with CodeFunction objects. If the CodeFunction objects they deal with are
        ///          direct references to managed objects, then casts operations are managed casts
        ///          (as opposed to QI calls), and they fail, since the managed type for IMethodXML
        ///          have different identity (since they are defined in different assemblies). The QI
        ///          works, since under the hood, the casts operations are converted to QI with 
        ///          a GUID which is shared between all these types.
        ///          The solution to this is to return to these managed clients a native object,
        ///          which wraps the managed implementation of these interface using aggregation.
        ///          This means the interfaces will be obtained through QI, while the implementation
        ///          will be forwarded to the managed implementation.
        /// </summary>
        internal static object CreateAggregatedObject(object managedObject)
            => WrapperPolicy.CreateAggregatedObject(managedObject);

        /// <summary>
        /// Return the RCW for the native IComWrapperFixed instance aggregating "managedObject"
        /// if there is one. Return "null" if "managedObject" is not aggregated.
        /// </summary>
        internal static IComWrapperFixed? TryGetWrapper(object managedObject)
            => WrapperPolicy.TryGetWrapper(managedObject);

        internal static T GetManagedObject<T>(object value) where T : class
        {
            Contract.ThrowIfNull(value, "value");

            if (value is IComWrapperFixed wrapper)
            {
                return GetManagedObject<T>(wrapper);
            }

            Debug.Assert(value is T, "Why are you casting an object to an reference type it doesn't support?");
            return (T)value;
        }

        internal static T GetManagedObject<T>(IComWrapperFixed comWrapper) where T : class
        {
            Contract.ThrowIfNull(comWrapper, "comWrapper");

            var handle = GCHandle.FromIntPtr(comWrapper.GCHandlePtr);
            var target = handle.Target;

            Contract.ThrowIfNull(target, "target");
            Debug.Assert(target is T, "Why are you casting an object to an reference type it doesn't support?");
            return (T)target;
        }

        internal static T? TryGetManagedObject<T>(object? value) where T : class
        {
            if (value is IComWrapperFixed wrapper)
            {
                return TryGetManagedObject<T>(wrapper);
            }

            return value as T;
        }

        internal static T? TryGetManagedObject<T>(IComWrapperFixed comWrapper) where T : class
        {
            if (comWrapper == null)
            {
                return null;
            }

            var handle = GCHandle.FromIntPtr(comWrapper.GCHandlePtr);
            return handle.Target as T;
        }
    }
}
