/* 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 "audio/audiostream.h"
#include "audio/mixer.h"
#include "audio/decoders/raw.h"

#include "toltecs/toltecs.h"
#include "toltecs/movie.h"
#include "toltecs/palette.h"
#include "toltecs/resource.h"
#include "toltecs/screen.h"
#include "toltecs/script.h"

namespace Toltecs {

enum ChunkTypes {
	kChunkFirstImage = 0,
	kChunkSubsequentImages = 1,
	kChunkPalette = 2,
	kChunkUnused = 3,
	kChunkAudio = 4,
	kChunkShowSubtitle = 5,
	kChunkShakeScreen = 6,
	kChunkSetupSubtitles = 7,
	kChunkStopSubtitles = 8
};

MoviePlayer::MoviePlayer(ToltecsEngine *vm) : _vm(vm), _isPlaying(false), _lastPrefetchOfs(0), _framesPerSoundChunk(0), _endPos(0), _audioStream(0) {
}

MoviePlayer::~MoviePlayer() {
}

void MoviePlayer::playMovie(uint resIndex) {
	const uint32 subtitleSlot = kMaxScriptSlots - 1;
	int16 savedSceneWidth = _vm->_sceneWidth;
	int16 savedSceneHeight = _vm->_sceneHeight;
	int16 savedCameraHeight = _vm->_cameraHeight;
	int16 savedCameraX = _vm->_cameraX;
	int16 savedCameraY = _vm->_cameraY;
	int16 savedGuiHeight = _vm->_guiHeight;
	byte moviePalette[768];

	_isPlaying = true;
	_vm->_isSaveAllowed = false;

	memset(moviePalette, 0, sizeof(moviePalette));

	_vm->_screen->finishTalkTextItems();

	_vm->_arc->openResource(resIndex);
	_endPos = _vm->_arc->pos() + _vm->_arc->getResourceSize(resIndex);

	/*_frameCount = */_vm->_arc->readUint32LE();
	uint32 chunkCount = _vm->_arc->readUint32LE();

	// TODO: Figure out rest of the header
	_vm->_arc->readUint32LE();
	_vm->_arc->readUint32LE();
	_framesPerSoundChunk = _vm->_arc->readUint32LE();
	int rate = _vm->_arc->readUint32LE();

	_vm->_sceneWidth = 640;
	_vm->_sceneHeight = 400;
	_vm->_cameraHeight = 400;
	_vm->_cameraX = 0;
	_vm->_cameraY = 0;
	_vm->_guiHeight = 0;

	_audioStream = Audio::makeQueuingAudioStream(rate, false);

	_vm->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_audioStreamHandle, _audioStream);

	_lastPrefetchOfs = 0;

	fetchAudioChunks();

	byte *chunkBuffer = NULL;
	uint32 chunkBufferSize = 0;
	uint32 frame = 0;
	bool abortMovie = false;
	uint32 soundChunkFramesLeft = 0;

	while (chunkCount-- && !abortMovie) {
		byte chunkType = _vm->_arc->readByte();
		uint32 chunkSize = _vm->_arc->readUint32LE();

		debug(0, "chunkType = %d; chunkSize = %d", chunkType, chunkSize);

		// Skip audio chunks - we've already queued them in
		// fetchAudioChunks()
		if (chunkType == kChunkAudio) {
			_vm->_arc->skip(chunkSize);
			soundChunkFramesLeft += _framesPerSoundChunk;
		} else {
			// Only reallocate the chunk buffer if the new chunk is bigger
			if (chunkSize > chunkBufferSize) {
				delete[] chunkBuffer;
				chunkBuffer = new byte[chunkSize];
				chunkBufferSize = chunkSize;
			}

			_vm->_arc->read(chunkBuffer, chunkSize);
		}

		switch (chunkType) {
		case kChunkFirstImage:
		case kChunkSubsequentImages:
			unpackRle(chunkBuffer, _vm->_screen->_backScreen);
			_vm->_screen->_fullRefresh = true;

			if (--soundChunkFramesLeft <= _framesPerSoundChunk) {
				fetchAudioChunks();
			}

			while (_vm->_mixer->getSoundElapsedTime(_audioStreamHandle) < (1000 * frame) / 9) {
				if (_vm->_screen->_shakeActive && _vm->_screen->updateShakeScreen()) {
					_vm->_screen->_fullRefresh = true;
				}
				if (!handleInput())
					abortMovie = true;
				_vm->drawScreen();
				// Note: drawScreen() calls delayMillis()
			}

			frame++;
			break;
		case kChunkPalette:
			unpackPalette(chunkBuffer, moviePalette, 256, 3);
			_vm->_palette->setFullPalette(moviePalette);
			break;
		case kChunkUnused:
			error("Chunk considered to be unused has been encountered");
		case kChunkAudio:
			// Already processed
			break;
		case kChunkShowSubtitle:
			memcpy(_vm->_script->getSlotData(subtitleSlot), chunkBuffer, chunkSize);
			// The last character of the subtitle determines if it should
			// always be displayed or not. If it's 0xFF, it should always be
			// displayed, otherwise, if it's 0xFE, it can be toggled.
			_vm->_screen->updateTalkText(subtitleSlot, 0, (chunkBuffer[chunkSize - 1] == 0xFF));
			break;
		case kChunkShakeScreen: // start/stop shakescreen effect
			if (chunkBuffer[0] == 0xFF)
				_vm->_screen->stopShakeScreen();
			else
				_vm->_screen->startShakeScreen(chunkBuffer[0]);
			break;
		case kChunkSetupSubtitles: // setup subtitle parameters
			_vm->_screen->_talkTextY = READ_LE_UINT16(chunkBuffer + 0);
			_vm->_screen->_talkTextX = READ_LE_UINT16(chunkBuffer + 2);
			_vm->_screen->_talkTextFontColor = ((chunkBuffer[4] << 4) & 0xF0) | ((chunkBuffer[4] >> 4) & 0x0F);
			debug(0, "_talkTextX = %d; _talkTextY = %d; _talkTextFontColor = %d",
				_vm->_screen->_talkTextX, _vm->_screen->_talkTextY, _vm->_screen->_talkTextFontColor);
			break;
		case kChunkStopSubtitles:
			_vm->_script->getSlotData(subtitleSlot)[0] = 0xFF;
			_vm->_screen->finishTalkTextItems();
			break;
		default:
			error("MoviePlayer::playMovie(%04X) Unknown chunk type %d at %08X", resIndex, chunkType, _vm->_arc->pos() - 5 - chunkSize);
		}

		if (!handleInput())
			abortMovie = true;
	}

	delete[] chunkBuffer;

	_audioStream->finish();
	_vm->_mixer->stopHandle(_audioStreamHandle);

	_vm->_arc->closeResource();

	debug(0, "playMovie() done");

	_vm->_sceneWidth = savedSceneWidth;
	_vm->_sceneHeight = savedSceneHeight;
	_vm->_cameraHeight = savedCameraHeight;
	_vm->_cameraX = savedCameraX;
	_vm->_cameraY = savedCameraY;
	_vm->_guiHeight = savedGuiHeight;

	_vm->_isSaveAllowed = true;
	_isPlaying = false;
}

