//------------------------------------------------------------------------------
// Lamp : Open source game middleware
// Copyright (C) 2004  Junpei Ohtani ( Email : junpee@users.sourceforge.jp )
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//------------------------------------------------------------------------------

/** @file
 * サウンドキャッシュ実装
 * @author Junpee
 */

#include "LampBasic.h"
#include "Sound/Utility/SoundCache.h"
#include "Sound/System/LampSound.h"
#include "Sound/System/SoundManager.h"
#include "Sound/Stereo/StaticSound.h"
#include "Sound/3D/StaticSound3D.h"
#include "Sound/Utility/SoundList.h"

namespace Lamp{

//------------------------------------------------------------------------------
// 生成、破棄
//------------------------------------------------------------------------------
// コンストラクタ
SoundCache::SoundCache(const String& basePath, const String& extension,
	Sound::Focus focus, int defaultMaxMixingCount) :
	basePath_(basePath), extension_(extension),
	focus_(focus), defaultMaxMixingCount_(defaultMaxMixingCount){
	Assert(defaultMaxMixingCount_ > 0);
}
//------------------------------------------------------------------------------
// デストラクタ
SoundCache::~SoundCache(){
	unloadAll();
}
//------------------------------------------------------------------------------
// サウンドのロード
//------------------------------------------------------------------------------
// 静的サウンドのロード
bool SoundCache::loadStaticSound(
	const String& name, bool loop, int priority, int maxMixingCount){
	// すでに存在していないかチェック
	if(cache_.get(name) != NULL){ Assert(false); return false; }
	// サウンド作成
	StaticSound* sound = LampSound::getSoundManager()->createStaticSound(
		basePath_ + name + extension_, focus_);
	if(sound == NULL){ Assert(false); return false; }
	sound->setLoop(loop);
	sound->setPriority(priority);
	// キャッシュ作成
	int soundCount = maxMixingCount;
	if(soundCount == 0){ soundCount = defaultMaxMixingCount_; }
	createCache(name, sound, soundCount);
	return true;
}
//------------------------------------------------------------------------------
// 静的3Dサウンドのロード
bool SoundCache::loadStaticSound3D(const String& name, bool loop, int priority,
	float minimumDistance, float maximumDistance, int maxMixingCount){
	// すでに存在していないかチェック
	if(cache_.get(name) != NULL){ Assert(false); return false; }
	// サウンド作成
	StaticSound3D* sound = LampSound::getSoundManager()->createStaticSound3D(
		basePath_ + name + extension_, focus_);
	if(sound == NULL){ Assert(false); return false; }
	sound->setLoop(loop);
	sound->setPriority(priority);
	sound->setDistance(minimumDistance, maximumDistance);
	// キャッシュ作成
	int mixingCount = maxMixingCount;
	if(mixingCount == 0){ mixingCount = defaultMaxMixingCount_; }
	createCache(name, sound, mixingCount);
	return true;
}
//------------------------------------------------------------------------------
// キャッシュ作成
void SoundCache::createCache(
	const String& name, Sound* sound, int maxMixingCount){
	SoundArray* soundArray = new SoundArray(sound, maxMixingCount);
	cache_.put(name, soundArray);
	list_.add(soundArray);
}
//------------------------------------------------------------------------------
// サウンドのアンロード
bool SoundCache::unloadSound(const String& name){
	SoundArray* soundArray = cache_.remove(name);
	if(soundArray == NULL){ return false; }
	list_.removeByValue(soundArray);
	delete soundArray;
	return true;
}
//------------------------------------------------------------------------------
// 全サウンドのアンロード
void SoundCache::unloadAll(){
	int count = list_.getCount();
	for(int i = count - 1; i >= 0; i--){ delete list_[i]; }
	list_.clear();
	cache_.clear();
}
//------------------------------------------------------------------------------
// サウンドリスト
//------------------------------------------------------------------------------
// サウンドリストのロード
bool SoundCache::loadSoundList(const String& filePath){
	return SoundList::load(this, basePath_ + filePath);
}
//------------------------------------------------------------------------------
// サウンドリストのロード
bool SoundCache::loadSoundList(TextReader* textReader){
	return SoundList::load(this, textReader);
}
//------------------------------------------------------------------------------
// サウンドの再生
//------------------------------------------------------------------------------
// サウンドの再生
Sound* SoundCache::playSound(const String& name, float volume, int frequency){
	// サウンドの検索
	SoundArray* array = cache_.get(name);
	if(array == NULL){ return NULL; }
	Sound* sound = array->getFreeSound();
	if(sound == NULL){ return NULL; }
	// サウンドの再生
	sound->reset(Sound::resetRuntime);
	sound->setVolume(volume);
	if(frequency != 0){ sound->setFrequency(frequency); }
	sound->play();
	return sound;
}
//------------------------------------------------------------------------------
// ステレオサウンドの再生
StereoSound* SoundCache::playStereoSound(
	const String& name, float volume, int frequency, float pan){
	// サウンドの検索
	SoundArray* array = cache_.get(name);
	if(array == NULL){ return NULL; }
	Sound* sound = array->getFreeSound();
	if(sound == NULL){ return NULL; }
	StereoSound* stereoSound = sound->castStereoSound();
	if(stereoSound == NULL){ return NULL; }
	// サウンドの再生
	stereoSound->reset(Sound::resetRuntime);
	stereoSound->setVolume(volume);
	if(frequency != 0){ stereoSound->setFrequency(frequency); }
	stereoSound->setPan(pan);
	stereoSound->play();
	return stereoSound;
}
//------------------------------------------------------------------------------
// 3Dサウンドの再生
Sound3D* SoundCache::playSound3D(
	const String& name, float volume, int frequency, const Vector3& position){
	// サウンドの検索
	SoundArray* array = cache_.get(name);
	if(array == NULL){ return NULL; }
	Sound* sound = array->getFreeSound();
	if(sound == NULL){ return NULL; }
	Sound3D* sound3D = sound->castSound3D();
	if(sound3D == NULL){ return NULL; }
	// サウンドの再生
	sound3D->reset(Sound::resetRuntime);
	sound3D->setVolume(volume);
	if(frequency != 0){ sound3D->setFrequency(frequency); }
	sound3D->setPosition(position);
	sound3D->play();
	return sound3D;
}
//------------------------------------------------------------------------------
// サウンドの取得、解放
//------------------------------------------------------------------------------
// サウンドの取得
Sound* SoundCache::getSound(const String& name, Sound::Reset resetFlag){
	// サウンドの検索
	SoundArray* array = cache_.get(name);
	if(array == NULL){ Assert(false); return NULL; }
	Sound* sound = array->getFreeSound();
	if(sound == NULL){ Assert(false); return NULL; }
	// 所有権を無くす
	Assert(sound->hasOwnership());
	sound->setOwnership(false);
	sound->reset(resetFlag);
	return sound;
}
//------------------------------------------------------------------------------
// ステレオサウンドの取得
StereoSound* SoundCache::getStereoSound(
	const String& name, Sound::Reset resetFlag){
	Sound* sound = getSound(name, resetFlag);
	if(sound == NULL){ return NULL; }
	StereoSound* stereoSound = sound->castStereoSound();
	if(stereoSound == NULL){
		releaseSound(sound);
		return NULL;
	}
	return stereoSound;
}
//------------------------------------------------------------------------------
// 3Dサウンドの取得
Sound3D* SoundCache::getSound3D(const String& name, Sound::Reset resetFlag){
	Sound* sound = getSound(name, resetFlag);
	if(sound == NULL){ return NULL; }
	Sound3D* sound3D = sound->castSound3D();
	if(sound3D == NULL){
		releaseSound(sound);
		return NULL;
	}
	return sound3D;
}
//------------------------------------------------------------------------------
// サウンドの解放
void SoundCache::releaseSound(Sound* sound){
	Assert(sound != NULL);
	// 所有権をもどす
	Assert(!sound->hasOwnership());
	sound->setOwnership(true);
}
//------------------------------------------------------------------------------
// その他
//------------------------------------------------------------------------------
// 全サウンドの再生を一時停止する
void SoundCache::suspendAll(){
	int listCount = list_.getCount();
	for(int i = 0; i < listCount; i++){
		SoundArray* soundArray = list_[i];
		int soundCount = soundArray->getSoundCount();
		for(int j = 0; j < soundCount; j++){
			soundArray->getSound(j)->suspend();
		}
	}
}
//------------------------------------------------------------------------------
// 全サウンドの再生を再開する
void SoundCache::resumeAll(){
	int listCount = list_.getCount();
	for(int i = 0; i < listCount; i++){
		SoundArray* soundArray = list_[i];
		int soundCount = soundArray->getSoundCount();
		for(int j = 0; j < soundCount; j++){
			soundArray->getSound(j)->resume();
		}
	}
}
//------------------------------------------------------------------------------
// 文字列への変換
String SoundCache::toString() const{
	String result;
	int listCount = list_.getCount();
	int cacheCount = 0;
	for(int i = 0; i < listCount; i++){
		cacheCount += list_[i]->getSoundCount();
	}
	result.format("Cache/List (%d/%d)  ", cacheCount, listCount);
	if(focus_ == Sound::focusNormal){
		result += "Normal\n";
	}else if(focus_ == Sound::focusSticky){
		result += "Sticky\n";
	}else if(focus_ == Sound::focusGlobal){
		result += "Global\n";
	}
	result += "Path " + basePath_ + "  Extension " + extension_ + "\n";
	for(int i = 0; i < listCount; i++){
		result += list_[i]->toString();
	}
	return result;
}
//------------------------------------------------------------------------------
// サウンド配列
//------------------------------------------------------------------------------
// コンストラクタ
SoundCache::SoundArray::SoundArray(Sound* sound, int maxMixingCount) :
	sounds_(maxMixingCount), maxMixingCount_(maxMixingCount){
	Assert(sound != NULL);
	sounds_.add(sound);
}
//------------------------------------------------------------------------------
// デストラクタ
SoundCache::SoundArray::~SoundArray(){
	// 全サウンドを破棄する
	int count = sounds_.getCount();
	SoundManager* manager = LampSound::getSoundManager();
	for(int i = count - 1; i >= 0; i--){ manager->destroy(sounds_[i]); }
	sounds_.clear();
}
//------------------------------------------------------------------------------
// フリーサウンド取得
Sound* SoundCache::SoundArray::getFreeSound(){
	Assert(!sounds_.isEmpty());
	Sound* topSound = sounds_[0];
	// サウンドの取得
	int count = sounds_.getCount();
	for(int i = 0; i < count; i++){
		Sound* sound = sounds_[i];
		// 所有権が無ければ無視
		if(!sound->hasOwnership()){ continue; }
		Sound::State state = sound->getState();
		// 再生、サスペンドは無視する
		// ここでロストを無視すると、サウンドを大量に生成する
		if((state == Sound::statePlay) ||
			(state == Sound::stateSuspend)){ continue; }
		return sound;
	}

	// 同音同時発音数制限
	int soundCount = sounds_.getCount();
	if(soundCount == maxMixingCount_){
		u_int maxCursor = 0, maxIndex = -1;
		for(int i = 0; i < soundCount; i++){
			Sound* sound = sounds_[i];
			// 所有権が無ければ無視
			if(!sound->hasOwnership()){ continue; }
			u_int cursor = sound->getCursor();
			if(cursor > maxCursor){
				maxCursor = cursor;
				maxIndex = i;
			}
		}
		if(maxIndex == -1){ return NULL; }
		return sounds_[maxIndex];
	}

	// 空きが見つからなかったので複製を作成する
	Sound* clone = topSound->clone();
	if(clone != NULL){
		sounds_.add(clone);
		return clone;
	}
	return NULL;
}
//------------------------------------------------------------------------------
// 文字列への変換
String SoundCache::SoundArray::toString() const{
	String result, temp;
	Sound* sound = sounds_[0];
	result.format("  %2d/%2d ", sounds_.getCount(), maxMixingCount_);
	if(sound->isStereoSound()){
		result += "Stereo ";
	}else{
		result += "3D     ";
	}
	if(sound->isLoop()){
		result += "Loop ";
	}else{
		result += "Once ";
	}
	temp.format("Pri%4d ", sound->getPriority());
	result += temp;

	// ファイル名の前についているパス情報は出力されない
	FilePath path(sound->getName());
	result += path.getName();

	Sound3D* sound3D = sound->castSound3D();
	if(sound3D != NULL){
		temp.format(" Dist %.1f-%.1f ",
			sound3D->getMinimumDistance(), sound3D->getMaximumDistance());
		result += temp;
	}
	result += "\n";
	return result;
}
//------------------------------------------------------------------------------
} // End of namespace Lamp
//------------------------------------------------------------------------------
