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

  \author Satofumi KAMIMURA

  $Id: SoundEffect.cpp 846 2009-05-10 14:15:25Z 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)
{
  if (! pimpl->initialized_) {
    return false;
  } else {
    return pimpl->isPlaying();
  }
}
