/*
MixBox v0.2.0
by Jonathan Dearborn

MixBox is a wrapper class for SDL_mixer that also holds the sound and music data for you.
Some other features are added as well:
  Simple positional sound by coordinates (2d or 3d)

The library exists as four classes:
MixBox - Controls and manages sounds and music.
SoundID - Represents a sound chunk and has methods to deal with that sound.
MusicID - Represents a music stream and has methods to deal with that music.
ChannelID - Represents a mixing channel and has methods to deal with sounds on that channel.


Unwrapped functions that I might use in the future:

Mix_FadingMusic
Mix_HookMusicFinished(musicDone);
Mix_LoadWAV_RW
Mix_PlayChannelTimed
Mix_FadeInChannel
Mix_FadeInChannelTimed
Mix_ExpireChannel
Mix_FadeOutChannel
Mix_FadingChannel
Mix_GetChunk


Copyright Jonathan Dearborn 2009

Boost Software License - Version 1.0 - August 17th, 2003

Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:

The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.


*/

#ifndef _MIXBOX_H__
#define _MIXBOX_H__

//#define MIXBOX_NO_SDL

#ifndef MIXBOX_NO_SDL
#include "SDL.h"
#endif

#include "SDL_mixer.h"
#include <vector>

namespace MB
{

/*
Base class for SoundID, MusicID, and ChannelID.
*/
class MixID
{
    protected:
    MixID(int id)
        : id(id)
    {}
    virtual ~MixID() = 0;
    
    int id;
    public:
    int getID() const
    {
        return id;
    }
    virtual bool isSound() const
    {
        return false;
    }
    virtual bool isMusic() const
    {
        return false;
    }
    virtual bool isChannel() const
    {
        return false;
    }

    virtual bool isGood() const
    {
        return (id >= 0);
    }
    virtual bool isBad() const
    {
        return (id < 0);
    }
};


// Defined below class MixBox for the sake of the inlined functions
class SoundID;
class MusicID;
class ChannelID;

/*
The main class that does most of the work.
*/
class MixBox
{
	// This breaks encapsulation, but it seems necessary... (see SoundID::setVolume())
	friend class SoundID;
	friend class MusicID;
	friend class ChannelID;
	
	private:
	    bool mixerIsOpen;
		unsigned int audio_rate;
		Uint16 audio_format;
		unsigned int audio_channels;  // Channels for mono, stereo, etc.
		unsigned int audio_buffers;

		unsigned int numChannels;  // Channels for mixing
		bool stereoSwap;

		MixBox()
			: mixerIsOpen(false)
			, audio_rate(22050)
			, audio_format(AUDIO_S16SYS)
			, audio_channels(2)
			, audio_buffers(4096)
			, numChannels(0)
			, stereoSwap(false)
		{}
		
		static MixBox Instance;

		std::vector<Mix_Chunk*> sounds;
		std::vector<Mix_Music*> musics;

		/*
		Nested helper class.  This class is used to reset effects on channels selectively.
		*/
		class ChannelData
		{
			public:
			static const Uint8 EFFECT_VOLUME = 1;
			static const Uint8 EFFECT_PANNING = 2;
			static const Uint8 EFFECT_POSITION = 4;
			static const Uint8 EFFECT_DISTANCE = 8;
			static const Uint8 EFFECT_5 = 16;
			static const Uint8 EFFECT_6 = 32;
			static const Uint8 EFFECT_7 = 64;
			static const Uint8 EFFECT_8 = 128;
			
			Uint8 oldVolume;  // The volume previous to any volume-changing effect (playVolume2D, etc.).
			Uint8 effects;  // Keeps track of which effects need to be cleared before the channel is reused.
			unsigned int channel;
			
			ChannelData(unsigned int channel)
				: oldVolume(128)
				, effects(0)
				, channel(channel)
			{}
			
			bool hasEffect(Uint8 flags)
			{
				return (effects & flags) == flags;
			}
			void addEffect(Uint8 flags)
			{
				effects |= flags;
			}
			
