// ==============================================================
//	This file is part of Glest Shared Library (www.glest.org)
//
//	Copyright (C) 2001-2008 Martiño Figueroa
//
//	You can redistribute this code and/or modify it under 
//	the terms of the GNU General Public License as published 
//	by the Free Software Foundation; either version 2 of the 
//	License, or (at your option) any later version
// ==============================================================

#include "sound_player_ds8.h"

#include <cassert>
#include "math_wrapper.h"

#include "util.h"
#include "leak_dumper.h"

namespace Shared{ namespace Sound{ namespace Ds8{

using namespace Util;

// =====================================================
//	class SoundBuffer
// =====================================================

// ===================== PUBLIC ========================

SoundBuffer::SoundBuffer(){
	dsBuffer= NULL;
	sound= NULL;
}

bool SoundBuffer::isFree(){
	if(dsBuffer==NULL){
		return true;
	}

	DWORD status;
	dsBuffer->GetStatus(&status);
    
    if(status & DSBSTATUS_BUFFERLOST){
        end();
        return true;
    }
    return false;
}

bool SoundBuffer::isReady(){
	DWORD status;
	dsBuffer->GetStatus(&status);

	if ((status & DSBSTATUS_PLAYING) || (status & DSBSTATUS_BUFFERLOST)){
        return false;
	}
    return true;
}

// ==================== PROTECTED ======================

void SoundBuffer::createDsBuffer(IDirectSound8 *dsObject){
	IDirectSoundBuffer *buffer;
	const SoundInfo *soundInfo= sound->getInfo();

	//format
	WAVEFORMATEX format;
    format.wFormatTag= WAVE_FORMAT_PCM;
    format.nChannels= soundInfo->getChannels();
    format.nSamplesPerSec= soundInfo->getSamplesPerSecond();
    format.nAvgBytesPerSec= (soundInfo->getBitsPerSample() * soundInfo->getSamplesPerSecond() * soundInfo->getChannels())/8;
    format.nBlockAlign= (soundInfo->getChannels() * soundInfo->getBitsPerSample())/8;
    format.wBitsPerSample= soundInfo->getBitsPerSample();
    format.cbSize= 0;

	//buffer desc
    DSBUFFERDESC dsBufferDesc; 
    memset(&dsBufferDesc, 0, sizeof(DSBUFFERDESC)); 
    dsBufferDesc.dwSize= sizeof(DSBUFFERDESC); 
    dsBufferDesc.dwFlags= DSBCAPS_CTRLVOLUME;
    dsBufferDesc.dwBufferBytes= size; 
    dsBufferDesc.lpwfxFormat= &format; 

	//create buffer
	HRESULT hr= dsObject->CreateSoundBuffer(&dsBufferDesc, &buffer, NULL);
	if (hr!=DS_OK){
        throw runtime_error("Failed to create direct sound buffer");
	}

	//query dx8 interface
	hr = buffer->QueryInterface(IID_IDirectSoundBuffer8, (void**) &dsBuffer);
    buffer->Release();
	if (hr!=S_OK){
        throw runtime_error("Failed to create direct sound 8 static buffer");
	}
}

// =====================================================
//	class StaticBuffer
// =====================================================

// ===================== PUBLIC ========================

void StaticSoundBuffer::init(IDirectSound8 *dsObject, Sound *sound){
	if(this->sound==NULL){
		this->sound= sound;
		this->size= sound->getInfo()->getSize();
		createDsBuffer(dsObject);
		dsBuffer->SetCurrentPosition(0);
		fillDsBuffer();
	}
}

void StaticSoundBuffer::end(){
	dsBuffer->Stop();
	dsBuffer->Release();
	dsBuffer= NULL;
	sound= NULL;
}

void StaticSoundBuffer::play(){
	dsBuffer->SetVolume(dsVolume(sound->getVolume()));
	dsBuffer->Play(0, 0, 0); 
}

// ===================== PRIVATE =======================

void StaticSoundBuffer::fillDsBuffer(){
    void * writePointer;
    unsigned long size;
	
	//lock
	HRESULT hr= dsBuffer->Lock(0, 0, &writePointer, &size, NULL, NULL, DSBLOCK_ENTIREBUFFER);
	if (hr!=DS_OK){
        throw runtime_error("Failed to Lock direct sound buffer");
	}

	//copy memory
	memcpy(writePointer, getStaticSound()->getSamples(), size);

	//unlock
    hr= dsBuffer->Unlock(writePointer, size, NULL, 0);
	if (hr!=DS_OK){
        throw runtime_error("Failed to Unlock direct sound buffer");
	}
}

// =====================================================
//	class StrBuffer
// =====================================================

// ===================== PUBLIC ========================

StrSoundBuffer::StrSoundBuffer(){
	state= sFree;
}

void StrSoundBuffer::init(IDirectSound8 *dsObject, Sound *sound, uint32 strBufferSize){
	state= sStopped;
	if(this->sound==NULL){
		this->sound= sound;
		this->size= strBufferSize;
		createDsBuffer(dsObject);
		dsBuffer->SetCurrentPosition(0);
		fillDsBuffer();
	}
	else if(this->sound!=sound){
		this->sound= sound;
		this->size= strBufferSize;
		dsBuffer->SetCurrentPosition(0);
		fillDsBuffer();
	}
}

void StrSoundBuffer::end(){
	state= sFree;
	dsBuffer->Stop();
	dsBuffer->Release();
	dsBuffer= NULL;
	sound= NULL;
}

void StrSoundBuffer::play(int64 fadeOn){
	assert(state==sStopped);
	lastPlayCursor= 0;
	if(fadeOn==0){
		state= sPlaying;
		dsBuffer->SetVolume(dsVolume(sound->getVolume()));
	}
	else{
		this->fade= fadeOn;
		state= sFadingOn;
		chrono.start();
		dsBuffer->SetVolume(dsVolume(0.f));
	}
	dsBuffer->Play(0, 0, DSBPLAY_LOOPING); 
}

void StrSoundBuffer::update(){
	
	switch(state){
	case sFadingOn:
		if(chrono.getMillis()>fade){
			dsBuffer->SetVolume(dsVolume(sound->getVolume()));
			state= sPlaying;
		}
		else{
			dsBuffer->SetVolume(dsVolume(sound->getVolume()*(static_cast<float>(chrono.getMillis())/fade)));
		}
		refreshDsBuffer();
		break;

	case sFadingOff:
		if(chrono.getMillis()>fade){
			state= sStopped;
			dsBuffer->Stop();
		}
		else{
			dsBuffer->SetVolume(dsVolume(sound->getVolume()*(1.0f-static_cast<float>(chrono.getMillis())/fade)));
			refreshDsBuffer();
		}
		break;

	case sPlaying:
		dsBuffer->SetVolume(dsVolume(sound->getVolume()));
		refreshDsBuffer();
		break;

	default:
		break;
	}
}

void StrSoundBuffer::stop(int64 fadeOff){

	if(fadeOff==0){
		dsBuffer->Stop();
		state= sStopped;
	}
	else{
		this->fade= fadeOff;
		state= sFadingOff;
		chrono.start();
	}

}

// ===================== PRIVATE =======================

void StrSoundBuffer::fillDsBuffer(){
    void * writePointer;
    unsigned long size;

	//lock
	HRESULT hr= dsBuffer->Lock(0, 0, &writePointer, &size, NULL, NULL, DSBLOCK_ENTIREBUFFER);
	if (hr!=DS_OK){
        throw runtime_error("Failed to Lock direct sound buffer");
	}

	//copy memory
	readChunk(writePointer, size);

	//unlock
	hr= dsBuffer->Unlock(writePointer, size, NULL, 0 );
	if (hr!=DS_OK){
        throw runtime_error("Failed to Unlock direct sound buffer");
	}
}


void StrSoundBuffer::refreshDsBuffer(){
    void *writePointer1, *writePointer2;
    DWORD size1, size2;
	DWORD playCursor;
	DWORD bytesToLock;

	DWORD status;
	dsBuffer->GetStatus(&status);
	assert(!(status & DSBSTATUS_BUFFERLOST));

	HRESULT hr= dsBuffer->GetCurrentPosition(&playCursor, NULL);
	if(hr!=DS_OK){
		throw runtime_error("Failed to Lock query play position");
	}

	//compute bytes to lock
	if(playCursor>=lastPlayCursor){
		bytesToLock= playCursor - lastPlayCursor;
	}
	else{
		bytesToLock= size - (lastPlayCursor - playCursor);
	}

	//copy data
	if(bytesToLock>0){

		//lock
		HRESULT hr=dsBuffer->Lock(lastPlayCursor, bytesToLock, &writePointer1, &size1, &writePointer2, &size2, 0);
		if (hr!=DS_OK){
			throw runtime_error("Failed to Lock direct sound buffer");
		}

		//copy memory
		assert(size1+size2==bytesToLock);

		readChunk(writePointer1, size1);
		readChunk(writePointer2, size2);

		//unlock
		hr= dsBuffer->Unlock(writePointer1, size1, writePointer2, size2);
		if (hr!=DS_OK){
			throw runtime_error("Failed to Unlock direct sound buffer");
		}
	}		

	lastPlayCursor= playCursor;
}

void StrSoundBuffer::readChunk(void *writePointer, uint32 size){
	StrSound *s= getStrSound();
	uint32 readSize= s->read(static_cast<int8*>(writePointer), size);
	if(readSize<size){
		StrSound *next= s->getNext()==NULL? s: s->getNext();
		next->restart();
		next->read(&static_cast<int8*>(writePointer)[readSize], size-readSize);
		next->setVolume(s->getVolume());
		sound= next;
	}
}

// =====================================================
//	class SoundPlayerDs8
// =====================================================

SoundPlayerDs8::SoundPlayerDs8(){
	SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s %d]\n",__FILE__,__FUNCTION__,__LINE__);
    dsObject= NULL;
    initOk = false;
}

bool SoundPlayerDs8::init(const SoundPlayerParams *params){
	SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s %d]\n",__FILE__,__FUNCTION__,__LINE__);

