/* 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 "made/pmvplayer.h"
#include "made/made.h"
#include "made/screen.h"
#include "made/graphics.h"

#include "common/file.h"
#include "common/debug.h"
#include "common/system.h"
#include "common/events.h"

#include "audio/decoders/raw.h"
#include "audio/audiostream.h"

#include "graphics/surface.h"

namespace Made {

PmvPlayer::PmvPlayer(MadeEngine *vm, Audio::Mixer *mixer) : _fd(nullptr), _vm(vm), _mixer(mixer) {
	_audioStream = nullptr;
	_surface = nullptr;
	_aborted = false;
}

PmvPlayer::~PmvPlayer() {
}

bool PmvPlayer::play(const char *filename) {
	_aborted = false;
	_surface = nullptr;

	_fd = new Common::File();
	if (!_fd->open(filename)) {
		delete _fd;
		return false;
	}

	uint32 chunkType, chunkSize, prevChunkSize = 0;

	readChunk(chunkType, chunkSize);	// "MOVE"
	if (chunkType != MKTAG('M','O','V','E')) {
		warning("Unexpected PMV video header, expected 'MOVE'");
		delete _fd;
		return false;
	}

	readChunk(chunkType, chunkSize);	// "MHED"
	if (chunkType != MKTAG('M','H','E','D')) {
		warning("Unexpected PMV video header, expected 'MHED'");
		delete _fd;
		return false;
	}

	uint frameDelay = _fd->readUint16LE();
	_fd->skip(4);	// always 0?
	uint frameCount = _fd->readUint16LE();
	_fd->skip(4);	// always 0?

	uint soundFreq = _fd->readUint16LE();
	// Note: There seem to be weird sound frequencies in PMV videos.
	// Not sure why, but leaving those original frequencies intact
	// results to sound being choppy. Therefore, we set them to more
	// "common" values here (11025 instead of 11127 and 22050 instead
	// of 22254)
	if (soundFreq == 11127)
		soundFreq = 11025;

	if (soundFreq == 22254)
		soundFreq = 22050;

	for (int i = 0; i < 22; i++) {
		int unk = _fd->readUint16LE();
		debug(2, "%i ", unk);
	}

	_mixer->stopAll();

	// Read palette
	_fd->read(_paletteRGB, 768);
	_vm->_screen->setRGBPalette(_paletteRGB);

	uint32 frameNumber = 0;
	uint16 chunkCount = 0;
	uint32 soundSize = 0;
	uint32 soundChunkOfs = 0, palChunkOfs = 0;
	uint32 palSize = 0;
	byte *frameData = nullptr, *audioData, *soundData, *palData, *imageData;
	bool firstTime = true;

	uint32 skipFrames = 0;

	uint32 bytesRead;
	uint16 width, height, cmdOffs, pixelOffs, maskOffs, lineSize;

	// TODO: Sound can still be a little choppy. A bug in the decoder or -
	// perhaps more likely - do we have to implement double buffering to
	// get it to work well?
	_audioStream = Audio::makeQueuingAudioStream(soundFreq, false);

	SoundDecoderData *soundDecoderData = new SoundDecoderData();

	while (!_vm->shouldQuit() && !_aborted && !_fd->eos() && frameNumber < frameCount) {

		int32 frameTime = _vm->getTotalPlayTime();

		readChunk(chunkType, chunkSize);
		if (chunkType != MKTAG('M','F','R','M')) {
			warning("Unknown chunk type");
		}

		// Only reallocate the frame data buffer if its size has changed
		if (prevChunkSize != chunkSize || !frameData) {
			delete[] frameData;
			frameData = new byte[chunkSize];
		}

		prevChunkSize = chunkSize;

		bytesRead = _fd->read(frameData, chunkSize);

		if (bytesRead < chunkSize || _fd->eos())
			break;

		soundChunkOfs = READ_LE_UINT32(frameData + 8);
		palChunkOfs = READ_LE_UINT32(frameData + 16);

		// Handle audio
		if (soundChunkOfs) {
			audioData = frameData + soundChunkOfs - 8;
			chunkSize = READ_LE_UINT16(audioData + 4);
			chunkCount = READ_LE_UINT16(audioData + 6);

			debug(1, "chunkCount = %d; chunkSize = %d; total = %d\n", chunkCount, chunkSize, chunkCount * chunkSize);

			soundSize = chunkCount * chunkSize;
			soundData = (byte *)malloc(soundSize);
			decompressSound(audioData + 8, soundData, chunkSize, chunkCount, nullptr, soundDecoderData);
			_audioStream->queueBuffer(soundData, soundSize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED);
		}

		// Handle palette
		if (palChunkOfs) {
			palData = frameData + palChunkOfs - 8;
			palSize = READ_LE_UINT32(palData + 4);
			decompressPalette(palData + 8, _paletteRGB, palSize);
			_vm->_screen->setRGBPalette(_paletteRGB);
		}

		// Handle video
		imageData = frameData + READ_LE_UINT32(frameData + 12) - 8;

		// frameNum @0
		width = READ_LE_UINT16(imageData + 8);
		height = READ_LE_UINT16(imageData + 10);
		cmdOffs = READ_LE_UINT16(imageData + 12);
		pixelOffs = READ_LE_UINT16(imageData + 16);
		maskOffs = READ_LE_UINT16(imageData + 20);
		lineSize = READ_LE_UINT16(imageData + 24);

		debug(2, "width = %d; height = %d; cmdOffs = %04X; pixelOffs = %04X; maskOffs = %04X; lineSize = %d\n",
			width, height, cmdOffs, pixelOffs, maskOffs, lineSize);

		if (!_surface) {
			_surface = new Graphics::Surface();
			_surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
		}

		decompressMovieImage(imageData, *_surface, cmdOffs, pixelOffs, maskOffs, lineSize);

		if (firstTime) {
			_mixer->playStream(Audio::Mixer::kSFXSoundType, &_audioStreamHandle, _audioStream);
			skipFrames = 0;
			firstTime = false;
		}

		handleEvents();
		updateScreen();

		if (skipFrames == 0) {
			uint32 soundElapsedTime = _vm->_mixer->getElapsedTime(_audioStreamHandle).msecs();
			int32 waitTime = (frameNumber * frameDelay) -
				soundElapsedTime - (_vm->getTotalPlayTime() - frameTime);

			if (waitTime < 0) {
				skipFrames = -waitTime / frameDelay;
				warning("Video A/V sync broken, skipping %d frame(s)", skipFrames + 1);
			} else if (waitTime > 0)
				g_system->delayMillis(waitTime);

		} else
			skipFrames--;

		frameNumber++;

	}

	delete soundDecoderData;
	delete[] frameData;

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

	//delete _audioStream;
	delete _fd;

	if(_surface)
		_surface->free();

	delete _surface;

	return !_aborted;

}

void PmvPlayer::readChunk(uint32 &chunkType, uint32 &chunkSize) {
	chunkType = _fd->readUint32BE();
	chunkSize = _fd->readUint32LE();

	debug(2, "ofs = %08X; chunkType = %c%c%c%c; chunkSize = %d\n",
		(int)_fd->pos(),
		(chunkType >> 24) & 0xFF, (chunkType >> 16) & 0xFF, (chunkType >> 8) & 0xFF, chunkType & 0xFF,
		chunkSize);

}

void PmvPlayer::handleEvents() {
	Common::Event event;
	while (_vm->_system->getEventManager()->pollEvent(event)) {
		switch (event.type) {
		case Common::EVENT_KEYDOWN:
			if (event.kbd.keycode == Common::KEYCODE_ESCAPE)
				_aborted = true;
			break;
		default:
			break;
		}
	}
}

void PmvPlayer::updateScreen() {
	_vm->_system->copyRectToScreen(_surface->getPixels(), _surface->pitch,
									(320 - _surface->w) / 2, (200 - _surface->h) / 2, _surface->w, _surface->h);
	_vm->_system->updateScreen();
}

void PmvPlayer::decompressPalette(byte *palData, byte *outPal, uint32 palDataSize) {
	byte *palDataEnd = palData + palDataSize;
	while (palData < palDataEnd) {
		byte count = *palData++;
		byte entry = *palData++;
		if (count == 255 && entry == 255)
			break;
		memcpy(&outPal[entry * 3], palData, (count + 1) * 3);
		palData += (count + 1) * 3;
	}
}

}
