// 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.Diagnostics;
using System.Threading;

#if DETECT_LEAKS
using System.Runtime.CompilerServices;

#endif

// define TRACE_LEAKS to get additional diagnostics that can lead to the leak sources. note: it will
// make everything about 2-3x slower
//
// #define TRACE_LEAKS

// define DETECT_LEAKS to detect possible leaks
// #if DEBUG
// #define DETECT_LEAKS  //for now always enable DETECT_LEAKS in debug.
// #endif

namespace Microsoft.AspNetCore.Razor.Language.Syntax
{
	/// <summary>
	/// Generic implementation of object pooling pattern with predefined pool size limit. The main
	/// purpose is that limited number of frequently used objects can be kept in the pool for
	/// further recycling.
	///
	/// Notes:
	/// 1) it is not the goal to keep all returned objects. Pool is not meant for storage. If there
	///    is no space in the pool, extra returned objects will be dropped.
	///
	/// 2) it is implied that if object was obtained from a pool, the caller will return it back in
	///    a relatively short time. Keeping checked out objects for long durations is ok, but
	///    reduces usefulness of pooling. Just new up your own.
	///
	/// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice.
	/// Rationale:
	///    If there is no intent for reusing the object, do not use pool - just use "new".
	/// </summary>
	internal class ObjectPool<T> where T : class
	{
		private struct Element
		{
			internal T Value;
		}

		/// <remarks>
		/// Not using <see cref="T:System.Func{T}"/> because this file is linked into the (debugger) Formatter,
		/// which does not have that type (since it compiles against .NET 2.0).
		/// </remarks>
		internal delegate T Factory();

		// storage for the pool objects.
		private readonly Element[] _items;

		// factory is stored for the lifetime of the pool. We will call this only when pool needs to
		// expand. compared to "new T()", Func gives more flexibility to implementers and faster
		// than "new T()".
		private readonly Factory _factory;

#if DETECT_LEAKS
        private static readonly ConditionalWeakTable<T, LeakTracker> leakTrackers = new ConditionalWeakTable<T, LeakTracker>();

        private class LeakTracker : IDisposable
        {
            private volatile bool disposed;

#if TRACE_LEAKS
            internal volatile System.Diagnostics.StackTrace Trace = null;
#endif

            public void Dispose()
            {
                disposed = true;
                GC.SuppressFinalize(this);
            }

            private string GetTrace()
            {
#if TRACE_LEAKS
                return Trace == null? "": Trace.ToString();
#else
                return "Leak tracing information is disabled. Define TRACE_LEAKS on ObjectPool`1.cs to get more info \n";
#endif
            }

            ~LeakTracker()
            {
                if (!this.disposed &&
                    !Environment.HasShutdownStarted &&
                    !AppDomain.CurrentDomain.IsFinalizingForUnload())
                {
                    string report = string.Format("Pool detected potential leaking of {0}. \n Location of the leak: \n {1} ",
                        typeof(T).ToString(),
                        GetTrace());

                    // If you are seeing this message it means that object has been allocated from the pool
                    // and has not been returned back. This is not critical, but turns pool into rather
                    // inefficient kind of "new".
                    Debug.WriteLine("TRACEOBJECTPOOLLEAKS_BEGIN\n" + report + "TRACEOBJECTPOOLLEAKS_END");
                }
            }
        }
#endif

		internal ObjectPool( Factory factory )
			: this( factory, Environment.ProcessorCount * 2 )
		{ }

		internal ObjectPool( Factory factory, int size )
		{
			_factory = factory;
			_items = new Element[size];
		}

		private T CreateInstance()
		{
			var inst = _factory();
			return inst;
		}

		/// <summary>
		/// Produces an instance.
		/// </summary>
		/// <remarks>
		/// Search strategy is a simple linear probing which is chosen for it cache-friendliness.
		/// Note that Free will try to store recycled objects close to the start thus statistically
		/// reducing how far we will typically search.
		/// </remarks>
		internal T Allocate()
		{
			var items = _items;
			T inst;

			for ( var i = 0; i < items.Length; i++ )
			{
				// Note that the read is optimistically not synchronized. That is intentional.
				// We will interlock only when we have a candidate. in a worst case we may miss some
				// recently returned objects. Not a big deal.
				inst = items[i].Value;
				if ( inst != null )
				{
					if ( inst == Interlocked.CompareExchange( ref items[i].Value, null, inst ) )
					{
						goto gotInstance;
					}
				}
			}

			inst = CreateInstance();
			gotInstance:

#if DETECT_LEAKS
            var tracker = new LeakTracker();
            leakTrackers.Add(inst, tracker);

#if TRACE_LEAKS
            var frame = new System.Diagnostics.StackTrace(false);
            tracker.Trace = frame;
#endif
#endif

			return inst;
		}

		/// <summary>
		/// Returns objects to the pool.
		/// </summary>
		/// <remarks>
		/// Search strategy is a simple linear probing which is chosen for it cache-friendliness.
		/// Note that Free will try to store recycled objects close to the start thus statistically
		/// reducing how far we will typically search in Allocate.
		/// </remarks>
		internal void Free( T obj )
		{
			Validate( obj );
			ForgetTrackedObject( obj );

			var items = _items;
			for ( var i = 0; i < items.Length; i++ )
			{
				if ( items[i].Value == null )
				{
					// Intentionally not using interlocked here.
					// In a worst case scenario two objects may be stored into same slot.
					// It is very unlikely to happen and will only mean that one of the objects will get collected.
					items[i].Value = obj;
					break;
				}
			}
		}

		/// <summary>
		/// Removes an object from leak tracking.
		///
		/// This is called when an object is returned to the pool.  It may also be explicitly
		/// called if an object allocated from the pool is intentionally not being returned
		/// to the pool.  This can be of use with pooled arrays if the consumer wants to
		/// return a larger array to the pool than was originally allocated.
		/// </summary>
		[Conditional( "DEBUG" )]
		internal void ForgetTrackedObject( T old, T replacement = null )
		{
#if DETECT_LEAKS
            LeakTracker tracker;
            if (leakTrackers.TryGetValue(old, out tracker))
            {
                tracker.Dispose();
                leakTrackers.Remove(old);
            }
            else
            {
                string report = string.Format("Object of type {0} was freed, but was not from pool. \n Callstack: \n {1} ",
                    typeof(T).ToString(),
                    new System.Diagnostics.StackTrace(false));

                Debug.WriteLine("TRACEOBJECTPOOLLEAKS_BEGIN\n" + report + "TRACEOBJECTPOOLLEAKS_END");
            }

            if (replacement != null)
            {
                tracker = new LeakTracker();
                leakTrackers.Add(replacement, tracker);
            }
#endif
		}

		[Conditional( "DEBUG" )]
		private void Validate( object obj )
		{
			Debug.Assert( obj != null, "freeing null?" );

			var items = _items;
			for ( var i = 0; i < items.Length; i++ )
			{
				var value = items[i].Value;
				if ( value == null )
				{
					return;
				}

				Debug.Assert( value != obj, "freeing twice?" );
			}
		}
	}
}
