/*
 *****************************************************************************
 *                                                                           *
 *              IMPINJ CONFIDENTIAL AND PROPRIETARY                          *
 *                                                                           *
 * This source code is the sole property of Impinj, Inc. Reproduction or     *
 * utilization of this source code in whole or in part is forbidden without  *
 * the prior written consent of Impinj, Inc.                                 *
 *                                                                           *
 * (c) Copyright Impinj, Inc. 2015. All rights reserved.                     *
 *                                                                           *
 *****************************************************************************/
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace Impinj
{
    /// <summary>
    /// A thread-safe queue class
    /// </summary>
    /// <typeparam name="T">
    /// Base object type for the SizeQueue
    /// </typeparam>
    class SizeQueue<T> : IEnumerable
    {
        // Private data members
        private readonly Queue<T> queue = new Queue<T>();
        private T last;
        private readonly int maxSize;
        private bool scrollingQueueEnabled;
        private bool closing = false;

        // Public accessors
        public T Last
        {
            get { return last; }
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="maxSize">
        /// Maximum queue size
        /// </param>
        /// <param name="enableScrollingQueue">
        /// Optional - enables scrolling queue functionality whereby when the
        /// queue is full, a dequeue is performed prior to doing an enqueue.
        /// Defaults to false.s
        /// </param>
        public SizeQueue(int maxSize, bool enableScrollingQueue = false)
        {
            this.maxSize = maxSize;
            scrollingQueueEnabled = enableScrollingQueue;
        }

        /// <summary>
        /// Returns the number of current entries in the queue
        /// </summary>
        public int Count
        {
            get
            {
                lock (queue)
                {
                    return queue.Count;
                }
            }
        }

        /// <summary>
        /// Returns the value of the oldest entry in the queue without
        /// dequeing it
        /// </summary>
        /// <returns>
        /// The oldest entry in the queue
        /// </returns>
        public T Peek()
        {
            T retVal = default(T);

            lock (queue)
            {
                if (0 != queue.Count)
                {
                    retVal = queue.Peek();
                }
            }

            return retVal;
        }

        /// <summary>
        /// If the queue base type is a double, return the arithmetic mean of
        /// the current queue entries
        /// </summary>
        /// <returns>
        /// If the base type is a double, the arithmetic mean of the current
        /// queue entries, otherwise returns zero.
        /// </returns>
        public double Average()
        {
            double average = 0;

            if (typeof(double) == average.GetType())
            {
                lock (queue)
                {
                    Queue<double> dblQ = queue as Queue<double>;
                    average = dblQ.Average();
                }
            }

            return average;
        }

        /// <summary>
        /// Add an item to the queue
        /// </summary>
        /// <param name="item">
        /// The item to add
        /// </param>
        public void Enqueue(T item)
        {
            lock (queue)
            {
                while (queue.Count >= maxSize)
                {
                    if (true == scrollingQueueEnabled)
                    {
                        queue.Dequeue();
                    }
                    else
                    {
                        Monitor.Wait(queue);
                    }
                }

                queue.Enqueue(item);
                last = item;

                if (queue.Count == 1)
                {
                    // wake up any blocked dequeue
                    Monitor.PulseAll(queue);
                }
            }
        }

        /// <summary>
        /// Remove the oldest item from the queue
        /// </summary>
        /// <returns>
        /// The oldest item from the queue
        /// </returns>
        public T Dequeue()
        {
            lock (queue)
            {
                while (queue.Count == 0)
                {
                    Monitor.Wait(queue);
                }

                T item = queue.Dequeue();

                if (queue.Count == maxSize - 1)
                {
                    // wake up any blocked enqueue
                    Monitor.PulseAll(queue);
                }
                return item;
            }
        }

        /// <summary>
        /// Non-blocking Dequeue that returns the oldest item from the queue
        /// </summary>
        /// <param name="value">
        /// An object reference into which the oldest item from the queue will
        /// be placed
        /// </param>
        /// <returns>
        /// A true if the Dequeue was successful, and a false if not
        /// </returns>
        public bool TryDequeue(out T value)
        {
            lock (queue)
            {
                while (queue.Count == 0)
                {
                    if (closing)
                    {
                        value = default(T);
                        return false;
                    }
                    Monitor.Wait(queue);
                }

                value = queue.Dequeue();

                if (queue.Count == maxSize - 1)
                {
                    // wake up any blocked enqueue
                    Monitor.PulseAll(queue);
                }
                return true;
            }
        }

        /// <summary>
        /// Performs a graceful close of the queue, releasing any
        /// synchronization objects and preventing the application from
        /// locking up
        /// </summary>
        public void Close()
        {
            lock (queue)
            {
                closing = true;
                Monitor.PulseAll(queue);
            }
        }

        /// <summary>
        /// Returns an enumerator handle to the base queue type
        /// </summary>
        /// <returns>
        /// An enumerator handle to the base queue type
        /// </returns>
        public IEnumerator GetEnumerator()
        {
            return queue.GetEnumerator();
        }
    }
}
