using System;
using System.Threading;

namespace System.Reactive.Disposables
{
	public sealed class RefCountDisposable : ICancelable, IDisposable
	{
		public RefCountDisposable(IDisposable disposable)
			: this(disposable, false)
		{
		}

		public RefCountDisposable(IDisposable disposable, bool throwWhenDisposed)
		{
			if (disposable == null)
			{
				throw new ArgumentNullException("disposable");
			}
			this._disposable = disposable;
			this._count = 0;
			this._throwWhenDisposed = throwWhenDisposed;
		}

		public bool IsDisposed
		{
			get
			{
				return Volatile.Read(ref this._count) == int.MinValue;
			}
		}

		public IDisposable GetDisposable()
		{
			int num2;
			for (int num = Volatile.Read(ref this._count); num != -2147483648; num = num2)
			{
				if ((num & 2147483647) == 2147483647)
				{
					throw new OverflowException(string.Format("RefCountDisposable can't handle more than {0} disposables", int.MaxValue));
				}
				num2 = Interlocked.CompareExchange(ref this._count, num + 1, num);
				if (num2 == num)
				{
					return new RefCountDisposable.InnerDisposable(this);
				}
			}
			if (this._throwWhenDisposed)
			{
				throw new ObjectDisposedException("RefCountDisposable");
			}
			return Disposable.Empty;
		}

		public void Dispose()
		{
			int num = Volatile.Read(ref this._count);
			while (((long)num & 2147483648L) == 0L)
			{
				int num2 = num & int.MaxValue;
				int num3 = int.MinValue | num2;
				int num4 = Interlocked.CompareExchange(ref this._count, num3, num);
				if (num4 == num)
				{
					if (num2 == 0)
					{
						IDisposable disposable = this._disposable;
						if (disposable != null)
						{
							disposable.Dispose();
						}
						this._disposable = null;
						return;
					}
					break;
				}
				else
				{
					num = num4;
				}
			}
		}

		private void Release()
		{
			int num = Volatile.Read(ref this._count);
			int num4;
			for (;;)
			{
				int num2 = (int)((long)num & 2147483648L);
				int num3 = num & int.MaxValue;
				num4 = num2 | (num3 - 1);
				int num5 = Interlocked.CompareExchange(ref this._count, num4, num);
				if (num5 == num)
				{
					break;
				}
				num = num5;
			}
			if (num4 == -2147483648)
			{
				IDisposable disposable = this._disposable;
				if (disposable != null)
				{
					disposable.Dispose();
				}
				this._disposable = null;
				return;
			}
		}

		private readonly bool _throwWhenDisposed;

		private IDisposable _disposable;

		private int _count;

		private sealed class InnerDisposable : IDisposable
		{
			public InnerDisposable(RefCountDisposable parent)
			{
				this._parent = parent;
			}

			public void Dispose()
			{
				RefCountDisposable refCountDisposable = Interlocked.Exchange<RefCountDisposable>(ref this._parent, null);
				if (refCountDisposable == null)
				{
					return;
				}
				refCountDisposable.Release();
			}

			private object _parent;
		}
	}
}
