//-----------------------------------------------------------------------------
//
// Copyright 1993-1996 id Software
// Copyright 1999-2016 Randy Heit
// Copyright 2002-2016 Christoph Oelckers
//
// 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/
//
//-----------------------------------------------------------------------------
//
// DESCRIPTION:
//		The not so system specific sound interface.
//
//-----------------------------------------------------------------------------


#ifndef __S_SOUND__
#define __S_SOUND__

#include "doomtype.h"
#include "i_soundinternal.h"

class AActor;
class FScanner;
class FSerializer;

//
// SoundFX struct.
//
struct sfxinfo_t
{
	// Next field is for use by the system sound interface.
	// A non-null data means the sound has been loaded.
	SoundHandle	data;
    // Also for the sound interface. Used for 3D positional
    // sounds, may be the same as data.
    SoundHandle data3d;

	FString		name;					// [RH] Sound name defined in SNDINFO
	int 		lumpnum;				// lump number of sfx

	unsigned int next, index;			// [RH] For hashing
	float		Volume;

	uint8_t		PitchMask;
	int16_t		NearLimit;				// 0 means unlimited
	float		LimitRange;				// Range for sound limiting (squared for faster computations)

	unsigned		bRandomHeader:1;
	unsigned		bPlayerReserve:1;
	unsigned		bLoadRAW:1;
	unsigned		bPlayerCompat:1;
	unsigned		b16bit:1;
	unsigned		bUsed:1;
	unsigned		bSingular:1;
	unsigned		bTentative:1;
	unsigned		bPlayerSilent:1;		// This player sound is intentionally silent.

	int		RawRate;				// Sample rate to use when bLoadRAW is true

	int			LoopStart;				// -1 means no specific loop defined

	unsigned int link;
	enum { NO_LINK = 0xffffffff };

	FRolloffInfo	Rolloff;
	float		Attenuation;			// Multiplies the attenuation passed to S_Sound.

	void		MarkUsed();				// Marks this sound as used.
};

// Rolloff types
enum
{
	ROLLOFF_Doom,		// Linear rolloff with a logarithmic volume scale
	ROLLOFF_Linear,		// Linear rolloff with a linear volume scale
	ROLLOFF_Log,		// Logarithmic rolloff (standard hardware type)
	ROLLOFF_Custom		// Lookup volume from SNDCURVE
};

int S_FindSound (const char *logicalname);

// the complete set of sound effects
extern TArray<sfxinfo_t> S_sfx;

// An index into the S_sfx[] array.
class FSoundID
{
public:
	FSoundID() = default;

	FSoundID(int id)
	{
		ID = id;
	}
	FSoundID(const char *name)
	{
		ID = S_FindSound(name);
	}
	FSoundID(const FString &name)
	{
		ID = S_FindSound(name.GetChars());
	}
	FSoundID(const FSoundID &other) = default;
	FSoundID &operator=(const FSoundID &other) = default;
	FSoundID &operator=(const char *name)
	{
		ID = S_FindSound(name);
		return *this;
	}
	FSoundID &operator=(const FString &name)
	{
		ID = S_FindSound(name.GetChars());
		return *this;
	}
	bool operator !=(FSoundID other) const
	{
		return ID != other.ID;
	}
	bool operator !=(int other) const
	{
		return ID != other;
	}
	operator int() const
	{
		return ID;
	}
	operator FString() const
	{
		return ID ? S_sfx[ID].name : "";
	}
	operator const char *() const
	{
		return ID ? S_sfx[ID].name.GetChars() : NULL;
	}
	void MarkUsed() const
	{
		S_sfx[ID].MarkUsed();
	}
private:
	int ID;
protected:
	enum EDummy { NoInit };
	FSoundID(EDummy) {}
};

class FSoundIDNoInit : public FSoundID
{
public:
	FSoundIDNoInit() : FSoundID(NoInit) {}
	using FSoundID::operator=;
};

extern FRolloffInfo S_Rolloff;
extern TArray<uint8_t> S_SoundCurve;


// Information about one playing sound.
struct sector_t;
struct FPolyObj;
struct FSoundChan : public FISoundChannel
{
	FSoundChan	*NextChan;	// Next channel in this list.
	FSoundChan **PrevChan;	// Previous channel in this list.
	FSoundID	SoundID;	// Sound ID of playing sound.
	FSoundID	OrgID;		// Sound ID of sound used to start this channel.
	float		Volume;
	int16_t		Pitch;		// Pitch variation.
	uint8_t		EntChannel;	// Actor's sound channel.
	int8_t		Priority;
	int16_t		NearLimit;
	uint8_t		SourceType;
	float		LimitRange;
	union
	{
		AActor			*Actor;		// Used for position and velocity.
		const sector_t	*Sector;	// Sector for area sounds.
		const FPolyObj	*Poly;		// Polyobject sound source.
		float			 Point[3];	// Sound is not attached to any source.
	};
};