	initOk = false;

    HRESULT hr;

    if(params == NULL) {
    	throw std::runtime_error("params == NULL");
    }

	this->params= *params;

	try {
		SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s %d]\n",__FILE__,__FUNCTION__,__LINE__);
		//reserve memory for buffers
		staticSoundBuffers.resize(params->staticBufferCount);
		strSoundBuffers.resize(params->strBufferCount);

		SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s %d]\n",__FILE__,__FUNCTION__,__LINE__);
		//create object
		hr=DirectSoundCreate8(NULL, &dsObject, NULL);
		if (hr!=DS_OK){
			throw runtime_error("Can't create direct sound object");
		}

		SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s %d]\n",__FILE__,__FUNCTION__,__LINE__);

		//Set cooperative level
		hr= dsObject->SetCooperativeLevel(GetActiveWindow(), DSSCL_PRIORITY);
		if (hr!=DS_OK){
			throw runtime_error("Can't set cooperative level of dsound");
		}
		initOk = true;

		SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s %d]\n",__FILE__,__FUNCTION__,__LINE__);
	}
	catch(const exception &ex) {
		SystemFlags::OutputDebug(SystemFlags::debugError,"In [%s::%s Line: %d] Error [%s]\n",__FILE__,__FUNCTION__,__LINE__,ex.what());
		SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s %d] error [%s]\n",__FILE__,__FUNCTION__,__LINE__,ex.what());
	}

    return initOk;
}

