/* 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "lastexpress/sound/entry.h"

#include "lastexpress/game/logic.h"
#include "lastexpress/game/savepoint.h"
#include "lastexpress/game/state.h"

#include "lastexpress/sound/queue.h"
#include "lastexpress/sound/sound.h"

#include "lastexpress/graphics.h"
#include "lastexpress/lastexpress.h"
#include "lastexpress/resource.h"

namespace LastExpress {

#define SOUNDCACHE_ENTRY_SIZE 92160
#define FILTER_BUFFER_SIZE 2940

//////////////////////////////////////////////////////////////////////////
// SoundEntry
//////////////////////////////////////////////////////////////////////////
SoundEntry::SoundEntry(LastExpressEngine *engine) : _engine(engine) {
	_status = 0;
	_tag = kSoundTagNone;

	_blockCount = 0;
	_startTime = 0;

	_stream = NULL;

	_volumeWithoutNIS = 0;
	_entity = kEntityPlayer;
	_initTimeMS = 0;
	_activateDelayMS = 0;
	_priority = 0;

	_subtitle = NULL;

	_soundStream = NULL;
}

SoundEntry::~SoundEntry() {
	// Entries that have been queued will have their streamed disposed automatically
	if (!_soundStream)
		SAFE_DELETE(_stream);

	SAFE_DELETE(_soundStream);

	_subtitle = NULL;
	_stream = NULL;

	// Zero passed pointers
	_engine = NULL;
}

void SoundEntry::open(Common::String name, SoundFlag flag, int priority) {
	_priority = priority;
	setupTag(flag);
	setupStatus(flag);
	loadStream(name);
}

void SoundEntry::close() {
	if (_soundStream) {
		delete _soundStream; // stops the sound in destructor
		_soundStream = NULL;
		_stream = NULL; // disposed by _soundStream
	}
	_status |= kSoundFlagClosed;

	// The original game remove the entry from the cache here,
	// but since we are called from within an iterator loop
	// we will remove the entry there
	// removeFromCache(entry);

	if (_subtitle) {
		_subtitle->close();
		SAFE_DELETE(_subtitle);
	}

	if (_entity) {
		if (_entity == kEntitySteam)
			getSound()->playAmbientSound(2);
		else if (_entity != kEntityTrain)
			getSavePoints()->push(kEntityPlayer, _entity, kActionEndSound);
	}
}

void SoundEntry::play(uint32 startTime) {
	if (_status & kSoundFlagClosed)
		return; // failed to load sound file

	if (!_stream)
		error("[SoundEntry::play] stream has been disposed");

	// Prepare sound stream
	if (_soundStream)
		error("[SoundEntry::play] already playing");

	// BUG: the original game never checks for sound type when loading subtitles.
	// NIS files and LNK files have the same base name,
	// so without extra caution NIS subtitles would be loaded for LNK sounds as well.
	// The original game instead separates calls to play() and setSubtitles()
	// and does not call setSubtitles() for linked-after sounds.
	// Unfortunately, that does not work well with save/load.
	if ((_status & kSoundTypeMask) != kSoundTypeLink && (_status & kSoundTypeMask) != kSoundTypeConcert) {
		// Get subtitles name
		uint32 size = (_name.size() > 4 ? _name.size() - 4 : _name.size());
		setSubtitles(Common::String(_name.c_str(), size));
	}

	_soundStream = new StreamedSound();

	_stream->seek(0);

	// Load the stream and start playing
	_soundStream->load(_stream, _status & kSoundVolumeMask, (_status & kSoundFlagLooped) != 0, startTime);

	_status |= kSoundFlagPlaying;
}

void SoundEntry::setupTag(SoundFlag flag) {
	switch (flag & kSoundTypeMask) {
	case kSoundTypeNormal:
		_tag = getSoundQueue()->generateNextTag();
		break;

	case kSoundTypeAmbient: {
		SoundEntry *previous2 = getSoundQueue()->getEntry(kSoundTagOldAmbient);
		if (previous2)
			previous2->fade();

		SoundEntry *previous = getSoundQueue()->getEntry(kSoundTagAmbient);
		if (previous) {
			previous->_tag = kSoundTagOldAmbient;
			previous->fade();
		}

		_tag = kSoundTagAmbient;
		}
		break;

	case kSoundTypeWalla: {
		SoundEntry *previous = getSoundQueue()->getEntry(kSoundTagWalla);
		if (previous) {
			previous->_tag = kSoundTagOldWalla;
			previous->fade();
		}

		_tag = kSoundTagWalla;
		}
		break;

	case kSoundTypeLink: {
		SoundEntry *previous = getSoundQueue()->getEntry(kSoundTagLink);
		if (previous)
			previous->_tag = kSoundTagOldLink;

		_tag = kSoundTagLink;
		}
		break;

	case kSoundTypeNIS: {
		SoundEntry *previous = getSoundQueue()->getEntry(kSoundTagNIS);
		if (previous)
			previous->_tag = kSoundTagOldNIS;

		_tag = kSoundTagNIS;
		}
		break;

	case kSoundTypeIntro: {
		SoundEntry *previous = getSoundQueue()->getEntry(kSoundTagIntro);
		if (previous)
			previous->_tag = kSoundTagOldMenu;

		_tag = kSoundTagIntro;
		}
		break;

	case kSoundTypeMenu: {
		SoundEntry *previous = getSoundQueue()->getEntry(kSoundTagMenu);
		if (previous)
			previous->_tag = kSoundTagOldMenu;

		_tag = kSoundTagMenu;
		}
		break;

	default:
		assert(false);
		break;
	}
}

void SoundEntry::setupStatus(SoundFlag flag) {
	_status = flag;

	// set the flag for the case that our savefiles
	// will be ever loaded by the original game
	if (!(_status & kSoundFlagLooped))
		_status |= kSoundFlagCloseOnDataEnd;
}

void SoundEntry::loadStream(Common::String name) {
	_name = name;

	// Load sound data
	_stream = getArchive(name);

	if (!_stream)
		_stream = getArchive("DEFAULT.SND");

	if (!_stream)
		_status = kSoundFlagClosed;

	// read total count of sound blocks for the case that our savefiles
	// will be ever loaded by the original game
	if (_stream) {
		_stream->readUint32LE();
		_blockCount = _stream->readUint16LE();
		_status |= kSoundFlagHeaderProcessed;
	}
}

void SoundEntry::setVolumeSmoothly(SoundFlag newVolume) {
	assert((newVolume & kSoundVolumeMask) == newVolume);

	if (_status & kSoundFlagFading)
		return;

	// the original game sets kSoundFlagVolumeChanging here
	uint32 requestedVolume = (uint32)newVolume;

	if (newVolume == kVolumeNone) {
		_status |= kSoundFlagFading;
	} else if (getSoundQueue()->getFlag() & 32) {
		_volumeWithoutNIS = requestedVolume;
		requestedVolume = requestedVolume / 2 + 1;
	}

	_status = (_status & ~kSoundVolumeMask) | requestedVolume;
	if (_soundStream)
		_soundStream->setVolumeSmoothly(requestedVolume);
}

bool SoundEntry::update() {
	if (_soundStream && _soundStream->isFinished())
		_status |= kSoundFlagClosed;

	if (_status & kSoundFlagClosed)
		return false;

	if (_status & kSoundFlagDelayedActivate) {
		// counter overflow is processed correctly
		if (_engine->_system->getMillis() - _initTimeMS >= _activateDelayMS) {
			_status &= ~kSoundFlagDelayedActivate;
			play();
		}
	} else {
		if (!(getSoundQueue()->getFlag() & 0x20)) {
			if (!(_status & kSoundFlagFixedVolume)) {
				if (_entity) {
					if (_entity < 0x80) {
						setVolume(getSound()->getSoundFlag(_entity));
					}
				}
			}
		}
		//if (_status & kSoundFlagHasUnreadData && !(_status & kSoundFlagMute) && v1->soundBuffer)
		//	Sound_FillSoundBuffer(v1);
	}

	return true;
}

void SoundEntry::setVolume(SoundFlag newVolume) {
	assert((newVolume & kSoundVolumeMask) == newVolume);

	if (newVolume == kVolumeNone) {
		_volumeWithoutNIS = 0;
	} else if (getSoundQueue()->getFlag() & 0x20 && _tag != kSoundTagNIS && _tag != kSoundTagLink) {
		setVolumeSmoothly(newVolume);
		return;
	}

	_status = (_status & ~kSoundVolumeMask) | newVolume;
	if (_soundStream)
		_soundStream->setVolume(_status & kSoundVolumeMask);
}

void SoundEntry::adjustVolumeIfNISPlaying() {
	if (getSoundQueue()->getFlag() & 32) {
		if (_tag != kSoundTagNIS && _tag != kSoundTagLink && _tag != kSoundTagConcert) {
			uint32 baseVolume = _status & kSoundVolumeMask;
			uint32 actualVolume = baseVolume / 2 + 1;

			assert((actualVolume & kSoundVolumeMask) == actualVolume);

			_volumeWithoutNIS = baseVolume;
			_status &= ~kSoundVolumeMask;
			_status |= actualVolume;
		}
	}
}

void SoundEntry::initDelayedActivate(unsigned activateDelay) {
	_initTimeMS = _engine->_system->getMillis();
	_activateDelayMS = activateDelay * 1000 / 15;
	_status |= kSoundFlagDelayedActivate;
}

void SoundEntry::setSubtitles(Common::String filename) {
	_subtitle = new SubtitleEntry(_engine);
	_subtitle->load(filename, this);

	if (_subtitle->getStatus() & 0x400) {
		_subtitle->close();
		SAFE_DELETE(_subtitle);
	} else {
		_status |= kSoundFlagHasSubtitles;
	}
}

void SoundEntry::saveLoadWithSerializer(Common::Serializer &s) {
	if (s.isLoading()) {
		// load the fields
		uint32 blocksLeft;

		s.syncAsUint32LE(_status);
		s.syncAsUint32LE(_tag);
		s.syncAsUint32LE(blocksLeft);
		s.syncAsUint32LE(_startTime);
		uint32 unused;
		s.syncAsUint32LE(unused);
		s.syncAsUint32LE(unused);
		s.syncAsUint32LE(_entity);

		uint32 activateDelay;
		s.syncAsUint32LE(activateDelay);
		s.syncAsUint32LE(_priority);

		char name[16];
		s.syncBytes((byte *)name, 16); // _linkAfter name, should be always empty for entries in savefile
		s.syncBytes((byte *)name, 16); // real name
		name[15] = 0;

		// load the sound
		_blockCount = blocksLeft + _startTime;

		// if we are loading a savefile from the original game
		// and this savefile has been saved at a bizarre moment,
		// we can see transient flags here.
		// Let's pretend that the IRQ handler has run once.
		if (_status & kSoundFlagPlayRequested)
			_status |= kSoundFlagPlaying;
		if (_status & (kSoundFlagCloseRequested | kSoundFlagFading))
			_status |= kSoundFlagClosed;
		_status &= kSoundVolumeMask
		         | kSoundFlagPlaying
		         | kSoundFlagClosed
		         | kSoundFlagCloseOnDataEnd
		         | kSoundFlagLooped
		         | kSoundFlagDelayedActivate
		         | kSoundFlagHasSubtitles
		         | kSoundFlagFixedVolume
		         | kSoundFlagHeaderProcessed
		         | kSoundTypeMask;

		loadStream(name); // also sets _name
		if (_status & kSoundFlagPlaying)
			play((_status & kSoundFlagLooped) ? 0 : _startTime); // also loads subtitles

		_initTimeMS = _engine->_system->getMillis();
		_activateDelayMS = activateDelay * 1000 / 30;

	} else {
		assert(_name.size() < 16);
		assert(needSaving());
		// we can save our flags as is
		// the original game can reconstruct kSoundFlagMute, kSoundFlagCyclicBuffer, kSoundFlagHasUnreadData,
		// and we set other important flags correctly
		s.syncAsUint32LE(_status);
		s.syncAsUint32LE(_tag);
		uint32 time = getTime();
		uint32 blocksLeft = _blockCount - time;
		s.syncAsUint32LE(blocksLeft);
		s.syncAsUint32LE(time);
		uint32 unused = 0;
		s.syncAsUint32LE(unused);
		s.syncAsUint32LE(unused);
		s.syncAsUint32LE(_entity);

		uint32 deltaMS = _initTimeMS + _activateDelayMS - _engine->_system->getMillis();
		if (deltaMS > 0x8000000u) // sanity check against overflow
			deltaMS = 0;
		uint32 delta = deltaMS * 30 / 1000;
		s.syncAsUint32LE(delta);
		s.syncAsUint32LE(_priority);

		char name[16] = {0};
		s.syncBytes((byte *)name, 16);

		strcpy((char *)name, _name.c_str());
		s.syncBytes((byte *)name, 16);
	}
}

//////////////////////////////////////////////////////////////////////////
// SubtitleEntry
//////////////////////////////////////////////////////////////////////////
SubtitleEntry::SubtitleEntry(LastExpressEngine *engine) : _engine(engine) {
	_status = 0;
	_sound = NULL;
	_data = NULL;
}

SubtitleEntry::~SubtitleEntry() {
	SAFE_DELETE(_data);

	// Zero-out passed pointers
	_sound = NULL;
	_engine = NULL;
}

void SubtitleEntry::load(Common::String filename, SoundEntry *soundEntry) {
	// Add ourselves to the list of active subtitles
	getSoundQueue()->addSubtitle(this);

	// Set sound entry and filename
	_filename = filename + ".SBE";
	_sound = soundEntry;

	// Load subtitle data
	if (_engine->getResourceManager()->hasFile(_filename)) {
		if (getSoundQueue()->getSubtitleFlag() & 2)
			return;

		loadData();
	} else {
		_status = kSoundFlagClosed;
	}
}

void SubtitleEntry::loadData() {
	_data = new SubtitleManager(_engine->getFont());
	_data->load(getArchive(_filename));

	getSoundQueue()->setSubtitleFlag(getSoundQueue()->getSubtitleFlag() | 2);
	getSoundQueue()->setCurrentSubtitle(this);
}

void SubtitleEntry::setupAndDraw() {
	if (!_sound)
		error("[SubtitleEntry::setupAndDraw] Sound entry not initialized");

	if (!_data) {
		_data = new SubtitleManager(_engine->getFont());
		_data->load(getArchive(_filename));
	}

	if (_data->getMaxTime() > _sound->getTime()) {
		_status = kSoundFlagClosed;
	} else {
		_data->setTime((uint16)_sound->getTime());

		if (getSoundQueue()->getSubtitleFlag() & 1)
			drawOnScreen();
	}

	getSoundQueue()->setCurrentSubtitle(this);

	// TODO Missing code
}

void SubtitleEntry::close() {
	// Remove ourselves from the queue
	getSoundQueue()->removeSubtitle(this);

	if (this == getSoundQueue()->getCurrentSubtitle()) {
		drawOnScreen();

		getSoundQueue()->setCurrentSubtitle(NULL);
		getSoundQueue()->setSubtitleFlag(0);
	}
}

void SubtitleEntry::drawOnScreen() {
	if (_data == NULL)
		return;

	getSoundQueue()->setSubtitleFlag(getSoundQueue()->getSubtitleFlag() & -2);
	_engine->getGraphicsManager()->draw(_data, GraphicsManager::kBackgroundOverlay);
}

} // End of namespace LastExpress