/* ScummVM - Scumm Interpreter
 * Copyright (C) 2003-2004 The ScummVM project
 *
 * This program is free software; you can redistribute it 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.

 * This program 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 General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Header$
 *
 */

#include "stdafx.h"
#include "sound.h"
#include "common/util.h"
#include "resman.h"
#include "logic.h"
#include "sword1.h"

namespace Sword1 {

#define SOUND_SPEECH_ID 1
#define SPEECH_FLAGS (SoundMixer::FLAG_16BITS | SoundMixer::FLAG_AUTOFREE | SoundMixer::FLAG_LITTLE_ENDIAN)

Sound::Sound(const char *searchPath, SoundMixer *mixer, ResMan *pResMan) {
	strcpy(_filePath, searchPath);
	_mixer = mixer;
	_resMan = pResMan;
	_cowHeader = NULL;
	_endOfQueue = 0;
	_currentCowFile = 0;
	_speechVolL = _speechVolR = _sfxVolL = _sfxVolR = 192;
}

int Sound::addToQueue(int32 fxNo) {
	bool alreadyInQueue = false;
	for (uint8 cnt = 0; (cnt < _endOfQueue) && (!alreadyInQueue); cnt++)
		if (_fxQueue[cnt].id == (uint32)fxNo)
			alreadyInQueue = true;
	if (!alreadyInQueue) {
		if (_endOfQueue == MAX_FXQ_LENGTH) {
			warning("Sound queue overflow");
			return 0;
		}
		_resMan->resOpen(_fxList[fxNo].sampleId);
		_fxQueue[_endOfQueue].id = fxNo;
		if (_fxList[fxNo].type == FX_SPOT)
			_fxQueue[_endOfQueue].delay = _fxList[fxNo].delay + 1;
		else
			_fxQueue[_endOfQueue].delay = 1;
		_endOfQueue++;
		return 1;
	}
	return 0;
}

void Sound::engine(void) {
	// first of all, add any random sfx to the queue...
	for (uint16 cnt = 0; cnt < TOTAL_FX_PER_ROOM; cnt++) {
		uint16 fxNo = _roomsFixedFx[Logic::_scriptVars[SCREEN]][cnt];
		if (fxNo) {
			if (_fxList[fxNo].type == FX_RANDOM) {
				if (_rnd.getRandomNumber(_fxList[fxNo].delay) == 0)
					addToQueue(fxNo);
			}
		} else
			break;
	}
	// now process the queue
	for (uint8 cnt2 = 0; cnt2 < _endOfQueue; cnt2++) {
		if (_fxQueue[cnt2].delay > 0) {
			_fxQueue[cnt2].delay--;
			if (_fxQueue[cnt2].delay == 0)
				playSample(&_fxQueue[cnt2]);
		} else {
			if (!_fxQueue[cnt2].handle.isActive()) { // sound finished
				_resMan->resClose(_fxList[_fxQueue[cnt2].id].sampleId);
				if (cnt2 != _endOfQueue-1)
					_fxQueue[cnt2] = _fxQueue[_endOfQueue - 1];
				_endOfQueue--;
			}
		}
	}
}

void Sound::fnStopFx(int32 fxNo) {
	_mixer->stopID(fxNo);
	for (uint8 cnt = 0; cnt < _endOfQueue; cnt++)
		if (_fxQueue[cnt].id == (uint32)fxNo) {
			if (!_fxQueue[cnt].delay) // sound was started
				_resMan->resClose(_fxList[_fxQueue[cnt].id].sampleId);
			if (cnt != _endOfQueue-1)
				_fxQueue[cnt] = _fxQueue[_endOfQueue-1];
			_endOfQueue--;
			return ;
		}
	debug(8, "fnStopFx: id not found in queue");
}

bool Sound::amISpeaking(void) {
	_waveVolPos++;
	return _waveVolume[_waveVolPos - 1];
}

bool Sound::speechFinished(void) {
	return !_speechHandle.isActive();
}

void Sound::newScreen(uint32 screen) {
	if (_currentCowFile != SwordEngine::_systemVars.currentCD) {
		if (_currentCowFile)
			closeCowSystem();
		initCowSystem();			
	}
}

void Sound::quitScreen(void) {
	// stop all running SFX
	while (_endOfQueue)
		fnStopFx(_fxQueue[0].id);
}

void Sound::playSample(QueueElement *elem) {
	uint8 *sampleData = (uint8*)_resMan->fetchRes(_fxList[elem->id].sampleId);
	for (uint16 cnt = 0; cnt < MAX_ROOMS_PER_FX; cnt++) {
		if (_fxList[elem->id].roomVolList[cnt].roomNo) {
			if ((_fxList[elem->id].roomVolList[cnt].roomNo == (int)Logic::_scriptVars[SCREEN]) ||
				(_fxList[elem->id].roomVolList[cnt].roomNo == -1)) {

					uint8 volL = (_fxList[elem->id].roomVolList[cnt].leftVol * 10 * _sfxVolL) / 255;
					uint8 volR = (_fxList[elem->id].roomVolList[cnt].rightVol * 10 * _sfxVolR) / 255;
					int8 pan = (volR - volL) / 2;
					uint8 volume = (volR + volL) / 2;
					uint32 size = READ_LE_UINT32(sampleData + 0x28);
					uint8 flags;
					if (READ_LE_UINT16(sampleData + 0x22) == 16)
						flags = SoundMixer::FLAG_16BITS | SoundMixer::FLAG_LITTLE_ENDIAN;
					else
						flags = SoundMixer::FLAG_UNSIGNED;
					if (READ_LE_UINT16(sampleData + 0x16) == 2)
						flags |= SoundMixer::FLAG_STEREO;
					_mixer->playRaw(&elem->handle, sampleData + 0x2C, size, 11025, flags, elem->id, volume, pan);
			}
		} else
			break;
	}
}

bool Sound::startSpeech(uint16 roomNo, uint16 localNo) {
	if (_cowHeader == NULL) {
		warning("Sound::startSpeech: COW file isn't open!");
		return false;
	}

	uint32 locIndex = _cowHeader[roomNo] >> 2;
	uint32 sampleSize = _cowHeader[locIndex + (localNo * 2)];
	uint32 index = _cowHeader[locIndex + (localNo * 2) - 1];
	debug(6, "startSpeech(%d, %d): locIndex %d, sampleSize %d, index %d", roomNo, localNo, locIndex, sampleSize, index);
	if (sampleSize) {
		uint32 size;
		int16 *data = uncompressSpeech(index + _cowHeaderSize, sampleSize, &size);
		uint8 speechVol = (_speechVolR + _speechVolL) / 2;
		int8 speechPan = (_speechVolR - _speechVolL) / 2;
		if (data)
			_mixer->playRaw(&_speechHandle, data, size, 11025, SPEECH_FLAGS, SOUND_SPEECH_ID, speechVol, speechPan);
		return true;
	} else
		return false;
}

int16 *Sound::uncompressSpeech(uint32 index, uint32 cSize, uint32 *size) {
	uint8 *fBuf = (uint8*)malloc(cSize);
	_cowFile.seek(index);
	_cowFile.read(fBuf, cSize);
	uint32 headerPos = 0;
	while ((READ_BE_UINT32(fBuf + headerPos) != 'data') && (headerPos < 100))
		headerPos++;
	if (headerPos < 100) {
		uint32 resSize = READ_LE_UINT32(fBuf + headerPos + 4) >> 1;
		int16 *srcData = (int16*)(fBuf + headerPos + 8);
		int16 *dstData = (int16*)malloc(resSize * 2);
		uint32 srcPos = 0;
		int16 *dstPos = dstData;
		cSize = (cSize - (headerPos + 8)) / 2;
		while (srcPos < cSize) {
			int16 length = (int16)READ_LE_UINT16(srcData + srcPos);
			srcPos++;
			if (length < 0) {
				length = -length;
				for (uint16 cnt = 0; cnt < (uint16)length; cnt++)
					*dstPos++ = srcData[srcPos];
				srcPos++;
			} else {
				memcpy(dstPos, srcData + srcPos, length * 2);
				dstPos += length;
				srcPos += length;
			}
		}
		free(fBuf);
		*size = resSize * 2;
		calcWaveVolume(dstData, resSize);
		return dstData;
	} else {
		free(fBuf);
		warning("Sound::uncompressSpeech(): DATA tag not found in wave header");
		*size = 0;
		return NULL;
	}
}

void Sound::calcWaveVolume(int16 *data, uint32 length) {
	int16 *blkPos = data + 918;
	uint32 cnt;
	for (cnt = 0; cnt < WAVE_VOL_TAB_LENGTH; cnt++)
		_waveVolume[cnt] = false;
	_waveVolPos = 0;
	for (uint32 blkCnt = 1; blkCnt < length / 918; blkCnt++) {
		if (blkCnt >= WAVE_VOL_TAB_LENGTH) {
			warning("Wave vol tab too small.");
			return;
		}
		int32 average = 0;		
		for (cnt = 0; cnt < 918; cnt++)
			average += blkPos[cnt];
		average /= 918;
		uint32 diff = 0;
		for (cnt = 0; cnt < 918; cnt++) {
			int16 smpDiff = *blkPos - average;
			diff += (uint32)ABS(smpDiff);
			blkPos++;
		}
		if (diff > WAVE_VOL_THRESHOLD)
			_waveVolume[blkCnt - 1] = true;
	}
}

void Sound::stopSpeech(void) {
	_mixer->stopID(SOUND_SPEECH_ID);
}

void Sound::initCowSystem(void) {
	char cowName[25];
	/* look for speech1/2.clu in the data dir
	   and speech/speech.clu (running from cd or using cd layout)
	*/
	sprintf(cowName, "SPEECH%d.CLU", SwordEngine::_systemVars.currentCD);
	_cowFile.open(cowName);
	if (!_cowFile.isOpen()) {
		_cowFile.open("speech.clu");
	}
	if (_cowFile.isOpen()) {
		_cowHeaderSize = _cowFile.readUint32LE();
		_cowHeader = (uint32*)malloc(_cowHeaderSize);
		if (_cowHeaderSize & 3)
			error("Unexpected cow header size %d", _cowHeaderSize);
		for (uint32 cnt = 0; cnt < (_cowHeaderSize / 4) - 1; cnt++)
			_cowHeader[cnt] = _cowFile.readUint32LE();
		_currentCowFile = SwordEngine::_systemVars.currentCD;
	} else
		warning("Sound::initCowSystem: Can't open SPEECH%d.CLU", SwordEngine::_systemVars.currentCD);	
}

void Sound::closeCowSystem(void) {
	if (_cowFile.isOpen())
		_cowFile.close();
	if (_cowHeader)
		free(_cowHeader);
	_cowHeader = NULL;
	_currentCowFile = 0;
}

} // End of namespace Sword1