/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
#ifndef plSimulationMgr_H
#define plSimulationMgr_H

#include "hsStlUtils.h"
#include "pnKeyedObject/hsKeyedObject.h"
#include "hsTemplates.h"

class plPXPhysical;
class plLOSDispatch;
class plStatusLog;
class plPhysicsSoundMgr;
class NxPhysicsSDK;
class NxScene;
class plCollideMsg;
struct hsPoint3;

class plSimulationMgr : public hsKeyedObject
{
public:
    CLASSNAME_REGISTER(plSimulationMgr);
    GETINTERFACE_ANY(plSimulationMgr, hsKeyedObject);

    static plSimulationMgr* GetInstance();
    static void Init();
    static void Shutdown();

    static bool fExtraProfile;
    static bool fSubworldOptimization;
    static bool fDoClampingOnStep;

    // initialiation of the PhysX simulation
    virtual bool InitSimulation();

    // Advance the simulation by the given number of seconds
    void Advance(float delSecs);

    hsBool MsgReceive(plMessage* msg);

    // The simulation won't run at all if it is suspended
    void Suspend() { fSuspended = true; }
    void Resume() { fSuspended = false; }
    bool IsSuspended() { return fSuspended; }

    // Output the given debug text to the simulation log.
    static void Log(const char* formatStr, ...);
    static void LogV(const char* formatStr, va_list args);
    static void ClearLog();

    // We've detected a collision, which may be grounds for synchronizing the involved
    // physicals over the network.
    void ConsiderSynch(plPXPhysical *physical, plPXPhysical *other);

    NxPhysicsSDK* GetSDK() const { return fSDK; }
    NxScene* GetScene(plKey world);
    // Called when an actor is removed from a scene, checks if it's time to delete
    // the scene
    void ReleaseScene(plKey world);

    int GetMaterialIdx(NxScene* scene, hsScalar friction, hsScalar restitution);

    // PHYSX FIXME - walk thru all the convex hull detector regions to see if we are in any... we're either coming or going
    void UpdateDetectorsInScene(plKey world, plKey avatar, hsPoint3& pos, bool entering);
    void UpdateAvatarInDetector(plKey world, plPXPhysical* detector);
    //Fix to Move collision messages and their handling out of the simulation step
    void AddCollisionMsg(plCollideMsg* msg);
#ifndef PLASMA_EXTERNAL_RELEASE
    static bool fDisplayAwakeActors;
#endif //PLASMA_EXTERNAL_RELEASE
protected:
    friend class ContactReport;

    void ISendUpdates();

    plSimulationMgr();
    virtual ~plSimulationMgr();

    // Set the maximum amount of time (in seconds) that the physics will advance
    // between frames. If a frame-to-frame delta is bigger than this, we'll
    // clamp it to this value.
    // WARNING: animation doesn't do this, so if we clamp the time animated
    // physicals and the avatar may move at a faster rate than usual.
    void SetMaxDelta(float maxDelta);
    float GetMaxDelta() const;
    
    // Set the number of steps per second that physics will advance.
    // The more steps per second, the less fallthough and more accurate
    // simulation response.
    void SetStepsPerSecond(int stepsPerSecond);
    int GetStepsPerSecond();

    // Walk through the synchronization requests and send them as appropriate.
    void IProcessSynchs();

    // PHYSX FIXME send a collision message  - should only be used with UpdateDetectorsInScene
    void ISendCollisionMsg(plKey receiver, plKey hitter, hsBool entering);

    NxPhysicsSDK* fSDK;

    plPhysicsSoundMgr* fSoundMgr;

    //a list of collision messages generated by the simulation steps. Added to by AddCollisionMsg(plCollideMsg* msg)
    //cleared by IDispatchCollisionMessages when done 
    hsTArray<plCollideMsg*> fCollisionMessages;

    void IDispatchCollisionMessages();

    // A mapping from a key to a PhysX scene.  The key is either the
    // SimulationMgr key, for the main world, or a SceneObject key if it's a
    // subworld.
    typedef std::map<plKey, NxScene*> SceneMap;
    SceneMap fScenes;

    plLOSDispatch* fLOSDispatch;

    // Is the entire physics world suspended? If so, the clock can still advance
    // but nothing will move.
    bool fSuspended;

    float fMaxDelta;
    float fStepSize;

    // A utility class to keep track of a request for a physical synchronization.
    // These requests must pass a certain criteria (see the code for the latest)
    // before they are actually either sent over the network or rejected.
    class SynchRequest
    {
    public:
        double fTime;   // when to synch
        plKey fKey;     // key of the object to be synched, so we can make sure it still lives

        static const double kDefaultTime;
        SynchRequest() : fTime(kDefaultTime) {};
    };

    // All currently pending synch requests. Keyed by the physical in question
    // so we can quickly eliminate redundant requests, which are very common.
    typedef std::map<plPXPhysical*, SynchRequest> PhysSynchMap;
    PhysSynchMap fPendingSynchs;

    plStatusLog *fLog;
#ifndef PLASMA_EXTERNAL_RELEASE
    void IDrawActiveActorList();
#endif //PLASMA_EXTERNAL_RELEASE
};

#define SIM_VERBOSE

#ifdef SIM_VERBOSE
#include <stdarg.h>     // only include when we need to call plSimulationMgr::Log

inline void SimLog(const char *str, ...)
{
    va_list args;
    va_start(args, str);
    plSimulationMgr::LogV(str, args);
    va_end(args);
}

#else

inline void SimLog(const char *str, ...)
{
    // will get stripped out
}

#endif

#endif