/*==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/>.

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

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==*/
//////////////////////////////////////////////////////////////////////////////
//                                                                          //
//  plSound.h - Base sound class header                                     //
//                                                                          //
//// History /////////////////////////////////////////////////////////////////
//                                                                          //
//  10.12.01 mcn    - Added preliminary soft region (volume) support.       //
//  7.12.02 mcn     - Added EAX support                                     //
//  7.15.02 mcn     - Added support for animated volumes                    //
//                                                                          //
//////////////////////////////////////////////////////////////////////////////

#ifndef plSound_h
#define plSound_h

#include "hsTemplates.h"
#include "hsGeometry3.h"
#include "plEAXEffects.h"
#include "pnNetCommon/plSynchedObject.h"
#include "plAvatar/plAGChannel.h"
#include "plAvatar/plAGApplicator.h"
#include "plAudioCore/plSoundBuffer.h"

class hsResMgr;
class hsStream;
class plSoundProxy;
class plDrawableSpans;
class hsGMaterial;
class plSoundMsg;
class plSoftVolume;
class plGraphPlate;
struct hsMatrix44;
class plSoundBuffer;
class plSceneObject;
class plSoundVolumeApplicator;

// Set this to 1 to do our own distance attenuation (model doesn't work yet tho)
#define MCN_HACK_OUR_ATTEN  0
#define MAX_INCIDENTALS 4

class plSound : public plSynchedObject
{
    friend class plSoundSDLModifier;
    friend class plSoundVolumeApplicator;

public:
    plSound();
    virtual ~plSound();

    CLASSNAME_REGISTER( plSound );
    GETINTERFACE_ANY( plSound, plSynchedObject );

    enum Property
    {
        kPropIs3DSound      = 0x00000001,
        kPropDisableLOD     = 0x00000002,
        kPropLooping        = 0x00000004,
        kPropAutoStart      = 0x00000008,
        kPropLocalOnly      = 0x00000010,   // Disables network synching and triggering
        kPropLoadOnlyOnCall = 0x00000020,   // Only load and unload when we're told to
        kPropFullyDisabled  = 0x00000040,   // This sound should never play while this is set
                                            // Only plWin32LinkSound uses it. Placed here as a TODO though...
        kPropDontFade       = 0x00000080,
        kPropIncidental     = 0x00000100    // Incidental sound, will be played thru the incidental manager
    };

    enum Type
    {
        kStartType,
        kSoundFX = kStartType,              // For now, 3D sounds are always marked as this
        kAmbience,
        kBackgroundMusic,
        kGUISound,
        kNPCVoices,
        kNumTypes
    };

    enum Refs
    {
        kRefSoftVolume = 0,
        kRefDataBuffer,     // plugins only
        kRefParentSceneObject,
        kRefSoftOcclusionRegion
    };

    enum 
    {
        kSoftRegion = 0
    };

    enum StreamType
    { 
        kNoStream, 
        kStreamFromRAM, 
        kStreamFromDisk, 
        kStreamCompressed 
    };

    class plFadeParams
    {
        friend class plSound;

        public:
            enum Type
            {
                kLinear,
                kLogarithmic,
                kExponential
            };

            float    fLengthInSecs;      // Time to take to fade
            float    fVolStart;          // Set one of these two for fade in/out,
            float    fVolEnd;            // the other becomes the current volume
            uint8_t       fType;
            hsBool      fStopWhenDone;      // Actually stop the sound once the fade is complete
            hsBool      fFadeSoftVol;       // Fade the soft volume instead of fCurrVolume

            plFadeParams() { fLengthInSecs = 0.f; fCurrTime = -1.f; fStopWhenDone = false; fFadeSoftVol = false; fVolStart = fVolEnd = 0.f; fType = kLinear; }

            plFadeParams( Type type, float len, float start, float end )
            {
                fLengthInSecs = len; fVolStart = start; fVolEnd = end; fType = type;
                fStopWhenDone = false;
                fFadeSoftVol = false;
            }

            void    Read( hsStream *s );
            void    Write( hsStream *s );

            float    InterpValue( void );

        protected:
            float    fCurrTime;          // -1 if we aren't active, else it's how far we're into the animation
    };

    virtual hsBool      LoadSound( hsBool is3D ) = 0;
    float            GetVirtualStartTime( void ) const { return (float)fVirtualStartTime; }