extern FSoundChan *Channels;

void S_ReturnChannel(FSoundChan *chan);
void S_EvictAllChannels();

void S_StopChannel(FSoundChan *chan);
void S_LinkChannel(FSoundChan *chan, FSoundChan **head);
void S_UnlinkChannel(FSoundChan *chan);

// Initializes sound stuff, including volume
// Sets channels, SFX and music volume,
//	allocates channel buffer, sets S_sfx lookup.
//
void S_Init ();
void S_InitData ();
void S_Shutdown ();

// Per level startup code.
// Kills playing sounds at start of level and starts new music.
//
void S_Start ();

// Called after a level is loaded. Ensures that most sounds are loaded.
void S_PrecacheLevel ();

// Loads a sound, including any random sounds it might reference.
void S_CacheSound (sfxinfo_t *sfx);

// Start sound for thing at <ent>
void S_Sound (int channel, FSoundID sfxid, float volume, float attenuation);
void S_Sound (AActor *ent, int channel, FSoundID sfxid, float volume, float attenuation);
void S_SoundMinMaxDist (AActor *ent, int channel, FSoundID sfxid, float volume, float mindist, float maxdist);
void S_Sound (const FPolyObj *poly, int channel, FSoundID sfxid, float volume, float attenuation);
void S_Sound (const sector_t *sec, int channel, FSoundID sfxid, float volume, float attenuation);
void S_Sound(const DVector3 &pos, int channel, FSoundID sfxid, float volume, float attenuation);

void S_SoundPitch (int channel, FSoundID sfxid, float volume, float attenuation, float pitch);
void S_SoundPitchActor (AActor *ent, int channel, FSoundID sfxid, float volume, float attenuation, float pitch);

// [Nash] Used by ACS and DECORATE
void S_PlaySound(AActor *a, int chan, FSoundID sid, float vol, float atten, bool local);
void S_PlaySoundPitch(AActor *a, int chan, FSoundID sid, float vol, float atten, bool local, float pitch);

// sound channels
// channel 0 never willingly overrides
// other channels (1-7) always override a playing sound on that channel
//
// CHAN_AUTO searches down from channel 7 until it finds a channel not in use
// CHAN_WEAPON is for weapons
// CHAN_VOICE is for oof, sight, or other voice sounds
// CHAN_ITEM is for small things and item pickup
// CHAN_BODY is for generic body sounds
// CHAN_PICKUP can optionally be set as a local sound only for "compatibility"

#define CHAN_AUTO				0
#define CHAN_WEAPON				1
#define CHAN_VOICE				2
#define CHAN_ITEM				3
#define CHAN_BODY				4

// Channel alias for sector sounds. These define how listener height is
// used when calculating 3D sound volume.
#define CHAN_FLOOR				1	// Sound comes from the floor.
#define CHAN_CEILING			2	// Sound comes from the ceiling.
#define CHAN_FULLHEIGHT			3	// Sound comes entire height of the sector.
#define CHAN_INTERIOR			4	// Sound comes height between floor and ceiling.

// modifier flags
#define CHAN_LISTENERZ			8
#define CHAN_MAYBE_LOCAL		16
#define CHAN_UI					32	// Do not record sound in savegames.
#define CHAN_NOPAUSE			64	// Do not pause this sound in menus.
#define CHAN_AREA				128	// Sound plays from all around. Only valid with sector sounds.
#define CHAN_LOOP				256

#define CHAN_PICKUP				(CHAN_ITEM|CHAN_MAYBE_LOCAL)

#define CHAN_IS3D				1	// internal: Sound is 3D.
#define CHAN_EVICTED			2	// internal: Sound was evicted.
#define CHAN_FORGETTABLE		4	// internal: Forget channel data when sound stops.
#define CHAN_JUSTSTARTED		512	// internal: Sound has not been updated yet.
#define CHAN_ABSTIME			1024// internal: Start time is absolute and does not depend on current time.
#define CHAN_VIRTUAL			2048// internal: Channel is currently virtual
#define CHAN_NOSTOP				4096// only for A_PlaySound. Does not start if channel is playing something.

