using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;

namespace System.Reactive.Disposables
{
	public sealed class CompositeDisposable : ICollection<IDisposable>, IEnumerable<IDisposable>, IEnumerable, ICancelable, IDisposable
	{
		public CompositeDisposable()
		{
			this._disposables = new List<IDisposable>();
		}

		public CompositeDisposable(int capacity)
		{
			if (capacity < 0)
			{
				throw new ArgumentOutOfRangeException("capacity");
			}
			this._disposables = new List<IDisposable>(capacity);
		}

		public CompositeDisposable(params IDisposable[] disposables)
		{
			if (disposables == null)
			{
				throw new ArgumentNullException("disposables");
			}
			this.Init(disposables, disposables.Length);
		}

		public CompositeDisposable(IEnumerable<IDisposable> disposables)
		{
			if (disposables == null)
			{
				throw new ArgumentNullException("disposables");
			}
			ICollection<IDisposable> collection = disposables as ICollection<IDisposable>;
			if (collection != null)
			{
				this.Init(disposables, collection.Count);
				return;
			}
			this.Init(disposables, 16);
		}

		private void Init(IEnumerable<IDisposable> disposables, int capacityHint)
		{
			List<IDisposable> list = new List<IDisposable>(capacityHint);
			foreach (IDisposable disposable in disposables)
			{
				if (disposable == null)
				{
					throw new ArgumentException(Strings_Core.DISPOSABLES_CANT_CONTAIN_NULL, "disposables");
				}
				list.Add(disposable);
			}
			this._disposables = list;
			Volatile.Write(ref this._count, this._disposables.Count);
		}

		public int Count
		{
			get
			{
				return Volatile.Read(ref this._count);
			}
		}

		public void Add(IDisposable item)
		{
			if (item == null)
			{
				throw new ArgumentNullException("item");
			}
			object gate = this._gate;
			lock (gate)
			{
				if (!this._disposed)
				{
					this._disposables.Add(item);
					Volatile.Write(ref this._count, this._count + 1);
					return;
				}
			}
			item.Dispose();
		}

		public bool Remove(IDisposable item)
		{
			if (item == null)
			{
				throw new ArgumentNullException("item");
			}
			object gate = this._gate;
			lock (gate)
			{
				if (this._disposed)
				{
					return false;
				}
				List<IDisposable> disposables = this._disposables;
				int num = disposables.IndexOf(item);
				if (num < 0)
				{
					return false;
				}
				disposables[num] = null;
				if (disposables.Capacity > 64 && this._count < disposables.Capacity / 2)
				{
					List<IDisposable> list = new List<IDisposable>(disposables.Capacity / 2);
					foreach (IDisposable disposable in disposables)
					{
						if (disposable != null)
						{
							list.Add(disposable);
						}
					}
					this._disposables = list;
				}
				Volatile.Write(ref this._count, this._count - 1);
			}
			item.Dispose();
			return true;
		}

		public void Dispose()
		{
			List<IDisposable> list = null;
			object gate = this._gate;
			lock (gate)
			{
				if (!this._disposed)
				{
					list = this._disposables;
					this._disposables = null;
					Volatile.Write(ref this._count, 0);
					Volatile.Write(ref this._disposed, true);
				}
			}
			if (list != null)
			{
				foreach (IDisposable disposable in list)
				{
					if (disposable != null)
					{
						disposable.Dispose();
					}
				}
			}
		}

		public void Clear()
		{
			IDisposable[] array = null;
			object gate = this._gate;
			lock (gate)
			{
				if (this._disposed)
				{
					return;
				}
				List<IDisposable> disposables = this._disposables;
				array = disposables.ToArray();
				disposables.Clear();
				Volatile.Write(ref this._count, 0);
			}
			foreach (IDisposable disposable in array)
			{
				if (disposable != null)
				{
					disposable.Dispose();
				}
			}
		}

		public bool Contains(IDisposable item)
		{
			if (item == null)
			{
				throw new ArgumentNullException("item");
			}
			object gate = this._gate;
			bool flag2;
			lock (gate)
			{
				if (this._disposed)
				{
					flag2 = false;
				}
				else
				{
					flag2 = this._disposables.Contains(item);
				}
			}
			return flag2;
		}

		public void CopyTo(IDisposable[] array, int arrayIndex)
		{
			if (array == null)
			{
				throw new ArgumentNullException("array");
			}
			if (arrayIndex >= 0 && arrayIndex < array.Length)
			{
				object gate = this._gate;
				lock (gate)
				{
					if (!this._disposed)
					{
						if (arrayIndex + this._count > array.Length)
						{
							throw new ArgumentOutOfRangeException("arrayIndex");
						}
						int num = arrayIndex;
						foreach (IDisposable disposable in this._disposables)
						{
							if (disposable != null)
							{
								array[num++] = disposable;
							}
						}
					}
					return;
				}
			}
			throw new ArgumentOutOfRangeException("arrayIndex");
		}

		public bool IsReadOnly
		{
			get
			{
				return false;
			}
		}

		public IEnumerator<IDisposable> GetEnumerator()
		{
			object gate = this._gate;
			IEnumerator<IDisposable> enumerator;
			lock (gate)
			{
				if (!this._disposed && this._count != 0)
				{
					enumerator = new CompositeDisposable.CompositeEnumerator(this._disposables.ToArray());
				}
				else
				{
					enumerator = CompositeDisposable.EmptyEnumerator;
				}
			}
			return enumerator;
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return this.GetEnumerator();
		}

		public bool IsDisposed
		{
			get
			{
				return Volatile.Read(ref this._disposed);
			}
		}

		private readonly object _gate = new object();

		private bool _disposed;

		private List<IDisposable> _disposables;

		private int _count;

		private static readonly CompositeDisposable.CompositeEnumerator EmptyEnumerator = new CompositeDisposable.CompositeEnumerator(Array.Empty<IDisposable>());

		private sealed class CompositeEnumerator : IEnumerator<IDisposable>, IDisposable, IEnumerator
		{
			public CompositeEnumerator(IDisposable[] disposables)
			{
				this._disposables = disposables;
				this._index = -1;
			}

			public IDisposable Current
			{
				get
				{
					return this._disposables[this._index];
				}
			}

			object IEnumerator.Current
			{
				get
				{
					return this._disposables[this._index];
				}
			}

			public void Dispose()
			{
				IDisposable[] disposables = this._disposables;
				Array.Clear(disposables, 0, disposables.Length);
			}

			public bool MoveNext()
			{
				IDisposable[] disposables = this._disposables;
				for (;;)
				{
					int num = this._index + 1;
					this._index = num;
					int num2 = num;
					if (num2 >= disposables.Length)
					{
						break;
					}
					if (disposables[num2] != null)
					{
						return true;
					}
				}
				return false;
			}

			public void Reset()
			{
				this._index = -1;
			}

			private readonly object _disposables;

			private int _index;
		}
	}
}
