// hardcoded sounds, details in server.h

enum
{    // beware: sound order is part of the game protocol
    S_JUMP = 0,
    S_SOFTLAND, S_HARDLAND,
    S_BULLETAIR1, S_BULLETAIR2, S_BULLETHIT, S_BULLETWATERHIT,
    S_KNIFE,
    S_PISTOL, S_RPISTOL,
    S_CARBINE, S_RCARBINE,
    S_SHOTGUN, S_RSHOTGUN,
    S_SUBGUN, S_RSUBGUN,
    S_SNIPER, S_RSNIPER,
    S_ASSAULT, S_RASSAULT,
    S_ITEMAMMO, S_ITEMHEALTH,
    S_ITEMARMOUR, S_ITEMAKIMBO,
    S_NOAMMO, S_AKIMBOOUT,
    S_PAIN1, S_PAIN2, S_PAIN3, S_PAIN4, S_PAIN5, S_PAIN6, // S_PAIN2..S_PAIN5 are hardcoded S_PAIN1+1..S_PAIN1+4
    S_DIE1, S_DIE2,
    S_FEXPLODE,
    S_SPLASH1, S_SPLASH2,
    S_FLAGDROP, S_FLAGPICKUP, S_FLAGRETURN, S_FLAGSCORE,
    S_GRENADEPULL, S_GRENADETHROW, S_GRENADEBOUNCE1, S_GRENADEBOUNCE2, S_RAKIMBO,
    S_GUNCHANGE,
    S_HITSOUND,
    S_GIB, S_HEADSHOT,
    S_CALLVOTE, S_VOTEPASS, S_VOTEFAIL,
    S_FOOTSTEPS, S_FOOTSTEPSCROUCH, S_WATERFOOTSTEPS, S_WATERFOOTSTEPSCROUCH, // hardcoded S_FOOTSTEPS+0..S_FOOTSTEPS+3
    S_CROUCH, S_UNCROUCH,
    S_MENUSELECT, S_MENUENTER,
    S_UNDERWATER,
    S_TINNITUS,
// begin: voicecoms
    S_AFFIRMATIVE,
    S_ALLRIGHTSIR,
    S_COMEONMOVE,
    S_COMINGINWITHTHEFLAG,
    S_COVERME,
    S_DEFENDTHEFLAG,
    S_ENEMYDOWN,
    S_GOGETEMBOYS,
    S_GOODJOBTEAM,
    S_IGOTONE,
    S_IMADECONTACT,
    S_IMATTACKING,
    S_IMONDEFENSE,
    S_IMONYOURTEAMMAN,
    S_NEGATIVE,
    S_NOCANDO,
    S_RECOVERTHEFLAG,
    S_SORRY,
    S_SPREADOUT,
    S_STAYHERE,
    S_STAYTOGETHER,
    S_THERESNOWAYSIR,
    S_WEDIDIT,
    S_YES,
    S_ONTHEMOVE1,
    S_ONTHEMOVE2,
    S_GOTURBACK,
    S_GOTUCOVERED,
    S_INPOSITION1,
    S_INPOSITION2,
    S_REPORTIN,
    S_NICESHOT, // begin: public
    S_THANKS1,
    S_THANKS2,
    S_AWESOME1,
    S_AWESOME2,
// end: voicecoms
    S_ITEMHELMET, // begin: Sounds for AssaultCube v1.1.0.4 onwards
    S_HEARTBEAT,
    S_KTFSCORE,
    S_CAMERA,
    S_NULL
};

enum { SC_PAIN = 0, SC_OWNPAIN, SC_WEAPON, SC_PICKUP, SC_MOVEMENT, SC_BULLET, SC_OTHER, SC_VOICECOM, SC_TEAM, SC_PUBLIC, SC_FFA, SC_FLAGONLY, SC_NUM };   // sound categories

#define gamesound_isvalid(x) ((x) >= 0 && (x) < S_NULL)                           // bounds check for soundcfg[]
#define gamesound_hasflag(x, mask) (gamesound_isvalid(x) && (soundcfg[x].flags & mask) != 0)
#define gamesound_isvoicecom(x)       gamesound_hasflag(x, 1 << SC_VOICECOM)      // all voicecoms
#define gamesound_ispublicvoicecom(x) gamesound_hasflag(x, 1 << SC_PUBLIC)        // voicecoms always sent to all players
#define gamesound_ispublicwhenffa(x) gamesound_hasflag(x, 1 << SC_FFA)            // voicecoms sent to all only in FFA modes 
#define gamesound_isflagvoicecom(x) gamesound_hasflag(x, 1 << SC_FLAGONLY)        // voicecoms pertaining to flag tactics 


