/* ScummVM - Scumm Interpreter
 * Copyright (C) 2003-2006 The ScummVM project
 *
 * 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.
 *
 * $URL$
 * $Id$
 *
 */

#include "common/stdafx.h"
#include "common/endian.h"

#include "sword1/credits.h"
#include "sword1/screen.h"
#include "sword1/sword1.h"

#include "sound/audiostream.h"
#include "sound/mixer.h"

#include "common/file.h"
#include "common/util.h"
#include "common/system.h"


#define CREDITS_X 480
#define CREDITS_Y 300
#define BUFSIZE_Y 640

#define START_X ((640 - CREDITS_X) / 2)
#define START_Y ((480 - CREDITS_Y) / 2)

#define SCROLL_TIMING (2000 / 59) // 29.5 frames per second

#define LOGO_FADEUP_TIME (133 * 1000)
#define LOGO_FADEDOWN_TIME (163 * 1000)

namespace Sword1 {

enum {
	FONT_PAL = 0,
	FONT,
	TEXT,
	REVO_PAL,
	REVO_LOGO,
	F_EOF
};

enum {
	FNT_LFT = 0,   // left column
	FNT_RGT,	   // right column
	FNT_CEN,	   // centered
	FNT_BIG = 64,  // big font
	FNT_EOL = 128, // linebreak
	FNT_EOB = 255  // end of textblock
};


CreditsPlayer::CreditsPlayer(OSystem *pSystem, Audio::Mixer *pMixer) {
	_system = pSystem;
	_mixer = pMixer;
	_smlFont = _bigFont = NULL;
}

bool spaceInBuf(uint16 blitSta, uint16 blitEnd, uint16 renderDest) {
	if (blitEnd > blitSta) {
		if ((renderDest > blitEnd) || (renderDest + 15 < blitSta))
			return true;
	} else {
		if ((renderDest > blitEnd) && (renderDest + 15 < blitSta))
			return true;
	}
	return false;
}

void CreditsPlayer::play(void) {
	Audio::AudioStream *bgSoundStream = Audio::AudioStream::openStreamFile("credits");
	if (bgSoundStream == NULL) {
		warning("\"credits.ogg\" not found, skipping credits sequence");
		return;
	}
	ArcFile credFile;
	if (!credFile.open("credits.dat")) {
		warning("\"credits.dat\" not found, skipping credits sequence");
		return;
	}

	uint8 *palSrc = credFile.fetchFile(FONT_PAL, &_palLen);
	for (uint32 cnt = 0; cnt < _palLen; cnt++)
		_palette[(cnt / 3) * 4 + cnt % 3] = palSrc[cnt];
	_palLen /= 3;

	generateFonts(&credFile);

	uint8 *textData = credFile.fetchFile(TEXT);
	textData += READ_LE_UINT32(textData + SwordEngine::_systemVars.language * 4);

	uint8 *screenBuf = (uint8*)malloc(CREDITS_X * BUFSIZE_Y);
	memset(screenBuf, 0, CREDITS_X * BUFSIZE_Y);
	_system->copyRectToScreen(screenBuf, 640, 0, 0, 640, 480);
	_system->setPalette(_palette, 0, _palLen);

	// everything's initialized, time to render and show the credits.
	Audio::SoundHandle bgSound;
	_mixer->playInputStream(Audio::Mixer::kMusicSoundType, &bgSound, bgSoundStream, 0);

	int relDelay = 0;
	uint16 scrollY = 0;
	uint16 renderY = BUFSIZE_Y / 2;
	uint16 clearY = 0xFFFF;
	bool clearLine = false;
	while (((*textData != FNT_EOB) || (scrollY != renderY)) && !SwordEngine::_systemVars.engineQuit) {
		if ((int32)_mixer->getSoundElapsedTime(bgSound) - relDelay < (SCROLL_TIMING * 2)) { // sync to audio
			if (scrollY < BUFSIZE_Y - CREDITS_Y)
				_system->copyRectToScreen(screenBuf + scrollY * CREDITS_X, CREDITS_X, START_X, START_Y, CREDITS_X, CREDITS_Y);
			else {
				_system->copyRectToScreen(screenBuf + scrollY * CREDITS_X, CREDITS_X, START_X, START_Y, CREDITS_X, BUFSIZE_Y - scrollY);
				_system->copyRectToScreen(screenBuf, CREDITS_X, START_X, START_Y + BUFSIZE_Y - scrollY, CREDITS_X, CREDITS_Y - (BUFSIZE_Y - scrollY));
			}
			_system->updateScreen();
		} else
			warning("frame skipped");

		while (spaceInBuf(scrollY, (scrollY + CREDITS_Y) % BUFSIZE_Y, renderY) && (*textData != FNT_EOB)) {
			if (*textData & FNT_EOL) {
				renderY = (renderY + 16) % BUFSIZE_Y; // linebreak
				clearLine = true;
				*textData &= ~FNT_EOL;
			}
			if (spaceInBuf(scrollY, (scrollY + CREDITS_Y) % BUFSIZE_Y, renderY)) {
				if (clearLine)
					memset(screenBuf + renderY * CREDITS_X, 0, 16 * CREDITS_X);
				clearLine = false;
				renderLine(screenBuf, textData + 1, renderY, *textData);
				if (*textData & FNT_BIG)
					renderY += 16;
				while (*++textData != 0) // search for the start of next string
					;
				textData++;
			}
			if (*textData == FNT_EOB)
				clearY = renderY;
		}
		if ((*textData == FNT_EOB) && spaceInBuf(scrollY, (scrollY + CREDITS_Y) % BUFSIZE_Y, clearY)) {
			memset(screenBuf + clearY * CREDITS_X, 0, 16 * CREDITS_X);
			clearY = (clearY + 16) % BUFSIZE_Y;
		}

		relDelay += SCROLL_TIMING;
		delay(relDelay - (int32)_mixer->getSoundElapsedTime(bgSound));
		scrollY = (scrollY + 1) % BUFSIZE_Y;
	}
	free(_smlFont);
	free(_bigFont);
	_smlFont = _bigFont = NULL;
	free(screenBuf);

	// credits done, now show the revolution logo
	uint8 *revoBuf = credFile.decompressFile(REVO_LOGO);
	uint8 *revoPal = credFile.fetchFile(REVO_PAL, &_palLen);
	_palLen /= 3;
	while ((_mixer->getSoundElapsedTime(bgSound) < LOGO_FADEUP_TIME) && !SwordEngine::_systemVars.engineQuit) {
		delay(100);
	}
	memset(_palette, 0, 256 * 4);
	_system->setPalette(_palette, 0, 256);
	_system->copyRectToScreen(revoBuf, 480, START_X, START_Y, CREDITS_X, CREDITS_Y);
	_system->updateScreen();

	fadePalette(revoPal, true, _palLen);
	while ((_mixer->getSoundElapsedTime(bgSound) < LOGO_FADEDOWN_TIME) && !SwordEngine::_systemVars.engineQuit) {
		delay(100);
	}
	fadePalette(revoPal, false, _palLen);
	delay(3000);

	if (SwordEngine::_systemVars.engineQuit)
		_mixer->stopAll();
	free(revoBuf);
}

void CreditsPlayer::fadePalette(uint8 *srcPal, bool fadeup, uint16 len) {
	int8 fadeDir = fadeup ? 1 : -1;
	int fadeStart = fadeup ? 0 : 12;

	int relDelay = _system->getMillis();
	for (int fadeStep = fadeStart; (fadeStep >= 0) && (fadeStep <= 12) && !SwordEngine::_systemVars.engineQuit; fadeStep += fadeDir) {
		for (uint16 cnt = 0; cnt < len * 3; cnt++)
			_palette[(cnt / 3) * 4 + (cnt % 3)] = (srcPal[cnt] * fadeStep) / 12;
		_system->setPalette(_palette, 0, 256);
		relDelay += 1000 / 12;
		delay(relDelay - _system->getMillis());
	}
}

void CreditsPlayer::renderLine(uint8 *screenBuf, uint8 *line, uint16 yBufPos, uint8 flags) {
	uint8 *font;
	uint16 fntSize = 16;
	if (flags & FNT_BIG) {
		font = _bigFont;
		fntSize = 32;
		flags &= ~FNT_BIG;
	} else
		font = _smlFont;

	uint16 width = getWidth(font, line);
	uint16 xBufPos = (flags == FNT_CEN) ? (CREDITS_X - width) / 2 : ((flags == FNT_LFT) ? (234 - width) : 255);
	uint8 *bufDest = screenBuf + yBufPos * CREDITS_X + xBufPos;
	while (*line) {
		uint8 *chrSrc = font + _numChars + (*line - 1) * fntSize * fntSize;
		for (uint16 cnty = 0; cnty < fntSize; cnty++) {
			for (uint16 cntx = 0; cntx < fntSize; cntx++)
				bufDest[cnty * CREDITS_X + cntx] = chrSrc[cntx];
			chrSrc += fntSize;
		}
		bufDest += font[*line++ - 1];
	}
}

uint16 CreditsPlayer::getWidth(uint8 *font, uint8 *line) {
	uint16 width = 0;
	while (*line)
		width += font[*line++ - 1];
	return width;
}

void CreditsPlayer::generateFonts(ArcFile *arcFile) {
	_bigFont = arcFile->decompressFile(FONT);
	_numChars = *_bigFont;
	memmove(_bigFont, _bigFont + 1, _numChars * (32 * 32 + 1));
	_smlFont = (uint8*)malloc(_numChars * (32 * 32 + 1));
	uint8 *src = _bigFont + _numChars;
	uint8 *dst = _smlFont + _numChars;
	for (uint16 cnt = 0; cnt < _numChars; cnt++) {
		_smlFont[cnt] = (_bigFont[cnt]++ + 1) / 2; // width table
		for (uint16 cnty = 0; cnty < 16; cnty++) {
			for (uint16 cntx = 0; cntx < 16; cntx++) {
				uint8 resR = (uint8)((_palette[src[0] * 4 + 0] + _palette[src[1] * 4 + 0] + _palette[src[32] * 4 + 0] + _palette[src[33] * 4 + 0]) >> 2);
				uint8 resG = (uint8)((_palette[src[0] * 4 + 1] + _palette[src[1] * 4 + 1] + _palette[src[32] * 4 + 1] + _palette[src[33] * 4 + 1]) >> 2);
				uint8 resB = (uint8)((_palette[src[0] * 4 + 2] + _palette[src[1] * 4 + 2] + _palette[src[32] * 4 + 2] + _palette[src[33] * 4 + 2]) >> 2);
				*dst++ = getPalIdx(resR, resG, resB);
				src += 2;
			}
			src += 32;
		}
	}
}

uint8 CreditsPlayer::getPalIdx(uint8 r, uint8 g, uint8 b) {
	for (uint16 cnt = 0; cnt < _palLen; cnt++)
		if ((_palette[cnt * 4 + 0] == r) && (_palette[cnt * 4 + 1] == g) && (_palette[cnt * 4 + 2] == b))
			return (uint8)cnt;
	assert(_palLen < 256);
	_palette[_palLen * 4 + 0] = r;
	_palette[_palLen * 4 + 1] = g;
	_palette[_palLen * 4 + 2] = b;
	return (uint8)_palLen++;
}

void CreditsPlayer::delay(int msecs) {

	OSystem::Event event;
	uint32 start = _system->getMillis();
	do {
		while (_system->pollEvent(event)) {
			switch (event.type) {
			case OSystem::EVENT_QUIT:
				SwordEngine::_systemVars.engineQuit = true;
				break;
			default:
				break;
			}
		}

		_system->updateScreen();

		if (msecs > 0)
			_system->delayMillis(10);

	} while ((_system->getMillis() < start + msecs) && !SwordEngine::_systemVars.engineQuit);
}

ArcFile::ArcFile(void) {
	_buf = NULL;
}

ArcFile::~ArcFile(void) {
	if (_buf)
		free(_buf);
}

bool ArcFile::open(const char *name) {
	Common::File arc;
	if (!arc.open(name))
		return false;
	_bufPos = _buf = (uint8*)malloc(arc.size());
	arc.read(_buf, arc.size());
	arc.close();
	return true;
}

void ArcFile::enterPath(uint32 id) {
	_bufPos += READ_LE_UINT32(_bufPos + id * 4);
}

uint8 *ArcFile::fetchFile(uint32 fileId, uint32 *size) {
	if (size)
		*size = READ_LE_UINT32(_bufPos + (fileId + 1) * 4) - READ_LE_UINT32(_bufPos + fileId * 4);
	return _bufPos + READ_LE_UINT32(_bufPos + fileId * 4);
}

uint8 *ArcFile::decompressFile(uint32 fileId) {
	uint32 size;
	uint8 *srcBuf = fetchFile(fileId, &size);
	uint8 *dstBuf = (uint8*)malloc(READ_LE_UINT32(srcBuf));
	uint8 *srcPos = srcBuf + 4;
	uint8 *dstPos = dstBuf;
	while (srcPos < srcBuf + size) {
		uint16 len = READ_LE_UINT16(srcPos);
		memset(dstPos, 0, len);
		dstPos += len;
		srcPos += 2;
		if (srcPos < srcBuf + size) {
			len = *srcPos++;
			memcpy(dstPos, srcPos, len);
			dstPos += len;
			srcPos += len;
		}
	}
	return dstBuf;
}

} // end of namespace Sword1