/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 */


#include "common/scummsys.h"

#include "audio/audiostream.h"
#include "audio/decoders/flac.h"
#include "audio/decoders/voc.h"
#include "audio/decoders/vorbis.h"
#include "audio/decoders/mp3.h"

#include "scumm/resource.h"
#include "scumm/scumm.h"
#include "scumm/imuse_digi/dimuse_bndmgr.h"
#include "scumm/imuse_digi/dimuse_codecs.h"
#include "scumm/imuse_digi/dimuse_sndmgr.h"

namespace Scumm {

ImuseDigiSndMgr::ImuseDigiSndMgr(ScummEngine *scumm) {
	for (int l = 0; l < MAX_IMUSE_SOUNDS; l++) {
		memset(&_sounds[l], 0, sizeof(SoundDesc));
	}
	_vm = scumm;
	_disk = 0;
	_cacheBundleDir = new BundleDirCache(scumm);
	assert(_cacheBundleDir);
	BundleCodecs::initializeImcTables();
}

ImuseDigiSndMgr::~ImuseDigiSndMgr() {
	for (int l = 0; l < MAX_IMUSE_SOUNDS; l++) {
		closeSound(&_sounds[l]);
	}

	delete _cacheBundleDir;
	BundleCodecs::releaseImcTables();
}

ImuseDigiSndMgr::SoundDesc *ImuseDigiSndMgr::allocSlot() {
	for (int l = 0; l < MAX_IMUSE_SOUNDS; l++) {
		if (!_sounds[l].inUse) {
			_sounds[l].inUse = true;
			_sounds[l].scheduledForDealloc = false;
			return &_sounds[l];
		}
	}

	return nullptr;
}

bool ImuseDigiSndMgr::openMusicBundle(SoundDesc *sound, int &disk) {
	bool result = false;
	bool compressed = false;

	sound->bundle = new BundleMgr(_vm, _cacheBundleDir);
	assert(sound->bundle);
	if (_vm->_game.id == GID_CMI) {
		if (_vm->_game.features & GF_DEMO) {
			result = sound->bundle->open("music.bun", compressed);
		} else {
			char musicfile[20];
			if (disk == -1)
				disk = _vm->VAR(_vm->VAR_CURRENTDISK);
			Common::sprintf_s(musicfile, "musdisk%d.bun", disk);
//			if (_disk != _vm->VAR(_vm->VAR_CURRENTDISK)) {
//				_vm->_DiMUSE_v1->parseScriptCmds(0x1000, 0, 0, 0, 0, 0, 0, 0);
//				_vm->_DiMUSE_v1->parseScriptCmds(0x2000, 0, 0, 0, 0, 0, 0, 0);
//				_vm->_DiMUSE_v1->stopAllSounds();
//				sound->bundle->closeFile();
//			}

			result = sound->bundle->open(musicfile, compressed);

			// FIXME: Shouldn't we only set _disk if result == true?
			_disk = (byte)_vm->VAR(_vm->VAR_CURRENTDISK);
		}
	} else if (_vm->_game.id == GID_DIG)
		result = sound->bundle->open("digmusic.bun", compressed);
	else
		error("ImuseDigiSndMgr::openMusicBundle() Don't know which bundle file to load");

	_vm->VAR(_vm->VAR_MUSIC_BUNDLE_LOADED) = result ? 1 : 0;

	return result;
}

bool ImuseDigiSndMgr::openVoiceBundle(SoundDesc *sound, int &disk) {
	bool result = false;
	bool compressed = false;

	sound->bundle = new BundleMgr(_vm, _cacheBundleDir);
	assert(sound->bundle);
	if (_vm->_game.id == GID_CMI) {
		if (_vm->_game.features & GF_DEMO) {
			result = sound->bundle->open("voice.bun", compressed);
		} else {
			char voxfile[20];
			if (disk == -1)
				disk = _vm->VAR(_vm->VAR_CURRENTDISK);
			Common::sprintf_s(voxfile, "voxdisk%d.bun", disk);
//			if (_disk != _vm->VAR(_vm->VAR_CURRENTDISK)) {
//				_vm->_DiMUSE_v1->parseScriptCmds(0x1000, 0, 0, 0, 0, 0, 0, 0);
//				_vm->_DiMUSE_v1->parseScriptCmds(0x2000, 0, 0, 0, 0, 0, 0, 0);
//				_vm->_DiMUSE_v1->stopAllSounds();
//				sound->bundle->closeFile();
//			}

			result = sound->bundle->open(voxfile, compressed);

			// FIXME: Shouldn't we only set _disk if result == true?
			_disk = (byte)_vm->VAR(_vm->VAR_CURRENTDISK);
		}
	} else if (_vm->_game.id == GID_DIG)
		result = sound->bundle->open("digvoice.bun", compressed);
	else
		error("ImuseDigiSndMgr::openVoiceBundle() Don't know which bundle file to load");

	_vm->VAR(_vm->VAR_VOICE_BUNDLE_LOADED) = result ? 1 : 0;

	return result;
}

ImuseDigiSndMgr::SoundDesc *ImuseDigiSndMgr::openSound(int32 soundId, const char *soundName, int soundType, int volGroupId, int disk) {
	assert(soundId >= 0);
	assert(soundType);

	SoundDesc *sound = allocSlot();
	if (!sound) {
		error("ImuseDigiSndMgr::openSound() can't alloc free sound slot");
	}

	const bool header_outside = ((_vm->_game.id == GID_CMI) && !(_vm->_game.features & GF_DEMO));
	bool result = false;
	byte *ptr = nullptr;

	switch (soundType) {
	case IMUSE_RESOURCE:
		assert(soundName[0] == 0);	// Paranoia check

		_vm->_res->lock(rtSound, soundId);
		ptr = _vm->getResourceAddress(rtSound, soundId);
		if (ptr == nullptr) {
			closeSound(sound);
			return nullptr;
		}
		sound->resPtr = ptr;
		sound->resSize = _vm->getResourceSize(rtSound, soundId) - 8;
		break;
	case IMUSE_BUNDLE:
		if (volGroupId == IMUSE_VOLGRP_VOICE)
			result = openVoiceBundle(sound, disk);
		else if (volGroupId == IMUSE_VOLGRP_MUSIC)
			result = openMusicBundle(sound, disk);
		else
			error("ImuseDigiSndMgr::openSound() Don't know how load sound: %d", soundId);
		if (!result) {
			closeSound(sound);
			return nullptr;
		}

		if (soundName[0] != 0) {
			if (sound->bundle->readFile(soundName, 0x2000, &ptr, header_outside) == 0 || ptr == nullptr) {
				closeSound(sound);
				free(ptr);
				return nullptr;
			}
		}

		sound->resPtr = nullptr;
		break;
	default:
		error("ImuseDigiSndMgr::openSound() Unknown soundType %d (trying to load sound %d)", soundType, soundId);
	}

	Common::strlcpy(sound->name, soundName, sizeof(sound->name));
	sound->soundId = soundId;

	if (soundType == IMUSE_BUNDLE) {
		free(ptr);
	}
	return sound;
}

void ImuseDigiSndMgr::closeSound(SoundDesc *soundDesc) {
	// Check if there's an actual sound to close...
	if (!checkForProperHandle(soundDesc))
		return;

	if (soundDesc->resPtr) {
		bool found = false;
		for (int l = 0; l < MAX_IMUSE_SOUNDS; l++) {
			if ((_sounds[l].soundId == soundDesc->soundId) && (&_sounds[l] != soundDesc))
				found = true;
		}
		if (!found)
			_vm->_res->unlock(rtSound, soundDesc->soundId);
	}

	delete soundDesc->bundle;

	memset(soundDesc, 0, sizeof(SoundDesc));
}

ImuseDigiSndMgr::SoundDesc *ImuseDigiSndMgr::findSoundById(int soundId) {
	SoundDesc *soundDesc = nullptr;
	for (int i = 0; i < MAX_IMUSE_SOUNDS; i++) {
		if (_sounds[i].soundId == soundId) {
			soundDesc = &_sounds[i];
			break;
		}
	}
	return soundDesc;
}

ImuseDigiSndMgr::SoundDesc *ImuseDigiSndMgr::getSounds() {
	return _sounds;
}

void ImuseDigiSndMgr::scheduleSoundForDeallocation(int soundId) {
	SoundDesc *soundDesc = nullptr;
	for (int i = 0; i < MAX_IMUSE_SOUNDS; i++) {
		if (_sounds[i].soundId == soundId) {
			soundDesc = &_sounds[i];
		}
	}

	// Check if there's an actual sound to deallocate...
	if (!checkForProperHandle(soundDesc))
		return;

	soundDesc->scheduledForDealloc = true;
}

void ImuseDigiSndMgr::closeSoundById(int soundId) {
	SoundDesc *soundDesc = nullptr;
	for (int i = 0; i < MAX_IMUSE_SOUNDS; i++) {
		if (_sounds[i].soundId == soundId) {
			soundDesc = &_sounds[i];
		}
	}

	if (soundDesc) {
		assert(checkForProperHandle(soundDesc));

		if (soundDesc->resPtr) {
			_vm->_res->unlock(rtSound, soundDesc->soundId);
		}

		delete soundDesc->bundle;

		memset(soundDesc, 0, sizeof(SoundDesc));
	}
}

bool ImuseDigiSndMgr::checkForProperHandle(SoundDesc *soundDesc) {
	if (!soundDesc)
		return false;
	for (int l = 0; l < MAX_IMUSE_SOUNDS; l++) {
		if (soundDesc == &_sounds[l])
			return true;
	}
	return false;
}

} // End of namespace Scumm