struct soundcfgitem {
    const char *name, *desc;
    uchar vol, loop, audibleradius, key;
    int flags;
};

extern soundcfgitem soundcfg[S_NULL];

// hardcoded music

enum
{
    M_FLAGGRAB = 0,
    M_LASTMINUTE1,
    M_LASTMINUTE2
};

// sound priorities

enum
{
    SP_LOW = 0,
    SP_NORMAL,
    SP_HIGH,
    SP_HIGHEST
};

#ifndef STANDALONE

// owner of an OpenAL source, used as callback interface

class sourceowner
{
public:
    virtual ~sourceowner() {}
    virtual void onsourcereassign(class source *s) = 0;
};

// represents an OpenAL source, an audio emitter in the 3D world

class source
{
public:
    ALuint id;

    sourceowner *owner;
    bool locked, valid;
    int priority;

    source();
    ~source();

    void lock();
    void unlock();
    void reset();
    void init(sourceowner *o);
    void onreassign();

    bool generate();
    bool delete_();
    bool buffer(ALuint buf_id);
    bool looping(bool enable);
    bool queuebuffers(ALsizei n, const ALuint *buffer_ids);
    bool unqueueallbuffers();
    bool gain(float g);
    bool pitch(float p);
    bool position(const vec &pos);
    bool position(float x, float y, float z);
    bool velocity(float x, float y, float z);
    vec position();
    bool sourcerelative(bool enable);
    int state();
    bool secoffset(float secs);
    float secoffset();
    bool playing();
    bool play();
    bool stop();
    bool rewind();

    void printposition();
};


// represents an OpenAL sound buffer

class sbuffer
{
public:
    ALuint id;
    const char *name;

    sbuffer();
    ~sbuffer();

    bool load(bool trydl = false);
    void unload();
};

// buffer storage

class bufferhashtable : public hashtable<const char *, sbuffer>
{
public:
    virtual ~bufferhashtable();
    virtual sbuffer *find(const char *name);
};

// manages available sources, abstracts audio channels
// is a singleton

class sourcescheduler
{
    int numsoundchannels;
    static sourcescheduler *inst;
    vector<source *> sources;

public:
    sourcescheduler();
    static sourcescheduler &instance(); // singleton access

    void init(int numsoundchannels);
    void reset();
    source *newsource(int priority, const vec &o);
    void releasesource(source *src);
};

// audio streaming

class oggstream : sourceowner
{
public:
    string name;
    bool valid;

    // file stream
    OggVorbis_File oggfile;
    bool isopen;
    vorbis_info *info;
    double totalseconds;
    static const int BUFSIZE = 1024 * 512; // 512kb buffer

    // OpenAL resources
    ALuint bufferids[2];
    source *src;
    ALenum format;

    // settings
    float volume, gain;
    int startmillis, endmillis, startfademillis, endfademillis;
    bool looping;

    oggstream();
    ~oggstream();

    void reset();
    bool open(const char *f);
    void onsourcereassign(source *s);
    bool stream(ALuint bufid);
    bool update();
    bool playing();
    void updategain();
    void setgain(float g);
    void setvolume(float v);
    void fadein(int startmillis, int fademillis);
    void fadeout(int endmillis, int fademillis);
    bool playback(bool looping = false);
    void seek(double offset);
};


struct soundconfig
{
    sbuffer *buf; // sound data
    int vol; // volume
    int uses, maxuses; // track uses
    bool loop;
    bool muted;
    int audibleradius;

    enum distancemodel
    {
        DM_DEFAULT = 0, // use openal distance model
        DM_LINEAR // custom linear model (used in conjunction with audibleradius)
    };

    distancemodel model;

    soundconfig(sbuffer *b, int vol, int maxuses, bool loop, int audibleradius);
    void onattach();
    void ondetach();
};


class worldobjreference
{
public:

    enum worldobjtype { WR_CAMERA, WR_PHYSENT, WR_ENTITY, WR_STATICPOS };
    int type;

    worldobjreference(int t) : type(t) {}
    virtual ~worldobjreference() {}
    virtual worldobjreference *clone() const = 0;
    virtual const vec &currentposition() const = 0;
    virtual bool nodistance() = 0;
    virtual bool operator==(const worldobjreference &other) = 0;
    virtual bool operator!=(const worldobjreference &other) { return !(*this==other); }
    virtual void attach() {}
    virtual void detach() {}
};


class camerareference : public worldobjreference
{
public:
    camerareference();

