﻿/*
 * 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.Threading;
using OMV = OpenMetaverse;

namespace OpenSim.Region.Physics.BulletSPlugin
{

public abstract class BSLinkset
{
    // private static string LogHeader = "[BULLETSIM LINKSET]";

    public enum LinksetImplementation
    {
        Constraint   = 0,   // linkset tied together with constraints
        Compound     = 1,   // linkset tied together as a compound object
        Manual       = 2    // linkset tied together manually (code moves all the pieces)
    }
    // Create the correct type of linkset for this child
    public static BSLinkset Factory(BSScene physScene, BSPrimLinkable parent)
    {
        BSLinkset ret = null;

        switch (parent.LinksetType)
        {
            case LinksetImplementation.Constraint:
                ret = new BSLinksetConstraints(physScene, parent);
                break;
            case LinksetImplementation.Compound:
                ret = new BSLinksetCompound(physScene, parent);
                break;
            case LinksetImplementation.Manual:
                // ret = new BSLinksetManual(physScene, parent);
                break;
            default:
                ret = new BSLinksetCompound(physScene, parent);
                break;
        }
        if (ret == null)
        {
            physScene.Logger.ErrorFormat("[BULLETSIM LINKSET] Factory could not create linkset. Parent name={1}, ID={2}", parent.Name, parent.LocalID);
        }
        return ret;
    }

    public class BSLinkInfo
    {
        public BSPrimLinkable member;
        public BSLinkInfo(BSPrimLinkable pMember)
        {
            member = pMember;
        }
        public virtual void ResetLink() { }
        public virtual void SetLinkParameters(BSConstraint constrain) { }
        // Returns 'true' if physical property updates from the child should be reported to the simulator
        public virtual bool ShouldUpdateChildProperties() { return false; }
    }

    public LinksetImplementation LinksetImpl { get; protected set; }

    public BSPrimLinkable LinksetRoot { get; protected set; }

    protected BSScene m_physicsScene { get; private set; }

    static int m_nextLinksetID = 1;
    public int LinksetID { get; private set; }

    // The children under the root in this linkset.
    // protected HashSet<BSPrimLinkable> m_children;
    protected Dictionary<BSPrimLinkable, BSLinkInfo> m_children;

    // We lock the diddling of linkset classes to prevent any badness.
    // This locks the modification of the instances of this class. Changes
    //    to the physical representation is done via the tainting mechenism.
    protected ReaderWriterLock m_linksetActivityLock = new ReaderWriterLock();

    // We keep the prim's mass in the linkset structure since it could be dependent on other prims
    public float LinksetMass { get; protected set; }

    public virtual bool LinksetIsColliding { get { return false; } }

    public OMV.Vector3 CenterOfMass
    {
        get { return ComputeLinksetCenterOfMass(); }
    }

    public OMV.Vector3 GeometricCenter
    {
        get { return ComputeLinksetGeometricCenter(); }
    }

    protected BSLinkset(BSScene scene, BSPrimLinkable parent)
    {
        // A simple linkset of one (no children)
        LinksetID = m_nextLinksetID++;
        // We create LOTS of linksets.
        if (m_nextLinksetID <= 0)
            m_nextLinksetID = 1;
        m_physicsScene = scene;
        LinksetRoot = parent;
        m_children = new Dictionary<BSPrimLinkable, BSLinkInfo>();
        LinksetMass = parent.RawMass;
        Rebuilding = false;

        parent.ClearDisplacement();
    }

    // Link to a linkset where the child knows the parent.
    // Parent changing should not happen so do some sanity checking.
    // We return the parent's linkset so the child can track its membership.
    // Called at runtime.
    public BSLinkset AddMeToLinkset(BSPrimLinkable child)
    {
        m_linksetActivityLock.AcquireWriterLock(-1);
        try
        {
            // Don't add the root to its own linkset
            if (!IsRoot(child))
                AddChildToLinkset(child);
            LinksetMass = ComputeLinksetMass();
        }
        finally
        {
            m_linksetActivityLock.ReleaseWriterLock();
        }
        return this;
    }

    // Remove a child from a linkset.
    // Returns a new linkset for the child which is a linkset of one (just the
    //    orphened child).
    // Called at runtime.
    public BSLinkset RemoveMeFromLinkset(BSPrimLinkable child, bool inTaintTime)
    {
        m_linksetActivityLock.AcquireWriterLock(-1);
        try
        {
            if (IsRoot(child))
            {
                // Cannot remove the root from a linkset.
                return this;
            }
            RemoveChildFromLinkset(child, inTaintTime);
            LinksetMass = ComputeLinksetMass();
        }
        finally
        {
            m_linksetActivityLock.ReleaseWriterLock();
        }

        // The child is down to a linkset of just itself
        return BSLinkset.Factory(m_physicsScene, child);
    }

    // Return 'true' if the passed object is the root object of this linkset
    public bool IsRoot(BSPrimLinkable requestor)
    {
        return (requestor.LocalID == LinksetRoot.LocalID);
    }

    public int NumberOfChildren { get { return m_children.Count; } }

    // Return 'true' if this linkset has any children (more than the root member)
    public bool HasAnyChildren { get { return (m_children.Count > 0); } }

    // Return 'true' if this child is in this linkset
    public bool HasChild(BSPrimLinkable child)
    {
        m_linksetActivityLock.AcquireReaderLock(-1);
        try
        {
            return m_children.ContainsKey(child);
        }
        finally
        {
            m_linksetActivityLock.ReleaseReaderLock();
        }
    }

    // Perform an action on each member of the linkset including root prim.
    // Depends on the action on whether this should be done at taint time.
    public delegate bool ForEachMemberAction(BSPrimLinkable obj);
    public virtual void ForEachMember(ForEachMemberAction action)
    {
        m_linksetActivityLock.AcquireReaderLock(-1);
        try
        {
            action(LinksetRoot);
            foreach (BSPrimLinkable po in m_children.Keys)
            {
                if (action(po))
                    break;
            }
        }
        finally
        {
            m_linksetActivityLock.ReleaseReaderLock();
        }
    }

    public bool TryGetLinkInfo(BSPrimLinkable child, out BSLinkInfo foundInfo)
    {
        m_linksetActivityLock.AcquireReaderLock(-1);
        try
        {
            return m_children.TryGetValue(child, out foundInfo);
        }
        finally
        {
            m_linksetActivityLock.ReleaseReaderLock();
        }
    }
    // Perform an action on each member of the linkset including root prim.
    // Depends on the action on whether this should be done at taint time.
    public delegate bool ForEachLinkInfoAction(BSLinkInfo obj);
    public virtual void ForEachLinkInfo(ForEachLinkInfoAction action)
    {
        m_linksetActivityLock.AcquireReaderLock(-1);
        try
        {
            foreach (BSLinkInfo po in m_children.Values)
            {
                if (action(po))
                    break;
            }
        }
        finally
        {
            m_linksetActivityLock.ReleaseReaderLock();
        }
    }

    // Check the type of the link and return 'true' if the link is flexible and the
    //    updates from the child should be sent to the simulator so things change.
    public virtual bool ShouldReportPropertyUpdates(BSPrimLinkable child)
    {
        bool ret = false;

        BSLinkInfo linkInfo;
        if (m_children.TryGetValue(child, out linkInfo))
        {
            ret = linkInfo.ShouldUpdateChildProperties();
        }

        return ret;
    }

    // Called after a simulation step to post a collision with this object.
    // Return 'true' if linkset processed the collision. 'false' says the linkset didn't have
    //     anything to add for the collision and it should be passed through normal processing.
    // Default processing for a linkset.
    public virtual bool HandleCollide(uint collidingWith, BSPhysObject collidee,
                                OMV.Vector3 contactPoint, OMV.Vector3 contactNormal, float pentrationDepth)
    {
        bool ret = false;

        // prims in the same linkset cannot collide with each other
        BSPrimLinkable convCollidee = collidee as BSPrimLinkable;
        if (convCollidee != null && (LinksetID == convCollidee.Linkset.LinksetID))
        {
            // By returning 'true', we tell the caller the collision has been 'handled' so it won't
            //     do anything about this collision and thus, effectivily, ignoring the collision.
            ret = true;
        }
        else
        {
            // Not a collision between members of the linkset. Must be a real collision.
            // So the linkset root can know if there is a collision anywhere in the linkset.
            LinksetRoot.SomeCollisionSimulationStep = m_physicsScene.SimulationStep;
        }

        return ret;
    }

    // I am the root of a linkset and a new child is being added
    // Called while LinkActivity is locked.
    protected abstract void AddChildToLinkset(BSPrimLinkable child);

    // I am the root of a linkset and one of my children is being removed.
    // Safe to call even if the child is not really in my linkset.
    protected abstract void RemoveChildFromLinkset(BSPrimLinkable child, bool inTaintTime);

    // When physical properties are changed the linkset needs to recalculate
    //   its internal properties.
    // May be called at runtime or taint-time.
    public virtual void Refresh(BSPrimLinkable requestor)
    {
        LinksetMass = ComputeLinksetMass();
    }

    // Flag denoting the linkset is in the process of being rebuilt.
    // Used to know not the schedule a rebuild in the middle of a rebuild.
    protected bool Rebuilding { get; set; }

    // The object is going dynamic (physical). Do any setup necessary
    //     for a dynamic linkset.
    // Only the state of the passed object can be modified. The rest of the linkset
    //     has not yet been fully constructed.
    // Return 'true' if any properties updated on the passed object.
    // Called at taint-time!
    public abstract bool MakeDynamic(BSPrimLinkable child);

    // The object is going static (non-physical). Do any setup necessary
    //     for a static linkset.
    // Return 'true' if any properties updated on the passed object.
    // Called at taint-time!
    public abstract bool MakeStatic(BSPrimLinkable child);

    // Called when a parameter update comes from the physics engine for any object
    //      of the linkset is received.
    // Passed flag is update came from physics engine (true) or the user (false).
    // Called at taint-time!!
    public abstract void UpdateProperties(UpdatedProperties whichUpdated, BSPrimLinkable physObject);

    // Routine used when rebuilding the body of the root of the linkset
    // Destroy all the constraints have have been made to root.
    // This is called when the root body is changing.
    // Returns 'true' of something was actually removed and would need restoring
    // Called at taint-time!!
    public abstract bool RemoveDependencies(BSPrimLinkable child);

    // ================================================================
    // Some physical setting happen to all members of the linkset
    public virtual void SetPhysicalFriction(float friction)
    {
        ForEachMember((member) =>
            {
                if (member.PhysBody.HasPhysicalBody)
                    m_physicsScene.PE.SetFriction(member.PhysBody, friction);
                return false;   // 'false' says to continue looping
            }
        );
    }
    public virtual void SetPhysicalRestitution(float restitution)
    {
        ForEachMember((member) =>
            {
                if (member.PhysBody.HasPhysicalBody)
                    m_physicsScene.PE.SetRestitution(member.PhysBody, restitution);
                return false;   // 'false' says to continue looping
            }
        );
    }
    public virtual void SetPhysicalGravity(OMV.Vector3 gravity)
    {
        ForEachMember((member) =>
            {
                if (member.PhysBody.HasPhysicalBody)
                    m_physicsScene.PE.SetGravity(member.PhysBody, gravity);
                return false;   // 'false' says to continue looping
            }
        );
    }
    public virtual void ComputeAndSetLocalInertia(OMV.Vector3 inertiaFactor, float linksetMass)
    {
        ForEachMember((member) =>
            {
                if (member.PhysBody.HasPhysicalBody)
                {
                    OMV.Vector3 inertia = m_physicsScene.PE.CalculateLocalInertia(member.PhysShape.physShapeInfo, linksetMass);
                    member.Inertia = inertia * inertiaFactor;
                    m_physicsScene.PE.SetMassProps(member.PhysBody, linksetMass, member.Inertia);
                    m_physicsScene.PE.UpdateInertiaTensor(member.PhysBody);
                    DetailLog("{0},BSLinkset.ComputeAndSetLocalInertia,m.mass={1}, inertia={2}", member.LocalID, linksetMass, member.Inertia);

                }
                return false;   // 'false' says to continue looping
            }
        );
    }
    public virtual void SetPhysicalCollisionFlags(CollisionFlags collFlags)
    {
        ForEachMember((member) =>
            {
                if (member.PhysBody.HasPhysicalBody)
                    m_physicsScene.PE.SetCollisionFlags(member.PhysBody, collFlags);
                return false;   // 'false' says to continue looping
            }
        );
    }
    public virtual void AddToPhysicalCollisionFlags(CollisionFlags collFlags)
    {
        ForEachMember((member) =>
            {
                if (member.PhysBody.HasPhysicalBody)
                    m_physicsScene.PE.AddToCollisionFlags(member.PhysBody, collFlags);
                return false;   // 'false' says to continue looping
            }
        );
    }
    public virtual void RemoveFromPhysicalCollisionFlags(CollisionFlags collFlags)
    {
        ForEachMember((member) =>
            {
                if (member.PhysBody.HasPhysicalBody)
                    m_physicsScene.PE.RemoveFromCollisionFlags(member.PhysBody, collFlags);
                return false;   // 'false' says to continue looping
            }
        );
    }
    // ================================================================
    protected virtual float ComputeLinksetMass()
    {
        float mass = LinksetRoot.RawMass;
        if (HasAnyChildren)
        {
            m_linksetActivityLock.AcquireReaderLock(-1);
            try
            {
                foreach (BSPrimLinkable bp in m_children.Keys)
                {
                    mass += bp.RawMass;
                }
            }
            finally
            {
                m_linksetActivityLock.ReleaseReaderLock();
            }
        }
        return mass;
    }

    // Computes linkset's center of mass in world coordinates.
    protected virtual OMV.Vector3 ComputeLinksetCenterOfMass()
    {
        OMV.Vector3 com;
        m_linksetActivityLock.AcquireReaderLock(-1);
        try
        {
            com = LinksetRoot.Position * LinksetRoot.RawMass;
            float totalMass = LinksetRoot.RawMass;

            foreach (BSPrimLinkable bp in m_children.Keys)
            {
                com += bp.Position * bp.RawMass;
                totalMass += bp.RawMass;
            }
            if (totalMass != 0f)
                com /= totalMass;
        }
        finally
        {
            m_linksetActivityLock.ReleaseReaderLock();
        }

        return com;
    }

    protected virtual OMV.Vector3 ComputeLinksetGeometricCenter()
    {
        OMV.Vector3 com;
        m_linksetActivityLock.AcquireReaderLock(-1);
        try
        {
            com = LinksetRoot.Position;

            foreach (BSPrimLinkable bp in m_children.Keys)
            {
                com += bp.Position;
            }
            com /= (m_children.Count + 1);
        }
        finally
        {
            m_linksetActivityLock.ReleaseReaderLock();
        }

        return com;
    }

    #region Extension
    public virtual object Extension(string pFunct, params object[] pParams)
    {
        return null;
    }
    #endregion // Extension

    // Invoke the detailed logger and output something if it's enabled.
    protected void DetailLog(string msg, params Object[] args)
    {
        if (m_physicsScene.PhysicsLogging.Enabled)
            m_physicsScene.DetailLog(msg, args);
    }
}
}
