/*==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 plParticleEffect_inc
#define plParticleEffect_inc

#include "pnKeyedObject/hsKeyedObject.h"
#include "hsMatrix44.h"

class plEffectTargetInfo;
class plConvexVolume;
class hsResMgr;
class plSceneObject;

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

    // Order is:
    //  PrepareEffect is called with a given target (including valid
    //      ParticleContext).
    //  ApplyEffect is called some once for each particle (maybe zero times). 
    //      It can return true to kill a particle. 
    //      Target and Context passed in with Prepare will be
    //      guaranteed to remain valid until,
    //  EndEffect marks no more particles will be processed with the above
    //      context (invalidating anything cached).
    // Defaults for Prepare and End are no-ops.
    virtual void PrepareEffect(const plEffectTargetInfo& target) {}
    virtual hsBool ApplyEffect(const plEffectTargetInfo& target, Int32 i) = 0;
    virtual void EndEffect(const plEffectTargetInfo& target) {}
};

class plParticleCollisionEffect : public plParticleEffect
{
public:
    plParticleCollisionEffect();
    ~plParticleCollisionEffect();

    CLASSNAME_REGISTER( plParticleCollisionEffect );
    GETINTERFACE_ANY( plParticleCollisionEffect, plParticleEffect );

    virtual void PrepareEffect(const plEffectTargetInfo& target);

    virtual void Read(hsStream *s, hsResMgr *mgr);
    virtual void Write(hsStream *s, hsResMgr *mgr);
    virtual hsBool MsgReceive(plMessage *msg);

protected:
    plSceneObject *fSceneObj;
    plConvexVolume *fBounds;
};

// Default particle blocker. Doesn't affect particle's velocity,
// so it'll keep "beat"ing on the deflector until the velocity
// dotted with the deflector normal slides it off an edge.
class plParticleCollisionEffectBeat : public plParticleCollisionEffect
{
public:
    plParticleCollisionEffectBeat();

    CLASSNAME_REGISTER( plParticleCollisionEffectBeat );
    GETINTERFACE_ANY( plParticleCollisionEffectBeat, plParticleCollisionEffect );

    virtual hsBool ApplyEffect(const plEffectTargetInfo& target, Int32 i);
};

// This particle blocker just kills any particles that hit it.
class plParticleCollisionEffectDie : public plParticleCollisionEffect
{
public:
    plParticleCollisionEffectDie();

    CLASSNAME_REGISTER( plParticleCollisionEffectDie );
    GETINTERFACE_ANY( plParticleCollisionEffectDie, plParticleCollisionEffect );

    virtual hsBool ApplyEffect(const plEffectTargetInfo& target, Int32 i);
};

class plParticleCollisionEffectBounce : public plParticleCollisionEffect
{
protected:
    hsScalar    fBounce;
    hsScalar    fFriction;
public:
    plParticleCollisionEffectBounce();

    CLASSNAME_REGISTER( plParticleCollisionEffectBounce );
    GETINTERFACE_ANY( plParticleCollisionEffectBounce, plParticleCollisionEffect );

    virtual hsBool ApplyEffect(const plEffectTargetInfo& target, Int32 i);

    virtual void Read(hsStream *s, hsResMgr *mgr);
    virtual void Write(hsStream *s, hsResMgr *mgr);

    void SetBounce(hsScalar b) { fBounce = b; }
    hsScalar GetBounce() const { return fBounce; }

    void SetFriction(hsScalar f) { fFriction = f; }
    hsScalar GetFriction() const { return fFriction; }
};

class plParticleFadeVolumeEffect : public plParticleEffect
{
protected:
    // Some cached properties. These will be the same for all
    // particles between matching PrepareEffect and EndEffect calls.
    hsPoint3    fMax;
    hsPoint3    fMin;
    hsPoint3    fNorm;

public:
    plParticleFadeVolumeEffect();
    ~plParticleFadeVolumeEffect();

    CLASSNAME_REGISTER( plParticleFadeVolumeEffect );
    GETINTERFACE_ANY( plParticleFadeVolumeEffect, plParticleEffect );

    virtual void PrepareEffect(const plEffectTargetInfo& target);
    virtual hsBool ApplyEffect(const plEffectTargetInfo& target, Int32 i);

    virtual void Read(hsStream *s, hsResMgr *mgr);
    virtual void Write(hsStream *s, hsResMgr *mgr);
    //virtual hsBool MsgReceive(plMessage *msg);

    hsScalar fLength;
    hsBool fIgnoreZ;
};

class plParticleWindEffect : public plParticleEffect
{
protected:
    // The properties that define the wind. These might/should be animatable.
    hsScalar    fStrength;
    hsScalar    fConstancy;
    hsScalar    fSwirl;
    hsBool      fHorizontal;
    hsVector3   fRefDir;

    // Some cached properties. These will be the same for all
    // particles between matching PrepareEffect and EndEffect calls.
    // These define the current state of the wind.
    hsVector3   fWindVec;
    hsVector3   fRandDir;
    hsVector3   fDir;
    double      fLastDirSecs;
public:

    plParticleWindEffect();
    ~plParticleWindEffect();

    CLASSNAME_REGISTER( plParticleWindEffect );
    GETINTERFACE_ANY( plParticleWindEffect, plParticleEffect );

    virtual void PrepareEffect(const plEffectTargetInfo& target);
    virtual hsBool ApplyEffect(const plEffectTargetInfo& target, Int32 i) = 0;

    virtual void Read(hsStream *s, hsResMgr *mgr);
    virtual void Write(hsStream *s, hsResMgr *mgr);

    void                SetStrength(hsScalar v) { fStrength = v; }
    hsScalar            GetStrength() const { return fStrength; }

    void                SetConstancy(hsScalar c) { fConstancy = c; }
    hsScalar            GetConstancy() const { return fConstancy; }

    void                SetSwirl(hsScalar s) { fSwirl = s; }
    hsScalar            GetSwirl() const { return fSwirl; }

    void                SetHorizontal(hsBool on) { fHorizontal = on; }
    hsBool              GetHorizontal() const { return fHorizontal; }

    void                SetRefDirection(const hsVector3& v);
    const hsVector3&    GetRefDirection() const { return fRefDir; }
};

class plParticleLocalWind : public plParticleWindEffect
{
protected:
    hsVector3   fScale;
    hsScalar    fSpeed;

    hsVector3   fPhase;
    hsVector3   fInvScale;
    double      fLastPhaseSecs;
    
public:
    plParticleLocalWind();
    ~plParticleLocalWind();

    CLASSNAME_REGISTER( plParticleLocalWind );
    GETINTERFACE_ANY( plParticleLocalWind, plParticleWindEffect );

    virtual void PrepareEffect(const plEffectTargetInfo& target);
    virtual hsBool ApplyEffect(const plEffectTargetInfo& target, Int32 i);

    void                SetScale(const hsVector3& v) { fScale = v; }
    const hsVector3&    GetScale() const { return fScale; }

    void                SetSpeed(hsScalar v) { fSpeed = v; }
    hsScalar            GetSpeed() const { return fSpeed; }

    virtual void Read(hsStream *s, hsResMgr *mgr);
    virtual void Write(hsStream *s, hsResMgr *mgr);

};

class plParticleUniformWind : public plParticleWindEffect
{
protected:

    hsScalar    fFreqMin;
    hsScalar    fFreqMax;
    hsScalar    fFreqCurr;
    hsScalar    fFreqRate;
    double      fCurrPhase;

    double      fLastFreqSecs;

    hsScalar    fCurrentStrength;
public:
    plParticleUniformWind();
    ~plParticleUniformWind();

    CLASSNAME_REGISTER( plParticleUniformWind );
    GETINTERFACE_ANY( plParticleUniformWind, plParticleWindEffect );

    virtual void PrepareEffect(const plEffectTargetInfo& target);
    virtual hsBool ApplyEffect(const plEffectTargetInfo& target, Int32 i);

    void        SetFrequencyRange(hsScalar minSecsPerCycle, hsScalar maxSecsPerCycle);
    void        SetFrequencyRate(hsScalar secsPerCycle);

    virtual void Read(hsStream *s, hsResMgr *mgr);
    virtual void Write(hsStream *s, hsResMgr *mgr);

};

class plParticleInfluenceInfo
{
public:
    hsVector3 fAvgVel;
    hsVector3 fRepDir;
};

class plParticleFlockEffect : public plParticleEffect
{
//protected:
protected:
    hsPoint3 fTargetOffset;     // Worldspace offset from our target to get the true goal
    hsPoint3 fDissenterTarget;  // Where to particles go when they get scared and leave us?
    hsScalar fInfAvgRadSq;      // Square of the radius of influence for average velocity matching.
    hsScalar fInfRepRadSq;      // Same, for repelling from neighbors.
    hsScalar fAvgVelStr;        // How strongly are we influenced by average dir?
    hsScalar fRepDirStr;        // Same for repelling
    hsScalar fGoalOrbitStr;     // Same for the goal (when we're within the desired distance)
    hsScalar fGoalChaseStr;     // Same for the goal (when we're too far away, and chasing it)
    hsScalar fGoalDistSq;
    hsScalar fFullChaseDistSq;
    hsScalar fMaxOrbitSpeed;
    hsScalar fMaxChaseSpeed;

    UInt16 fMaxParticles;
    hsScalar *fDistSq;          // Table of distances from particle to particle
    plParticleInfluenceInfo *fInfluences; 

    void IUpdateDistances(const plEffectTargetInfo &target);
    void IUpdateInfluences(const plEffectTargetInfo &target);

public:
    plParticleFlockEffect();
    ~plParticleFlockEffect();

    CLASSNAME_REGISTER( plParticleFlockEffect );
    GETINTERFACE_ANY( plParticleFlockEffect, plParticleEffect );

    virtual void PrepareEffect(const plEffectTargetInfo& target);
    virtual hsBool ApplyEffect(const plEffectTargetInfo& target, Int32 i);  

    void SetTargetOffset(const hsPoint3 &offset) { fTargetOffset = offset; }
    void SetDissenterTarget(const hsPoint3 &target) { fDissenterTarget = target; }
    void SetInfluenceAvgRadius(hsScalar val) { fInfAvgRadSq = val * val; }
    void SetInfluenceRepelRadius(hsScalar val) { fInfRepRadSq = val * val; }
    void SetGoalRadius(hsScalar val) { fGoalDistSq = val * val; }
    void SetFullChaseRadius(hsScalar val) { fFullChaseDistSq = val * val; }
    void SetConformStr(hsScalar val) { fAvgVelStr = val; }
    void SetRepelStr(hsScalar val) { fRepDirStr = val; }
    void SetGoalOrbitStr(hsScalar val) { fGoalOrbitStr = val; }
    void SetGoalChaseStr(hsScalar val) { fGoalChaseStr = val; }
    void SetMaxOrbitSpeed(hsScalar val) { fMaxOrbitSpeed = val; }
    void SetMaxChaseSpeed(hsScalar val) { fMaxChaseSpeed = val; }
    void SetMaxParticles(UInt16 num);

    virtual void Read(hsStream *s, hsResMgr *mgr);
    virtual void Write(hsStream *s, hsResMgr *mgr);
    virtual hsBool MsgReceive(plMessage *msg);  
};  

class plParticleFollowSystemEffect : public plParticleEffect
{
public:
    CLASSNAME_REGISTER( plParticleFollowSystemEffect );
    GETINTERFACE_ANY( plParticleFollowSystemEffect, plParticleEffect );
    
    plParticleFollowSystemEffect();

    virtual void PrepareEffect(const plEffectTargetInfo& target);
    virtual hsBool ApplyEffect(const plEffectTargetInfo& target, Int32 i);
    virtual void EndEffect(const plEffectTargetInfo& target);
    
protected:
    hsMatrix44 fOldW2L;
    hsBool fEvalThisFrame;
};

#endif