/*
 * 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 copyrightD
 *       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 System.Text;
using OMV = OpenMetaverse;
using OpenSim.Framework;
using OpenSim.Region.PhysicsModules.SharedBase;

namespace OpenSim.Region.PhysicsModule.BulletS
{
    public sealed class BSShapeCollection : IDisposable
    {
#pragma warning disable 414
        private static string LogHeader = "[BULLETSIM SHAPE COLLECTION]";
#pragma warning restore 414

        private BSScene m_physicsScene { get; set; }
	    
        private Object m_collectionActivityLock = new Object();
	    
        private bool DDetail = false;
	    
        public BSShapeCollection(BSScene physScene)
        {
            m_physicsScene = physScene;
            // Set the next to 'true' for very detailed shape update detailed logging (detailed details?)
            // While detailed debugging is still active, this is better than commenting out all the
            //     DetailLog statements. When debugging slows down, this and the protected logging
            //     statements can be commented/removed.
            DDetail = true;
        }
	    
        public void Dispose()
        {
            // TODO!!!!!!!!!
        }
	    
        // Callbacks called just before either the body or shape is destroyed.
        // Mostly used for changing bodies out from under Linksets.
        // Useful for other cases where parameters need saving.
        // Passing 'null' says no callback.
        public delegate void PhysicalDestructionCallback(BulletBody pBody, BulletShape pShape);
	    
        // Called to update/change the body and shape for an object.
        // The object has some shape and body on it. Here we decide if that is the correct shape
        //    for the current state of the object (static/dynamic/...).
        // If bodyCallback is not null, it is called if either the body or the shape are changed
        //    so dependencies (like constraints) can be removed before the physical object is dereferenced.
        // Return 'true' if either the body or the shape changed.
        // Called at taint-time.
        public bool GetBodyAndShape(bool forceRebuild, BulletWorld sim, BSPhysObject prim, PhysicalDestructionCallback bodyCallback)
        {
            bool ret = false;
	    
            // This lock could probably be pushed down lower but building shouldn't take long
            lock (m_collectionActivityLock)
            {
                // Do we have the correct geometry for this type of object?
                // Updates prim.BSShape with information/pointers to shape.
                // Returns 'true' of BSShape is changed to a new shape.
                bool newGeom = CreateGeom(forceRebuild, prim, bodyCallback);
                // If we had to select a new shape geometry for the object,
                //    rebuild the body around it.
                // Updates prim.BSBody with information/pointers to requested body
                // Returns 'true' if BSBody was changed.
                bool newBody = CreateBody((newGeom || forceRebuild), prim, m_physicsScene.World, bodyCallback);
                ret = newGeom || newBody;
            }
            DetailLog("{0},BSShapeCollection.GetBodyAndShape,taintExit,force={1},ret={2},body={3},shape={4}",
                                    prim.LocalID, forceRebuild, ret, prim.PhysBody, prim.PhysShape);
	    
            return ret;
        }
	    
        public bool GetBodyAndShape(bool forceRebuild, BulletWorld sim, BSPhysObject prim)
        {
            return GetBodyAndShape(forceRebuild, sim, prim, null);
        }
	    
        // If the existing prim's shape is to be replaced, remove the tie to the existing shape
        //     before replacing it.
        private void DereferenceExistingShape(BSPhysObject prim, PhysicalDestructionCallback shapeCallback)
        {
            if (prim.PhysShape.HasPhysicalShape)
            {
                if (shapeCallback != null)
                    shapeCallback(prim.PhysBody, prim.PhysShape.physShapeInfo);
                prim.PhysShape.Dereference(m_physicsScene);
            }
            prim.PhysShape = new BSShapeNull();
        }
	    
        // Create the geometry information in Bullet for later use.
        // The objects needs a hull if it's physical otherwise a mesh is enough.
        // if 'forceRebuild' is true, the geometry is unconditionally rebuilt. For meshes and hulls,
        //     shared geometries will be used. If the parameters of the existing shape are the same
        //     as this request, the shape is not rebuilt.
        // Info in prim.BSShape is updated to the new shape.
        // Returns 'true' if the geometry was rebuilt.
        // Called at taint-time!
        public const int AvatarShapeCapsule = 0;
        public const int AvatarShapeCube = 1;
        public const int AvatarShapeOvoid = 2;
        public const int AvatarShapeMesh = 3;
        private bool CreateGeom(bool forceRebuild, BSPhysObject prim, PhysicalDestructionCallback shapeCallback)
        {
            bool ret = false;
            bool haveShape = false;
            bool nativeShapePossible = true;
            PrimitiveBaseShape pbs = prim.BaseShape;
	    
            // Kludge to create the capsule for the avatar.
            // TDOD: Remove/redo this when BSShapeAvatar is working!!
            BSCharacter theChar = prim as BSCharacter;
            if (theChar != null)
            {
                DereferenceExistingShape(prim, shapeCallback);
                switch (BSParam.AvatarShape)
                {
                    case AvatarShapeCapsule:
                        prim.PhysShape = BSShapeNative.GetReference(m_physicsScene, prim,
                                                BSPhysicsShapeType.SHAPE_CAPSULE, FixedShapeKey.KEY_CAPSULE);
                        ret = true;
                        haveShape = true;
                        break;
                    case AvatarShapeCube:
                        prim.PhysShape = BSShapeNative.GetReference(m_physicsScene, prim,
                                                BSPhysicsShapeType.SHAPE_BOX, FixedShapeKey.KEY_CAPSULE);
                        ret = true;
                        haveShape = true;
                        break;
                    case AvatarShapeOvoid:
                        // Saddly, Bullet doesn't scale spheres so this doesn't work as an avatar shape
                        prim.PhysShape = BSShapeNative.GetReference(m_physicsScene, prim,
                                                BSPhysicsShapeType.SHAPE_SPHERE, FixedShapeKey.KEY_CAPSULE);
                        ret = true;
                        haveShape = true;
                        break;
                    case AvatarShapeMesh:
                        break;
                    default:
                        break;
                }
            }
	    
            // If the prim attributes are simple, this could be a simple Bullet native shape
            // Native shapes work whether to object is static or physical.
            if (!haveShape
                    && nativeShapePossible
                    && pbs != null
                    && PrimHasNoCuts(pbs)
                    && ( !pbs.SculptEntry || (pbs.SculptEntry && !BSParam.ShouldMeshSculptedPrim) )
                )
            {
                // Get the scale of any existing shape so we can see if the new shape is same native type and same size.
                OMV.Vector3 scaleOfExistingShape = OMV.Vector3.Zero;
                if (prim.PhysShape.HasPhysicalShape)
                    scaleOfExistingShape = m_physicsScene.PE.GetLocalScaling(prim.PhysShape.physShapeInfo);
	    
                if (DDetail) DetailLog("{0},BSShapeCollection.CreateGeom,maybeNative,force={1},primScale={2},primSize={3},primShape={4}",
                            prim.LocalID, forceRebuild, prim.Scale, prim.Size, prim.PhysShape.physShapeInfo.shapeType);
	    
                // It doesn't look like Bullet scales native spheres so make sure the scales are all equal
                if ((pbs.ProfileShape == ProfileShape.HalfCircle && pbs.PathCurve == (byte)Extrusion.Curve1)
                                    && pbs.Scale.X == pbs.Scale.Y && pbs.Scale.Y == pbs.Scale.Z)
                {
                    haveShape = true;
                    if (forceRebuild
                            || prim.PhysShape.ShapeType != BSPhysicsShapeType.SHAPE_SPHERE
                        )
                    {
                        DereferenceExistingShape(prim, shapeCallback);
                        prim.PhysShape = BSShapeNative.GetReference(m_physicsScene, prim,
                                                BSPhysicsShapeType.SHAPE_SPHERE, FixedShapeKey.KEY_SPHERE);
                        ret = true;
                    }
                    if (DDetail) DetailLog("{0},BSShapeCollection.CreateGeom,sphere,force={1},rebuilt={2},shape={3}",
                                            prim.LocalID, forceRebuild, ret, prim.PhysShape);
                }
                // If we didn't make a sphere, maybe a box will work.
                if (!haveShape && pbs.ProfileShape == ProfileShape.Square && pbs.PathCurve == (byte)Extrusion.Straight)
                {
                    haveShape = true;
                    if (forceRebuild
                            || prim.Scale != scaleOfExistingShape
                            || prim.PhysShape.ShapeType != BSPhysicsShapeType.SHAPE_BOX
                            )
                    {
                        DereferenceExistingShape(prim, shapeCallback);
                        prim.PhysShape = BSShapeNative.GetReference(m_physicsScene, prim,
                                                BSPhysicsShapeType.SHAPE_BOX, FixedShapeKey.KEY_BOX);
                        ret = true;
                    }
                    if (DDetail) DetailLog("{0},BSShapeCollection.CreateGeom,box,force={1},rebuilt={2},shape={3}",
                                            prim.LocalID, forceRebuild, ret, prim.PhysShape);
                }
            }
	    
            // If a simple shape is not happening, create a mesh and possibly a hull.
            if (!haveShape && pbs != null)
            {
                ret = CreateGeomMeshOrHull(prim, shapeCallback);
            }
	    
            m_physicsScene.PE.ResetBroadphasePool(m_physicsScene.World);    // DEBUG DEBUG
	    
            return ret;
        }
	    
        // return 'true' if this shape description does not include any cutting or twisting.
        public static bool PrimHasNoCuts(PrimitiveBaseShape pbs)
        {
            return pbs.ProfileBegin == 0 && pbs.ProfileEnd == 0
                && pbs.ProfileHollow == 0
                && pbs.PathTwist == 0 && pbs.PathTwistBegin == 0
                && pbs.PathBegin == 0 && pbs.PathEnd == 0
                && pbs.PathTaperX == 0 && pbs.PathTaperY == 0
                && pbs.PathScaleX == 100 && pbs.PathScaleY == 100
                && pbs.PathShearX == 0 && pbs.PathShearY == 0;
        }
	    
        // return 'true' if the prim's shape was changed.
        private bool CreateGeomMeshOrHull(BSPhysObject prim, PhysicalDestructionCallback shapeCallback)
        {
	    
            bool ret = false;
            // Note that if it's a native shape, the check for physical/non-physical is not
            //     made. Native shapes work in either case.
            if (prim.IsPhysical && BSParam.ShouldUseHullsForPhysicalObjects)
            {
                // Use a simple, single mesh convex hull shape if the object is simple enough
                BSShape potentialHull = null;
	    
                PrimitiveBaseShape pbs = prim.BaseShape;
                // Use a simple, one section convex shape for prims that are probably convex (no cuts or twists)
                if (BSParam.ShouldUseSingleConvexHullForPrims
                    && pbs != null
                    && !pbs.SculptEntry
                    && PrimHasNoCuts(pbs)
                    )
                {
                    potentialHull = BSShapeConvexHull.GetReference(m_physicsScene, false /* forceRebuild */, prim);
                }
                // Use the GImpact shape if it is a prim that has some concaveness
                if (potentialHull == null
                    && BSParam.ShouldUseGImpactShapeForPrims
                    && pbs != null
                    && !pbs.SculptEntry
                    )
                {
                        potentialHull = BSShapeGImpact.GetReference(m_physicsScene, false /* forceRebuild */, prim);
                }
                // If not any of the simple cases, just make a hull
                if (potentialHull == null)
                {
                    potentialHull = BSShapeHull.GetReference(m_physicsScene, false /*forceRebuild*/, prim);
                }
	    
                // If the current shape is not what is on the prim at the moment, time to change.
                if (!prim.PhysShape.HasPhysicalShape
                            || potentialHull.ShapeType != prim.PhysShape.ShapeType
                            || potentialHull.physShapeInfo.shapeKey != prim.PhysShape.physShapeInfo.shapeKey)
                {
                    DereferenceExistingShape(prim, shapeCallback);
                    prim.PhysShape = potentialHull;
                    ret = true;
                }
                else
                {
                    // The current shape on the prim is the correct one. We don't need the potential reference.
                    potentialHull.Dereference(m_physicsScene);
                }
                if (DDetail) DetailLog("{0},BSShapeCollection.CreateGeom,hull,shape={1}", prim.LocalID, prim.PhysShape);
            }
            else
            {
                // Non-physical objects should be just meshes.
                BSShape potentialMesh = BSShapeMesh.GetReference(m_physicsScene, false /*forceRebuild*/, prim);
                // If the current shape is not what is on the prim at the moment, time to change.
                if (!prim.PhysShape.HasPhysicalShape
                            || potentialMesh.ShapeType != prim.PhysShape.ShapeType
                            || potentialMesh.physShapeInfo.shapeKey != prim.PhysShape.physShapeInfo.shapeKey)
                {
                    DereferenceExistingShape(prim, shapeCallback);
                    prim.PhysShape = potentialMesh;
                    ret = true;
                }
                else
                {
                    // We don't need this reference to the mesh that is already being using.
                    potentialMesh.Dereference(m_physicsScene);
                }
                if (DDetail) DetailLog("{0},BSShapeCollection.CreateGeom,mesh,shape={1}", prim.LocalID, prim.PhysShape);
            }
            return ret;
        }
	    
        // Track another user of a body.
        // We presume the caller has allocated the body.
        // Bodies only have one user so the body is just put into the world if not already there.
        private void ReferenceBody(BulletBody body)
        {
            lock (m_collectionActivityLock)
            {
                if (DDetail) DetailLog("{0},BSShapeCollection.ReferenceBody,newBody,body={1}", body.ID, body);
                if (!m_physicsScene.PE.IsInWorld(m_physicsScene.World, body))
                {
                    m_physicsScene.PE.AddObjectToWorld(m_physicsScene.World, body);
                    if (DDetail) DetailLog("{0},BSShapeCollection.ReferenceBody,addedToWorld,ref={1}", body.ID, body);
                }
            }
        }
	    
        // Release the usage of a body.
        // Called when releasing use of a BSBody. BSShape is handled separately.
        // Called in taint time.
        public void DereferenceBody(BulletBody body, PhysicalDestructionCallback bodyCallback )
        {
            if (!body.HasPhysicalBody)
                return;
	    
            lock (m_collectionActivityLock)
            {
                if (DDetail) DetailLog("{0},BSShapeCollection.DereferenceBody,DestroyingBody,body={1}", body.ID, body);
                // If the caller needs to know the old body is going away, pass the event up.
                if (bodyCallback != null)
                    bodyCallback(body, null);
	    
                // Removing an object not in the world is a NOOP
                m_physicsScene.PE.RemoveObjectFromWorld(m_physicsScene.World, body);
	    
                // Zero any reference to the shape so it is not freed when the body is deleted.
                m_physicsScene.PE.SetCollisionShape(m_physicsScene.World, body, null);
	    
                m_physicsScene.PE.DestroyObject(m_physicsScene.World, body);
            }
        }
	    
        // Create a body object in Bullet.
        // Updates prim.BSBody with the information about the new body if one is created.
        // Returns 'true' if an object was actually created.
        // Called at taint-time.
        private bool CreateBody(bool forceRebuild, BSPhysObject prim, BulletWorld sim, PhysicalDestructionCallback bodyCallback)
        {
            bool ret = false;
	    
            // the mesh, hull or native shape must have already been created in Bullet
            bool mustRebuild = !prim.PhysBody.HasPhysicalBody;
	    
            // If there is an existing body, verify it's of an acceptable type.
            // If not a solid object, body is a GhostObject. Otherwise a RigidBody.
            if (!mustRebuild)
            {
                CollisionObjectTypes bodyType = (CollisionObjectTypes)m_physicsScene.PE.GetBodyType(prim.PhysBody);
                if (prim.IsSolid && bodyType != CollisionObjectTypes.CO_RIGID_BODY
                    || !prim.IsSolid && bodyType != CollisionObjectTypes.CO_GHOST_OBJECT)
                {
                    // If the collisionObject is not the correct type for solidness, rebuild what's there
                    mustRebuild = true;
                    if (DDetail) DetailLog("{0},BSShapeCollection.CreateBody,forceRebuildBecauseChangingBodyType,bodyType={1}", prim.LocalID, bodyType);
                }
            }
	    
            if (mustRebuild || forceRebuild)
            {
                // Free any old body
                DereferenceBody(prim.PhysBody, bodyCallback);
	    
                BulletBody aBody;
                if (prim.IsSolid)
                {
                    aBody = m_physicsScene.PE.CreateBodyFromShape(sim, prim.PhysShape.physShapeInfo, prim.LocalID, prim.RawPosition, prim.RawOrientation);
                    if (DDetail) DetailLog("{0},BSShapeCollection.CreateBody,rigid,body={1}", prim.LocalID, aBody);
                }
                else
                {
                    aBody = m_physicsScene.PE.CreateGhostFromShape(sim, prim.PhysShape.physShapeInfo, prim.LocalID, prim.RawPosition, prim.RawOrientation);
                    if (DDetail) DetailLog("{0},BSShapeCollection.CreateBody,ghost,body={1}", prim.LocalID, aBody);
                }
	    
                ReferenceBody(aBody);
	    
                prim.PhysBody = aBody;
	    
                ret = true;
            }
	    
            return ret;
        }
	    
        private void DetailLog(string msg, params Object[] args)
        {
            if (m_physicsScene.PhysicsLogging.Enabled)
                m_physicsScene.DetailLog(msg, args);
        }
    }
}