    virtual void        Play();
    void                SynchedPlay( unsigned bytes );
    void                SynchedPlay( float virtualStartTime );
    virtual void        Stop();
    virtual void        FastForwardPlay();
    virtual void        FastForwardToggle();
    virtual void        SetMin(const int m); // sets minimum falloff distance
    virtual void        SetMax(const int m); // sets maximum falloff distance
    virtual int         GetMin() const;
    virtual int         GetMax() const;
    virtual void        SetVolume(const float volume);
    virtual float       GetVolume(void) const { return fCurrVolume; }
    float            GetMaxVolume() { return fMaxVolume; }
    virtual hsBool      IsPlaying() { return fPlaying; }
    void                SetTime(double t);
    virtual double      GetTime( void ) { return 0.f; }
    virtual void        Activate(hsBool forcePlay = false);
    virtual void        DeActivate();
    virtual void        SetLength(double l) { fLength = l; }
    virtual void        SetMuted( hsBool muted );
    virtual hsBool      IsMuted( void ) { return fMuted; }
    void                Disable() { fDistAttenuation = 0; }
    virtual plSoundMsg* GetStatus(plSoundMsg* pMsg){return NULL;}
    virtual void        SetConeOrientation(float x, float y, float z);
    virtual void        SetOuterVolume( const int v ); // volume for the outer cone (if applicable)
    virtual void        SetConeAngles( int inner, int outer );
    virtual void        SetPosition(const hsPoint3 pos);
    virtual void        SetVelocity(const hsVector3 vel);
    virtual hsPoint3    GetPosition() const;
    virtual hsVector3   GetVelocity() const;

    virtual void        Update();
    
    plSoundBuffer *     GetDataBuffer( void ) const { return (plSoundBuffer *)fDataBufferKey->ObjectIsLoaded(); }
    float            QueryCurrVolume( void ) const;  // Returns the current volume, attenuated

    const char *        GetFileName( void ) const;
    virtual double      GetLength();

    void                SetProperty( Property prop, hsBool on ) { if( on ) fProperties |= prop; else fProperties &= ~prop; }
    hsBool              IsPropertySet( Property prop ) const { return ( fProperties & prop ) ? true : false; }

    virtual void        RefreshVolume( void );

    virtual void        SetStartPos(unsigned bytes) = 0;
    virtual unsigned    GetByteOffset(){return 0;}
    virtual float       GetActualTimeSec() = 0;

    virtual void        AddCallbacks(plSoundMsg* pMsg) = 0;
    virtual void        RemoveCallbacks(plSoundMsg* pMsg) = 0;

    virtual uint8_t       GetChannelSelect( void ) const { return 0; }    // Only defined on Win32Sound right now, should be here tho

    virtual void        Read(hsStream* s, hsResMgr* mgr);
    virtual void        Write(hsStream* s, hsResMgr* mgr);
    
    virtual void        SetFadeInEffect( plFadeParams::Type type, float length );
    virtual void        SetFadeOutEffect( plFadeParams::Type type, float length );
    virtual float    CalcSoftVolume( hsBool enable, float distToListenerSquared );
    virtual void        UpdateSoftVolume( hsBool enable, hsBool firstTime = false );

    virtual hsBool      MsgReceive( plMessage* pMsg );
    virtual hsBool      DirtySynchState( const char *sdlName = nil, uint32_t sendFlags = 0 ); // call when state has changed

    // Tests whether this sound is within range of the given position, not counting soft regions
    hsBool              IsWithinRange( const hsPoint3 &listenerPos, float *distSquared );

    // Type setting and getting, from the Types enum
    void                SetType( uint8_t type ) { fType = type; }
    uint8_t               GetType( void ) const { return fType; }

    // Priority stuff
    void                SetPriority( uint8_t pri ) { fPriority = pri; }
    uint8_t               GetPriority( void ) const { return fPriority; }

    // Visualization
    virtual plDrawableSpans*    CreateProxy(const hsMatrix44& l2w, hsGMaterial* mat, hsTArray<uint32_t>& idx, plDrawableSpans* addTo);

    // Forced loading/unloading (for when the audio system's LOD just doesn't cut it)
    virtual void        ForceLoad(  );
    virtual void        ForceUnload( void );

    // Note: ONLY THE AUDIOSYS SHOULD CALL THIS. If you're not the audioSys, get lost.
    static void         SetCurrDebugPlate( const plKey soundKey );

    void                RegisterOnAudioSys( void );
    void                UnregisterOnAudioSys( void );

    // Also only for the audio system
    float            GetVolumeRank( void );
    void                ForceUnregisterFromAudioSys( void );

    static void         SetLoadOnDemand( hsBool activate ) { fLoadOnDemandFlag = activate; }
    static void         SetLoadFromDiskOnDemand( hsBool activate ) { fLoadFromDiskOnDemand = activate; }

    const plEAXSourceSettings   &GetEAXSettings( void ) const { return fEAXSettings; }
    plEAXSourceSettings         &GetEAXSettings( void ) { return fEAXSettings; }
    virtual StreamType          GetStreamType() const { return kNoStream; }
    virtual void    FreeSoundData();


protected:
    hsBool      fPlaying;
    hsBool      fActive;
    double      fTime;
    int         fMaxFalloff;
    int         fMinFalloff;
    float    fCurrVolume;
    float    fDesiredVol;        // Equal to fCurrVolume except when we're fading or muted
    float    fFadedVolume;
    float    fMaxVolume;

