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

// Video script opcodes for Simon1/Simon2


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

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

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

namespace AGOS {

void AGOSEngine_Elvira2::setupVideoOpcodes(VgaOpcodeProc *op) {
	AGOSEngine::setupVideoOpcodes(op);

	op[17] = &AGOSEngine::vc17_waitEnd;
	op[19] = &AGOSEngine::vc19_loop;
	op[22] = &AGOSEngine::vc22_setPalette;
	op[28] = &AGOSEngine::vc28_playSFX;
	op[32] = &AGOSEngine::vc32_saveScreen;
	op[37] = &AGOSEngine::vc37_pokePalette;
	op[45] = &AGOSEngine::vc45_setWindowPalette;
	op[46] = &AGOSEngine::vc46_setPaletteSlot1;
	op[47] = &AGOSEngine::vc47_setPaletteSlot2;
	op[48] = &AGOSEngine::vc48_setPaletteSlot3;
	op[53] = &AGOSEngine::vc53_dissolveIn;
	op[54] = &AGOSEngine::vc54_dissolveOut;
	op[57] = &AGOSEngine::vc57_blackPalette;
	op[56] = &AGOSEngine::vc56_fullScreen;
	op[58] = &AGOSEngine::vc58_checkCodeWheel;
	op[59] = &AGOSEngine::vc59_ifEGA;
}

void AGOSEngine::vc43_ifBitSet() {
	if (!getBitFlag(vcReadNextWord())) {
		vcSkipNextInstruction();
	}
}

void AGOSEngine::vc44_ifBitClear() {
	if (getBitFlag(vcReadNextWord())) {
		vcSkipNextInstruction();
	}
}

void AGOSEngine::vc45_setWindowPalette() {
	uint16 num = vcReadNextWord();
	uint16 color = vcReadNextWord();

	const uint16 *vlut = &_videoWindows[num * 4];
	uint8 width = vlut[2] * 8;
	uint8 height = vlut[3];

	if (num == 4) {
		byte *dst = (byte *)_window4BackScn->getPixels();

		for (uint8 h = 0; h < height; h++) {
			for (uint8 w = 0; w < width; w++) {
				uint16 val = READ_LE_UINT16(dst + w * 2);
				val &= 0xF0F;
				val |= color * 16;
				WRITE_LE_UINT16(dst + w * 2, val);
			}
			dst += width * 2;
		}
	} else {
		Graphics::Surface *screen = getBackendSurface();
		byte *dst = (byte *)screen->getBasePtr(vlut[0] * 16, vlut[1]);

		if (getGameType() == GType_ELVIRA2 && num == 7) {
			dst -= 8;
			width += 4;
		}

		for (uint8 h = 0; h < height; h++) {
			for (uint8 w = 0; w < width; w++) {
				uint16 val = READ_LE_UINT16(dst + w * 2);
				val &= 0xF0F;
				val |= color * 16;
				WRITE_LE_UINT16(dst + w * 2, val);
			}
			dst += screen->pitch;
		}

		updateBackendSurface();
	}
}

void AGOSEngine::setPaletteSlot(uint16 srcOffs, uint8 dstOffs) {
	byte *offs, *palptr, *src;
	uint16 num;

	palptr = _displayPalette + dstOffs * 3 * 16;
	offs = _curVgaFile1 + READ_BE_UINT16(_curVgaFile1 + 6);
	src = offs + srcOffs * 32;
	num = 16;

	do {
		uint16 color = READ_BE_UINT16(src);
		palptr[0] = ((color & 0xf00) >> 8) * 32;
		palptr[1] = ((color & 0x0f0) >> 4) * 32;
		palptr[2] = ((color & 0x00f) >> 0) * 32;

		palptr += 3;
		src += 2;
	} while (--num);

	_paletteFlag = 2;
}

void AGOSEngine::vc46_setPaletteSlot1() {
	uint16 srcOffs = vcReadNextWord();
	setPaletteSlot(srcOffs, 1);
}

void AGOSEngine::vc47_setPaletteSlot2() {
	uint16 srcOffs = vcReadNextWord();
	setPaletteSlot(srcOffs, 2);
}

void AGOSEngine::vc48_setPaletteSlot3() {
	uint16 srcOffs = vcReadNextWord();
	setPaletteSlot(srcOffs, 3);
}

void AGOSEngine::vc49_setBit() {
	uint16 bit = vcReadNextWord();
	if (getGameType() == GType_FF && bit == 82) {
		_variableArrayPtr = _variableArray2;
	}
	setBitFlag(bit, true);
}

void AGOSEngine::vc50_clearBit() {
	uint16 bit = vcReadNextWord();
	if (getGameType() == GType_FF && bit == 82) {
		_variableArrayPtr = _variableArray;
	}
	setBitFlag(bit, false);
}

void AGOSEngine::vc51_enableBox() {
	enableBox(vcReadNextWord());
}

void AGOSEngine::vc52_playSound() {
	bool ambient = false;

	uint16 sound = vcReadNextWord();
	if (sound >= 0x8000) {
		ambient = true;
		sound = -sound;
	}

	if (getGameType() == GType_FF || getGameType() == GType_PP) {
		int16 pan = vcReadNextWord();
		int16 vol = vcReadNextWord();

		if (ambient)
			loadSound(sound, pan, vol, Sound::TYPE_AMBIENT);
		else
			loadSound(sound, pan, vol, Sound::TYPE_SFX);
	} else if (getGameType() == GType_SIMON2) {
		if (ambient)
			_sound->playAmbient(sound);
		else
			_sound->playEffects(sound);
	} else if (getFeatures() & GF_TALKIE) {
		_sound->playEffects(sound);
	} else {
		// Waxworks uses 2 opcodes to play SFX: vc28 for digital SFX and vc52
		// for MIDI SFX. If a sound effect has both a MIDI and a digital
		// version, both opcodes are triggered. Only one of them should play
		// a sound effect.
		playSfx(sound, 0, 0, false, getGameType() == GType_WW);
	}
}

void AGOSEngine::vc53_dissolveIn() {
	uint16 num = vcReadNextWord();
	uint16 speed = vcReadNextWord() + 1;

	byte *src, *dst, *srcOffs, *srcOffs2, *dstOffs, *dstOffs2;
	int16 xoffs, yoffs;
	uint8 color = 0;

	// Only uses Video Window 4
	num = 4;

	uint16 dissolveX = _videoWindows[num * 4 + 2] * 8;
	uint16 dissolveY = (_videoWindows[num * 4 + 3] + 1) / 2;
	uint16 dissolveCheck = dissolveY * dissolveX * 4;
	uint16 dissolveDelay = dissolveCheck * 2 / speed;
	uint16 dissolveCount = dissolveCheck * 2 / speed;

	int16 x = _videoWindows[num * 4 + 0] * 16;
	int16 y = _videoWindows[num * 4 + 1];

	uint16 count = dissolveCheck * 2;
	while (count--) {
		Graphics::Surface *screen = getBackendSurface();
		byte *dstPtr = (byte *)screen->getBasePtr(x, y);

		yoffs = _rnd.getRandomNumber(dissolveY);
		dst = dstPtr + yoffs * screen->pitch;
		src = (byte *)_window4BackScn->getBasePtr(0, yoffs);

		xoffs = _rnd.getRandomNumber(dissolveX);
		dst += xoffs;
		src += xoffs;

		*dst &= color;
		*dst |= *src & 0xF;

		dstOffs = dst;
		srcOffs = src;

		xoffs = dissolveX * 2 - 1 - (xoffs * 2);
		dst += xoffs;
		src += xoffs;

		*dst &= color;
		*dst |= *src & 0xF;

		srcOffs2 = src;
		dstOffs2 = dst;

		yoffs = (dissolveY - 1) * 2 - (yoffs * 2);
		src = srcOffs + yoffs * _window4BackScn->pitch;
		dst = dstOffs + yoffs * screen->pitch;

		color = 0xF0;
		*dst &= color;
		*dst |= *src & 0xF;

		dst = dstOffs2 + yoffs * screen->pitch;
		src = srcOffs2 + yoffs * _window4BackScn->pitch;

		*dst &= color;
		*dst |= *src & 0xF;

		updateBackendSurface();

		dissolveCount--;
		if (!dissolveCount) {
			if (count >= dissolveCheck)
				dissolveDelay++;

			dissolveCount = dissolveDelay;
			delay(1);
		}
	}
}

void AGOSEngine::vc54_dissolveOut() {
	uint16 num = vcReadNextWord();
	uint16 color = vcReadNextWord();
	uint16 speed = vcReadNextWord() + 1;

	byte *dst, *dstOffs;
	int16 xoffs, yoffs;

	uint16 dissolveX = _videoWindows[num * 4 + 2] * 8;
	uint16 dissolveY = (_videoWindows[num * 4 + 3] + 1) / 2;
	uint16 dissolveCheck = dissolveY * dissolveX * 4;
	uint16 dissolveDelay = dissolveCheck * 2 / speed;
	uint16 dissolveCount = dissolveCheck * 2 / speed;

	int16 x = _videoWindows[num * 4 + 0] * 16;
	int16 y = _videoWindows[num * 4 + 1];

	uint16 count = dissolveCheck * 2;
	while (count--) {
		Graphics::Surface *screen = getBackendSurface();
		byte *dstPtr = (byte *)screen->getBasePtr(x, y);
		color |= dstPtr[0] & 0xF0;

		yoffs = _rnd.getRandomNumber(dissolveY);
		xoffs = _rnd.getRandomNumber(dissolveX);
		dst = dstPtr + xoffs + yoffs * screen->pitch;
		*dst = color;

		dstOffs = dst;

		xoffs = dissolveX * 2 - 1 - (xoffs * 2);
		dst += xoffs;
		*dst = color;

		yoffs = (dissolveY - 1) * 2 - (yoffs * 2);
		dst = dstOffs + yoffs * screen->pitch;
		*dst = color;

		dst += xoffs;
		*dst = color;

		updateBackendSurface();

		dissolveCount--;
		if (!dissolveCount) {
			if (count >= dissolveCheck)
				dissolveDelay++;

			dissolveCount = dissolveDelay;
			delay(1);
		}
	}
}

void AGOSEngine::vc55_moveBox() {
	HitArea *ha = _hitAreas;
	uint count = ARRAYSIZE(_hitAreas);
	uint16 id = vcReadNextWord();
	int16 x = vcReadNextWord();
	int16 y = vcReadNextWord();

	for (;;) {
		if (ha->id == id) {
			ha->x += x;
			ha->y += y;
			break;
		}
		ha++;
		if (!--count)
			break;
	}

	_needHitAreaRecalc++;
}

void AGOSEngine::fullFade() {
	uint8 *srcPal, *dstPal;
	int c, p;

	for (c = 64; c != 0; c --) {
		srcPal = _curVgaFile2 + 32;
		dstPal = _currentPalette;
		for (p = 768; p !=0; p -= 3) {
			uint8 r = srcPal[0] * 4;
			if (dstPal[0] != r)
				dstPal[0] += 4;
			uint8 g = srcPal[1] * 4;
			if (dstPal[1] != g)
				dstPal[1] += 4;
			uint8 b = srcPal[2] * 4;
			if (dstPal[2] != b)
				dstPal[2] += 4;
			srcPal += 3;
			dstPal += 3;
		}
		_system->getPaletteManager()->setPalette(_currentPalette, 0, 256);
		delay(5);
	}
}

void AGOSEngine::vc56_fullScreen() {
	Graphics::Surface *screen = getBackendSurface();
	byte *dst = (byte *)screen->getPixels();
	byte *src = _curVgaFile2 + 800;

	for (int i = 0; i < _screenHeight; i++) {
		memcpy(dst, src, _screenWidth);
		src += 320;
		dst += screen->pitch;
	}
	updateBackendSurface();

	fullFade();
}

void AGOSEngine::vc57_blackPalette() {
	memset(_currentPalette, 0, sizeof(_currentPalette));
	_system->getPaletteManager()->setPalette(_currentPalette, 0, 256);
}

void AGOSEngine::vc58_checkCodeWheel() {
	_variableArray[0] = 0;
}

void AGOSEngine::vc59_ifEGA() {
	// Skip if not EGA
	vcSkipNextInstruction();
}

} // End of namespace AGOS
