/* 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.
 *
 */



#ifdef ENABLE_AGOS2

#include "common/endian.h"
#include "common/events.h"
#include "common/file.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/translation.h"

#include "graphics/cursorman.h"
#include "graphics/palette.h"
#include "graphics/surface.h"

#include "agos/animation.h"
#include "agos/intern.h"
#include "agos/agos.h"

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

#include "gui/message.h"

namespace AGOS {

MoviePlayer::MoviePlayer(AGOSEngine_Feeble *vm)
	: _vm(vm) {
	_mixer = _vm->_mixer;

	_leftButtonDown = false;
	_rightButtonDown = false;
	_skipMovie = false;

	memset(baseName, 0, sizeof(baseName));

	_ticks = 0;
	_bgSoundStream = nullptr;
}

MoviePlayer::~MoviePlayer() {
}

void MoviePlayer::play() {
	if (_vm->getBitFlag(40)) {
		_vm->setBitFlag(42, false);
		startSound();
		return;
	}

	_leftButtonDown = false;
	_rightButtonDown = false;
	_skipMovie = false;

	_vm->_mixer->stopAll();

	_ticks = _vm->_system->getMillis();

	startSound();

	playVideo();
	stopVideo();

	_vm->o_killAnimate();

	if (_vm->getBitFlag(41)) {
		_vm->fillBackFromFront();
	} else {
		uint8 palette[768];
		memset(palette, 0, sizeof(palette));
		_vm->clearSurfaces();
		_vm->_system->getPaletteManager()->setPalette(palette, 0, 256);
	}

	_vm->fillBackGroundFromBack();
	_vm->_fastFadeOutFlag = true;
}

void MoviePlayer::handleNextFrame() {
	Common::Event event;
	Common::EventManager *eventMan = _vm->_system->getEventManager();
	while (eventMan->pollEvent(event)) {
		switch (event.type) {
		case Common::EVENT_KEYDOWN:
			if (event.kbd.keycode == Common::KEYCODE_ESCAPE) {
				_leftButtonDown = true;
				_rightButtonDown = true;
			} else if (event.kbd.keycode == Common::KEYCODE_PAUSE) {
				_vm->pause();
			}
			break;
		case Common::EVENT_LBUTTONDOWN:
			_leftButtonDown = true;
			break;
		case Common::EVENT_RBUTTONDOWN:
			_rightButtonDown = true;
			break;
		case Common::EVENT_LBUTTONUP:
			_leftButtonDown = false;
			break;
		case Common::EVENT_RBUTTONUP:
			_rightButtonDown = false;
			break;
		default:
			break;
		}
	}

	if (_leftButtonDown && _rightButtonDown && !_vm->getBitFlag(41)) {
		_skipMovie = true;
		_mixer->stopHandle(_bgSound);
	}
}

///////////////////////////////////////////////////////////////////////////////
// Movie player for DXA movies
///////////////////////////////////////////////////////////////////////////////

const char *const MoviePlayerDXA::_sequenceList[90] = {
	"agent32",
	"Airlock",
	"Badluck",
	"bentalk1",
	"bentalk2",
	"bentalk3",
	"BigFight",
	"BLOWLAB",
	"breakdown",
	"bridge",
	"button2",
	"cargo",
	"COACH",
	"Colatalk",
	"cygnus2",
	"dream",
	"escape2",
	"FASALL",
	"fbikewurb",
	"feebdel",
	"Feebohno",
	"feebpump",
	"feefone1",
	"feefone2",
	"founder2",
	"founder3",
	"founder4",
	"fxmadsam",
	"fxwakeup",
	"gate",
	"Get Car",
	"getaxe",
	"getlift",
	"icetrench",
	"intomb1",
	"intomb2",
	"Jackpot",
	"knockout",
	"labocto",
	"longfeeb",
	"Mainmin",
	"maznat",
	"meetsquid",
	"mflirt",
	"mfxHappy",
	"Mix_Feeb1",
	"Mix_Feeb2",
	"Mix_Feeb3",
	"Mix_Guardscn",
	"Mlights1",
	"MLights2",
	"MProtest",
	"mudman",
	"munlock",
	"MUS5P2",
	"MUSOSP1",
	"Omenter",
	"Omnicofe",
	"OUTMIN~1",
	"Readbook",
	"Rebelhq",
	"RebelHQ2",
	"Reedin",
	"rescue1",
	"rescue2",
	"samcar",
	"Samdead",
	"scanner",
	"Sleepy",
	"spitbrai",
	"statue1",
	"statue2",
	"sva1",
	"sva2",
	"Teeter",
	"Temple2",
	"Temple3",
	"Temple4",
	"Temple5",
	"Temple6",
	"Temple7",
	"Temple8",
	"Tic-tac2",
	"torture",
	"transmit",
	"Typey",
	"ventfall",
	"ventoff",
	"wasting",
	"wurbatak"
};

MoviePlayerDXA::MoviePlayerDXA(AGOSEngine_Feeble *vm, const char *name)
	: MoviePlayer(vm) {
	debug(0, "Creating DXA cutscene player");

	memset(baseName, 0, sizeof(baseName));
	memcpy(baseName, name, strlen(name));

	_sequenceNum = 0;
}

bool MoviePlayerDXA::load() {
	if ((_vm->getPlatform() == Common::kPlatformAmiga || _vm->getPlatform() == Common::kPlatformMacintosh) &&
		_vm->_language != Common::EN_ANY) {
		_sequenceNum = 0;
		for (uint i = 0; i < 90; i++) {
			if (!scumm_stricmp(baseName, _sequenceList[i]))
				_sequenceNum = i;
		}
	}

	Common::String videoName = Common::String::format("%s.dxa", baseName);
	Common::File *videoStream = new Common::File();
	if (!videoStream->open(videoName))
		error("Failed to load video file %s", videoName.c_str());
	if (!loadStream(videoStream))
		error("Failed to load video stream from file %s", videoName.c_str());

	debug(0, "Playing video %s", videoName.c_str());

	CursorMan.showMouse(false);
	return true;
}

void MoviePlayerDXA::copyFrameToBuffer(byte *dst, uint x, uint y, uint pitch) {
	uint h = getHeight();
	uint w = getWidth();

	const Graphics::Surface *surface = decodeNextFrame();

	if (!surface)
		return;

	const byte *src = (const byte *)surface->getPixels();
	dst += y * pitch + x;

	do {
		memcpy(dst, src, w);
		dst += pitch;
		src += w;
	} while (--h);

	if (hasDirtyPalette())
		g_system->getPaletteManager()->setPalette(getPalette(), 0, 256);
}

void MoviePlayerDXA::playVideo() {
	// Most of the videos included in the Amiga version, reduced the
	// resolution to 384 x 280, so require the screen to be cleared,
	// before starting playing those videos.
	if (getWidth() == 384 && getHeight() == 280) {
		_vm->clearSurfaces();
	}

	while (!endOfVideo() && !_skipMovie && !_vm->shouldQuit())
		handleNextFrame();
}

void MoviePlayerDXA::stopVideo() {
	close();
	_mixer->stopHandle(_bgSound);
}

void MoviePlayerDXA::startSound() {
	start();

	if (_bgSoundStream != NULL) {
		_vm->_mixer->stopHandle(_bgSound);
		_vm->_mixer->playStream(Audio::Mixer::kSFXSoundType, &_bgSound, _bgSoundStream, -1, getVolume(), getBalance());
	}
}

void MoviePlayerDXA::nextFrame() {
	if (_bgSoundStream && _vm->_mixer->isSoundHandleActive(_bgSound) && needsUpdate()) {
		copyFrameToBuffer(_vm->getBackBuf(), 465, 222, _vm->_screenWidth);
		return;
	}

	if (_vm->_interactiveVideo == TYPE_LOOPING && endOfVideo()) {
		rewind();
		startSound();
	}

	if (!endOfVideo()) {
		if (_vm->_interactiveVideo == TYPE_OMNITV) {
			copyFrameToBuffer(_vm->getBackBuf(), 465, 222, _vm->_screenWidth);
		} else if (_vm->_interactiveVideo == TYPE_LOOPING) {
			copyFrameToBuffer(_vm->getBackBuf(), (_vm->_screenWidth - getWidth()) / 2, (_vm->_screenHeight - getHeight()) / 2, _vm->_screenWidth);
		}
	} else if (_vm->_interactiveVideo == TYPE_OMNITV) {
		close();
		_vm->_interactiveVideo = 0;
		_vm->_variableArray[254] = 6747;
	}
}

void MoviePlayerDXA::handleNextFrame() {
	if (processFrame())
		_vm->_system->updateScreen();

	MoviePlayer::handleNextFrame();
}

bool MoviePlayerDXA::processFrame() {
	Graphics::Surface *screen = _vm->_system->lockScreen();
	copyFrameToBuffer((byte *)screen->getPixels(), (_vm->_screenWidth - getWidth()) / 2, (_vm->_screenHeight - getHeight()) / 2, screen->pitch);
	_vm->_system->unlockScreen();

	uint32 soundTime = _mixer->getSoundElapsedTime(_bgSound);
	uint32 nextFrameStartTime = ((Video::VideoDecoder::VideoTrack *)getTrack(0))->getNextFrameStartTime();

	if ((_bgSoundStream == NULL) || soundTime < nextFrameStartTime) {

		if (_bgSoundStream && _mixer->isSoundHandleActive(_bgSound)) {
			while (_mixer->isSoundHandleActive(_bgSound) && soundTime < nextFrameStartTime) {
				_vm->_system->delayMillis(10);
				soundTime = _mixer->getSoundElapsedTime(_bgSound);
			}
			// In case the background sound ends prematurely, update
			// _ticks so that we can still fall back on the no-sound
			// sync case for the subsequent frames.
			_ticks = _vm->_system->getMillis();
		} else {
			_ticks += getTimeToNextFrame();
			while (_vm->_system->getMillis() < _ticks)
				_vm->_system->delayMillis(10);
		}

		return true;
	}

	warning("dropped frame %i", getCurFrame());
	return false;
}

void MoviePlayerDXA::readSoundData(Common::SeekableReadStream *stream) {
	uint32 tag = stream->readUint32BE();

	if (tag == MKTAG('W','A','V','E')) {
		uint32 size = stream->readUint32BE();

		if (_sequenceNum) {
			Common::File in;

			stream->skip(size);

			in.open("audio.wav");
			if (!in.isOpen()) {
				error("Can't read offset file 'audio.wav'");
			}

			in.seek(_sequenceNum * 8, SEEK_SET);
			uint32 offset = in.readUint32LE();
			size = in.readUint32LE();

			in.seek(offset, SEEK_SET);
			_bgSoundStream = Audio::makeWAVStream(in.readStream(size), DisposeAfterUse::YES);
			in.close();
		} else {
			_bgSoundStream = Audio::makeWAVStream(stream->readStream(size), DisposeAfterUse::YES);
		}
	} else {
		_bgSoundStream = Audio::SeekableAudioStream::openStreamFile(baseName);
	}
}

///////////////////////////////////////////////////////////////////////////////
// Movie player for Smacker movies
///////////////////////////////////////////////////////////////////////////////


MoviePlayerSMK::MoviePlayerSMK(AGOSEngine_Feeble *vm, const char *name)
	: MoviePlayer(vm), SmackerDecoder() {
	debug(0, "Creating SMK cutscene player");

	memset(baseName, 0, sizeof(baseName));
	memcpy(baseName, name, strlen(name));
}

bool MoviePlayerSMK::load() {
	Common::String videoName = Common::String::format("%s.smk", baseName);

	Common::File *videoStream = new Common::File();
	if (!videoStream->open(videoName))
		error("Failed to load video file %s", videoName.c_str());
	if (!loadStream(videoStream))
		error("Failed to load video stream from file %s", videoName.c_str());

	debug(0, "Playing video %s", videoName.c_str());

	CursorMan.showMouse(false);

	return true;
}

void MoviePlayerSMK::copyFrameToBuffer(byte *dst, uint x, uint y, uint pitch) {
	uint h = getHeight();
	uint w = getWidth();

	const Graphics::Surface *surface = decodeNextFrame();

	if (!surface)
		return;

	const byte *src = (const byte *)surface->getPixels();
	dst += y * pitch + x;

	do {
		memcpy(dst, src, w);
		dst += pitch;
		src += w;
	} while (--h);

	if (hasDirtyPalette())
		g_system->getPaletteManager()->setPalette(getPalette(), 0, 256);
}

void MoviePlayerSMK::playVideo() {
	while (!endOfVideo() && !_skipMovie && !_vm->shouldQuit())
		handleNextFrame();
}

void MoviePlayerSMK::stopVideo() {
	close();
}

void MoviePlayerSMK::startSound() {
	start();
}

void MoviePlayerSMK::handleNextFrame() {
	processFrame();

	MoviePlayer::handleNextFrame();
}

void MoviePlayerSMK::nextFrame() {
	if (_vm->_interactiveVideo == TYPE_LOOPING && endOfVideo())
		rewind();

	if (!endOfVideo()) {
		decodeNextFrame();
		if (_vm->_interactiveVideo == TYPE_OMNITV) {
			copyFrameToBuffer(_vm->getBackBuf(), 465, 222, _vm->_screenWidth);
		} else if (_vm->_interactiveVideo == TYPE_LOOPING) {
			copyFrameToBuffer(_vm->getBackBuf(), (_vm->_screenWidth - getWidth()) / 2, (_vm->_screenHeight - getHeight()) / 2, _vm->_screenWidth);
		}
	} else if (_vm->_interactiveVideo == TYPE_OMNITV) {
		close();
		_vm->_interactiveVideo = 0;
		_vm->_variableArray[254] = 6747;
	}
}

bool MoviePlayerSMK::processFrame() {
	Graphics::Surface *screen = _vm->_system->lockScreen();
	copyFrameToBuffer((byte *)screen->getPixels(), (_vm->_screenWidth - getWidth()) / 2, (_vm->_screenHeight - getHeight()) / 2, screen->pitch);
	_vm->_system->unlockScreen();

	uint32 waitTime = getTimeToNextFrame();

	if (!waitTime && !endOfVideoTracks()) {
		warning("dropped frame %i", getCurFrame());
		return false;
	}

	_vm->_system->updateScreen();

	// Wait before showing the next frame
	_vm->_system->delayMillis(waitTime);
	return true;
}

///////////////////////////////////////////////////////////////////////////////
// Factory function for creating the appropriate cutscene player
///////////////////////////////////////////////////////////////////////////////

MoviePlayer *makeMoviePlayer(AGOSEngine_Feeble *vm, const char *name) {
	char baseName[40];
	char filename[45];

	int baseLen = strlen(name) - 4;
	memset(baseName, 0, sizeof(baseName));
	memcpy(baseName, name, baseLen);

	if (vm->getLanguage() == Common::DE_DEU && baseLen >= 8) {
		// Check short filename to work around
		// bug in a German Windows 2CD version.
		char shortName[10];
		memset(shortName, 0, sizeof(shortName));
		memcpy(shortName, baseName, 6);

		sprintf(filename, "%s~1.dxa", shortName);
		if (Common::File::exists(filename)) {
			memset(baseName, 0, sizeof(baseName));
			memcpy(baseName, filename, 8);
		}

		sprintf(filename, "%s~1.smk", shortName);
		if (Common::File::exists(filename)) {
			memset(baseName, 0, sizeof(baseName));
			memcpy(baseName, filename, 8);
		}
	}

	sprintf(filename, "%s.dxa", baseName);
	if (Common::File::exists(filename)) {
		return new MoviePlayerDXA(vm, baseName);
	}

	sprintf(filename, "%s.smk", baseName);
	if (Common::File::exists(filename)) {
		return new MoviePlayerSMK(vm, baseName);
	}

	Common::String buf = Common::String::format(_("Cutscene file '%s' not found!"), baseName);
	GUI::MessageDialog dialog(buf, _("OK"));
	dialog.runModal();

	return NULL;
}

} // End of namespace AGOS

#endif // ENABLE_AGOS2