			void removeEffect(Uint8 flags)
			{
				if((flags & EFFECT_VOLUME) && (effects & EFFECT_VOLUME))
				{
					effects ^= EFFECT_VOLUME;
					Mix_Volume(channel, oldVolume);
				}
				if((flags & EFFECT_PANNING) && (effects & EFFECT_PANNING))
				{
					effects ^= EFFECT_PANNING;
					Mix_SetPanning(channel, 255, 255);
				}
				if((flags & EFFECT_POSITION) && (effects & EFFECT_POSITION))
				{
					effects ^= EFFECT_POSITION;
					Mix_SetPosition(channel, 0, 0);
				}
				if((flags & EFFECT_DISTANCE) && (effects & EFFECT_DISTANCE))
				{
					effects ^= EFFECT_DISTANCE;
					Mix_SetDistance(channel, 0);
				}
			}
			
			void removeStandardEffects()
			{
				if(effects & EFFECT_VOLUME)
				{
					effects ^= EFFECT_VOLUME;
					Mix_Volume(channel, oldVolume);
				}
				if(effects & EFFECT_PANNING)
				{
					effects ^= EFFECT_PANNING;
					Mix_SetPanning(channel, 255, 255);
				}
				if(effects & EFFECT_POSITION)
				{
					effects ^= EFFECT_POSITION;
					Mix_SetPosition(channel, 0, 0);
				}
				if(effects & EFFECT_DISTANCE)
				{
					effects ^= EFFECT_DISTANCE;
					Mix_SetDistance(channel, 0);
				}
			}
		};

		std::vector<ChannelData> channelData;
		
		/*
		The underlying functions that play sound that has volume and/or panning adjustment for distance and direction.
		*/
		ChannelID playDistance(const SoundID& id, int channel, float distance, float maxRange, int loops = 0);
		ChannelID playPanDistance(const SoundID& id, int channel, float distance, float maxRange, float direction, int loops = 0);
		ChannelID playPan2dDistance(const SoundID& id, int channel, float distance, float maxRange, float direction, int loops = 0);

	public:
		// The only way to get the MixBox (a singleton)
		static MixBox& instance()
		{
			return Instance;
		}
		
		// How necessary is this for a static object?
		/*~MixBox()
		{
			if(mixerIsOpen)
				closeMixer();
			freeSounds();
			freeMusic();
		}*/
		
		
		/*
		Sets the audio format.  The mixer must be reopened for this to take effect.
		MIX_DEFAULT_FORMAT, AUDIO_U8, AUDIO_S8, AUDIO_U16LSB, AUDIO_S16LSB, AUDIO_U16MSB, AUDIO_S16MSB, AUDIO_U16, AUDIO_S16, AUDIO_U16SYS, AUDIO_S16SYS
        MIX_DEFAULT_FORMAT is the same as AUDIO_S16SYS.
		*/
		void setAudioFormat(Uint16 format);
		
		/*
		Returns:
            Uint16 - The audio format.
		*/
		Uint16 getAudioFormat();
		
		/*
		Sets the audio playback rate in bytes per second.  The mixer must be reopened for this to take effect.
		A common rate for games is 22050.  CD quality is 44100.
		*/
		void setAudioRate(unsigned int rate);
		
		/*
		Returns:
            unsigned int - The audio playback rate in bytes per second.
		*/
		unsigned int getAudioRate();
		
		/*
		Sets the number of output (not mixing) channels.  The mixer must be reopened for this to take effect.
		1 for mono, 2 for stereo, etc.
		*/
		void setNumOutputs(unsigned int num);
		
		/*
		Returns:
            unsigned int - The number of output (not mixing) channels.
		*/
		unsigned int getNumOutputs();
		
		/*
		Sets the size of the audio buffer in bytes.  The mixer must be reopened for this to take effect.
		*/
		void setBufferSize(unsigned int size);
		
		/*
		Returns:
            unsigned int - The size of the audio buffer in bytes.
		*/
		unsigned int getBufferSize();
		
        /*
        Initializes SDL's audio subsystem, opens SDL_mixer, and gets the number of available mixing channels.
        Side Effects:
            Reports error messages to stderr.
        Returns:
            true on success
            false on failure
        */
		bool openMixer();
		
        /*
        Closes SDL_mixer and zeros the number of available mixing channels.
        */
		void closeMixer();
		
        /*
        Switches the stereo output's left and right signals.
        Returns:
            true on success
            false on failure
        */
		bool swapStereo();
		
        /*
        Returns:
            true if the stereo output's left and right signals have been switched.
            false if the stereo output's left and right signals have not been switched.
        */
		bool isStereoSwapped() const;
        
        /*
        Loads sound data from a file.
        Returns:
            SoundID of the new sound.  If this SoundID's getID() returns -1, then there was an error.
        */
		SoundID loadSound(const char* filename);

