// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Threading;
namespace System.Reactive.Disposables
{
    /// 
    /// Represents a disposable resource that only disposes its underlying disposable resource when all dependent disposable objects have been disposed.
    /// 
    public sealed class RefCountDisposable : ICancelable
    {
        private readonly object _gate = new object();
        private IDisposable _disposable;
        private bool _isPrimaryDisposed;
        private int _count;
        /// 
        /// Initializes a new instance of the  class with the specified disposable.
        /// 
        /// Underlying disposable.
        ///  is null.
        public RefCountDisposable(IDisposable disposable)
        {
            if (disposable == null)
                throw new ArgumentNullException("disposable");
            _disposable = disposable;
            _isPrimaryDisposed = false;
            _count = 0;
        }
        /// 
        /// Gets a value that indicates whether the object is disposed.
        /// 
        public bool IsDisposed
        {
            get { return _disposable == null; }
        }
        /// 
        /// Returns a dependent disposable that when disposed decreases the refcount on the underlying disposable.
        /// 
        /// A dependent disposable contributing to the reference count that manages the underlying disposable's lifetime.
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Backward compat + non-trivial work for a property getter.")]
        public IDisposable GetDisposable()
        {
            lock (_gate)
            {
                if (_disposable == null)
                {
                    return Disposable.Empty;
                }
                else
                {
                    _count++;
                    return new InnerDisposable(this);
                }
            }
        }
        /// 
        /// Disposes the underlying disposable only when all dependent disposables have been disposed.
        /// 
        public void Dispose()
        {
            var disposable = default(IDisposable);
            lock (_gate)
            {
                if (_disposable != null)
                {
                    if (!_isPrimaryDisposed)
                    {
                        _isPrimaryDisposed = true;
                        if (_count == 0)
                        {
                            disposable = _disposable;
                            _disposable = null;
                        }
                    }
                }
            }
            if (disposable != null)
                disposable.Dispose();
        }
        private void Release()
        {
            var disposable = default(IDisposable);
            lock (_gate)
            {
                if (_disposable != null)
                {
                    _count--;
                    System.Diagnostics.Debug.Assert(_count >= 0);
                    if (_isPrimaryDisposed)
                    {
                        if (_count == 0)
                        {
                            disposable = _disposable;
                            _disposable = null;
                        }
                    }
                }
            }
            if (disposable != null)
                disposable.Dispose();
        }
        sealed class InnerDisposable : IDisposable
        {
            private RefCountDisposable _parent;
            public InnerDisposable(RefCountDisposable parent)
            {
                _parent = parent;
            }
            public void Dispose()
            {
                var parent = Interlocked.Exchange(ref _parent, null);
                if (parent != null)
                    parent.Release();
            }
        }
    }
}