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

  \author Satofumi KAMIMURA

  $Id: SoundEffect.cpp 1957 2011-07-27 23:13:30Z satofumi $

  \todo ループ再生用のメソッドを追加する
*/

#include "SoundEffect.h"
#include "Audio.h"
#include "log_printf.h"
#include <AL/alut.h>
#include <vector>
#include <map>
#include <string>

using namespace qrk;
using namespace std;


namespace
{
    class AlResource
    {
    public:
        ALuint buffer_;
        ALuint source_;


        AlResource(const string& play_file)
            : buffer_(alutCreateBufferFromFile(play_file.c_str()))
        {
            if (buffer_ == AL_NONE) {
                ALenum error = alGetError();
                log_printf("alutCreateBufferFromFile: %s: %s\n",
                           play_file.c_str(), alGetString(error));
            }

            alGenSources(1, &source_);
            alSourcei(source_, AL_BUFFER, buffer_);
        }
    };


    class Resources
    {
    public:
        size_t size_;
        vector<AlResource> al_resources_;

        Resources(void) : size_(0)
        {
        }
    };


    /*
      シングルトンで再生用の buffer を管理する

      「buffer 数 > SoundEffect の個数 * 2」が成立したら buffer を 1/ 4 解放する
    */
    class PlayResource
    {
    public:
        map<string, Resources> resources_;


        static PlayResource* object(void)
        {
            static PlayResource singleton_object;
            return &singleton_object;
        }


        ~PlayResource(void)
        {
            for (map<string, Resources>::iterator it = resources_.begin();
                 it != resources_.end(); ++it) {
                vector<AlResource>& al_resources = it->second.al_resources_;

                for (vector<AlResource>::iterator each_it =
                         al_resources.begin();
                     each_it != al_resources.end(); ++each_it) {
                    deleteAlResource(*each_it);
                }
            }
        }


        void deleteAlResource(AlResource& al_resource)
        {
            alDeleteSources(1, &al_resource.source_);
            alDeleteBuffers(1, &al_resource.buffer_);
        }


        int create(const string& play_file)
        {
            Resources& resource = resources_[play_file];

            size_t last_index = resource.al_resources_.size();
            if (last_index >= resource.size_) {
                // リソースに空きがなければ作成して追加する
                AlResource al_resource(play_file);
                resource.al_resources_.push_back(al_resource);
            } else {
                // 既存のリソースを返す
                ++resource.size_;
            }
            return last_index;
        }


        void play(const string& play_file,
                  int index, float x, float y, float z)
        {
            Resources& resource = resources_[play_file];
            updatePosition(play_file, index, x, y, z);

            alSourcePlay(resource.al_resources_[index].source_);
        }


        void updatePosition(const string& play_file,
                            int index, float x, float y, float z)
        {
            ALfloat position[3];
            position[0] = x;
            position[1] = y;
            position[2] = z;

            Resources& resource = resources_[play_file];
            alSourcefv(resource.al_resources_[index].source_,
                       AL_POSITION, position);
        }


        bool isPlaying(const string& play_file, int index)
        {
            ALint source = resources_[play_file].al_resources_[index].source_;

            ALint status;
            alGetSourcei(source, AL_SOURCE_STATE, &status);
            return (status == AL_PLAYING) ? true : false;
        }
    };
}


struct SoundEffect::pImpl : private Audio
{
    enum {
        InvalidIndex = -1,
    };
    bool initialized_;
    PlayResource* resource_;
    string play_file_;
    int resource_index_;


    pImpl(const char* play_file)
        : initialized_(Audio::isInitialized()),
          resource_(PlayResource::object()),
          play_file_(play_file), resource_index_(InvalidIndex)
    {
    }


    void play(float x, float y, float z)
    {
        // リソースが割り振られていなければ、作成する
        if (resource_index_ == InvalidIndex) {
            resource_index_ = resource_->create(play_file_);
        }

        resource_->play(play_file_, resource_index_, x, y, z);
    }


    void updatePosition(float x, float y, float z)
    {
        if (resource_index_ == InvalidIndex) {
            return;
        }
        resource_->updatePosition(play_file_, resource_index_, x, y, z);
    }


    bool isPlaying(void)
    {
        return resource_->isPlaying(play_file_, resource_index_);
    }
};


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


SoundEffect::~SoundEffect(void)
{
}


//! \attention 未実装
void SoundEffect::setVolume(size_t percent)
{
    static_cast<void>(percent);
    // !!!
}


void SoundEffect::play(float x, float y, float z)
{
    if (! pimpl->initialized_) {
        return;
    }
    pimpl->play(x, y, z);
}


void SoundEffect::updatePosition(float x, float y, float z)
{
    if (! pimpl->initialized_) {
        return;
    }
    pimpl->updatePosition(x, y, z);
}


bool SoundEffect::isPlaying(void) const
{
    if (! pimpl->initialized_) {
        return false;
    } else {
        return pimpl->isPlaying();
    }
}
