/* 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.
 *
 * Additional copyright for this file:
 * Copyright (C) 1994-1998 Revolution Software Ltd.
 *
 * 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: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-0-12-0/engines/sword2/palette.cpp $
 * $Id: palette.cpp 32369 2008-05-29 20:32:27Z eriktorbjorn $
 */


#include "common/system.h"

#include "sword2/sword2.h"
#include "sword2/defs.h"
#include "sword2/header.h"
#include "sword2/logic.h"
#include "sword2/resman.h"
#include "sword2/screen.h"

namespace Sword2 {

/**
 * Start layer palette fading up
 */

void Screen::startNewPalette() {
	// If the screen is still fading down then wait for black - could
	// happen when everythings cached into a large memory model
	waitForFade();

	byte *screenFile = _vm->_resman->openResource(_thisScreen.background_layer_id);

	memcpy(_paletteMatch, _vm->fetchPaletteMatchTable(screenFile), PALTABLESIZE);
	setPalette(0, 256, _vm->fetchPalette(screenFile), RDPAL_FADE);

	// Indicating that it's a screen palette
	_lastPaletteRes = 0;

	_vm->_resman->closeResource(_thisScreen.background_layer_id);
	fadeUp();
	_thisScreen.new_palette = 0;
}

void Screen::setFullPalette(int32 palRes) {
	// fudge for hut interior
	// - unpausing should restore last palette as normal (could be screen
	// palette or 'dark_palette_13')
	// - but restoring the screen palette after 'dark_palette_13' should
	// now work properly too!

	// "Hut interior" refers to the watchman's hut in Marseille, and this
	// is apparently needed for the palette to be restored properly when
	// you turn the light off. (I didn't even notice the light switch!)

	if (_vm->_logic->readVar(LOCATION) == 13) {
		// unpausing
		if (palRes == -1) {
			// restore whatever palette was last set (screen
			// palette or 'dark_palette_13')
			palRes = _lastPaletteRes;
		}
	} else {
		// check if we're just restoring the current screen palette
		// because we might actually need to use a separate palette
		// file anyway eg. for pausing & unpausing during the eclipse

		// unpausing (fudged for location 13)
		if (palRes == -1) {
			// we really meant '0'
			palRes = 0;
		}

		if (palRes == 0 && _lastPaletteRes)
			palRes = _lastPaletteRes;
	}

	// If non-zero, set palette to this separate palette file. Otherwise,
	// set palette to current screen palette.

	if (palRes) {
		byte *pal = _vm->_resman->openResource(palRes);

		assert(_vm->_resman->fetchType(pal) == PALETTE_FILE);

		pal += ResHeader::size();

		// always set colour 0 to black because most background screen
		// palettes have a bright colour 0 although it should come out
		// as black in the game!

		pal[0] = 0;
		pal[1] = 0;
		pal[2] = 0;
		pal[3] = 0;

		setPalette(0, 256, pal, RDPAL_INSTANT);
		_vm->_resman->closeResource(palRes);
	} else {
		if (_thisScreen.background_layer_id) {
			byte *data = _vm->_resman->openResource(_thisScreen.background_layer_id);
			memcpy(_paletteMatch, _vm->fetchPaletteMatchTable(data), PALTABLESIZE);
			setPalette(0, 256, _vm->fetchPalette(data), RDPAL_INSTANT);
			_vm->_resman->closeResource(_thisScreen.background_layer_id);
		} else
			error("setFullPalette(0) called, but no current screen available!");
	}

	if (palRes != CONTROL_PANEL_PALETTE)
		_lastPaletteRes = palRes;
}

/**
 * Matches a colour triplet to a palette index.
 * @param r red colour component
 * @param g green colour component
 * @param b blue colour component
 * @return the palette index of the closest matching colour in the palette
 */

// FIXME: This used to be inlined - probably a good idea - but the
// linker complained when I tried to use it in sprite.cpp.

uint8 Screen::quickMatch(uint8 r, uint8 g, uint8 b) {
	return _paletteMatch[((int32)(r >> 2) << 12) + ((int32)(g >> 2) << 6) + (b >> 2)];
}

/**
 * Sets the palette.
 * @param startEntry the first colour entry to set
 * @param noEntries the number of colour entries to set
 * @param colourTable the new colour entries
 * @param fadeNow whether to perform the change immediately or delayed
 */

void Screen::setPalette(int16 startEntry, int16 noEntries, byte *colourTable, uint8 fadeNow) {
	assert(noEntries > 0);

	memcpy(&_palette[4 * startEntry], colourTable, noEntries * 4);

	if (fadeNow == RDPAL_INSTANT) {
		setSystemPalette(_palette, startEntry, noEntries);
		setNeedFullRedraw();
	}
}

void Screen::dimPalette(bool dim) {
	if (dim != _dimPalette) {
		_dimPalette = dim;
		setSystemPalette(_palette, 0, 256);
		setNeedFullRedraw();
	}
}

/**
 * Fades the palette up from black to the current palette.
 * @param time the time it will take the palette to fade up
 */

int32 Screen::fadeUp(float time) {
	if (getFadeStatus() != RDFADE_BLACK && getFadeStatus() != RDFADE_NONE)
		return RDERR_FADEINCOMPLETE;

	_fadeTotalTime = (int32)(time * 1000);
	_fadeStatus = RDFADE_UP;
	_fadeStartTime = _vm->_system->getMillis();

	return RD_OK;
}

/**
 * Fades the palette down to black from the current palette.
 * @param time the time it will take the palette to fade down
 */

int32 Screen::fadeDown(float time) {
	if (getFadeStatus() != RDFADE_BLACK && getFadeStatus() != RDFADE_NONE)
		return RDERR_FADEINCOMPLETE;

	_fadeTotalTime = (int32)(time * 1000);
	_fadeStatus = RDFADE_DOWN;
	_fadeStartTime = _vm->_system->getMillis();

	return RD_OK;
}

/**
 * Get the current fade status
 * @return RDFADE_UP (fading up), RDFADE_DOWN (fading down), RDFADE_NONE
 * (not faded), or RDFADE_BLACK (completely faded down)
 */

uint8 Screen::getFadeStatus() {
	return _fadeStatus;
}

void Screen::waitForFade() {
	while (getFadeStatus() != RDFADE_NONE && getFadeStatus() != RDFADE_BLACK && !_vm->_quit) {
		updateDisplay();
		_vm->_system->delayMillis(20);
	}
}

void Screen::fadeServer() {
	static int32 previousTime = 0;
	byte fadePalette[256 * 4];
	byte *newPalette = fadePalette;
	int32 currentTime;
	int16 fadeMultiplier;
	int16 i;

	// If we're not in the process of fading, do nothing.
	if (getFadeStatus() != RDFADE_UP && getFadeStatus() != RDFADE_DOWN)
		return;

	// I don't know if this is necessary, but let's limit how often the
	// palette is updated, just to be safe.
	currentTime = _vm->_system->getMillis();
	if (currentTime - previousTime <= 25)
		return;

	previousTime = currentTime;

	if (getFadeStatus() == RDFADE_UP) {
		if (currentTime >= _fadeStartTime + _fadeTotalTime) {
			_fadeStatus = RDFADE_NONE;
			newPalette = _palette;
		} else {
			fadeMultiplier = (int16)(((int32)(currentTime - _fadeStartTime) * 256) / _fadeTotalTime);
			for (i = 0; i < 256; i++) {
				newPalette[i * 4 + 0] = (_palette[i * 4 + 0] * fadeMultiplier) >> 8;
				newPalette[i * 4 + 1] = (_palette[i * 4 + 1] * fadeMultiplier) >> 8;
				newPalette[i * 4 + 2] = (_palette[i * 4 + 2] * fadeMultiplier) >> 8;
			}
		}
	} else {
		if (currentTime >= _fadeStartTime + _fadeTotalTime) {
			_fadeStatus = RDFADE_BLACK;
			memset(newPalette, 0, sizeof(fadePalette));
		} else {
			fadeMultiplier = (int16)(((int32)(_fadeTotalTime - (currentTime - _fadeStartTime)) * 256) / _fadeTotalTime);
			for (i = 0; i < 256; i++) {
				newPalette[i * 4 + 0] = (_palette[i * 4 + 0] * fadeMultiplier) >> 8;
				newPalette[i * 4 + 1] = (_palette[i * 4 + 1] * fadeMultiplier) >> 8;
				newPalette[i * 4 + 2] = (_palette[i * 4 + 2] * fadeMultiplier) >> 8;
			}
		}
	}

	setSystemPalette(newPalette, 0, 256);
	setNeedFullRedraw();
}

void Screen::setSystemPalette(const byte *colors, uint start, uint num) {
	const byte *palette;

	if (_dimPalette) {
		byte pal[256 * 4];

		for (uint i = start * 4; i < 4 * (start + num); i++)
			pal[i] = colors[i] / 2;

		palette = pal;
	} else
		palette = colors;

	_vm->_system->setPalette(palette, start, num);
}

} // End of namespace Sword2
