using System;
using System.Collections.Concurrent;
using System.Threading;

namespace EonaCat.LogStack.Pooling;

// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.

/// <summary>
/// High-performance object pool for reusing expensive objects without external dependencies.
/// Supports automatic cleanup and size limiting.
/// </summary>
public sealed class ObjectPool<T> : IDisposable where T : class
{
    private readonly ConcurrentBag<T> _pool;
    private readonly Func<T> _factory;
    private readonly Action<T>? _resetAction;
    private readonly int _maxSize;
    private int _currentCount;
    private bool _isDisposed;

    public ObjectPool(Func<T> factory, Action<T>? resetAction = null, int maxSize = 100)
    {
        _factory = factory ?? throw new ArgumentNullException(nameof(factory));
        _resetAction = resetAction;
        _maxSize = maxSize;
        _pool = new ConcurrentBag<T>();
        _currentCount = 0;
    }

    /// <summary>
    /// Rents an object from the pool, creating a new one if necessary
    /// </summary>
    public T Rent()
    {
        if (_isDisposed)
        {
            throw new ObjectDisposedException(nameof(ObjectPool<T>));
        }

        if (_pool.TryTake(out var item))
        {
            Interlocked.Decrement(ref _currentCount);
            return item;
        }

        return _factory();
    }

    /// <summary>
    /// Returns an object to the pool after optional reset
    /// </summary>
    public void Return(T item)
    {
        if (_isDisposed || item == null)
        {
            return;
        }

        if (Interlocked.Increment(ref _currentCount) <= _maxSize)
        {
            _resetAction?.Invoke(item);
            _pool.Add(item);
        }
        else
        {
            Interlocked.Decrement(ref _currentCount);
            (item as IDisposable)?.Dispose();
        }
    }

    /// <summary>
    /// Gets the current number of pooled objects
    /// </summary>
    public int PooledCount => _currentCount;

    /// <summary>
    /// Clears all pooled objects
    /// </summary>
    public void Clear()
    {
        while (_pool.TryTake(out var item))
        {
            (item as IDisposable)?.Dispose();
            Interlocked.Decrement(ref _currentCount);
        }
    }

    public void Dispose()
    {
        if (_isDisposed)
        {
            return;
        }

        _isDisposed = true;
        Clear();
    }
}

/// <summary>
/// Pooled object wrapper that returns itself to the pool when disposed
/// </summary>
public sealed class PooledObject<T> : IDisposable where T : class
{
    private readonly ObjectPool<T> _pool;
    private readonly T _item;

    public PooledObject(ObjectPool<T> pool)
    {
        _pool = pool ?? throw new ArgumentNullException(nameof(pool));
        _item = pool.Rent();
    }

    public T Item => _item;

    public void Dispose()
    {
        _pool.Return(_item);
    }
}
