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

#include "common/endian.h"
#include "common/stream.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/resources.h"
#include "gob/goblin.h"
#include "gob/inter.h"
#include "gob/scenery.h"
#include "gob/map.h"
#include "gob/video.h"
#include "gob/videoplayer.h"

namespace Gob {

Mult_v2::Mult_v2(GobEngine *vm) : Mult_v1(vm) {
	_renderObjs = 0;
	_multData = 0;
	for (int i = 0; i < 8; i++)
		_multDatas[i] = 0;
}

Mult_v2::~Mult_v2() {
	freeMultKeys();
	for (int i = 0; i < 8; i++) {
		_multData = _multDatas[i];
		freeMultKeys();
	}
}

void Mult_v2::loadMult(int16 resId) {
	int8 index;
	uint8 staticCount;
	uint8 animCount;
	bool hasImds;

	index = (resId & 0x8000) ? _vm->_game->_script->readByte() : 0;
	resId &= 0x7FFF;

	debugC(4, kDebugGameFlow, "Loading mult %d", index);

	_multData = new Mult_Data;
	memset(_multData, 0, sizeof(Mult_Data));

	_multDatas[index] = _multData;

	for (int i = 0; i < 4; i++)
		_multData->animObjs[0][i] = i;

	_multData->sndSlotsCount = 0;
	_multData->frameStart = 0;

	Resource *resource = _vm->_game->_resources->getResource(resId);
	if (!resource)
		return;

	Common::SeekableReadStream &data = *resource->stream();

	_multData->staticCount = staticCount = data.readSByte();
	_multData->animCount = animCount = data.readSByte();
	staticCount++;
	animCount++;

	hasImds = (staticCount & 0x80) != 0;
	staticCount &= 0x7F;

	debugC(7, kDebugGraphics, "statics: %u, anims: %u, imds: %u",
			staticCount, animCount, hasImds);

	for (int i = 0; i < 10; i++) {
		_multData->staticLoaded[i] = false;
		_multData->animLoaded[i] = false;
	}

	for (int i = 0; i < staticCount; i++, data.seek(14, SEEK_CUR)) {
		_multData->staticIndices[i] = _vm->_scenery->loadStatic(1);

		if (_multData->staticIndices[i] >= 100) {
			_multData->staticIndices[i] -= 100;
			_multData->staticLoaded[i] = true;
		}
	}

	for (int i = 0; i < animCount; i++, data.seek(14, SEEK_CUR)) {
		_multData->animIndices[i] = _vm->_scenery->loadAnim(1);

		if (_multData->animIndices[i] >= 100) {
			_multData->animIndices[i] -= 100;
			_multData->animLoaded[i] = true;
		}
	}

	_multData->frameRate = data.readSint16LE();
	_multData->staticKeysCount = data.readSint16LE();
	_multData->staticKeys = new Mult_StaticKey[_multData->staticKeysCount];
	for (int i = 0; i < _multData->staticKeysCount; i++) {
		_multData->staticKeys[i].frame = data.readSint16LE();
		_multData->staticKeys[i].layer = data.readSint16LE();
	}

	for (int i = 0; i < 4; i++) {
		_multData->imdKeysCount[i] = 0;
		_multData->imdKeys[i] = 0;
		_multData->imdIndices[i] = -1;

		for (int j = 0; j < 4; j++) {
			_multData->animKeysIndices[i][j] = 0;
			_multData->imdKeysIndices[i][j] = 0;
		}

		_multData->animKeysFrames[i] = -1;
		_multData->animKeysCount[i] = data.readSint16LE();
		_multData->animKeys[i] = new Mult_AnimKey[_multData->animKeysCount[i]];
		for (int j = 0; j < _multData->animKeysCount[i]; j++) {
			_multData->animKeys[i][j].frame = data.readSint16LE();
			_multData->animKeys[i][j].layer = data.readSint16LE();
			_multData->animKeys[i][j].posX = data.readSint16LE();
			_multData->animKeys[i][j].posY = data.readSint16LE();
			_multData->animKeys[i][j].order = data.readSint16LE();
		}
	}

	for (int i = 0; i < 5; i++) {
		for (int j = 0; j < 16; j++) {
			_multData->fadePal[i][j].red = data.readByte();
			_multData->fadePal[i][j].green = data.readByte();
			_multData->fadePal[i][j].blue = data.readByte();
		}
	}

	_multData->palFadeKeysCount = data.readSint16LE();
	_multData->palFadeKeys = new Mult_PalFadeKey[_multData->palFadeKeysCount];
	for (int i = 0; i < _multData->palFadeKeysCount; i++) {
		_multData->palFadeKeys[i].frame = data.readSint16LE();
		_multData->palFadeKeys[i].fade = data.readSint16LE();
		_multData->palFadeKeys[i].palIndex = data.readSint16LE();
		_multData->palFadeKeys[i].flag = data.readSByte();
	}

	_multData->palKeysCount = data.readSint16LE();
	_multData->palKeys = new Mult_PalKey[_multData->palKeysCount];
	for (int i = 0; i < _multData->palKeysCount; i++) {
		_multData->palKeys[i].frame = data.readSint16LE();
		_multData->palKeys[i].cmd = data.readSint16LE();
		_multData->palKeys[i].rates[0] = data.readSint16LE();
		_multData->palKeys[i].rates[1] = data.readSint16LE();
		_multData->palKeys[i].rates[2] = data.readSint16LE();
		_multData->palKeys[i].rates[3] = data.readSint16LE();
		_multData->palKeys[i].unknown0 = data.readSint16LE();
		_multData->palKeys[i].unknown1 = data.readSint16LE();
		data.read(_multData->palKeys[i].subst, 64);
	}

	_multData->textKeysCount = data.readSint16LE();
	_multData->textKeys = new Mult_TextKey[_multData->textKeysCount];
	for (int i = 0; i < _multData->textKeysCount; i++) {
		_multData->textKeys[i].frame = data.readSint16LE();
		_multData->textKeys[i].cmd = data.readSint16LE();
		if (!hasImds)
			data.seek(24, SEEK_CUR);
	}

	_multData->sndKeysCount = data.readSint16LE();
	_multData->sndKeys = new Mult_SndKey[_multData->sndKeysCount];
	for (int i = 0; i < _multData->sndKeysCount; i++) {
		int j;

		_multData->sndKeys[i].frame = data.readSint16LE();
		_multData->sndKeys[i].cmd = data.readSint16LE();
		_multData->sndKeys[i].freq = data.readSint16LE();
		_multData->sndKeys[i].fadeLength = data.readSint16LE();
		_multData->sndKeys[i].repCount = data.readSint16LE();
		_multData->sndKeys[i].soundIndex = -1;
		_multData->sndKeys[i].resId = -1;
		data.seek(2, SEEK_CUR);
		if (!hasImds)
			data.seek(24, SEEK_CUR);

		switch (_multData->sndKeys[i].cmd) {
		case 1:
		case 4:
			_multData->sndKeys[i].resId = _vm->_game->_script->peekUint16();
			for (j = 0; j < i; j++) {
				if (_multData->sndKeys[j].resId ==
						_multData->sndKeys[i].resId) {
					_multData->sndKeys[i].soundIndex =
						_multData->sndKeys[j].soundIndex;
					_vm->_game->_script->skip(2);
					break;
				}
			}
			if (i == j) {
				_multData->sndSlot[_multData->sndSlotsCount] =
					_vm->_inter->loadSound(1);
				_multData->sndKeys[i].soundIndex =
					_multData->sndSlot[_multData->sndSlotsCount] & 0x7FFF;
				_multData->sndSlotsCount++;
			}
			break;
		case 3:
			_vm->_game->_script->skip(4);
			break;

		case -1:
			break;

		default:
			warning("Mult_v2::loadMult(): Unknown sound key command (%d)",
					_multData->sndKeys[i].cmd);
		}
	}

	_multData->imdFiles = 0;
	_multData->somepointer10 = 0;

	if (hasImds)
		loadImds(data);

	delete resource;
}

void Mult_v2::loadImds(Common::SeekableReadStream &data) {
	int16 size;

	size = _vm->_game->_script->readInt16();
	_multData->execPtr = _vm->_game->_script->getData() + _vm->_game->_script->pos();
	_vm->_game->_script->skip(size * 2);

	if (_vm->_game->_script->getVersionMinor() < 3)
		return;

	size = data.readSint16LE();
	if (size > 0) {
		_multData->somepointer10 = new char[size * 20];
		data.read(_multData->somepointer10, size * 20);
	}

	size = _vm->_game->_script->readInt16();
	if (size <= 0)
		return;

	_multData->imdFiles = new char[size * 14];
	memcpy(_multData->imdFiles,
			_vm->_game->_script->getData() + _vm->_game->_script->pos(), size * 14);

	// WORKAROUND: The Windows versions of Lost in Time and Gob3 have VMD not
	//             IMD files, but they are still referenced as IMD.
	if (((_vm->getGameType() == kGameTypeLostInTime) || (_vm->getGameType() == kGameTypeGob3)) &&
	    (_vm->getPlatform() == Common::kPlatformWindows)) {

		for (int i = 0; i < size; i++) {
			char *dot = strrchr(_multData->imdFiles + (i * 14), '.');
			if (dot)
				*dot = '\0';
		}
	}

	_vm->_game->_script->skip(size * 14);
	data.seek(2, SEEK_CUR);
	for (int i = 0; i < 4; i++) {
		_multData->imdKeysCount[i] = data.readSint16LE();
		_multData->imdKeys[i] = new Mult_ImdKey[_multData->imdKeysCount[i]];
		for (int j = 0; j < _multData->imdKeysCount[i]; j++) {
			_multData->imdKeys[i][j].frame = data.readSint16LE();
			_multData->imdKeys[i][j].imdFile = data.readSint16LE();
			_multData->imdKeys[i][j].field_4 = data.readSint16LE();
			_multData->imdKeys[i][j].field_6 = data.readSint16LE();
			_multData->imdKeys[i][j].flags = data.readUint16LE();
			_multData->imdKeys[i][j].palFrame = data.readSint16LE();
			_multData->imdKeys[i][j].lastFrame = data.readSint16LE();
			_multData->imdKeys[i][j].palStart = data.readSByte();
			_multData->imdKeys[i][j].palEnd = data.readSByte();
		}
	}
}

void Mult_v2::freeMultKeys() {
	uint8 animCount;
	uint8 staticCount;

	if (!_multData)
		return;

	staticCount = (_multData->staticCount + 1) & 0x7F;
	animCount = _multData->animCount + 1;

	for (int i = 0; i < staticCount; i++)
		if (_multData->staticLoaded[i])
			_vm->_scenery->freeStatic(_multData->staticIndices[i]);

	for (int i = 0; i < animCount; i++)
		if (_multData->animLoaded[i])
			_vm->_scenery->freeAnim(_multData->animIndices[i]);

	delete[] _multData->staticKeys;

	for (int i = 0; i < 4; i++) {
		delete[] _multData->animKeys[i];
		delete[] _multData->imdKeys[i];
	}

	delete[] _multData->palFadeKeys;
	delete[] _multData->palKeys;
	delete[] _multData->textKeys;

	for (int i = 0; i < _multData->sndSlotsCount; i++)
		if (!(_multData->sndSlot[i] & 0x8000))
			_vm->_game->freeSoundSlot(_multData->sndSlot[i]);

	delete[] _multData->sndKeys;

	delete[] _multData->imdFiles;
	delete[] _multData->somepointer10;

	if (_animDataAllocated) {
		freeMult();

		delete _animArrayX;
		delete _animArrayY;
		delete[] _animArrayData;

		_animArrayX = 0;
		_animArrayY = 0;
		_animArrayData = 0;

		_animDataAllocated = false;
	}

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

	delete _multData;
	_multData = 0;
}

bool Mult_v2::hasMultData(uint16 multIndex) {
	if (multIndex > 7)
		error("Multindex out of range");

	return _multDatas[multIndex] != 0;
}

void Mult_v2::setMultData(uint16 multIndex) {
	if (multIndex > 7)
		error("Multindex out of range");

	debugC(4, kDebugGameFlow, "Switching to mult %d", multIndex);
	_multData = _multDatas[multIndex];
}

void Mult_v2::zeroMultData(uint16 multIndex) {
	if (multIndex > 7)
		error("Multindex out of range");

	_multDatas[multIndex] = 0;
}

void Mult_v2::multSub(uint16 multIndex) {
	uint16 flags;
	int16 expr;
	int16 index;
	int16 startFrame, stopFrame, firstFrame;

	flags = multIndex;
	multIndex = (multIndex >> 12) & 0xF;

	if (multIndex > 7)
		error("Multindex out of range");

	_vm->_util->notifyNewAnim();

	debugC(4, kDebugGameFlow, "Sub mult %d", multIndex);
	_multData = _multDatas[multIndex];

	if (!_multData) {
		_vm->_game->_script->readValExpr();
		_vm->_game->_script->readValExpr();
		_vm->_game->_script->readValExpr();
		_vm->_game->_script->readValExpr();
		return;
	}

	if (flags & 0x200)
		index = 3;
	else if (flags & 0x100)
		index = 2;
	else if (flags & 0x80)
		index = 1;
	else
		index = 0;

	if (flags & 0x400) {
		flags = 0x400;
		_multData->animDirection = -1;
	} else {
		_multData->animDirection = 1;
		flags &= 0x7F;
	}

	_multData->animObjs[index][0] = flags;
	for (int i = 1; i < 4; i++)
		_multData->animObjs[index][i] = _vm->_game->_script->readValExpr();

	expr = _vm->_game->_script->readValExpr();
	_multData->animKeysFrames[index] = expr;
	_multData->animKeysStartFrames[index] = expr;

	WRITE_VAR(18 + index, expr);
	if (expr == -1) {
		if (!_objects)
			return;

		for (int i = 0; i < 4; i++) {
			int obj = _multData->animObjs[index][i];

			if ((obj == -1) || (obj == 1024))
				continue;

			Mult_AnimData &animData = *(_objects[obj].pAnimData);
			animData.animType = animData.animTypeBak;
		}

		return;
	}

	startFrame = _multData->animKeysStartFrames[index];
	stopFrame = _multData->animKeysStopFrames[index];

	if (_multData->animDirection == 1) {
		stopFrame = 32000;
		for (int i = 0; i < _multData->textKeysCount; i++) {
			int16 textFrame = _multData->textKeys[i].frame;

			if ((textFrame > startFrame) && (textFrame < stopFrame))
				stopFrame = textFrame;
		}
	} else {
		stopFrame = 0;
		for (int i = 0; i < _multData->textKeysCount; i++) {
			int16 textFrame = _multData->textKeys[i].frame;

			if ((textFrame < startFrame) && (textFrame > stopFrame))
				stopFrame = textFrame;
		}
	}

	if (_objects) {
		for (int i = 0; i < 4; i++) {
			int obj = _multData->animObjs[index][i];

			if ((obj != -1) && (obj != 1024))
				_objects[obj].pAnimData->animTypeBak = _objects[obj].pAnimData->animType;
		}
	}

	for (int i = 0; i < 4; i++) {
		_multData->animKeysIndices[index][i] = 0;

		for (int j = 0; j < _multData->animKeysCount[i]; j++)
			if (_multData->animKeys[i][j].frame >= startFrame) {
				_multData->animKeysIndices[index][i] = j;
				break;
			}
	}

	if (_multData->animDirection == -1) {
		int i = 0;
		while (_multData->imdKeys[index][i].frame <= startFrame)
			i++;

		_multData->imdIndices[index] = i - 1;
	}

	firstFrame = (_multData->animDirection == 1) ? startFrame : stopFrame;
	for (int i = 0; i < 4; i++) {
		_multData->imdKeysIndices[index][i] = 0;

		for (int j = 0; j < _multData->imdKeysCount[i]; j++)
			if (_multData->imdKeys[i][j].frame >= firstFrame) {
				_multData->imdKeysIndices[index][i] = j;
				break;
			}
	}

	_multData->animKeysStartFrames[index] = startFrame;
	_multData->animKeysStopFrames[index] = stopFrame;
}

void Mult_v2::playMultInit() {
	_doPalSubst = false;
	_palFadingRed = 0;
	_palFadingGreen = 0;
	_palFadingBlue = 0;

	_oldPalette = _vm->_global->_pPaletteDesc->vgaPal;

	if (!_animSurf) {
		int16 width, height;

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

		delete[] _objects;

		_vm->_util->setFrameRate(_multData->frameRate);
		_animTop = 0;
		_animLeft = 0;
		_animWidth = _vm->_video->_surfWidth;
		_animHeight = _vm->_video->_surfHeight;
		_objCount = 4;

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

		_objects = new Mult_Object[_objCount];
		_orderArray = new int8[_objCount];
		_renderObjs = new Mult_Object*[_objCount];
		_animArrayX = new VariablesLE(_objCount * 4);
		_animArrayY = new VariablesLE(_objCount * 4);
		_animArrayData = new Mult_AnimData[_objCount];

		memset(_objects, 0, _objCount * sizeof(Mult_Object));
		memset(_orderArray, 0, _objCount * sizeof(int8));
		memset(_renderObjs, 0, _objCount * sizeof(Mult_Object *));
		memset(_animArrayData, 0, _objCount * sizeof(Mult_AnimData));

		for (_counter = 0; _counter < _objCount; _counter++) {
			Mult_Object &multObj = _objects[_counter];
			Mult_AnimData &animData = _animArrayData[_counter];

			multObj.pPosX = new VariableReference(*_animArrayX, _counter * 4);
			multObj.pPosY = new VariableReference(*_animArrayY, _counter * 4);
			multObj.pAnimData = &animData;

			animData.isStatic = 1;

			multObj.lastLeft = -1;
			multObj.lastTop = -1;
			multObj.lastRight = -1;
			multObj.lastBottom = -1;
		}

		width = _animWidth;
		height = _animHeight;
		_vm->_draw->adjustCoords(0, &width, &height);
		_vm->_draw->initSpriteSurf(Draw::kAnimSurface, width, height, 0);
		_animSurf = _vm->_draw->_spritesArray[Draw::kAnimSurface];

		_vm->_draw->_spritesArray[Draw::kAnimSurface]->blit(*_vm->_draw->_spritesArray[Draw::kBackSurface],
				0, 0, _vm->_video->_surfWidth, _vm->_video->_surfHeight, 0, 0);

		for (_counter = 0; _counter < _objCount; _counter++)
			_multData->palAnimIndices[_counter] = _counter;

		_animDataAllocated = true;
	} else
		_animDataAllocated = false;

	_frame = 0;
}

void Mult_v2::drawStatics(bool &stop) {
	int staticIndex;

	if (_multData->staticKeys[_multData->staticKeysCount - 1].frame > _frame)
		stop = false;

	for (_counter = 0; _counter < _multData->staticKeysCount; _counter++) {
		if ((_multData->staticKeys[_counter].frame != _frame)
		    || (_multData->staticKeys[_counter].layer == -1))
			continue;

		if (_multData->staticKeys[_counter].layer >= 0) {
			int i = 0;
			_vm->_scenery->_curStatic = 0;
			_vm->_scenery->_curStaticLayer =
				_multData->staticKeys[_counter].layer;

			staticIndex = _multData->staticIndices[i];
			while (_vm->_scenery->getStaticLayersCount(staticIndex) <=
					_vm->_scenery->_curStaticLayer) {
				_vm->_scenery->_curStaticLayer -=
					_vm->_scenery->getStaticLayersCount(staticIndex);

				staticIndex = _multData->staticIndices[++i];
				_vm->_scenery->_curStatic++;
			}
			_vm->_scenery->_curStatic =
				_multData->staticIndices[_vm->_scenery->_curStatic];
			_vm->_scenery->renderStatic(_vm->_scenery->_curStatic,
					_vm->_scenery->_curStaticLayer);
		} else {
			int layer = -_multData->staticKeys[_counter].layer - 2;

			_vm->_draw->_spriteLeft =
				READ_LE_UINT16(_multData->execPtr + layer * 2);
			_vm->_draw->_destSpriteX = 0;
			_vm->_draw->_destSpriteY = 0;
			_vm->_draw->_destSurface = Draw::kBackSurface;
			_vm->_draw->_transparency = 0;
			_vm->_draw->spriteOperation(DRAW_LOADSPRITE);
			_vm->_scenery->_curStatic = -1;
		}

		_vm->_draw->_spritesArray[Draw::kAnimSurface]->blit(*_vm->_draw->_spritesArray[Draw::kBackSurface],
				0, 0, _vm->_video->_surfWidth, _vm->_video->_surfHeight, 0, 0);
	}
}

void Mult_v2::drawAnims(bool &stop) {
	int16 count;
	int animIndex;

	for (int i = 0; i < 4; i++) {
		int16 animKeysCount = _multData->animKeysCount[i];
		if ((animKeysCount > 0) && ((uint16) _multData->animKeys[i][animKeysCount - 1].frame > _frame))
			stop = false;
	}

	for (_index = 0; _index < 4; _index++) {
		int16 animKeysCount = _multData->animKeysCount[_index];
		for (_counter = 0; _counter < animKeysCount; _counter++) {
			Mult_AnimKey &key = _multData->animKeys[_index][_counter];
			Mult_Object &animObj = _objects[_multData->animObjs[0][_index]];
			Mult_AnimData &animData = *(animObj.pAnimData);

			if (key.frame != _frame)
				continue;

			if (key.layer != -1) {
				*(animObj.pPosX) = key.posX;
				*(animObj.pPosY) = key.posY;

				animData.frame = 0;
				animData.order = key.order;
				animData.animType = 1;

				animData.isPaused = 0;
				animData.isStatic = 0;
				animData.maxTick = 0;
				animObj.tick = 0;
				animData.layer = key.layer;

				int i = 0;
				animIndex = _multData->animIndices[i];
				count = _vm->_scenery->getAnimLayersCount(animIndex);
				while (animData.layer >= count) {
					animData.layer -= count;
					animIndex = _multData->animIndices[++i];

					count = _vm->_scenery->getAnimLayersCount(animIndex);
				}
				animData.animation = animIndex;

			} else
				animData.isStatic = 1;
		}
	}
}

void Mult_v2::newCycleAnim(Mult_Object &animObj) {
	Mult_AnimData &animData = *(animObj.pAnimData);
	Scenery::AnimLayer *animLayer = 0;

	if (animData.animation >= 0) {
		int nAnim = animData.animation, nLayer = animData.layer;

		if (_vm->_scenery->getAnimLayersCount(nAnim) <= nLayer)
			return;

		animLayer = _vm->_scenery->getAnimLayer(nAnim, nLayer);
	} else {
		if (animObj.videoSlot > 0) {
			_vm->_video->retrace();
			_vm->_vidPlayer->waitEndFrame(animObj.videoSlot - 1, true);
		}
	}

	if (animData.animType == 4) {
		animData.frame = 0;
		animData.isPaused = 1;
		if ((animData.animation < 0) && (animObj.videoSlot > 0)) {
			_vm->_vidPlayer->closeVideo(animObj.videoSlot - 1);
			animObj.videoSlot = 0;
		}
		return;
	}

	if (animData.animType == 12)
		animData.animType = 11;

	if (animData.animType == 11) {
		if (animData.isBusy != 0)
			warning("Woodruff Stub: AnimType 11");
		return;
	}

	if (animData.animType != 8)
		animData.frame++;

	if (animData.animation < 0) {
		if ((animObj.videoSlot > 0) &&
		    ((_vm->_vidPlayer->getCurrentFrame(animObj.videoSlot - 1) + 1) <
		      _vm->_vidPlayer->getFrameCount(animObj.videoSlot - 1))) {
			animData.newCycle = 0;
			return;
		}
	} else {
		if (animData.frame < animLayer->framesCount) {
			animData.newCycle = 0;
			return;
		}
	}


	switch (animData.animType) {
	case 0:
		animData.frame = 0;
		break;

	case 1:
		animData.frame = 0;
		if (animLayer) {
			*(animObj.pPosX) += animLayer->animDeltaX;
			*(animObj.pPosY) += animLayer->animDeltaY;
		}
		break;

	case 2:
		animData.frame = 0;
		animData.animation = animData.newAnimation;
		animData.layer = animData.newLayer;
		break;

	case 3:
		animData.animType = 4;
		animData.frame = 0;
		break;

	case 5:
		animData.isStatic = 1;
		animData.frame = 0;
		if ((animData.animation < 0) && (animObj.videoSlot > 0)) {
			_vm->_vidPlayer->closeVideo(animObj.videoSlot - 1);
			animObj.videoSlot = 0;
		}

		break;

	case 6:
	case 7:
		animData.frame--;
		animData.isPaused = 1;
/*
		if ((animData.animation < 0) && (animObj.videoSlot > 0)) {
			if (_vm->_vidPlayer->getFlags(animObj.videoSlot - 1) & 0x1000) {
				_vm->_vidPlayer->closeVideo(animObj.videoSlot - 1);
				animObj.videoSlot = 0;
			}
		}
*/
		break;

	case 10:
		warning("Woodruff Stub: AnimType 10");
		break;
	}

	animData.newCycle = 1;
}

void Mult_v2::animate() {
	int8 minOrder = 100;
	int8 maxOrder = 0;
	int8 *orderArray;
	int orderArrayPos = 0;
	int8 animIndices[150];
	int numAnims = 0;

	if (!_objects)
		return;

	if (_objCount > 0) {
		if (!_orderArray)
			return;
		orderArray = _orderArray;
	} else
		orderArray = 0;

	advanceAllObjects();

	// Find relevant objects
	for (int i = 0; i < _objCount; i++) {
		Mult_Object &animObj = _objects[i];
		Mult_AnimData &animData = *(animObj.pAnimData);

		if (_vm->_map->_mapUnknownBool) {
			// TODO!
		}

		animData.intersected = 200;
		if (animData.isStatic != 2) {
			if ((animData.isStatic == 0) || (animObj.lastLeft != -1)) {
				animIndices[numAnims] = i;
				_renderObjs[numAnims] = &animObj;
				numAnims++;
			}
		}
	}

	// Find dirty areas
	for (int i = 0; i < numAnims; i++) {
		Mult_Object &animObj = *_renderObjs[i];
		Mult_AnimData &animData = *(animObj.pAnimData);

		animObj.needRedraw = 0;
		animObj.newTop     = 1000;
		animObj.newLeft    = 1000;
		animObj.newBottom  = 0;
		animObj.newRight   = 0;

		if (animData.isStatic == 2)
			continue;

		if (!animData.isStatic && !animData.isPaused &&
				(animData.maxTick == animObj.tick)) {

			animObj.needRedraw = 1;
			_vm->_scenery->updateAnim(animData.layer, animData.frame,
					animData.animation, 8, *animObj.pPosX, *animObj.pPosY, 0);
			if (animObj.lastLeft == -1) {
				animObj.newLeft = _vm->_scenery->_toRedrawLeft;
				animObj.newTop = _vm->_scenery->_toRedrawTop;
				animObj.newRight = _vm->_scenery->_toRedrawRight;
				animObj.newBottom = _vm->_scenery->_toRedrawBottom;
			} else {
				animObj.newLeft =
					MIN(animObj.lastLeft, _vm->_scenery->_toRedrawLeft);
				animObj.newTop =
					MIN(animObj.lastTop, _vm->_scenery->_toRedrawTop);
				animObj.newRight =
					MAX(animObj.lastRight, _vm->_scenery->_toRedrawRight);
				animObj.newBottom =
					MAX(animObj.lastBottom, _vm->_scenery->_toRedrawBottom);

				if ((_vm->_game->_script->getVersionMinor() > 2) &&
						(animObj.newLeft == animObj.lastLeft) &&
						(animObj.newTop == animObj.lastTop) &&
						(animObj.newRight == animObj.lastRight) &&
						(animObj.newBottom == animObj.lastBottom) &&
						(animData.redrawLayer == animData.layer) &&
						(animData.redrawFrame == animData.frame) &&
						(animData.redrawAnimation == animData.animation)) {
					animObj.needRedraw = 0;
				}
			}

		} else if (!animData.isStatic) {

			if (animObj.lastLeft == -1) {
				animObj.needRedraw = 1;
				_vm->_scenery->updateAnim(animData.layer, animData.frame,
					animData.animation, 8, *animObj.pPosX, *animObj.pPosY, 0);

				animObj.newLeft = _vm->_scenery->_toRedrawLeft;
				animObj.newTop = _vm->_scenery->_toRedrawTop;
				animObj.newRight = _vm->_scenery->_toRedrawRight;
				animObj.newBottom = _vm->_scenery->_toRedrawBottom;
			} else {
				animObj.newLeft = animObj.lastLeft;
				animObj.newTop = animObj.lastTop;
				animObj.newRight = animObj.lastRight;
				animObj.newBottom = animObj.lastBottom;
			}

		} else if (animObj.lastLeft != -1) {
			animObj.needRedraw = 1;
			animObj.newLeft = animObj.lastLeft;
			animObj.newTop = animObj.lastTop;
			animObj.newRight = animObj.lastRight;
			animObj.newBottom = animObj.lastBottom;
		}

		animData.redrawLayer = animData.layer;
		animData.redrawFrame = animData.frame;
		animData.redrawAnimation = animData.animation;
		if (animObj.needRedraw || !animData.isStatic) {
			minOrder = MIN(minOrder, animData.order);
			maxOrder = MAX(maxOrder, animData.order);
		}
	}

	// Restore dirty areas
	for (int i = 0; i < numAnims; i++) {
		Mult_Object &animObj = *_renderObjs[i];

		if (!animObj.needRedraw || (animObj.lastLeft == -1))
			continue;

		animObj.lastLeft = -1;

		int maxleft = MAX(animObj.newLeft, _animLeft);
		int maxtop = MAX(animObj.newTop, _animTop);
		int right = animObj.newRight - maxleft + 1;
		int bottom = animObj.newBottom - maxtop + 1;

		if ((right <= 0) || (bottom <= 0))
			continue;

		_vm->_draw->_sourceSurface = Draw::kAnimSurface;
		_vm->_draw->_destSurface = Draw::kBackSurface;
		_vm->_draw->_spriteLeft = maxleft - _animLeft;
		_vm->_draw->_spriteTop = maxtop - _animTop;
		_vm->_draw->_spriteRight = right;
		_vm->_draw->_spriteBottom = bottom;
		_vm->_draw->_destSpriteX = maxleft;
		_vm->_draw->_destSpriteY = maxtop;
		_vm->_draw->_transparency = 0;
		_vm->_draw->spriteOperation(DRAW_BLITSURF);
	}

	// Figure out the correct drawing order
	for (int i = minOrder; i <= maxOrder; i++) {
		for (int j = 0; j < numAnims; j++) {
			Mult_Object &animObj = *_renderObjs[j];
			Mult_AnimData &animData = *(animObj.pAnimData);

			if (animData.order == i)
				if (animObj.needRedraw || !animData.isStatic)
					orderArray[orderArrayPos++] = j;
		}
	}

	// Put the goblins in correct drawing order as well
	if (_vm->_goblin->_gobsCount >= 0) {
		for (int i = 0; i < orderArrayPos; i++) {
			Mult_Object &animObj1 = *_renderObjs[orderArray[i]];
			Mult_AnimData &animData1 = *(animObj1.pAnimData);

			if (!animObj1.goblinStates)
				continue;

			for (int j = i+1; j < orderArrayPos; j++) {
				Mult_Object &animObj2 = *_renderObjs[orderArray[j]];
				Mult_AnimData &animData2 = *(animObj2.pAnimData);

				if ((animData1.order == animData2.order) &&
						((animObj1.newBottom > animObj2.newBottom) ||
						((animObj1.newBottom == animObj2.newBottom) &&
						 (animData1.isBusy == 1))))
						SWAP(orderArray[i], orderArray[j]);
			}
		}
	}

	// Update view
	for (int i = 0; i < orderArrayPos; i++) {
		Mult_Object &animObj1 = *_renderObjs[orderArray[i]];
		Mult_AnimData &animData1 = *(animObj1.pAnimData);

		if (!animObj1.needRedraw) {

			if (!animData1.isStatic) {
				for (int j = 0; j < orderArrayPos; j++) {
					Mult_Object &animObj2 = *_renderObjs[orderArray[j]];

					if (!animObj2.needRedraw)
						continue;

					if ((animObj1.newRight >= animObj2.newLeft) &&
							(animObj2.newRight >= animObj1.newLeft) &&
							(animObj1.newBottom >= animObj2.newTop) &&
							(animObj2.newBottom >= animObj1.newTop)) {

						_vm->_scenery->_toRedrawLeft = animObj2.newLeft;
						_vm->_scenery->_toRedrawRight = animObj2.newRight;
						_vm->_scenery->_toRedrawTop = animObj2.newTop;
						_vm->_scenery->_toRedrawBottom = animObj2.newBottom;

						_vm->_scenery->updateAnim(animData1.layer, animData1.frame,
								animData1.animation, 12, *animObj1.pPosX, *animObj1.pPosY, 1);
						_vm->_scenery->updateStatic(animData1.order + 1);
					}
				}
			}

		} else {

			if (animData1.isStatic != 0) {
				_vm->_scenery->_toRedrawLeft = animObj1.newLeft;
				_vm->_scenery->_toRedrawRight = animObj1.newRight;
				_vm->_scenery->_toRedrawTop = animObj1.newTop;
				_vm->_scenery->_toRedrawBottom = animObj1.newBottom;
			} else {
				_vm->_scenery->updateAnim(animData1.layer, animData1.frame,
						animData1.animation, 10, *animObj1.pPosX, *animObj1.pPosY, 1);

				if (_vm->_scenery->_toRedrawLeft != -12345) {
					animObj1.lastLeft = _vm->_scenery->_toRedrawLeft;
					animObj1.lastRight = _vm->_scenery->_toRedrawRight;
					animObj1.lastTop = _vm->_scenery->_toRedrawTop;
					animObj1.lastBottom = _vm->_scenery->_toRedrawBottom;
				} else {
					animObj1.lastLeft = -1;
				}

			}

			_vm->_scenery->updateStatic(animData1.order + 1);

		}

	}

	// Advance animations
	for (int i = 0; i < numAnims; i++) {
		Mult_Object &animObj = *_renderObjs[i];
		Mult_AnimData &animData = *(animObj.pAnimData);

		if (animData.isStatic)
			continue;

		if ((animData.animType == 7) && (animData.newState != -1)) {
			animData.layer = animData.newState;
			animData.frame = 0;
			animData.newState = -1;
			animData.isPaused = 0;
		}
		if (animData.isPaused)
			continue;

		if (animData.maxTick == animObj.tick) {
			animObj.tick = 0;
			if ((animData.animType < 100) || (_vm->_goblin->_gobsCount < 0))
				newCycleAnim(animObj);
			else if (animData.animType == 100)
				_vm->_goblin->moveAdvance(&animObj, 0, 0, 0);
			else if (animData.animType == 101)
				_vm->_goblin->animate(&animObj);
		} else
			animObj.tick++;
	}

	// Find intersections
	for (int i = 0; i < numAnims; i++) {
		Mult_Object &animObj1 = *_renderObjs[i];
		Mult_AnimData &animData1 = *(animObj1.pAnimData);

		if (animData1.isStatic || (animObj1.lastLeft == -1))
			continue;

		for (int j = 0; j < numAnims; j++) {
			Mult_Object &animObj2 = *_renderObjs[j];
			Mult_AnimData &animData2 = *(animObj2.pAnimData);

			if (i == j)
				continue;
			if ((animData2.isStatic) || (animObj2.lastLeft == -1))
				continue;

			if ((animObj2.lastRight >= animObj1.lastLeft) &&
			    (animObj2.lastLeft <= animObj1.lastRight) &&
			    (animObj2.lastBottom >= animObj1.lastTop) &&
			    (animObj2.lastTop <= animObj1.lastBottom))
				animData2.intersected = animIndices[i];
		}
	}

}

void Mult_v2::playImd(const char *imdFile, Mult::Mult_ImdKey &key, int16 dir,
		int16 startFrame) {

	VideoPlayer::Properties props;

	if (_vm->_draw->_renderFlags & 0x100) {
		props.x = VAR(55);
		props.y = VAR(56);
	}

	if (key.imdFile == -1) {
		_vm->_vidPlayer->closeVideo();
		return;
	}

	props.flags = (key.flags >> 8) & 0xFF;
	if (props.flags & 0x20)
		props.flags = (props.flags & 0x9F) | 0x80;

	props.palStart  = key.palStart;
	props.palEnd    = key.palEnd;
	props.palFrame  = key.palFrame;
	props.lastFrame = key.lastFrame;

	if ((props.palFrame != -1) && (props.lastFrame != -1))
		if ((props.lastFrame - props.palFrame) < props.startFrame)
			if (!(key.flags & 0x4000)) {
				_vm->_vidPlayer->closeVideo();
				return;
			}

	_vm->_vidPlayer->evaluateFlags(props);

	int slot;
	if ((slot = _vm->_vidPlayer->openVideo(true, imdFile, props)) < 0)
		return;

	if (props.palFrame == -1)
		props.palFrame = 0;

	if (props.lastFrame == -1)
		props.lastFrame = _vm->_vidPlayer->getFrameCount() - 1;

	uint32 baseFrame = startFrame % (props.lastFrame - props.palFrame + 1);

	props.endFrame   = props.lastFrame;
	props.startFrame = baseFrame + props.palFrame;
	props.lastFrame  = baseFrame + props.palFrame;

	props.flags &= 0x7F;

	debugC(2, kDebugVideo, "Playing mult video \"%s\" @ %d+%d, frame %d, "
			"paletteCmd %d (%d - %d; %d), flags %X", imdFile,
			props.x, props.y, props.startFrame,
			props.palCmd, props.palStart, props.palEnd, props.endFrame, props.flags);

	_vm->_vidPlayer->play(slot, props);
}

void Mult_v2::advanceObjects(int16 index) {
	int16 frame;
	bool stop = false;

	frame = _multData->animKeysFrames[index];
	if (frame == -1)
		return;

	for (int i = 0; i < 4; i++) {
		int obj = _multData->animObjs[index][i];

		if ((obj != -1) && (obj != 1024)) {
			int keyIndex = _multData->animKeysIndices[index][i];
			int count = _multData->animKeysCount[i];

			for (int j = keyIndex; j < count; j++) {
				Mult_AnimKey &key = _multData->animKeys[i][j];
				Mult_Object &animObj = _objects[obj];
				Mult_AnimData &animData = *(animObj.pAnimData);

				if (key.frame > frame)
					break;
				else if (key.frame < frame)
					continue;

				if (key.layer > -1) {
					int16 layer;
					int16 layers;
					int16 curAnim;

					_multData->animKeysIndices[index][i] = j;
					*(animObj.pPosX) = key.posX;
					*(animObj.pPosY) = key.posY;
					animData.frame = 0;
					animData.animType = 1;
					animData.isStatic = 0;
					animData.isPaused = 0;
					animData.maxTick = 0;
					animData.animation = 0;
					animObj.tick = 0;

					curAnim = _multData->animIndices[0];
					layer = key.layer;
					layers = _vm->_scenery->getAnimLayersCount(curAnim);
					while (layer >= layers) {
						layer -= layers;
						animData.animation++;
						curAnim = _multData->animIndices[animData.animation];
						layers = _vm->_scenery->getAnimLayersCount(curAnim);
					}
					animData.layer = layer;
					animData.animation =
						_multData->animIndices[animData.animation];
					break;
				} else
					animData.isStatic = 1;
			}
		}

		if (obj != -1) {
			int keyIndex = _multData->imdKeysIndices[index][i];
			int count = _multData->imdKeysCount[i];

			for (int j = keyIndex; j < count; j++) {
				Mult_ImdKey &key1 = _multData->imdKeys[i][j];
				Mult_ImdKey &key2 = _multData->imdKeys[i][j - 1];

				if (key1.frame > frame)
					break;
				else if (key1.frame < frame)
					continue;

				if (key1.imdFile != -1) {
					_multData->imdIndices[0] = -1;
					_multData->imdIndices[1] = -1;
					_multData->imdIndices[2] = -1;
					_multData->imdIndices[3] = -1;
					if ((_multData->animDirection == 1) || (key2.imdFile == -1))
						_multData->imdIndices[i] = j;
					else if (_multData->animKeysStopFrames[index] == frame)
						_multData->imdIndices[i] = -1;
					else
						_multData->imdIndices[i] = j - 1;
				} else
					_multData->imdIndices[i] = -1;
			}
		}

		if (_multData->imdIndices[i] != -1) {
			int fileN;
			char *imdFile;
			int dir;
			int startFrame;

			Mult_ImdKey &key = _multData->imdKeys[i][_multData->imdIndices[i]];

			fileN = -key.imdFile - 2;
			if (fileN < 0)
				return;

			imdFile = _multData->imdFiles + fileN * 14;
			dir = _multData->animDirection;
			startFrame = frame - key.frame;

			if ((dir != 1) && (--startFrame < 0))
				startFrame = 0;

			playImd(imdFile, key, dir, startFrame);
		}
	}

	doSoundAnim(stop, frame);
	WRITE_VAR(22, frame);

	if (_multData->animKeysStopFrames[index] == frame) {
		_multData->imdIndices[0] = -1;
		_multData->imdIndices[1] = -1;
		_multData->imdIndices[2] = -1;
		_multData->imdIndices[3] = -1;
		frame = -1;
		for (int i = 0; i < 4; i++) {
			int obj = _multData->animObjs[index][i];

			if ((obj == -1) || (obj == 1024))
				continue;

			Mult_Object &animObj = _objects[_multData->animObjs[index][i]];
			animObj.pAnimData->animType = animObj.pAnimData->animTypeBak;
		}
	} else if (_multData->animDirection == 1)
		frame++;
	else
		frame--;

	_multData->animKeysFrames[index] = frame;
	WRITE_VAR(18 + index, frame);
}

void Mult_v2::advanceAllObjects() {
	Mult_Data *multData = _multData;

	for (int i = 0; i < 8; i++) {
		if (_multDatas[i]) {
			_multData = _multDatas[i];
			for (int j = 0; j < 4; j++)
				advanceObjects(j);
		}
	}

	_multData = multData;
}

} // End of namespace Gob