void MoviePlayer::fetchAudioChunks() {
	uint32 startOfs = _vm->_arc->pos();
	uint prefetchChunkCount = 0;

	if (_lastPrefetchOfs != 0)
		_vm->_arc->seek(_lastPrefetchOfs, SEEK_SET);

	while (prefetchChunkCount < _framesPerSoundChunk / 2 && _vm->_arc->pos() < _endPos) {
		byte chunkType = _vm->_arc->readByte();
		uint32 chunkSize = _vm->_arc->readUint32LE();
		if (chunkType == kChunkAudio) {
			byte *chunkBuffer = (byte *)malloc(chunkSize);
			_vm->_arc->read(chunkBuffer, chunkSize);
			_audioStream->queueBuffer(chunkBuffer, chunkSize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED);
			chunkBuffer = NULL;
			prefetchChunkCount++;
		} else {
			_vm->_arc->seek(chunkSize, SEEK_CUR);
		}
	}

	_lastPrefetchOfs = _vm->_arc->pos();

	_vm->_arc->seek(startOfs, SEEK_SET);
}

void MoviePlayer::unpackPalette(byte *source, byte *dest, int elemCount, int elemSize) {
	int ofs = 0, size = elemCount * elemSize;
	while (ofs < size) {
		byte len;
		len = *source++;
		if (len == 0) {
			len = *source++;
		} else {
			byte value = *source++;
			memset(dest, value, len);
		}
		ofs += len;
		dest += len;
	}
}

void MoviePlayer::unpackRle(byte *source, byte *dest) {
	int size = 256000;	// 640x400
	//int packedSize = 0;
	while (size > 0) {
		byte a = *source++;
		byte b = *source++;
		//packedSize += 2;
		if (a == 0) {
			dest += b;
			size -= b;
		} else {
			memset(dest, b, a);
			dest += a;
			size -= a;
		}
	}
	//debug("Packed RLE size: %d", packedSize);
}

bool MoviePlayer::handleInput() {
	Common::Event event;
	Common::EventManager *eventMan = g_system->getEventManager();
	while (eventMan->pollEvent(event)) {
		switch (event.type) {
		case Common::EVENT_KEYDOWN:
			if (event.kbd.keycode == Common::KEYCODE_ESCAPE)
				return false;
			if (event.kbd.keycode == Common::KEYCODE_F10) {
				// TODO: The original would bring up a stripped down
				// main menu dialog, without the save/restore options.
			}
			break;
		case Common::EVENT_LBUTTONDOWN:
		case Common::EVENT_RBUTTONDOWN:
			return false;
		case Common::EVENT_QUIT:
			return false;
		default:
			break;
		}
	}
	return !_vm->shouldQuit();
}

} // End of namespace Toltecs