void SoundPlayerDs8::end(){
	SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s %d]\n",__FILE__,__FUNCTION__,__LINE__);

	stopAllSounds();

	SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s %d]\n",__FILE__,__FUNCTION__,__LINE__);
}

void SoundPlayerDs8::play(StaticSound *staticSound){
	if(initOk == false) return;

	int bufferIndex= -1;

    assert(staticSound!=NULL);
	
	//if buffer found, play the sound
	if (findStaticBuffer(staticSound, &bufferIndex)){
		staticSoundBuffers[bufferIndex].init(dsObject, staticSound);
		staticSoundBuffers[bufferIndex].play(); 
    }

}

void SoundPlayerDs8::play(StrSound *strSound, int64 fadeOn){
	if(initOk == false) return;
	int bufferIndex= -1;
	
	//play sound if buffer found
	if(findStrBuffer(strSound, &bufferIndex)){
		strSoundBuffers[bufferIndex].init(dsObject, strSound, params.strBufferSize);
		strSoundBuffers[bufferIndex].play(fadeOn);
	}
}

void SoundPlayerDs8::stop(StrSound *strSound, int64 fadeOff){
	if(initOk == false) return;
	//find the buffer with this sound and stop it
	for(unsigned int i= 0; i<params.strBufferCount; ++i){
		if(strSoundBuffers[i].getSound()==strSound){
			strSoundBuffers[i].stop(fadeOff);
		}
	}
}

