﻿using System;
using System.Threading;

namespace AsyncToolkit.Coordination
{
    internal class WaiterNode<T> : Promise<VoidType>
        where T : WaiterNode<T>
    {
        public T Previous { get; set; }
        public T Next { get; set; }
    }

    internal class WaiterQueue<T>
        where T : WaiterNode<T>
    {
        private T _head;

        public T First => _head;

        public T Last => _head?.Previous;

        public bool IsEmpty => _head == null;

        public void AddFirst(T waiter)
        {
            var head = _head;

            if (head == null)
            {
                waiter.Next = waiter;
                waiter.Previous = waiter;
                _head = waiter;
            }
            else
            {
                waiter.Next = head;
                waiter.Previous = head.Previous;
                head.Previous.Next = waiter;
                head.Previous = waiter;
                _head = waiter;
            }
        }

        public void AddLast(T waiter)
        {
            var head = _head;

            if (head == null)
            {
                waiter.Next = waiter;
                waiter.Previous = waiter;
                _head = waiter;
            }
            else
            {
                waiter.Next = head;
                waiter.Previous = head.Previous;
                head.Previous.Next = waiter;
                head.Previous = waiter;
            }
        }

        public bool TryPeek(out T waiter)
        {
            waiter = First;

            return waiter != null;
        }

        public bool TryTake(out T waiter)
        {
            if (First == null)
            {
                waiter = null;
                return false;
            }

            waiter = _head;

            waiter.Next.Previous = waiter.Previous;
            waiter.Previous.Next = waiter.Next;
            if (_head == waiter)
            {
                _head = waiter.Next;
            }

            waiter.Previous = null;
            waiter.Next = null;

            return true;
        }

        public T Take()
        {
            T waiter;
            if (!TryTake(out waiter))
                throw new InvalidOperationException("The queue is empty.");

            return waiter;
        }
    }

    public sealed class AsyncSemaphore : IDisposable
    {
        private class SemaphoreSession : WaiterNode<SemaphoreSession>, IDisposable
        {
            private AsyncSemaphore _semaphore;

            public int Count { get; }

            public SemaphoreSession(AsyncSemaphore semaphore, int count)
            {
                _semaphore = semaphore;
                Count = count;
            }

            public void Dispose()
            {
                var semaphore = Interlocked.Exchange(ref _semaphore, null);
                semaphore?.Release(Count);
            }
        }

        private class CompletedSemaphoreSession : IDisposable
        {
            private AsyncSemaphore _semaphore;
            private readonly int _count;

            public CompletedSemaphoreSession(AsyncSemaphore semaphore, int count)
            {
                _semaphore = semaphore;
                _count = count;
            }

            public void Dispose()
            {
                var semaphore = Interlocked.Exchange(ref _semaphore, null);
                semaphore?.Release(_count);
            }
        }

        public const int MaximumCountLimit = int.MaxValue;

        private WaiterQueue<SemaphoreSession> _waiters;
        private int _currentCount;

        /// <summary>
        /// Gets the number of remaining requests available in the semaphore.
        /// </summary>
        public int CurrentCount => Volatile.Read(ref _currentCount);

        /// <summary>
        /// Gets the maximum number of requests that can be granted concurrently.
        /// </summary>
        public int MaximumCount { get; }

        /// <summary>
        /// Creates an AsyncSemaphore.
        /// </summary>
        /// <param name="initialCount">The initial number of requests for the semaphore that can be granted concurrently.</param>
        public AsyncSemaphore(int initialCount)
            : this(initialCount, MaximumCountLimit)
        { }

        /// <summary>
        /// Creates an AsyncSemaphore.
        /// </summary>
        /// <param name="initialCount">The initial number of requests for the semaphore that can be granted concurrently.</param>
        /// <param name="maximumCount">The maximum number of requests for the semaphore that can be granted concurrently.</param>
        public AsyncSemaphore(int initialCount, int maximumCount)
        {
            if (initialCount > maximumCount)
                throw new ArgumentException("Value cannot be greater than maximumCount", nameof(initialCount));

            if (initialCount < 0)
                throw new ArgumentException("Value cannot be lesser than zero.", nameof(initialCount));

            MaximumCount = maximumCount;
            _currentCount = initialCount;

            _waiters = new WaiterQueue<SemaphoreSession>();
        }

        public Future<IDisposable> TakeOneAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            return TakeAsync(1, false, cancellationToken);
        }

        public Future<IDisposable> TakeAsync(int count, CancellationToken cancellationToken = default(CancellationToken))
        {
            return TakeAsync(count, false, cancellationToken);
        }

        public Future<IDisposable> TakeAsync(int count, bool prioritize, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (count <= 0)
                throw new ArgumentException("Value must be greater than zero.", nameof(count));

            if (count > MaximumCount)
                throw new ArgumentException("Value cannot be greater than MaximumCount", nameof(count));

            while (true)
            {
                int currentCount = CurrentCount;
                
                if (Interlocked.CompareExchange(ref _currentCount, currentCount - count, currentCount) == currentCount)
                    return Future.FromValue<IDisposable>(new CompletedSemaphoreSession(this, count));
            }


            return new Future<IDisposable>();
        }

        public int TakeAllAvailable(int maximumCount = MaximumCountLimit)
        {
            return 0;
        }

        public bool TryTake(int count)
        {
            return false;
        }

        public void ReleaseOne()
        {
            Release(1);
        }

        public void Release(int count)
        {

        }

        public void Dispose()
        {

        }
    }
}
