/*!
  \file
  \brief 効果音の再生

  \author Satofumi KAMIMURA

  $Id: SoundEffect.cpp 1965 2011-08-13 12:02:33Z satofumi $
*/

#include "SoundEffect.h"
#include "Audio.h"
#include <SDL_mixer.h>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <map>
#include <string>

using namespace qrk;
using namespace boost;
using namespace std;


namespace
{
    typedef map<int, int*> Channels;
    typedef map<string, weak_ptr<Mix_Chunk> > ChunkMap;
}


enum {
    DefaultVolumePercent = 30,
};


struct SoundEffect::pImpl
{
    static bool initialized_;
    static Channels channels_;
    static size_t master_percent_;
    static bool is_mute_;
    static ChunkMap mix_chunks_;

    Audio audio_;
    int channel_id_;
    int first_mix_volume_;
    size_t percent_;
    shared_ptr<Mix_Chunk> chunk_;


    pImpl(const char* play_file)
        : channel_id_(-1), percent_(100),
          chunk_(load_mix_chunk(play_file))
    {
        if (!initialized_) {
            Mix_ChannelFinished(finished);
            initialized_ = true;
        }
        update_volume();
    }


    ~pImpl(void)
    {
        if (channel_id_ > 0) {
            finished(channel_id_);
        }
    }


    shared_ptr<Mix_Chunk> load_mix_chunk(const char* play_file)
    {
        ChunkMap::iterator it = mix_chunks_.find(play_file);
        if ((it != mix_chunks_.end()) && !it->second.expired()) {
            // 見つかれば、その資源を返す
            return shared_ptr<Mix_Chunk>(it->second);
        }

        // 見つからなければ、新規に作成して返す
        shared_ptr<Mix_Chunk> chunk(Mix_LoadWAV(play_file));
        mix_chunks_[play_file] = chunk;
        return chunk;
    }


    void play(void)
    {
        channel_id_ = Mix_PlayChannel(-1, chunk_.get(), 0);
        update_volume();
        Mix_Volume(channel_id_, first_mix_volume_);

        if (channel_id_ > 0) {
            channels_[channel_id_] = &channel_id_;
        }
    }


    bool isPlaying(void)
    {
        if (channel_id_ < 0) {
            return false;
        }

        return (Mix_Playing(channel_id_) == 0) ? false : true;
    }


    static void finished(int channel_id)
    {
        channels_.erase(channel_id);
    }


    void update_volume(void)
    {
        int mix_volume = static_cast<int>(MIX_MAX_VOLUME *
                                          (percent_ / 100.0) *
                                          (master_percent_ / 100.0));
        first_mix_volume_ = mix_volume;
        if (isPlaying()) {
            Mix_Volume(channel_id_, mix_volume);
        }
    }
};

bool SoundEffect::pImpl::initialized_ = false;
Channels SoundEffect::pImpl::channels_;
ChunkMap SoundEffect::pImpl::mix_chunks_;
size_t SoundEffect::pImpl::master_percent_ = DefaultVolumePercent;
bool SoundEffect::pImpl::is_mute_ = false;


SoundEffect::SoundEffect(const char* play_file) : pimpl(new pImpl(play_file))
{
}


SoundEffect::~SoundEffect(void)
{
}


void SoundEffect::setMute(bool is_mute)
{
    if (is_mute) {
        int saved_master_percent = pimpl->master_percent_;
        Mix_Volume(-1, 0);
        setMasterVolume(0);
        pimpl->master_percent_ = saved_master_percent;
    } else {
        // ミュートする前の音量で再生を再開する
        setMasterVolume(pimpl->master_percent_);
    }
    pimpl->is_mute_ = is_mute;
}


bool SoundEffect::isMute(void) const
{
    return pimpl->is_mute_;
}


void SoundEffect::setMasterVolume(size_t percent)
{
    pimpl->master_percent_ = min(percent, static_cast<size_t>(100));
    pimpl->update_volume();
}


void SoundEffect::setVolume(size_t percent)
{
    if (!pimpl->audio_.isInitialized()) {
        return;
    }
    pimpl->percent_ = min(percent, static_cast<size_t>(100));
    pimpl->update_volume();
}


// !attention x, y, z は無視される
void SoundEffect::play(float x, float y, float z )
{
    static_cast<void>(x);
    static_cast<void>(y);
    static_cast<void>(z);

    if (pimpl->is_mute_) {
        return;
    }

    // !!! 余力があれば SDL_mixer の Effect を適用してもよい

    if (!pimpl->audio_.isInitialized()) {
        return;
    }

    pimpl->play();
}


void SoundEffect::stop(void)
{
    if (!pimpl->audio_.isInitialized()) {
        return;
    }

    if (!pimpl->isPlaying()) {
        return;
    }

    Mix_HaltChannel(pimpl->channel_id_);
}


// !attention x, y, z は無視される
void SoundEffect::updatePosition(float x, float y, float z)
{
    static_cast<void>(x);
    static_cast<void>(y);
    static_cast<void>(z);

    // !!! 余力があれば SDL_mixer の Effect を適用してもよい

    // 実装しない
}


bool SoundEffect::isPlaying(void) const
{
    if (!pimpl->audio_.isInitialized()) {
        return false;
    }

    return pimpl->isPlaying();
}
