﻿// The MIT License (MIT)
// 
// Copyright (c) 2017 Jesse Sweetland
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using System.Collections.Concurrent;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;

namespace Platibus.Queueing
{
    /// <inheritdoc cref="IMessageQueueingService" />
    /// <inheritdoc cref="IDisposable"/>
    /// <summary>
    /// An abstract base class for implementing message queueing services
    /// </summary>
    public abstract class AbstractMessageQueueingService<TQueue> : IMessageQueueingService, IDisposable
        where TQueue : AbstractMessageQueue
    {
        private readonly ConcurrentDictionary<QueueName, TQueue> _queues = new ConcurrentDictionary<QueueName, TQueue>();

        private bool _disposed;

        /// <inheritdoc />
        public async Task CreateQueue(QueueName queueName, IQueueListener listener, QueueOptions options = null, CancellationToken cancellationToken = default(CancellationToken))
        {
            var queue = await InternalCreateQueue(queueName, listener, options, cancellationToken);
            if (!_queues.TryAdd(queueName, queue))
            {
                throw new QueueAlreadyExistsException(queueName);
            }
            await queue.Init(cancellationToken);
        }

        /// <summary>
        /// Creates an uninitialized concrete instance of <typeparamref name="TQueue"/>
        /// </summary>
        /// <param name="queueName">The name of the queeue</param>
        /// <param name="listener">The object that will consume messages from the queue</param>
        /// <param name="options">(Optional) Options that govern how the queue behaves</param>
        /// <param name="cancellationToken">(Optional) A cancellation token that can be used
        /// by the caller to cancel queue creation if necessary</param>
        /// <returns>Returns a task whose result is an uninitialized concrete instance of
        /// <typeparamref name="TQueue"/></returns>
        protected abstract Task<TQueue> InternalCreateQueue(QueueName queueName, IQueueListener listener,
            QueueOptions options = null, CancellationToken cancellationToken = default(CancellationToken));

        /// <inheritdoc />
        public Task EnqueueMessage(QueueName queueName, Message message, IPrincipal senderPrincipal, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (!_queues.TryGetValue(queueName, out TQueue queue))
            {
                throw new QueueNotFoundException(queueName);
            }
            return queue.Enqueue(message, senderPrincipal, cancellationToken);
        }

        /// <summary>
        /// Finalizer that ensures that all resources are released
        /// </summary>
        ~AbstractMessageQueueingService()
        {
            Dispose(false);
        }

        /// <inheritdoc />
        public void Dispose()
        {
            if (_disposed) return;
            Dispose(true);
            GC.SuppressFinalize(this);
            _disposed = true;
        }

        /// <summary>
        /// Called by the <see cref="Dispose()"/> method or by the finalizer to free held resources
        /// </summary>
        /// <param name="disposing">Indicates whether this method is called from the 
        /// <see cref="Dispose()"/> method (<c>true</c>) or from the finalizer (<c>false</c>)</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                foreach (var queue in _queues.Values)
                {
                    queue.Dispose();
                }
            }
        }

        /// <summary>
        /// Throws an <see cref="ObjectDisposedException"/> if the message queueing service has
        /// been disposed
        /// </summary>
        /// <exception cref="ObjectDisposedException">Thrown if the object is already disposed</exception>
        protected void CheckDisposed()
        {
            if (_disposed) throw new ObjectDisposedException(GetType().FullName);
        }
    }
}