void SoundPlayerDs8::stopAllSounds(int64 fadeOff){
	if(initOk == false) return;

	for(unsigned int i=0; i<params.strBufferCount; ++i){
		if(!strSoundBuffers[i].isFree()){
			strSoundBuffers[i].stop(fadeOff);
			strSoundBuffers[i].end();
		}
	}
	for(unsigned int i=0; i<params.staticBufferCount; ++i){
		if(!staticSoundBuffers[i].isFree()){
			staticSoundBuffers[i].end();
		}
	}
}

void SoundPlayerDs8::updateStreams(){
	if(initOk == false) return;

	for(unsigned int i=0; i<params.strBufferCount; ++i){
		strSoundBuffers[i].update();
	}
}

// ===================== PRIVATE =======================
			
bool SoundPlayerDs8::findStaticBuffer(Sound *sound, int *bufferIndex){
    bool bufferFound= false;
    if(initOk == false) return bufferFound;

    assert(sound!=NULL);
	
    //1st: we try fo find a stopped buffer with the same sound
	for(unsigned int i=0; i<staticSoundBuffers.size(); ++i){
		if(sound==staticSoundBuffers[i].getSound() && staticSoundBuffers[i].isReady()){
			*bufferIndex= i;
			bufferFound= true;
			break;
		}
	}

    //2nd: we try to find a free buffer
    if(!bufferFound){
        for(uint32 i=0; i<staticSoundBuffers.size(); ++i){
			if(staticSoundBuffers[i].isFree()){
				*bufferIndex= i;
				bufferFound= true;
                break;
            }
        }
    }

    //3rd: we try to find a stopped buffer
    if(!bufferFound){
        for(unsigned int i=0; i<staticSoundBuffers.size(); ++i){
			if(staticSoundBuffers[i].isReady()){
				staticSoundBuffers[i].end();
				*bufferIndex= i;
				bufferFound= true;
                break;
            }
        }
    }

	return bufferFound;
}

bool SoundPlayerDs8::findStrBuffer(Sound *sound, int *bufferIndex){
    bool bufferFound= false;
    if(initOk == false) return bufferFound;

    assert(sound!=NULL);
	assert(sound->getVolume()<=1.0f && sound->getVolume()>=0.0f);

    //We try to find a free or ready buffer
    if(!bufferFound){
        for(uint32 i=0; i<strSoundBuffers.size(); ++i){
			if(strSoundBuffers[i].isFree() || strSoundBuffers[i].isReady()){
				*bufferIndex= i;
				bufferFound= true;
                break;
            }
        }
    }

	return bufferFound;
}

// =====================================================
//	Misc
// =====================================================

long dsVolume(float floatVolume){
#ifdef USE_STREFLOP
	float correctedVol= streflop::log10f(floatVolume*9.f+1.f);
#else
	float correctedVol= log10f(floatVolume*9.f+1.f);
#endif

	long vol= static_cast<long>(DSBVOLUME_MIN+correctedVol*(DSBVOLUME_MAX-DSBVOLUME_MIN));
	return clamp(vol, DSBVOLUME_MIN, DSBVOLUME_MAX);
}

}}}//end namespace
