/*
 * 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 System;
using System.Collections.Generic;
using OpenSim.Framework;

namespace OpenSim.Region.Framework.Scenes
{
    /// <summary>
    /// Specifies the fields that have been changed when sending a prim or
    /// avatar update
    /// </summary>
    [Flags]
    public enum ObjectPropertyUpdateFlags : byte
    {
        None = 0,
        Family = 1,
        Object = 2,

        NoFamily = unchecked((byte)~Family),
        NoObject = unchecked((byte)~Object)
    }

    public class EntityUpdate
    {
        // for priority queue
        public int PriorityQueue;
        public int PriorityQueueIndex;
        public ulong EntryOrder;

        private ISceneEntity m_entity;
        private PrimUpdateFlags m_flags;
        public ObjectPropertyUpdateFlags m_propsFlags;

        public ObjectPropertyUpdateFlags PropsFlags
        {
            get
            {
                return m_propsFlags;
            }
            set
            {
                m_propsFlags = value;
            }
        }

        public ISceneEntity Entity
        {
            get
            {
                return m_entity;
            }
            internal set
            {
                m_entity = value;
            }
        }

        public PrimUpdateFlags Flags
        {
            get { return m_flags; }
            set { m_flags = value; }
        }

        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        public void Update(int pqueue, ulong entry)
        {
            if ((m_flags & PrimUpdateFlags.CancelKill) != 0)
            {
                if ((m_flags & PrimUpdateFlags.UpdateProbe) != 0)
                    m_flags = PrimUpdateFlags.UpdateProbe;
                else
                    m_flags = PrimUpdateFlags.FullUpdatewithAnim;
            }

            PriorityQueue = pqueue;
            EntryOrder = entry;
        }

        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        public void UpdateFromNew(EntityUpdate newupdate, int pqueue)
        {
            m_propsFlags |= newupdate.PropsFlags;
            PrimUpdateFlags newFlags = newupdate.Flags;

            if ((newFlags & PrimUpdateFlags.UpdateProbe) != 0)
                m_flags &= ~PrimUpdateFlags.UpdateProbe;

            if ((newFlags & PrimUpdateFlags.CancelKill) != 0)
            {
                if ((newFlags & PrimUpdateFlags.UpdateProbe) != 0)
                    m_flags = PrimUpdateFlags.UpdateProbe;
                else
                    newFlags = PrimUpdateFlags.FullUpdatewithAnim;
            }
            else
                m_flags |= newFlags;

            PriorityQueue = pqueue;
        }

        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
        public void Free()
        {
            m_entity = null;
            PriorityQueueIndex = -1;
            EntityUpdatesPool.Free(this);
        }

        public EntityUpdate(ISceneEntity entity, PrimUpdateFlags flags)
        {
            m_entity = entity;
            m_flags = flags;
        }

        public EntityUpdate(ISceneEntity entity, PrimUpdateFlags flags, bool sendfam, bool sendobj)
        {
            m_entity = entity;
            m_flags = flags;

            if (sendfam)
                m_propsFlags |= ObjectPropertyUpdateFlags.Family;

            if (sendobj)
                m_propsFlags |= ObjectPropertyUpdateFlags.Object;
        }

        public override string ToString()
        {
            return String.Format("[{0},{1},{2}]", PriorityQueue, EntryOrder, m_entity.LocalId);
        }
    }

    public static class EntityUpdatesPool
    {
        const int MAXSIZE = 32768;
        const int PREALLOC = 16384;
        private static readonly EntityUpdate[] m_pool = new EntityUpdate[MAXSIZE];
        private static readonly object m_poollock = new object();
        private static int m_poolPtr;
        //private static int m_min = int.MaxValue;
        //private static int m_max = int.MinValue;

        static EntityUpdatesPool()
        {
            for(int i = 0; i < PREALLOC; ++i)
                m_pool[i] = new EntityUpdate(null, 0);
            m_poolPtr = PREALLOC - 1;
        }

        public static EntityUpdate Get(ISceneEntity entity, PrimUpdateFlags flags)
        {
            lock (m_poollock)
            {
                if (m_poolPtr >= 0)
                {
                    EntityUpdate eu = m_pool[m_poolPtr];
                    m_pool[m_poolPtr] = null;
                    m_poolPtr--;
                    //if (m_min > m_poolPtr)
                    //    m_min = m_poolPtr;
                    eu.Entity = entity;
                    eu.Flags = flags;
                    return eu;
                }
            }
            return new EntityUpdate(entity, flags);
        }

        public static EntityUpdate Get(ISceneEntity entity, PrimUpdateFlags flags, bool sendfam, bool sendobj)
        {
            lock (m_poollock)
            {
                if (m_poolPtr >= 0)
                {
                    EntityUpdate eu = m_pool[m_poolPtr];
                    m_pool[m_poolPtr] = null;
                    m_poolPtr--;
                    //if (m_min > m_poolPtr)
                    //    m_min = m_poolPtr;
                    eu.Entity = entity;
                    eu.Flags = flags;
                    ObjectPropertyUpdateFlags tmp = 0;
                    if (sendfam)
                        tmp |= ObjectPropertyUpdateFlags.Family;

                    if (sendobj)
                        tmp |= ObjectPropertyUpdateFlags.Object;

                    eu.PropsFlags = tmp;
                    return eu;
                }
            }
            return new EntityUpdate(entity, flags, sendfam, sendobj);
        }

        public static void Free(EntityUpdate eu)
        {
            lock (m_poollock)
            {
                if (m_poolPtr < MAXSIZE - 1)
                {
                    m_poolPtr++;
                    //if (m_max < m_poolPtr)
                    //    m_max = m_poolPtr;
                    m_pool[m_poolPtr] = eu;
                }
            }
        }
    }
}