    int         fOuterVol;
    int         fInnerCone;
    int         fOuterCone;
    double      fLength;
    
    int         fProperties;
    uint8_t       fType;
    uint8_t       fPriority;

    hsBool      fMuted, fFading, fRegisteredForTime, fPlayOnReactivate, fFreeData;
    hsBool      fNotHighEnoughPriority;     // Set whenever the audioSys calls UpdateSoftVolume() with enable=false,
                                            // thus indicating that we slipped off the top 16 most wanted list. 

    // Do these need to be synched values? They weren't before...
    hsVector3   fConeOrientation;
    hsPoint3    f3DPosition;
    hsVector3   f3DVelocity;
    hsBool      fPlayWhenLoaded;

    double      fSynchedStartTimeSec;
    
    // Just around for reference and sending messages upward (synched state)
    plSceneObject       *fOwningSceneObject;

    // EAX Settings storage here
    plEAXSourceSettings fEAXSettings;
    hsBool fQueued;

    plFadeParams    fFadeInParams, fFadeOutParams;
    plFadeParams    fCoolSoftVolumeTrickParams;
    plFadeParams    *fCurrFadeParams;

    plSoftVolume    *fSoftRegion;
    float        fSoftVolume;
    float        fDistAttenuation, fDistToListenerSquared;
    double          fVirtualStartTime;
    hsBool          fRegistered;
    static unsigned fIncidentalsPlaying;

    plSoftVolume    *fSoftOcclusionRegion;

    plSoundBuffer   *fDataBuffer;           // Not always around
    hsBool          fDataBufferLoaded;
    plKey           fDataBufferKey;     // Always around

    static plGraphPlate *fDebugPlate;
    static plSound      *fCurrDebugPlateSound;

    static hsBool       fLoadOnDemandFlag, fLoadFromDiskOnDemand;
    hsBool              fLoading;

    void            IUpdateDebugPlate( void );
    void            IPrintDbgMessage( const char *msg, hsBool isErr = false );

    virtual void    ISetActualVolume(const float v) = 0;
    virtual void    IActuallyStop( void );
    virtual hsBool  IActuallyPlaying( void ) = 0;
    virtual void    IActuallyPlay( void ) = 0;
    virtual void    IFreeBuffers( void ) = 0;

    //NOTE: if isIncidental is true the entire sound will be loaded. 
    virtual plSoundBuffer::ELoadReturnVal   IPreLoadBuffer( hsBool playWhenLoaded, hsBool isIncidental = false );   
    virtual void        ISetActualTime( double t ) = 0;
    
    virtual hsBool      IActuallyLoaded( void ) = 0;
    virtual void        IRefreshEAXSettings( hsBool force = false ) = 0;

    virtual float    IGetChannelVolume( void ) const;

    void    ISynchToStartTime( void );
    void    ISynchedPlay( double virtualStartTime );
    void    IStartFade( plFadeParams *params, float offsetIntoFade = 0.f );
    void    IStopFade( hsBool shuttingDown = false, hsBool SetVolEnd = true);
    
    hsBool  IWillBeAbleToPlay( void );

    void        ISetSoftRegion( plSoftVolume *region );
    float    IAttenuateActualVolume( float volume ) const;
    void        ISetSoftOcclusionRegion( plSoftVolume *region );

    // Override to make sure the buffer is available before the base class is called
    virtual void    IRefreshParams( void );

    virtual bool    ILoadDataBuffer( void );
    virtual void    IUnloadDataBuffer( void );

    //virtual void  ISetMinDistance( const int m ) = 0;
    //virtual void  ISetMaxDistance( const int m ) = 0;
    //virtual void  ISetOuterVolume( const int v ) = 0;
    //virtual void  ISetConeAngles( int inner, int outer ) = 0;
    //virtual void  ISetActualConeOrient( hsVector3 &vector ) = 0;
    //virtual void  ISetVelocity( const hsVector3 vel ) = 0;
    //virtual void  ISetPosition( const hsPoint3 pos ) = 0;

    virtual void    IRead( hsStream *s, hsResMgr *mgr );
    virtual void    IWrite( hsStream *s, hsResMgr *mgr );
};


//// plSoundVolumeApplicator /////////////////////////////////////////////////
//  Tiny helper for handling animated volumes

class plSoundVolumeApplicator : public plAGApplicator
{
public:
    plSoundVolumeApplicator() { }
    plSoundVolumeApplicator( uint32_t index ) { fIndex = index; }

    CLASSNAME_REGISTER( plSoundVolumeApplicator );
    GETINTERFACE_ANY( plSoundVolumeApplicator, plAGApplicator );

    virtual plAGApplicator *CloneWithChannel( plAGChannel *channel );
    virtual void            Write( hsStream *stream, hsResMgr *mgr );
    virtual void            Read( hsStream *s, hsResMgr *mgr );

protected:
    uint32_t      fIndex;
    virtual void IApply( const plAGModifier *mod, double time );
};

#endif //plWin32Sound_h