    worldobjreference *clone() const;
    const vec &currentposition() const;
    bool nodistance();
    bool operator==(const worldobjreference &other);
};


class physentreference : public worldobjreference
{
public:
    class physent *phys;

    physentreference(physent *ref);
    worldobjreference *clone() const;
    const vec &currentposition() const;
    bool nodistance();
    bool operator==(const worldobjreference &other);
};


struct entityreference : public worldobjreference
{
public:
    struct entity *ent;

    entityreference(entity *ref);
    worldobjreference *clone() const;
    const vec &currentposition() const;
    bool nodistance();
    bool operator==(const worldobjreference &other);
};


struct staticreference : public worldobjreference
{
public:
    vec pos;

    staticreference(const vec &ref);
    worldobjreference *clone() const;
    const vec &currentposition() const;
    bool nodistance();
    bool operator==(const worldobjreference &other);
};


class location : sourceowner
{
public:
    soundconfig *cfg;
    source *src;
    worldobjreference *ref;

    bool stale;
    int playmillis;

    location(int sound, const worldobjreference &r, int priority = SP_NORMAL);
    ~location();
    void attachworldobjreference(const worldobjreference &r);
    void evaluateworldobjref();
    void onsourcereassign(source *s);
    void updatepos();
    void update();
    void play(bool loop = false);
    void pitch(float p);
    bool setvolume(float v);
    void offset(float secs);
    float offset();
    void drop();
};


struct locvector : vector<location *>
{
    virtual ~locvector() {}

    location *find(int sound, worldobjreference *ref/* = NULL*/, const vector<soundconfig> &soundcollection /* = gamesounds*/);
    void delete_(int i);
    void replaceworldobjreference(const worldobjreference &oldr, const worldobjreference &newr);
    void updatelocations();
    void forcepitch(float pitch);
    void deleteworldobjsounds();
};


// audio interface to the engine

class audiomanager
{
    bool nosound;
    float currentpitch;
    vector<char *> musics;
    ALCdevice *device;
    ALCcontext *context;

    bufferhashtable bufferpool;
    oggstream *gamemusic;

public:

    locvector locations;

    audiomanager();

    // init & setup
    void initsound();
    bool preloadmapsound(entity &e, bool trydl = false);
    bool preloadmapsounds(bool trydl = false);
    void applymapsoundchanges();

    // configuration
    void setchannels(int num);
    void setlistenervol(int vol);
    int addsound(const char *name, int vol, int maxuses, bool loop, vector<soundconfig> &sounds, bool load, int audibleradius);
    void registermusic(char *name);
    void mutesound(int n, int off);
    void unmuteallsounds();
    int soundmuted(int n);

    // cleanup
    void soundcleanup();
    void clearworldsounds(bool fullclean = true);
    void mapsoundreset();
    void stopsound();

    // music handling
    void music(char *name, int millis, char *cmd);
    void musicpreload(int id);
    void musicsuggest(int id, int millis, bool rndofs);
    void musicfadeout(int id);
    void setmusicvol(int musicvol);

    // sound handling
    location *_playsound(int n, const worldobjreference &r, int priority, float offset = 0.0f, bool loop = false);
    void playsound(int n, int priority = SP_NORMAL);
    void playsound(int n, class physent *p, int priority = SP_NORMAL);
    void playsound(int n, struct entity *e, int priority = SP_NORMAL);
    void playsound(int n, const vec *v, int priority = SP_NORMAL);
    void playsoundname(char *s, const vec *loc, int vol);
    void playsoundc(int n, physent *p = NULL, int priority = SP_NORMAL);
    void sound(int n);
    int findsound(const char *name, int vol, vector<soundconfig> &sounds);
    void detachsounds(class playerent *owner);

    // update
    void updateplayerfootsteps(class playerent *p);
    location *updateloopsound(int sound, bool active, float vol = 1.0f);
    void updateaudio();

    // misc
    void writesoundconfig(stream *f);
};

void alclearerr();
bool alerr(bool msg = true, int line = 0, const char *s = NULL, ...) PRINTFARGS(3, 4);
#define ALERR alerr(true, __LINE__)
#define ALERRF(fmt, ...) alerr(true, __LINE__, fmt, __VA_ARGS__)

extern vector<soundconfig> gamesounds, mapsounds;
extern ov_callbacks oggcallbacks;
extern int soundvol;
extern int audiodebug;
extern int voicecomsounds;
extern audiomanager audiomgr;
extern void registerdefaultsounds();
#endif //#ifndef STANDALONE