		/*
        Opens a music file for playing.
        Returns:
            MusicID of the new music.  If this MusicID's getID() returns -1, then there was an error.
        */
		MusicID loadMusic(const char* filename);
        
        /*
        Deletes the memory of each held sound and clears MixBox's sound container.
        */
		void freeSounds();
		
        /*
        Deletes the memory of the given sound.
        */
		void free(const SoundID& id);
		
        /*
        Deletes the memory of each held music and clears MixBox's music container.
        */
		void freeMusic();
		
        /*
        Deletes the memory of the given music.
        */
		void free(const MusicID& id);
		
        /*
        Returns:
            SoundID - The SoundID that represents the given index into the private container of sounds.
        */
		SoundID getSound(unsigned int index) const;
		
        /*
        Returns:
            MusicID - The MusicID that represents the given index into the private container of music.
        */
		MusicID getMusic(unsigned int index) const;
		
        /*
        Returns:
            ChannelID - The ChannelID that represents the given channel index.
        */
		ChannelID getChannel(unsigned int index) const;
		
        /*
        Returns:
            unsigned int - The number of sounds in the private sound container.
        */
		unsigned int getNumSounds() const;
		
        /*
        Returns:
            unsigned int - The number of music samples in the private music container.
        */
		unsigned int getNumMusic() const;
		
        /*
        Returns:
            The number of available mixing channels.
        */
		unsigned int getNumChannels() const;
				
        /*
        Sets the number of available mixing channels.
        Returns:
            The number of available mixing channels.  A possible success condition is
            whether the return value equals the input value.
        */
        unsigned int setNumChannels(unsigned int numChannels);

        /*
        Plays the given sound on an available channel.
        Returns:
            ChannelID of the channel that the sound is played on.  If this ChannelID's getID() returns -1, then there was an error.
        */
		ChannelID play(const SoundID& id, int loops = 0);

        /*
        Plays the given sound on the given channel.
        */
		void play(const SoundID& sound, const ChannelID& channel, int loops = 0);

        /*
        Stops the current sounds playing on each channel.
        */
		void stop();
		
        /*
        Stops the current sound playing on the given channel.
        */
		void stop(const ChannelID& id);
		
        /*
        Pauses the given channel.
        */
        void pause(const ChannelID& id);
		
        /*
        Returns:
            true if the given channel is paused.
            false if the given channel is not paused.
        */
        bool isPaused(const ChannelID& id) const;

        /*
        Resumes the given channel when paused.
        */
        void resume(const ChannelID& id);

        /*
        Plays music.
        */
		void play(const MusicID& id, int loops = -1);
		
        /*
        Plays the given sound on an available channel with the volume controlled by the distance from the origin.
        Returns:
            ChannelID of the channel that the sound is played on.  If this ChannelID's getID() returns -1, then there was an error.
        */
		ChannelID playVolume2D(const SoundID& id, float x, float y, float maxRange, int loops = 0);
		
        /*
        Plays the given sound on an available channel with the volume controlled by the distance from the origin.
        Returns:
            ChannelID of the channel that the sound is played on.  If this ChannelID's getID() returns -1, then there was an error.
        */
		ChannelID playVolume3D(const SoundID& id, float x, float y, float z, float maxRange, int loops = 0);
		
        /*
        Plays the given sound on an available channel with the volume controlled by the distance and panning controlled by the direction from the origin.
        Returns:
            ChannelID of the channel that the sound is played on.  If this ChannelID's getID() returns -1, then there was an error.
        */
		ChannelID playPan2D(const SoundID& id, float x, float y, float maxRange, int loops = 0);
		
        /*
        Plays the given sound on an available channel with the volume controlled by the distance and panning controlled by the direction from the origin.
        Returns:
            ChannelID of the channel that the sound is played on.  If this ChannelID's getID() returns -1, then there was an error.
        */
		ChannelID playPan3D(const SoundID& id, float x, float y, float z, float maxRange, int loops = 0);
		
        /*
        Plays the given sound on the given channel with the volume controlled by the distance from the origin.
        */
		void playVolume2D(const SoundID& id, const ChannelID& channel, float x, float y, float maxRange, int loops = 0);
		
        /*
        Plays the given sound on the given channel with the volume controlled by the distance from the origin.
        */
		void playVolume3D(const SoundID& id, const ChannelID& channel, float x, float y, float z, float maxRange, int loops = 0);
		