// sound attenuation values
#define ATTN_NONE				0.f	// full volume the entire level
#define ATTN_NORM				1.f
#define ATTN_IDLE				1.001f
#define ATTN_STATIC				3.f	// diminish very rapidly with distance

struct FSoundLoadBuffer;
int S_PickReplacement (int refid);
void S_CacheRandomSound (sfxinfo_t *sfx);

// Checks if a copy of this sound is already playing.
bool S_CheckSingular (int sound_id);

// Stops a sound emanating from one of an emitter's channels.
void S_StopSound (AActor *ent, int channel);
void S_StopSound (const sector_t *sec, int channel);
void S_StopSound (const FPolyObj *poly, int channel);

// Stops an origin-less sound from playing from this channel.
void S_StopSound (int channel);

// Stop sound for all channels
void S_StopAllChannels (void);

// Is the sound playing on one of the emitter's channels?
bool S_GetSoundPlayingInfo (const AActor *actor, int sound_id);
bool S_GetSoundPlayingInfo (const sector_t *sector, int sound_id);
bool S_GetSoundPlayingInfo (const FPolyObj *poly, int sound_id);

bool S_IsActorPlayingSomething (AActor *actor, int channel, int sound_id);

// Change a playing sound's volume
void S_ChangeSoundVolume(AActor *actor, int channel, double volume);

// Change a playing sound's pitch
void S_ChangeSoundPitch(AActor *actor, int channel, double pitch);
void S_SetPitch(FSoundChan *chan, float dpitch);

// Moves all sounds from one mobj to another
void S_RelinkSound (AActor *from, AActor *to);

// Stores/retrieves playing channel information in an archive.
void S_SerializeSounds(FSerializer &arc);

// Stop and resume music, during game PAUSE.
void S_PauseSound (bool notmusic, bool notsfx);
void S_ResumeSound (bool notsfx);
void S_SetSoundPaused (int state);

//
// Updates music & sounds
//
void S_UpdateSounds (AActor *listener);

void S_RestoreEvictedChannels();

// [RH] S_sfx "maintenance" routines
void S_ParseSndInfo (bool redefine);
void S_ParseReverbDef ();
void S_UnloadReverbDef ();

void S_HashSounds ();
int S_FindSoundNoHash (const char *logicalname);
bool S_AreSoundsEquivalent (AActor *actor, int id1, int id2);
bool S_AreSoundsEquivalent (AActor *actor, const char *name1, const char *name2);
int S_LookupPlayerSound (const char *playerclass, int gender, const char *logicalname);
int S_LookupPlayerSound (const char *playerclass, int gender, FSoundID refid);
int S_FindSkinnedSound (AActor *actor, FSoundID refid);
int S_FindSkinnedSoundEx (AActor *actor, const char *logicalname, const char *extendedname);
int S_FindSoundByLump (int lump);
int S_AddSound (const char *logicalname, const char *lumpname, FScanner *sc=NULL);	// Add sound by lumpname
int S_AddSoundLump (const char *logicalname, int lump);	// Add sound by lump index
int S_AddPlayerSound (const char *playerclass, const int gender, int refid, const char *lumpname);
int S_AddPlayerSound (const char *playerclass, const int gender, int refid, int lumpnum, bool fromskin=false);
int S_AddPlayerSoundExisting (const char *playerclass, const int gender, int refid, int aliasto, bool fromskin=false);
void S_MarkPlayerSounds (AActor *player);
void S_ShrinkPlayerSoundLists ();
void S_UnloadSound (sfxinfo_t *sfx);
sfxinfo_t *S_LoadSound(sfxinfo_t *sfx, FSoundLoadBuffer *pBuffer = nullptr);
unsigned int S_GetMSLength(FSoundID sound);
void A_PlaySound(AActor *self, int soundid, int channel, double volume, int looping, double attenuation, int local, double pitch);

// [RH] Prints sound debug info to the screen.
//		Modelled after Hexen's noise cheat.
void S_NoiseDebug ();

extern ReverbContainer *Environments;
extern ReverbContainer *DefaultEnvironments[26];

void S_SetEnvironment (const ReverbContainer *settings);
ReverbContainer *S_FindEnvironment (const char *name);
ReverbContainer *S_FindEnvironment (int id);
void S_AddEnvironment (ReverbContainer *settings);
void S_UnloadAllSounds();
void S_SoundReset();
	
#endif