/*****************************************************************************
 * $LastChangedDate: 2011-04-23 21:07:07 -0400 (Sat, 23 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Sound class (SDL).
 *//*
 * LEGAL:   COPYRIGHT (C) 2009 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#if COMPILE_SDL

#define SOUND_SOUND_SDL_CC 1
#include "base/module.hh"
#include "base/stream.hh"
using namespace base;
#include "glue/module.hh"
#include "glue/conf.hh"
using namespace glue;
#include "sound/module.hh"
#include "sound/conf.hh"
#include "sound/sound_buffer.hh"
#include "sound/sound_sdl.hh"
#include "sound/sound_sample_sdl_music.hh"
#include "sound/sound_sample_sdl_wave.hh"

namespace sound {

////////////////////////////////////////////////////////////////////////////////
///////////////////////////  functions, macros  ////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Print SDL error.
 *****************************************************************************/
void
PRINT_SDL_ERROR( void )
{
    const char* sdlErr = Mix_GetError();
    if ( sdlErr != NULL )
        CERROR << "SDL error: '" << sdlErr << "'" << std::endl;
}

/*****************************************************************************
 * RETURN_IF_SOUND_DISABLED().
 *****************************************************************************/
#define RETURN_IF_SOUND_DISABLED                            \
{{                                                          \
    if ( UX( not IfEnabled() ) )                            \
        return true;  /* return false only if error */      \
}}

////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////  SoundSDL  ///////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

DEFINE_SINGLETON_CLASS_VARS( SoundSDL )

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
SoundSDL::SoundSDL( void )
:   SoundBase(),
    mEnabled(false),  // initially disabled
    mWasEnabled(false),
    mVolume(defs::DEFAULT_VOLUME)
{
    // NOP
}

SoundSDL::~SoundSDL()
{
    // NOP
}

/*****************************************************************************
 * Enable/disable sound.
 * @return True if enabled.
 *****************************************************************************/
bool
SoundSDL::Enable( const bool enable )
{
    //..........................................................................
    // int Mix_OpenAudio(int frequency, Uint16 format, int channels, int chunksize)
    // frequency : Output sampling frequency in samples per second (Hz).
    //             you might use MIX_DEFAULT_FREQUENCY(22050) since that is a good value for most games. 
    // format    : Output sample format.
    // channels  : Number of sound channels in output.
    //             Set to 2 for stereo, 1 for mono. This has nothing to do with mixing channels. 
    // chunksize : Bytes used per output sample. 
    // Initialize the mixer API.
    // This must be called before using other functions in this library.
    // SDL must be initialized with SDL_INIT_AUDIO before this call.
    // frequency would be 44100 for 44.1KHz, which is CD audio rate.
    // Most games use 22050, because 44100 requires too much CPU power on older computers.
    // chunksize is the size of each mixed sample. The smaller this is the more your hooks will be called.
    // If make this too small on a slow system, sound may skip.
    // If made to large, sound effects will lag behind the action more.
    // You want a happy medium for your target computer.
    // You also may make this 4096, or larger, if you are just playing music.
    // MIX_CHANNELS(8) mixing channels will be allocated by default.
    // You may call this function multiple times, however you will have to call Mix_CloseAudio
    // just as many times for the device to actually close.
    // The format will not changed on subsequent calls until fully closed.
    // So you will have to close all the way before trying to open with different format parameters. 
    //..........................................................................
    // void Mix_Pause(int channel)
    // channel : Channel to pause on, or -1 for all channels. 
    // Pause channel, or all playing channels if -1 is passed in.
    // You may still halt a paused channel.
    // Note: Only channels which are actively playing will be paused. 
    //..........................................................................
    // void Mix_Resume(int channel)
    // channel : Channel to resume playing, or -1 for all channels. 
    // Unpause channel, or all playing and paused channels if -1 is passed in. 
    //..........................................................................

    // Permanently disabled?  Is SDL disabled?
    if ( SOUND_CONF.mPermanentlyDisabled
      or (not GLUE_CONF.mEnableSDL) )
    {
        // Force disabling.
        mEnabled = false;
        return true;
    }

    // The first time Enable(true) is called then initialize SDL_mixer.
    // Thereafter, use Mix_Pause()/Mix_Resume() to pause/unpause sound.
    if ( not mWasEnabled )
    {
        if ( enable and (not mEnabled) )  // going to enable
        {
            if ( Mix_OpenAudio( defs::SOUND_SDL_FREQ, defs::SOUND_SDL_FORMAT, defs::SOUND_SDL_STEREO_OR_MONO, defs::SOUND_SDL_CHUNK_SIZE ) == 0 )
            {
                mEnabled = mWasEnabled = true;
                CLOG << "Enabled SDL_mixer sound." << std::endl;
            }
            else
            {
                mEnabled = false;
                CERROR << "WARNING: Failed to init SDL_mixer sound!" << std::endl;
                // continue
            }
        }
    }
    else  // pause/unpause after sound was initially enabled
    {
        if ( enable and (not mEnabled) )  // going to enable
        {
            mEnabled = true;
            Mix_Resume( -1 );   // resume waves
            Mix_ResumeMusic();  // resume music
            #if SOUND_VERBOSE
            CLOG << "SDL: resuming sound." << std::endl;
            #endif
        }
        else if ( (not enable) and mEnabled )  // going to disable
        {
            mEnabled = false;
            Mix_Pause( -1 );   // pause waves
            Mix_PauseMusic();  // pause music
            #if SOUND_VERBOSE
            CLOG << "SDL: pausing sound." << std::endl;
            #endif
        }
        // else no change
    }

    return mEnabled;
}

