/* 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 "graphics/cursorman.h"

#include "bagel/spacebar/baglib/fmovie.h"
#include "bagel/boflib/string.h"
#include "bagel/boflib/palette.h"
#include "bagel/spacebar/boflib/app.h"
#include "bagel/spacebar/baglib/master_win.h"
#include "bagel/spacebar/baglib/pan_window.h"
#include "bagel/spacebar/boflib/std_keys.h"

namespace Bagel {
namespace SpaceBar {

CBagFMovie::CBagFMovie(CBofWindow *parent, const char *filename, CBofRect *bounds, bool useNewPalette, bool blackOutWindow) {
	// Allow movie to not shift to new palette.
	_useNewPaletteFl = useNewPalette;

	// Black out first and last frame of flythroughs and examine movies
	_blackOutWindowFl = blackOutWindow;

	CBagFMovie::initialize(parent);
	CBagFMovie::open(filename, bounds);
}

CBagFMovie::~CBagFMovie() {
	CBagFMovie::closeMovie();
}

ErrorCode CBagFMovie::initialize(CBofWindow *pParent) {
	// Movie Stuff
	_movieStatus = MOVIE_STOPPED;
	_escCanStopFl = true;
	_smackerPal = nullptr;
	_bmpBuf = nullptr;
	_filterBmp = nullptr;
	_bufferStart = nullptr;
	_bufferLength = 0;

	// Smacker Stuff
	_smk = nullptr;
	_loopFl = false;

	// Call dialog box creates
	if (create("MovieWin", 0, 0, 1, 1, pParent, 1) == ERR_NONE) {
		setCapture();
	}

	return ERR_NONE;
}

bool CBagFMovie::open(const char *filename, CBofRect *bounds) {
	// No filename, so put up an open file box
	if (filename == nullptr) {
		assert(filename);
		return false;
	}

	if (bounds != nullptr) {
		_cRect = *bounds;
	}

	if (openMovie(filename)) {
		// We were given specific rect for movie
		if (bounds)
			reSize(bounds, true);
		else
			// Center the movie to the parent window
			centerRect();

		// Paint the image to the screen.
		_filterBmp->paint(this, 0, 0);

		return true;
	}

	return false;
}

bool CBagFMovie::openMovie(const char *sFilename) {
	assert(sFilename[0] != '\0');

	if (_smk) {
		closeMovie();
	}
	_smk = new Video::SmackerDecoder();
	_smk->setSoundType(Audio::Mixer::kSFXSoundType);

	// Opened failed ?
	if (!_smk->loadFile(sFilename)) {
		error("Movie not found=%s", sFilename);
	}


	// Allocate the bitmaps.
	_smackerPal = new CBofPalette();

	_bmpBuf = new CBofBitmap(_smk->getWidth(), _smk->getHeight(), _smackerPal, false);

	_filterBmp = new CBofBitmap(_smk->getWidth(), _smk->getHeight(), _smackerPal, false);
	_filterBmp->lock();

	selectPalette(_smackerPal);

	if (_bmpBuf) {
		_bmpBuf->lock();
		_bmpBuf->fillRect(nullptr, _smackerPal->getNearestIndex(CTEXT_WHITE));

		_bufferStart = (char *)_bmpBuf->getPixelAddress(0, _bmpBuf->isTopDown() ? 0 : (_bmpBuf->height() - 1));
		_bufferLength = ABS(_bmpBuf->height() * _bmpBuf->width());

		const Graphics::Surface *frame = _smk->decodeNextFrame();
		_smackerPal->setData(_smk->getPalette());
		if (frame) {
			_bmpBuf->getSurface().blitFrom(*frame);
		}
	}
	const bool repaintFl = true;

	_bounds = CBofRect(0, 0, (uint16)_bmpBuf->width() - 1, (uint16)_bmpBuf->height() - 1);
	reSize(&_bounds, repaintFl);

	CBagMasterWin *curWin = CBagel::getBagApp()->getMasterWnd();
	if (curWin != nullptr) {
		CBagStorageDevWnd *curSDev = curWin->getCurrentStorageDev();
		if ((curSDev != nullptr) && curSDev->isFiltered()) {
			const uint16 filterId = curSDev->getFilterId();
			const FilterFunction filterFunction = curSDev->getFilter();
			_bmpBuf->paint(_filterBmp);
			(*filterFunction)(filterId, _filterBmp, &_bounds);
		}
	}

	return true;
}

void CBagFMovie::onKeyHit(uint32 keyCode, uint32 /* repCount */) {
	if (_escCanStopFl && keyCode == BKEY_ESC) {
		// Clean up and exit
		_loopFl = false;
		stop();
		onMovieDone();
	}
}

