// License: Apache 2.0. See LICENSE file in root directory.
// Copyright(c) 2017 Intel Corporation. All Rights Reserved.

namespace Intel.RealSense
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq.Expressions;
    using System.Reflection;
    using ObjectFactory = System.Func<System.IntPtr, object>;
    using PooledStack = System.Collections.Generic.Stack<Base.PooledObject>;

    /// <summary>
    /// Object pool to reuse objects, avoids allocation and GC pauses
    /// </summary>
    public static class ObjectPool
    {
        private static readonly Dictionary<Type, PooledStack> Pools = new Dictionary<Type, PooledStack>(TypeComparer.Default);
        private static readonly Dictionary<Type, ObjectFactory> Factories = new Dictionary<Type, ObjectFactory>(TypeComparer.Default);

        private class TypeComparer : IEqualityComparer<Type>
        {
            public static readonly TypeComparer Default = new TypeComparer();

            public bool Equals(Type x, Type y)
            {
                return x == y;
            }

            public int GetHashCode(Type obj)
            {
                return obj.GetHashCode();
            }
        }

        private static PooledStack GetPool(Type t)
        {
            Stack<Base.PooledObject> s;
            if (Pools.TryGetValue(t, out s))
            {
                return s;
            }

            lock ((Pools as ICollection).SyncRoot)
            {
                return Pools[t] = new PooledStack();
            }
        }

        private static object CreateInstance(Type t, IntPtr ptr)
        {
            Func<IntPtr, object> factory;
            if (Factories.TryGetValue(t, out factory))
            {
                return factory(ptr);
            }

            var ctorinfo = t.GetConstructor(
                    BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.CreateInstance,
                    null,
                    new Type[] { typeof(IntPtr) },
                    null);
            var args = new ParameterExpression[] { Expression.Parameter(typeof(IntPtr), "ptr") };
            var lambda = Expression.Lambda<ObjectFactory>(Expression.New(ctorinfo, args), args).Compile();
            lock ((Factories as ICollection).SyncRoot)
            {
                Factories[t] = lambda;
            }

            return lambda(ptr);
        }

        private static object Get(Type t, IntPtr ptr)
        {
            var stack = GetPool(t);
            lock ((stack as ICollection).SyncRoot)
            {
                if (stack.Count > 0)
                {
                    Base.PooledObject obj;
                    obj = stack.Pop();

                    obj.m_instance.Reset(ptr);
                    obj.Initialize();
                    return obj;
                }
            }

            return CreateInstance(t, ptr);
        }

        /// <summary>
        /// Get an object from the pool, should be released back
        /// </summary>
        /// <typeparam name="T">type of object</typeparam>
        /// <param name="ptr">native handle</param>
        /// <returns>an object of type <typeparamref name="T"/></returns>
        public static T Get<T>(IntPtr ptr)
            where T : Base.PooledObject
        {
            if (ptr == IntPtr.Zero)
            {
                throw new ArgumentNullException(nameof(ptr));
            }

            return Get(typeof(T), ptr) as T;
        }

        /// <summary>
        /// Return an object to the pool
        /// </summary>
        /// <typeparam name="T">type of object</typeparam>
        /// <param name="obj">object to return to pool</param>
        public static void Release<T>(T obj)
            where T : Base.PooledObject
        {
            var stack = GetPool(obj.GetType());
            lock ((stack as ICollection).SyncRoot)
            {
                stack.Push(obj);
            }
        }
    }
}