/*
 * Copyright (c) Contributors, http://opensimulator.org/
 * See CONTRIBUTORS.TXT for a full list of copyright holders.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSimulator Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using OpenMetaverse;
using OpenSim.Framework;
using System.Collections.Generic;

namespace OpenSim.Region.Framework.Scenes
{
    /// <summary>
    /// Send FetchInventoryReply information to clients asynchronously on a single thread rather than asynchronously via
    /// multiple threads.
    /// </summary>
    /// <remarks>
    /// If the main root inventory is right-clicked on a version 1 viewer for a user with a large inventory, a very
    /// very large number of FetchInventory requests are sent to the simulator.  Each is handled on a separate thread
    /// by the IClientAPI, but the sheer number of requests overwhelms the number of threads available and ends up
    /// freezing the inbound packet handling.
    ///
    /// This class makes the first FetchInventory packet thread process the queue.  If new requests come
    /// in while it is processing, then the subsequent threads just add the requests and leave it to the original
    /// thread to process them.
    ///
    /// This might slow down outbound packets but these are limited by the IClientAPI outbound queues
    /// anyway.
    ///
    /// It might be possible to ignore FetchInventory requests altogether, particularly as they are redundant wrt to
    /// FetchInventoryDescendents requests, but this would require more investigation.
    /// </remarks>
    public class AsyncInventorySender
    {
        //        private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        /// <summary>
        /// Signal whether a queue is currently being processed or not.
        /// </summary>
        protected volatile bool m_processing;

        protected Scene m_scene;

        /// <summary>
        /// Queues fetch requests
        /// </summary>
        private Queue<FetchHolder> m_fetchHolder = new Queue<FetchHolder>();

        public AsyncInventorySender(Scene scene)
        {
            m_processing = false;
            m_scene = scene;
        }

        /// <summary>
        /// Handle a fetch inventory request from the client
        /// </summary>
        /// <param name="remoteClient"></param>
        /// <param name="itemID"></param>
        /// <param name="ownerID"></param>
        public void HandleFetchInventory(IClientAPI remoteClient, UUID itemID, UUID ownerID)
        {
            lock (m_fetchHolder)
            {
                //                m_log.DebugFormat(
                //                    "[ASYNC INVENTORY SENDER]: Putting request from {0} for {1} on queue", remoteClient.Name, itemID);

                m_fetchHolder.Enqueue(new FetchHolder(remoteClient, itemID));
            }

            if (!m_processing)
            {
                m_processing = true;
                ProcessQueue();
            }
        }

        /// <summary>
        /// Process the queue of fetches
        /// </summary>
        protected void ProcessQueue()
        {
            FetchHolder fh = null;

            while (true)
            {
                lock (m_fetchHolder)
                {
                    //                    m_log.DebugFormat("[ASYNC INVENTORY SENDER]: {0} items left to process", m_fetchHolder.Count);

                    if (m_fetchHolder.Count == 0)
                    {
                        m_processing = false;
                        return;
                    }
                    else
                    {
                        fh = m_fetchHolder.Dequeue();
                    }
                }

                if (!fh.Client.IsActive)
                    continue;

                //                m_log.DebugFormat(
                //                    "[ASYNC INVENTORY SENDER]: Handling request from {0} for {1} on queue", fh.Client.Name, fh.ItemID);

                InventoryItemBase item = new InventoryItemBase(fh.ItemID, fh.Client.AgentId);
                item = m_scene.InventoryService.GetItem(item);

                if (item != null)
                    fh.Client.SendInventoryItemDetails(item.Owner, item);

                // TODO: Possibly log any failure
            }
        }
    }

    internal class FetchHolder
    {
        public FetchHolder(IClientAPI client, UUID itemID)
        {
            Client = client;
            ItemID = itemID;
        }

        public IClientAPI Client { get; private set; }

        public UUID ItemID { get; private set; }
    }
}