void CBagFMovie::onMainLoop() {
	if (!_smk->needsUpdate() || (_movieStatus == MOVIE_STOPPED))
		return;


	// Smack the current frame into the buffer
	const Graphics::Surface *frame = _smk->decodeNextFrame();
	if (_smk->hasDirtyPalette()) {
		_smackerPal->setData(_smk->getPalette());
	}
	if (frame) {
		_bmpBuf->getSurface().blitFrom(*frame);
	}

	_bmpBuf->paint1To1(_filterBmp);

	// Filter the bitmap.
	CBagMasterWin *curWin = CBagel::getBagApp()->getMasterWnd();
	if (curWin != nullptr) {
		CBagStorageDevWnd *curSDev = curWin->getCurrentStorageDev();
		if ((curSDev != nullptr) && curSDev->isFiltered()) {
			const uint16 filterId = curSDev->getFilterId();
			const FilterFunction filterFunction = curSDev->getFilter();
			(*filterFunction)(filterId, _filterBmp, &_bounds);
		}
	}

	// Paint the buffer to the screen.
	_filterBmp->paint(this, 0, 0);

	if (_movieStatus == MOVIE_FORWARD) {
		if (_smk->getCurFrame() == (int)_smk->getFrameCount() - 1) {
			if (_loopFl == false) {
				onMovieDone();
			} else {
				seekToStart();
				_smk->start();
			}
		}
	} else if (_movieStatus == MOVIE_REVERSE) {
		if (_smk->getCurFrame() == 0 || _smk->getCurFrame() == 1) {
			if (_loopFl == false) {
				onMovieDone();
			} else {
				seekToEnd();
			}
		} else {
			setFrame(_smk->getCurFrame() - 2); // HACK: Reverse playback
		}
	}
}

void CBagFMovie::onPaint(CBofRect *) {
}

void CBagFMovie::closeMovie() {
	delete _smk;
	_smk = nullptr;

	if (_filterBmp != nullptr) {
		_filterBmp->unlock();
		delete _filterBmp;
		_filterBmp = nullptr;
	}

	if (_bmpBuf != nullptr) {
		_bmpBuf->unlock();
		delete _bmpBuf;
		_bmpBuf = nullptr;
	}

	delete _smackerPal;
	_smackerPal = nullptr;

	_bufferStart = nullptr;
	_bufferLength = 0;
}


void CBagFMovie::onClose() {
	closeMovie();
	CBofDialog::onClose();
}


void CBagFMovie::onMovieDone() {
	if (!_loopFl) {
		if (_bCaptured)
			releaseCapture();

		getParent()->enable();
		_bEndDialog = true;
	}
}


bool CBagFMovie::play(bool loop, bool escCanStop) {
	_escCanStopFl = escCanStop;
	_loopFl = loop;

	const bool retVal = play();

	getParent()->disable();
	getParent()->flushAllMessages();

	CursorMan.showMouse(false);

	doModal();

	CursorMan.showMouse(true);

	return retVal;
}


bool CBagFMovie::play() {
	if (!_smk)
		return false;

	_smk->pauseVideo(false);
	// _smk->setReverse(false); // TODO: Not supported by SMK
	_smk->start();
	_movieStatus = MOVIE_FORWARD;
	return true;
}

bool CBagFMovie::reverse(bool loop, bool escCanStop) {
	_escCanStopFl = escCanStop;
	_loopFl = loop;

	const bool retVal = reverse();

	getParent()->disable();
	getParent()->flushAllMessages();
	doModal();

	return retVal;
}

bool CBagFMovie::reverse() {
	if (!_smk)
		return false;

	_smk->pauseVideo(false);
	// _smk->setReverse(true); // TODO: Not supported by SMK
	_smk->start();
	_movieStatus = MOVIE_REVERSE;
	return true;
}

bool CBagFMovie::stop() {
	if (!_smk)
		return false;

	_smk->stop();
	_movieStatus = MOVIE_STOPPED;
	return true;
}

bool CBagFMovie::pause() {
	if (!_smk)
		return false;

	_smk->pauseVideo(true);
	_movieStatus = MOVIE_PAUSED;
	return true;

}

bool CBagFMovie::seekToStart() {
	if (!_smk)
		return false;

	_smk->rewind();
	return true;
}

bool CBagFMovie::seekToEnd() {
	if (!_smk)
		return false;

	setFrame(_smk->getFrameCount() - 2); // HACK: Reverse rewind
	return true;
}

uint32 CBagFMovie::getFrame() {
	if (_smk) {
		return _smk->getCurFrame();
	}

	return (uint32) -1;
}

bool CBagFMovie::setFrame(uint32 frameNum) {
	if (!_smk)
		return false;

	frameNum = CLIP<uint32>(frameNum, 0, _smk->getFrameCount() - 1);
	_smk->forceSeekToFrame(frameNum);
	return true;
}

bool CBagFMovie::centerRect() {
	CBofRect clientRect = getParent()->getClientRect();
	const RECT parentRect = clientRect.getWinRect();
	const int clientWidth = parentRect.right - parentRect.left;
	const int clientHeight = parentRect.bottom - parentRect.top;

	// Get Movies width and height
	const int movieWidth = _smk->getWidth();
	const int movieHeight = _smk->getHeight();

	RECT movieBounds;
	movieBounds.left = (clientWidth - movieWidth) / 2;
	movieBounds.top = (clientHeight - movieHeight) / 2;
	movieBounds.right = movieBounds.left + movieWidth;
	movieBounds.bottom = movieBounds.top + movieHeight;

	// Reposition the playback window
	clientRect = movieBounds;
	reSize(&clientRect, true);

	return true;
}

} // namespace SpaceBar
} // namespace Bagel
