/* 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 "common/endian.h"

#include "gob/gob.h"
#include "gob/mult.h"
#include "gob/global.h"
#include "gob/util.h"
#include "gob/draw.h"
#include "gob/game.h"
#include "gob/script.h"
#include "gob/palanim.h"
#include "gob/scenery.h"
#include "gob/video.h"
#include "gob/videoplayer.h"
#include "gob/inter.h"
#include "gob/sound/sound.h"

namespace Gob {

Mult::Mult(GobEngine *vm) : _vm(vm) {
	_multData = nullptr;

	_frame = 0;

	_objCount = 0;
	_objects = nullptr;

	_renderData = nullptr;
	_renderObjs = nullptr;

	_orderArray = nullptr;

	_index = 0;
	_counter = 0;
	_animDataAllocated = false;

	for (int i = 0; i < 8; i++)
		_multDatas[i] = nullptr;

	_doPalSubst = false;

	_animArrayX = nullptr;
	_animArrayY = nullptr;
	_animArrayData = nullptr;

	_palKeyIndex = 0;
	_oldPalette = nullptr;
	for (int i = 0; i < 256; i++) {
		_palAnimPalette[i].red = 0;
		_palAnimPalette[i].green = 0;
		_palAnimPalette[i].blue = 0;
	}

	_palAnimKey = 0;
	for (int i = 0; i < 4; i++) {
		_palAnimRed[i] = 0;
		_palAnimGreen[i] = 0;
		_palAnimBlue[i] = 0;
	}

	_palFadingRed = 0;
	_palFadingGreen = 0;
	_palFadingBlue = 0;

	_animLeft = 0;
	_animTop = 0;
	_animWidth = 0;
	_animHeight = 0;
}

Mult::~Mult() {
	if (_objects)
		for (int i = 0; i < _objCount; i++) {
			delete _objects[i].pPosX;
			delete _objects[i].pPosY;
		}

	delete[] _objects;
	delete[] _orderArray;
	delete[] _renderData;
	delete[] _renderObjs;
	delete _animArrayX;
	delete _animArrayY;
	delete[] _animArrayData;
	delete _multData;
}

void Mult::initAll() {
	_objects = nullptr;
	_animSurf.reset();
	_renderData = nullptr;

	_vm->_scenery->init();
}

void Mult::freeAll() {
	freeMult();

	for (int i = 0; i < 10; i++) {
		_vm->_scenery->freeAnim(i);
		_vm->_scenery->freeStatic(i);
	}
}

void Mult::freeMult(bool freeObjectSprites) {
	clearObjectVideos();

	if (_objects)
		for (int i = 0; i < _objCount; i++) {
			delete _objects[i].pPosX;
			delete _objects[i].pPosY;
			if (_objects[i].ownAnimVariables) {
				delete _objects[i].animVariables;
				_objects[i].animVariables = nullptr;
			}

			if (freeObjectSprites)
				_vm->_draw->freeSprite(50 + i);
		}

	delete[] _objects;
	delete[] _renderData;
	delete[] _renderObjs;
	delete[] _orderArray;

	_objects = nullptr;
	_renderData = nullptr;
	_renderObjs = nullptr;
	_orderArray = nullptr;

	_animSurf.reset();
	_vm->_draw->freeSprite(Draw::kAnimSurface);
}

void Mult::checkFreeMult() {
	if (_multData)
		freeMultKeys();
}

void Mult::zeroMultData() {
	_multData = nullptr;
}

void Mult::playMult(int16 startFrame, int16 endFrame, char checkEscape,
	    char handleMouse) {
	bool stopNoClear;
	bool stop;

	if (!_multData)
		return;

	stopNoClear = false;
	_frame = startFrame;
	if (endFrame == -1)
		endFrame = 32767;

	if (_frame == -1)
		playMultInit();

	do {
		stop = true;

		if (VAR(58) == 0) {
			drawStatics(stop);
			drawAnims(stop);
		}

		animate();
		if (handleMouse)
			_vm->_draw->animateCursor(-1);
		else
			_vm->_draw->blitInvalidated();

		if (VAR(58) == 0)
			drawText(stop, stopNoClear);

		prepPalAnim(stop);
		doPalAnim();

		doFadeAnim(stop);
		doSoundAnim(stop, _frame);

		if (_frame >= endFrame)
			stopNoClear = true;

		if (_vm->_sound->blasterPlayingSound())
			stop = false;

		_vm->_util->processInput();
		if (checkEscape && (_vm->_util->checkKey() == kKeyEscape))
			stop = true;

		_frame++;
		_vm->_util->waitEndFrame();
	} while (!stop && !stopNoClear && !_vm->shouldQuit());

	if (!stopNoClear) {
		if (_animDataAllocated) {
			clearObjectVideos();

			if (_objects)
				for (int i = 0; i < _objCount; i++) {
					delete _objects[i].pPosX;
					delete _objects[i].pPosY;
				}

			delete[] _objects;
			delete[] _renderData;
			delete[] _renderObjs;
			delete _animArrayX;
			delete _animArrayY;
			delete[] _animArrayData;
			delete[] _orderArray;

			_objects = nullptr;
			_renderObjs = nullptr;
			_renderData = nullptr;
			_animArrayX = nullptr;
			_animArrayY = nullptr;
			_animArrayData = nullptr;
			_orderArray = nullptr;

			_animSurf.reset();
			_vm->_draw->freeSprite(Draw::kAnimSurface);

			_animDataAllocated = false;
		}

		if (_vm->_sound->blasterPlayingSound())
			_vm->_sound->blasterStop(10);

		WRITE_VAR(57, (uint32) -1);
	} else
		WRITE_VAR(57, _frame - _multData->frameStart - 1);
}

void Mult::drawText(bool &stop, bool &stopNoClear) {
	int16 cmd;
	for (_index = 0; _index < _multData->textKeysCount; _index++) {
		if (_multData->textKeys[_index].frame != _frame)
			continue;

		cmd = _multData->textKeys[_index].cmd;
		if (cmd == 0) {
			stop = false;
		} else if (cmd == 1) {
			stopNoClear = true;
			_multData->frameStart = 0;
		} else if (cmd == 3) {
			warning("Mult::drawText, cmd == 3");
			stop = false;
//			uint32 startPos = _vm->_game->_script->pos();
//			_vm->_global->_inter_execPtr = _multData->textKeys[_index].script;
		}
	}
}

void Mult::prepPalAnim(bool &stop) {
	_palKeyIndex = -1;
	do {
		_palKeyIndex++;
		if (_palKeyIndex >= _multData->palKeysCount)
			return;
	} while (_multData->palKeys[_palKeyIndex].frame != _frame);

	if (_multData->palKeys[_palKeyIndex].cmd == -1) {
		stop = false;
		_doPalSubst = false;
		_vm->_global->_pPaletteDesc->vgaPal = _oldPalette;
		_vm->_video->setFullPalette(_vm->_global->_pPaletteDesc);
	} else {
		stop = false;
		_doPalSubst = true;
		_palAnimKey = _palKeyIndex;

		_multData->palAnimIndices[0] = 0;
		_multData->palAnimIndices[1] = 0;
		_multData->palAnimIndices[2] = 0;
		_multData->palAnimIndices[3] = 0;

		memcpy((char *)_palAnimPalette,
				(char *)_vm->_global->_pPaletteDesc->vgaPal, 768);
		_vm->_global->_pPaletteDesc->vgaPal = _palAnimPalette;
	}
}

void Mult::doPalAnim() {
	int16 off;
	int16 off2;
	Video::Color *palPtr;
	Mult_PalKey *palKey;

	if (!_doPalSubst)
		return;

	for (_index = 0; _index < 4; _index++) {
		palKey = &_multData->palKeys[_palAnimKey];

		if ((_frame % palKey->rates[_index]) != 0)
			continue;

		_palAnimRed[_index] =
		    _vm->_global->_pPaletteDesc->vgaPal[palKey->subst[0][_index] - 1].red;
		_palAnimGreen[_index] =
		    _vm->_global->_pPaletteDesc->vgaPal[palKey->subst[0][_index] - 1].green;
		_palAnimBlue[_index] =
		    _vm->_global->_pPaletteDesc->vgaPal[palKey->subst[0][_index] - 1].blue;

		while (1) {
			off = palKey->subst[(_multData->palAnimIndices[_index] + 1) % 16][_index];
			if (off == 0) {
				off = palKey->subst[_multData->palAnimIndices[_index]][_index] - 1;

				_vm->_global->_pPaletteDesc->vgaPal[off].red = _palAnimRed[_index];
				_vm->_global->_pPaletteDesc->vgaPal[off].green = _palAnimGreen[_index];
				_vm->_global->_pPaletteDesc->vgaPal[off].blue = _palAnimBlue[_index];
			} else {
				off = palKey->subst[(_multData->palAnimIndices[_index] + 1) % 16][_index] - 1;
				off2 = palKey->subst[_multData->palAnimIndices[_index]][_index] - 1;

				_vm->_global->_pPaletteDesc->vgaPal[off2].red =
					_vm->_global->_pPaletteDesc->vgaPal[off].red;
				_vm->_global->_pPaletteDesc->vgaPal[off2].green =
					_vm->_global->_pPaletteDesc->vgaPal[off].green;
				_vm->_global->_pPaletteDesc->vgaPal[off2].blue =
					_vm->_global->_pPaletteDesc->vgaPal[off].blue;
			}

			_multData->palAnimIndices[_index] = (_multData->palAnimIndices[_index] + 1) % 16;

			off = palKey->subst[_multData->palAnimIndices[_index]][_index];

			if (off == 0) {
				_multData->palAnimIndices[_index] = 0;
				off = palKey->subst[0][_index] - 1;

				_palAnimRed[_index] = _vm->_global->_pPaletteDesc->vgaPal[off].red;
				_palAnimGreen[_index] = _vm->_global->_pPaletteDesc->vgaPal[off].green;
				_palAnimBlue[_index] = _vm->_global->_pPaletteDesc->vgaPal[off].blue;
			}
			if (_multData->palAnimIndices[_index] == 0)
				break;
		}
	}

	if (_vm->_global->_colorCount == 256) {
		_vm->_video->waitRetrace();

		palPtr = _vm->_global->_pPaletteDesc->vgaPal;
		for (_counter = 0; _counter < 16; _counter++, palPtr++)
			_vm->_video->setPalElem(_counter, palPtr->red, palPtr->green,
					palPtr->blue, 0, 0x13);

		palPtr = _vm->_global->_pPaletteDesc->vgaPal;
		for (_counter = 0; _counter < 16; _counter++, palPtr++) {
			_vm->_global->_redPalette[_counter] = palPtr->red;
			_vm->_global->_greenPalette[_counter] = palPtr->green;
			_vm->_global->_bluePalette[_counter] = palPtr->blue;
		}

	} else
		_vm->_video->setFullPalette(_vm->_global->_pPaletteDesc);
}

void Mult::doFadeAnim(bool &stop) {
	Mult_PalFadeKey *fadeKey;

	for (_index = 0; _index < _multData->palFadeKeysCount; _index++) {
		fadeKey = &_multData->palFadeKeys[_index];

		if (fadeKey->frame != _frame)
			continue;

		stop = false;
		if (!(fadeKey->flag & 1)) {
			if (fadeKey->fade == 0) {
				_vm->_global->_pPaletteDesc->vgaPal =
					_multData->fadePal[fadeKey->palIndex];
				_vm->_video->setFullPalette(_vm->_global->_pPaletteDesc);
			} else {
				_vm->_global->_pPaletteDesc->vgaPal =
					_multData->fadePal[fadeKey->palIndex];
				_vm->_palAnim->fade(_vm->_global->_pPaletteDesc, fadeKey->fade, 0);
			}
		} else {
			_vm->_global->_pPaletteDesc->vgaPal =
				_multData->fadePal[fadeKey->palIndex];
			_vm->_palAnim->fade(_vm->_global->_pPaletteDesc, fadeKey->fade, -1);

			_palFadingRed = (fadeKey->flag >> 1) & 1;
			_palFadingGreen = (fadeKey->flag >> 2) & 1;
			_palFadingBlue = (fadeKey->flag >> 3) & 1;
		}
	}

	if (_palFadingRed) {
		_palFadingRed = !_vm->_palAnim->fadeStep(1);
		stop = false;
	}
	if (_palFadingGreen) {
		_palFadingGreen = !_vm->_palAnim->fadeStep(2);
		stop = false;
	}
	if (_palFadingBlue) {
		_palFadingBlue = !_vm->_palAnim->fadeStep(3);
		stop = false;
	}
}

void Mult::doSoundAnim(bool &stop, int16 frame) {
	Mult_SndKey *sndKey;
	for (_index = 0; _index < _multData->sndKeysCount; _index++) {
		sndKey = &_multData->sndKeys[_index];
		if (sndKey->frame != frame)
			continue;

		if (sndKey->cmd != -1) {
			if ((sndKey->cmd == 1) || (sndKey->cmd == 4)) {
				SoundDesc *sample = _vm->_sound->sampleGetBySlot(sndKey->soundIndex);

				_vm->_sound->blasterStop(0);
				if (sample && !sample->empty())
					_vm->_sound->blasterPlay(sample, sndKey->repCount,
							sndKey->freq, sndKey->fadeLength);
			}
		} else {
			if (_vm->_sound->blasterPlayingSound())
				_vm->_sound->blasterStop(sndKey->fadeLength);
		}
	}
}

int Mult::openObjVideo(const Common::String &file, VideoPlayer::Properties &properties, int animation) {
	if (animation >= 0)
		return -1;

	Mult_Object &object = _objects[-animation - 1];
	if (object.videoSlot > 0)
		_vm->_vidPlayer->closeVideo(object.videoSlot - 1);

	Common::strlcpy(object.animName, file.c_str(), 16);
	int slot = _vm->_vidPlayer->openVideo(false, file, properties);
	object.videoSlot = slot + 1;
	return slot;
}

void Mult::closeObjVideo(Mult_Object &object) {
	if (object.videoSlot > 0) {
		_vm->_draw->freeSprite(50 - object.pAnimData->animation - 1);

		_vm->_vidPlayer->closeVideo(object.videoSlot - 1);
		object.videoSlot = 0;
		object.animName[0] = 0;
	}
}

void Mult::clearObjectVideos() {
	if (!_objects)
		return;

	for (int i = 0; i < _objCount; i++)
		if (_objects[i].videoSlot > 0)
			_vm->_vidPlayer->closeVideo(_objects[i].videoSlot - 1);
}

} // End of namespace Gob