/*****************************************************************************
 * @return Master volume.
 *****************************************************************************/
fp
SoundSDL::GetVolume( void )
{
    return mVolume;
}

/*****************************************************************************
 * Set volume (for all samples).
 *****************************************************************************/
bool
SoundSDL::SetVolume( const fp volume )
{
    // int Mix_Volume(int channel, int volume)
    // channel : Channel to set mix volume for.
    //           -1 will set the volume for all allocated channels. 
    // volume  : The volume to use from 0 to MIX_MAX_VOLUME(128).
    //           If greater than MIX_MAX_VOLUME,
    //           then it will be set to MIX_MAX_VOLUME.
    //           If less than 0 then the volume will not be set. 
    // Set the volume for any allocated channel. If channel is -1 then all channels at are set at once.
    // The volume is applied during the final mix, along with the sample volume.
    // So setting this volume to 64 will halve the output of all samples played on the specified channel.
    // All channels default to a volume of 128, which is the max. Newly allocated channels
    // will have the max volume set, so setting all channels volumes does not affect subsequent channel allocations.
    //
    // Returns: current volume of the channel. If channel is -1, the average volume is returned.

    RETURN_IF_SOUND_DISABLED;

    mVolume = volume;  // for GetVolume()

    Mix_Volume( -1, MIX_MAX_VOLUME * volume );

    return true;
}

/*****************************************************************************
 * Create specific Sample object (this is called by SoundBase).
 * SDL: Creates either a SampleSDLWave or SampleSDLMusic object
 * according to the kind of sound file.
 * Throws exception if error.
 *****************************************************************************/
shptr<Sample>
SoundSDL::CreateSample( const Sample::Name& sampleName )
{
    //..........................................................................
    // Mix_Chunk* Mix_LoadWAV_RW(SDL_RWops *src, int freesrc)
    // src     : The source SDL_RWops as a pointer. The sample is loaded from this. 
    // freesrc : A non-zero value mean is will automatically close/free the src for you. 
    // Load src for use as a sample.
    // This can load WAVE, AIFF, RIFF, OGG, and VOC formats.
    // Using SDL_RWops is not covered here, but they enable you to load from almost any source.
    // Note: You must call SDL_OpenAudio before this.
    // It must know the output characteristics so it can convert the sample for playback,
    // it does this conversion at load time.
    // Returns: a pointer to the sample as a Mix_Chunk. NULL is returned on errors. 
    //..........................................................................
    // Ownership of buffer used by SDL_RWops:
    // SDL won't take ownership of buffer (SDL won't free it).
    // Confirmed by looking at SDL code and testing with Valgrind.
    // Therefore, SoundBuffer will auto-destroy it when this function returns.
    //..........................................................................

    // Load sound file into a memory buffer.
    // This is done to support uncompress sound files.
    //
    // SoundBuffer will throw an exception if it fails to load sound file.
    // SoundBuffer is an auto-destroying object (it will automatically free its buffer).

    SoundBuffer soundBuffer( sampleName );
    void* const buf     = soundBuffer.GetBuf();
    const long  bufSize = soundBuffer.GetSize();

    // Create a SDL_Rwops object over memory buffer.
    SDL_RWops* rwops = SDL_RWFromMem( buf, bufSize );
    if ( UX( rwops == NULL ) )
    {
        const string msg = string("ERROR[SoundSDL::CreateSample] SDL_RWFromMem() ") + sampleName;
        #if LOG_SOUND_ERRORS
        CERROR << msg << std::endl; PRINT_SDL_ERROR();
        #endif
        throw Exception( msg );
    }

    // Try to load sound file as an SDL wave object.
    // The second arg "freesrc" to Mix_LoadWAV_RW() applies to the SDL_RWops, not the memory buffer.
    Mix_Chunk* mixChunk = Mix_LoadWAV_RW( rwops, true );  // misnomer, can load other kinds of sound files
    if ( mixChunk != NULL )
    {
        // Ready to rock!
        #if SOUND_VERBOSE
        CLOG << "SDL: Loaded " << sampleName << " as SDL wave." << std::endl;
        #endif
        return new SampleSDLWave( sampleName, mixChunk );
    }

    // Try to load sound file as an SDL music object.
    Mix_Music* mixMusic = Mix_LoadMUS_RW( rwops );  // has no second arg unlike its counterpart
    if ( mixMusic != NULL )
    {
        // Ready to roll!
        #if SOUND_VERBOSE
        CLOG << "SDL: Loaded " << sampleName << " as SDL music." << std::endl;
        #endif
        return new SampleSDLMusic( sampleName, mixMusic );
    }

    // Failed.
    const string msg = string("ERROR[SoundSDL::CreateSample] failed to load sound file (unsupported format?) ") + sampleName;
    #if LOG_SOUND_ERRORS
    CERROR << msg << std::endl; PRINT_SDL_ERROR();
    #endif
    throw Exception( msg );
    return NULL;  // shutup compiler
}

} // namespace sound

#endif // COMPILE_SDL