        /*
        Plays the given sound on the given channel with the volume controlled by the distance and panning controlled by the direction from the origin.
        */
		void playPan2D(const SoundID& id, const ChannelID& channel, float x, float y, float maxRange, int loops = 0);
		
        /*
        Plays the given sound on the given channel with the volume controlled by the distance and panning controlled by the direction from the origin.
        */
		void playPan3D(const SoundID& id, const ChannelID& channel, float x, float y, float z, float maxRange, int loops = 0);
		
		
        /*
        Returns:
            true if any channel is playing
            false if no channels are playing
        */
		bool isPlaying() const;
		
        /*
        Returns:
            true if the given channel is playing
            false if the given channel is not playing
        */
		bool isPlaying(const ChannelID& id) const;
		
        /*
        Sets the volume of the given channel in a range of 0 (silent) to 1 (max).
        */
		void setVolume(const ChannelID& id, float volume = 1.0f);
		
        /*
        Returns:
            float - The volume of the given channel in a range of 0 (silent) to 1 (max).
        */
		float getVolume(const ChannelID& id) const;
        
        /*
        Removes all MixBox effects from the given channel.
        */
		void clearEffects(const ChannelID& id);
		
        /*
        Returns:
            true if music is playing
            false if music is not playing
        */
		bool isMusicPlaying() const;
		
        /*
        Stops the current music.
        */
		void stopMusic();
		
        /*
        Pauses the current music.
        */
		void pauseMusic();
		
        /*
        Returns:
            true if the music is paused.
            false if the music is not paused.
        */
        bool isMusicPaused() const;
		
        /*
        Resets the current music to the beginning of the file.
        */
		void rewindMusic();
		
        /*
        Resumes play of music that had been paused.
        */
		void resumeMusic();
		
        /*
        Moves the playing position in the current music.
        Returns:
            true on success
            false on failure
        This can fail if the file format does not support skipping.
        */
		bool skipMusic(double position);
		
        /*
        Begins playing music from the given skipPosition, fading in over the given time.
        Returns:
            true on success
            false on failure
        */
		bool fadeInMusic(const MusicID& id, unsigned int milliseconds, double skipPosition = 0, int loops = -1);
		
        /*
        Begins fading out the currently playing music over the given time.
        Returns:
            true on success
            false on failure
        */
		bool fadeOutMusic(unsigned int milliseconds);

};



class SoundID : public MixID
{
    public:
    SoundID()
        : MixID(-1)
    {}
    SoundID(int id)
        : MixID(id)
    {}
    virtual ~SoundID()
    {}
    
    virtual bool isSound() const
    {
        return true;
    }

    /*
    Returns:
        true if the SoundID represents a valid sound.
        false if the SoundID does not represent a valid sound.
    */
    virtual bool isGood() const;
    /*
    Returns:
        true if the SoundID does not represent a valid sound.
        false if the SoundID represents a valid sound.
    */
    virtual bool isBad() const;
    
    /*
    Sets the volume of the sound chunk.
    */
    void setVolume(float volume = 1.0f);
    
    /*
    Returns:
        float - The volume of the sound in a range of 0 (silent) to 1 (max).
    */
    float getVolume() const;
    
    /*
    Plays the sound on an available channel.
    Returns:
        ChannelID of the channel that the sound is played on.  If this ChannelID's getID() returns -1, then there was an error.
    */
    ChannelID play(int loops = 0);
    
    /*
    Plays the sound on the given channel.
    */
    void play(const ChannelID& channel, int loops = 0);
    
    /*
    Plays the sound on an available channel with the volume controlled by the distance from the origin.
    Returns:
        ChannelID of the channel that the sound is played on.  If this ChannelID's getID() returns -1, then there was an error.
    */
    ChannelID playVolume2D(float x, float y, float maxRange, int loops = 0);
    
    /*
    Plays the sound on an available channel with the volume controlled by the distance from the origin.
    Returns:
        ChannelID of the channel that the sound is played on.  If this ChannelID's getID() returns -1, then there was an error.
    */
    ChannelID playVolume3D(float x, float y, float z, float maxRange, int loops = 0);

    /*
    Plays the sound on an available channel with the volume controlled by the distance and panning controlled by the direction from the origin.
    Returns:
        ChannelID of the channel that the sound is played on.  If this ChannelID's getID() returns -1, then there was an error.
    */
    ChannelID playPan2D(float x, float y, float maxRange, int loops = 0);
    
    /*
    Plays the sound on an available channel with the volume controlled by the distance and panning controlled by the direction from the origin.
    Returns:
        ChannelID of the channel that the sound is played on.  If this ChannelID's getID() returns -1, then there was an error.
    */
    ChannelID playPan3D(float x, float y, float z, float maxRange, int loops = 0);
    
    /*
    Plays the sound on the given channel with the volume controlled by the distance from the origin.
    */
    void playVolume2D(const ChannelID& channel, float x, float y, float maxRange, int loops = 0);
    
    /*
    Plays the sound on the given channel with the volume controlled by the distance from the origin.
    */
    void playVolume3D(const ChannelID& channel, float x, float y, float z, float maxRange, int loops = 0);

    /*
    Plays the sound on the given channel with the volume controlled by the distance and panning controlled by the direction from the origin.
    */
    void playPan2D(const ChannelID& channel, float x, float y, float maxRange, int loops = 0);
    
    /*
    Plays the sound on the given channel with the volume controlled by the distance and panning controlled by the direction from the origin.
    */
    void playPan3D(const ChannelID& channel, float x, float y, float z, float maxRange, int loops = 0);
};

class MusicID : public MixID
{
    public:
    MusicID()
        : MixID(-1)
    {}
    MusicID(int id)
        : MixID(id)
    {}
    virtual ~MusicID()
    {}
    
    virtual bool isMusic() const
    {
        return true;
    }

    /*
    Returns:
        true if the MusicID represents a valid music sample.
        false if the MusicID does not represent a valid music sample.
    */
    virtual bool isGood() const;
    /*
    Returns:
        true if the MusicID does not represent a valid music sample.
        false if the MusicID represents a valid music sample.
    */
    virtual bool isBad() const;
    
    /*
    Sets the volume of the music sample in a range of 0 (silent) to 1 (max).
    */
    void setVolume(float volume = 1.0f);
    
    /*
    Returns:
        float - The volume of the music sample in a range of 0 (silent) to 1 (max).
    */
    float getVolume() const;
    
    /*
    Plays the music.
    */
    void play(int loops = -1);
};


class ChannelID : public MixID
{
    public:
    ChannelID()
        : MixID(-1)
    {}
    ChannelID(int id)
        : MixID(id)
    {}
    virtual ~ChannelID()
    {}
    
    virtual bool isChannel() const
    {
        return true;
    }

    /*
    Returns:
        true if the ChannelID represents a valid channel.
        false if the ChannelID does not represent a valid channel.
    */
    virtual bool isGood() const;
    /*
    Returns:
        true if the ChannelID does not represent a valid channel.
        false if the ChannelID represents a valid channel.
    */
    virtual bool isBad() const;
    
    /*
    Sets the volume of the channel in a range of 0 (silent) to 1 (max).
    */
    void setVolume(float volume = 1.0f);
    	
    /*
    Returns:
        float - The volume of the channel in a range of 0 (silent) to 1 (max).
    */
    float getVolume() const;
    
    /*
    Removes all MixBox effects from the channel.
    */
    void clearEffects();
    
    /*
    Returns:
        true if the channel is playing
        false if the channel is not playing
    */
    bool isPlaying() const;
    
    /*
    Plays the given sound on the channel.
    */
    void play(const SoundID& sound, int loops = 0);
    
    /*
    Stops the current sound playing on the channel.
    */
    void stop();
    
    /*
    Pauses the channel.
    */
    void pause();
		
    /*
    Returns:
        true if the channel is paused.
        false if the channel is not paused.
    */
    bool isPaused() const;
    
    /*
    Resumes the channel when paused.
    */
    void resume();
    
    /*
    Plays the given sound on the channel with the volume controlled by the distance from the origin.
    */
    void playVolume2D(const SoundID& id, float x, float y, float maxRange, int loops = 0);
    
    /*
    Plays the given sound on the channel with the volume controlled by the distance from the origin.
    */
    void playVolume3D(const SoundID& id, float x, float y, float z, float maxRange, int loops = 0);

    /*
    Plays the given sound on the channel with the volume controlled by the distance and panning controlled by the direction from the origin.
    */
    void playPan2D(const SoundID& id, float x, float y, float maxRange, int loops = 0);
    
    /*
    Plays the given sound on the channel with the volume controlled by the distance and panning controlled by the direction from the origin.
    */
    void playPan3D(const SoundID& id, float x, float y, float z, float maxRange, int loops = 0);
};


}

#endif
