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

#include "fullpipe/objects.h"
#include "fullpipe/ngiarchive.h"
#include "fullpipe/statics.h"
#include "fullpipe/messages.h"
#include "fullpipe/interaction.h"
#include "fullpipe/motion.h"

#include "fullpipe/constants.h"
#include "fullpipe/objectnames.h"

namespace Fullpipe {

StepArray::StepArray() {
	_currPointIndex = 0;
	_isEos = false;
}

void StepArray::clear() {
	_currPointIndex = 0;
	_isEos = false;
	_points.clear();
}

Common::Point StepArray::getCurrPoint() const {
	if (_isEos || !_points.size()) {
		return Common::Point();
	}

	return Common::Point(_points[_currPointIndex].x,
						 _points[_currPointIndex].y);
}

Common::Point StepArray::getPoint(int index, int offset) const {
	if (index == -1)
		index = _currPointIndex;

	if (index + offset > getPointsCount() - 1)
		offset = getPointsCount() - index;

	Common::Point point;

	while (offset >= 1) {
		point.x += _points[index].x;
		point.y += _points[index].y;

		index++;
		offset--;
	}

	return point;
}

bool StepArray::gotoNextPoint() {
	if (_currPointIndex < getPointsCount() - 1) {
		_currPointIndex++;
		return true;
	} else {
		_isEos = true;
		return false;
	}
}

void StepArray::insertPoints(const PointList &points) {
	_points.resize(_currPointIndex + points.size());
	Common::copy(points.begin(), points.end(), _points.begin() + _currPointIndex);
}

StaticANIObject::StaticANIObject() {
	_shadowsOn = 1;
	_field_30 = 0;
	_field_34 = 1;
	_initialCounter = 0;
	_messageQueueId = 0;
	_animExFlag = 0;
	_counter = 0;
	_movement = 0;
	_statics = 0;
	_flags = 0;
	_callback1 = 0; // Really NULL
	_callback2 = 0; // Really NULL
	_sceneId = -1;
	_someDynamicPhaseIndex = -1;

	_field_32 = 0;
	_field_96 = 0;
	_messageNum = 0;
	_objtype = kObjTypeStaticANIObject;
}

StaticANIObject::~StaticANIObject() {
	Common::for_each(_staticsList.begin(), _staticsList.end(), Common::DefaultDeleter<Statics>());
	Common::for_each(_movements.begin(), _movements.end(), Common::DefaultDeleter<Movement>());
	g_fp->_aniHandler->detachAllObjects();
}

StaticANIObject::StaticANIObject(StaticANIObject *src) : GameObject(src) {
	_shadowsOn = src->_shadowsOn;
	_field_30 = src->_field_30;
	_field_34 = 1;
	_initialCounter = 0;

	_field_32 = 0;
	_field_96 = 0;
	_messageNum = 0;

	_messageQueueId = 0;
	_animExFlag = 0;
	_counter = 0;
	_someDynamicPhaseIndex = -1;
	_sceneId = src->_sceneId;
	_callback1 = src->_callback1;
	_callback2 = src->_callback2;
	_objtype = kObjTypeStaticANIObject;

	for (uint i = 0; i < src->_staticsList.size(); i++)
		_staticsList.push_back(new Statics(src->_staticsList[i], false));

	_movement = 0;
	_statics = 0;

	for (uint i = 0; i < src->_movements.size(); i++) {
		Movement *newmov;

		if (src->_movements[i]->_currMovement) {
			// This is weird code. Logically it should be
			// newmov = new Movement(src->getMovementById(src->getMovementIdById(src->_movements[i]->_id)), this);
			newmov = new Movement(getMovementById(src->getMovementIdById(src->_movements[i]->_id)), this);
			newmov->_id = src->_movements[i]->_id;
		} else {
			newmov = new Movement(src->_movements[i], 0, -1, this);
		}

		_movements.push_back(newmov);
	}
}

bool StaticANIObject::load(MfcArchive &file) {
	debugC(5, kDebugLoading, "StaticANIObject::load()");

	GameObject::load(file);

	debugC(6, kDebugXML, "%% <OLDANI %s>", GameObject::toXML().c_str());

	int count = file.readUint16LE();

	for (int i = 0; i < count; i++) {
		Statics *st = new Statics();

		st->load(file);
		_staticsList.push_back(st);
	}

	count = file.readUint16LE();
	debugC(7, kDebugLoading, "Movements: %d", count);

	for (int i = 0; i < count; i++) {
		int movNum = file.readUint16LE();

		Common::String movname = genFileName(_id, movNum, "mov");

		Common::ScopedPtr<Common::SeekableReadStream> f(g_fp->_currArchive->createReadStreamForMember(movname));

		Movement *mov = new Movement();
		_movements.push_back(mov);

		MfcArchive archive(f.get());

		mov->load(archive, this);
	}

	Common::Point pt;
	if (count) { // We have movements
		pt = _movements[0]->getCurrDynamicPhaseXY();
	} else {
		pt.x = pt.y = 100;
	}

	debugC(6, kDebugXML, "%% </OLDANI>");

	setOXY(pt.x, pt.y);

	return true;
}

void StaticANIObject::setOXY(int x, int y) {
	_ox = x;
	_oy = y;

	if (_movement)
		_movement->setOXY(x, y);
}

void StaticANIObject::clearFlags() {
	_flags = 0;

	deleteFromGlobalMessageQueue();
	_messageQueueId = 0;
	_movement = 0;
	_statics = 0;
	_animExFlag = 0;
	_counter = 0;
	_messageNum = 0;
	_stepArray.clear();
}

void StaticANIObject::setFlags40(bool state) {
	if (state) {
		_flags |= 0x40;
	} else {
		if (_flags & 0x40)
			_flags ^= 0x40;
	}
}

void StaticANIObject::deleteFromGlobalMessageQueue() {
	while (_messageQueueId) {
		if (g_fp->_globalMessageQueueList->getMessageQueueById(_messageQueueId)) {
			if (!isIdle())
				return;

			g_fp->_globalMessageQueueList->deleteQueueById(_messageQueueId);
		} else {
			_messageQueueId = 0;
		}
	}
}

bool StaticANIObject::queueMessageQueue(MessageQueue *mq) {
	if (_flags & 0x80)
		return false;

	if (isIdle()) {
		deleteFromGlobalMessageQueue();
		_messageQueueId = 0;
		_messageNum = 0;

		if (_flags & 2)
			_flags ^= 2;

		if (mq) {
			_animExFlag = 0;
			if (_movement)
				_messageQueueId = mq->_id;
			else
				mq->sendNextCommand();
		} else {
			_messageQueueId = 0;
		}
	}

	return true;
}

void StaticANIObject::restartMessageQueue(MessageQueue *mq) {
	ExCommand *ex = mq->getExCommandByIndex(0);
	if (ex) {
		while (ex->_messageKind != 1 || ex->_parentId != _id) {
			ex->_parId = 0;
			ex->_excFlags |= 2;
			ex->handleMessage();

			mq->deleteExCommandByIndex(0, 0);

			ex = mq->getExCommandByIndex(0);

			if (!ex)
				return;
		}

		if (ex) {
			startAnim(ex->_messageNum, mq->_id, -1);
			mq->deleteExCommandByIndex(0, 1);
		}
	}
}

MessageQueue *StaticANIObject::getMessageQueue() {
	if (this->_messageQueueId <= 0)
		return 0;

	return g_fp->_globalMessageQueueList->getMessageQueueById(_messageQueueId);
}

bool StaticANIObject::trySetMessageQueue(int msgNum, int qId) {
	if (_messageQueueId || !msgNum) {
		updateGlobalMessageQueue(qId, _id);
		return false;
	}

	_flags |= 2;

	_messageNum = msgNum;
	_messageQueueId = qId;

	return true;
}

void StaticANIObject::startMQIfIdle(int qId, int flag) {
	MessageQueue *msg = g_fp->_currentScene->getMessageQueueById(qId);

	if (msg && isIdle() && !(_flags & 0x100)) {
		MessageQueue *mq = new MessageQueue(msg, 0, 0);

		mq->setFlags(mq->getFlags() | flag);

		ExCommand *ex = mq->getExCommandByIndex(0);

		if (ex) {
			while (ex->_messageKind != 1 || ex->_parentId != _id) {
				ex->_parId = 0;
				ex->_excFlags |= 2;
				ex->handleMessage();

				mq->deleteExCommandByIndex(0, 0);

				ex = mq->getExCommandByIndex(0);

				if (!ex)
					return;
			}

			if (ex) {
				startAnim(ex->_messageNum, mq->_id, -1);
				mq->deleteExCommandByIndex(0, 1);
			}
		}
	}
}

bool StaticANIObject::isIdle() {
	assert(_objtype == kObjTypeStaticANIObject);

	if (_messageQueueId) {
		MessageQueue *m = g_fp->_globalMessageQueueList->getMessageQueueById(_messageQueueId);

		if (m && m->getFlags() & 1)
			return false;
	}

	return true;
}

Statics *StaticANIObject::getStaticsById(int itemId) {
	for (uint i = 0; i < _staticsList.size(); i++)
		if (_staticsList[i]->_staticsId == itemId)
			return _staticsList[i];

	return 0;
}

Statics *StaticANIObject::getStaticsByName(const Common::String &name) {
	for (uint i = 0; i < _staticsList.size(); i++)
		if (_staticsList[i]->_staticsName == name)
			return _staticsList[i];

	return 0;
}

Movement *StaticANIObject::getMovementById(int itemId) {
	for (uint i = 0; i < _movements.size(); i++)
		if (_movements[i]->_id == itemId)
			return _movements[i];

	return 0;
}

int StaticANIObject::getMovementIdById(int itemId) const {
	for (uint i = 0; i < _movements.size(); i++) {
		Movement *mov = _movements[i];

		if (mov->_currMovement) {
			if (mov->_id == itemId)
				return mov->_currMovement->_id;

			if (mov->_currMovement->_id == itemId)
				return mov->_id;
		}
	}

	return 0;
}

Movement *StaticANIObject::getMovementByName(const Common::String &name) {
	for (uint i = 0; i < _movements.size(); i++)
		if (_movements[i]->_objectName == name)
			return _movements[i];

	return 0;
}

bool StaticANIObject::isPixelHitAtPos(int x, int y) {
	return getPixelAtPos(x, y, 0, true);
}

bool StaticANIObject::getPixelAtPos(int x, int y, uint32 *pixel, bool hitOnly) {
	bool res = false;
	Picture *pic;

	if (_movement)
		pic = _movement->_currDynamicPhase;
	else
		pic = _statics;

	if (!pic)
		return false;

	int ongoing;
	int xani, yani;
	int oxani, oyani;

	if (_movement)
		ongoing = _movement->_currMovement != 0;
	else
		ongoing = _statics->_staticsId & 0x4000;

	if (_movement) {
		const Common::Point point = _movement->getCurrDynamicPhaseXY();
		xani = point.x;
		yani = point.y;
		oxani = _movement->_ox;
		oyani = _movement->_oy;
	} else {
		const Common::Point point = _statics->getSomeXY();
		xani = point.x;
		yani = point.y;
		oxani = _ox;
		oyani = _oy;
	}

	int xtarget = x - (oxani - xani);
	int ytarget = y - (oyani - yani);

	if (ongoing && _movement)
		xtarget = pic->getDimensions().x - xtarget;

	x = pic->_x;
	y = pic->_y;
	pic->_x = 0;
	pic->_y = 0;

	if (hitOnly)
		return pic->isPixelHitAtPos(xtarget, ytarget);

	if (pic->isPixelHitAtPos(xtarget, ytarget)) {
		*pixel = pic->getPixelAtPos(xtarget, ytarget);

		res = true;
	} else {
		res = false;
	}
	pic->_x = x;
	pic->_y = y;

	return res;
}

void Movement::draw(bool flipFlag, int angle) {
	debugC(3, kDebugDrawing, "Movement::draw(%d, %d)", flipFlag, angle);

	Common::Point point = getCurrDynamicPhaseXY();

	int x = _ox - point.x;
	int y = _oy - point.y;

	if (_currDynamicPhase->getPaletteData().size())
		g_fp->_globalPalette = &_currDynamicPhase->getPaletteData();

	Common::ScopedPtr<Bitmap> bmp;
	if (_currMovement) {
		bmp.reset(_currDynamicPhase->getPixelData()->reverseImage());
	} else {
		bmp.reset(_currDynamicPhase->getPixelData()->reverseImage(false));
	}

	if (flipFlag) {
		bmp->flipVertical()->drawShaded(1, x, y + 30 + _currDynamicPhase->_rect.bottom, _currDynamicPhase->getPaletteData(), _currDynamicPhase->getAlpha());
	} else if (angle) {
		bmp->drawRotated(x, y, angle, _currDynamicPhase->getPaletteData(), _currDynamicPhase->getAlpha());
	} else {
		bmp->putDib(x, y, _currDynamicPhase->getPaletteData(), _currDynamicPhase->getAlpha());
	}

	if (_currDynamicPhase->_rect.top) {
		if (!_currDynamicPhase->getConvertedBitmap()) {
			//v12 = Picture_getPixelData(v5);
			//v13 = Bitmap_convertTo16Bit565(v12, (unsigned int *)&_currDynamicPhase->rect);
			//_currDynamicPhase->convertedBitmap = v13;
		}

		if (_currDynamicPhase->getConvertedBitmap()) {
			if (_currMovement) {
				//vrtSetAlphaBlendMode(g_vrtDrawHandle, 1, LOBYTE(_currDynamicPhase->rect.top));
				bmp.reset(_currDynamicPhase->getConvertedBitmap()->reverseImage());
				bmp->putDib(x, y, _currDynamicPhase->getPaletteData(), _currDynamicPhase->getAlpha());
				//vrtSetAlphaBlendMode(g_vrtDrawHandle, 0, 255);
			} else {
				//vrtSetAlphaBlendMode(g_vrtDrawHandle, 1, LOBYTE(_currDynamicPhase->rect.top));
				bmp.reset(_currDynamicPhase->getConvertedBitmap()->reverseImage(false));
				bmp->putDib(x, y, _currDynamicPhase->getPaletteData(), _currDynamicPhase->getAlpha());
				//vrtSetAlphaBlendMode(g_vrtDrawHandle, 0, 255);
			}
		}
	}
}

void StaticANIObject::loadMovementsPixelData() {
	for (uint i = 0; i < _movements.size(); i++)
		_movements[i]->loadPixelData();
}

void StaticANIObject::freeMovementsPixelData() {
	for (uint i = 0; i < _movements.size(); i++)
		_movements[i]->freePixelData();
}

Statics *StaticANIObject::addReverseStatics(Statics *st) {
	assert(st);
	Statics *res = getStaticsById(st->_staticsId ^ 0x4000);

	if (!res) {
		res = new Statics(st, true);
		_staticsList.push_back(res);
	}

	return res;
}

void StaticANIObject::draw() {
	if ((_flags & 4) == 0)
		return;

	debugC(6, kDebugDrawing, "StaticANIObject::draw() (%s) [%d] [%d, %d]", transCyrillic(_objectName), _id, _ox, _oy);

	if (_shadowsOn && g_fp->_currentScene && g_fp->_currentScene->_shadows
		&& (getCurrDimensions().x != 1 || getCurrDimensions().y != 1)) {

		DynamicPhase *dyn;

		if (!_movement || _flags & 0x20)
			dyn = _statics;
		else
			dyn = _movement->_currDynamicPhase;

		if (!dyn) {
			warning("HACK: StaticANIObject::draw(): dyn is missing");
			return;
		}

		if (dyn->getDynFlags() & 4) {
			const Common::Rect &rect = dyn->_rect;

			DynamicPhase *shd = g_fp->_currentScene->_shadows->findSize(rect.width(), rect.height());
			if (shd) {
				const Dims dims = shd->getDimensions();
				int midx = _ox - dims.x / 2 - dyn->_someX;
				int midy = _oy - dims.y / 2 - dyn->_someY + rect.bottom - 3;
				int shdw =  dims.y;

				int px;
				if (!_movement || (_flags & 0x20))
					px = _statics->getCenter().x;
				else
					px = _movement->getCenter().x;

				if (_shadowsOn != 1)
					midy = _shadowsOn - shdw / 2;

				shd->draw(px + midx, midy, 0, 0);
			}
		}
	}

	int angle = 0;
	if (_field_30 & 0xC000) {
		if (_field_30 & 0x8000)
			angle = -(_field_30 ^ 0x8000);
		else
			angle = _field_30 ^ 0x4000;
	}

	if (!_movement || (_flags & 0x20)) {
		const Common::Point point = _statics->getSomeXY();
		_statics->_x = _ox - point.x;
		_statics->_y = _oy - point.y;
		_statics->draw(_statics->_x, _statics->_y, 0, angle);
	} else {
		_movement->draw(0, angle);
	}
}

void StaticANIObject::draw2() {
	debugC(6, kDebugDrawing, "StatciANIObject::draw2(): id: (%s) %d [%d, %d]", transCyrillic(_objectName), _id, _ox, _oy);

	if ((_flags & 4) && (_flags & 0x10)) {
		if (_movement) {
			_movement->draw(1, 0);
		} else {
			const Common::Point point = _statics->getSomeXY();
			_statics->draw(_ox - point.x, _oy - point.y, 1, 0);
		}
	}
}

MovTable *StaticANIObject::countMovements() {
	GameVar *preloadSubVar = g_fp->getGameLoaderGameVar()->getSubVarByName(getName())->getSubVarByName("PRELOAD");

	if (!preloadSubVar || preloadSubVar->getSubVarsCount() == 0)
		return nullptr;

	MovTable *movTable = new MovTable;
	movTable->reserve(_movements.size());
	for (uint i = 0; i < _movements.size(); i++) {
		int16 value = 2;
		for (GameVar *sub = preloadSubVar->_subVars; sub; sub = sub->_nextVarObj) {
			if (scumm_stricmp(_movements[i]->getName().c_str(), sub->_varName.c_str()) == 0) {
				value = 1;
				break;
			}
		}
		movTable->push_back(value);
	}

	return movTable;
}

void StaticANIObject::setSpeed(int speed) {
	GameVar *var = g_fp->getGameLoaderGameVar()->getSubVarByName(getName())->getSubVarByName("SpeedUp");

	if (!var)
		return;

	for (var = var->_subVars; var; var = var->_nextVarObj) {
		Movement *mov = getMovementById(var->_value.intValue);

		if (mov) {
			if (speed) {
				if (mov->_counterMax == 83)
					mov->_counterMax = 41;
			} else if (mov->_counterMax == 41) {
				mov->_counterMax = 83;
			}
		}
	}

}

void StaticANIObject::setAlpha(int alpha) {
	for (uint i = 0; i < _movements.size(); i++)
		_movements[i]->setAlpha(alpha);

	for (uint i = 0; i < _staticsList.size(); i++)
		_staticsList[i]->setAlpha(alpha);
}

void StaticANIObject::initMovements() {
	for (uint i = 0; i < _movements.size(); i++)
		_movements[i]->removeFirstPhase();
}

void StaticANIObject::preloadMovements(MovTable *mt) {
	if (mt) {
		for (uint i = 0; i < _movements.size(); i++) {
			Movement *mov = _movements[i];

			if ((*mt)[i] == 1)
				mov->loadPixelData();
			else if ((*mt)[i] == 2)
				mov->freePixelData();
		}
	}
}

Dims StaticANIObject::getCurrDimensions() const {
	Picture *pic;

	if (_movement)
		pic = _movement->_currDynamicPhase;
	else
		pic = _statics;

	if (pic) {
		return pic->getDimensions();
	}

	return Dims();
}

Common::Point StaticANIObject::getSomeXY() const {
	if (_movement) {
		return _movement->getCurrDynamicPhaseXY();
	}

	if (_statics)
		return _statics->getSomeXY();

	error("No someXY found");
}

void StaticANIObject::update(int counterdiff) {
	int mqid;

	debugC(6, kDebugAnimation, "StaticANIObject::update() (%s) [%d] [%d, %d] fl: %x", transCyrillic(_objectName), _id, _ox, _oy, _flags);

	if (_flags & 2) {
		_messageNum--;
		if (_messageNum)
			return;

		mqid = _messageQueueId;
		_messageQueueId = 0;
		_flags ^= 2;

		updateGlobalMessageQueue(mqid, _id);
		return;
	}

	ExCommand *ex, *newex;

	if (_movement) {
		_movement->_counter += counterdiff;

		if (_movement->_counter < _movement->_counterMax)
			return;

		_movement->_counter = 0;

		if (_flags & 1) {
			if (_counter) {
				_counter--;

				return;
			}

			DynamicPhase *dyn = _movement->_currDynamicPhase;
			if (dyn->_initialCountdown == dyn->_countdown) {

				ex = dyn->getExCommand();
				if (ex && ex->_messageKind != 35) {
					newex = ex->createClone();
					newex->_excFlags |= 2;
					if (newex->_messageKind == 17) {
						newex->_parentId = _id;
						newex->_param = _odelay;
					}
					newex->sendMessage();

					if (!_movement)
						return;
				}
			}

			if (dyn->_initialCountdown == dyn->_countdown && dyn->_field_68 != 0) {
				newex = new ExCommand(_id, 17, dyn->_field_68, 0, 0, 0, 1, 0, 0, 0);
				newex->_excFlags = 2;
				newex->_param = _odelay;
				newex->sendMessage();

				if (!_movement)
					return;
			}

			if (!_movement->gotoNextFrame(_callback1, _callback2)) {
				stopAnim_maybe();
			} else {
				setOXY(_movement->_ox, _movement->_oy);
				_counter = _initialCounter;

				if (dyn->_initialCountdown == dyn->_countdown) {
					ex = dyn->getExCommand();
					if (ex) {
						if (ex->_messageKind == 35) {
							newex = ex->createClone();
							newex->_excFlags |= 2;
							newex->sendMessage();
						}
					}
				}
			}
			if (!_movement)
				return;

			const Common::Point point = _stepArray.getCurrPoint();
			setOXY(point.x + _ox, point.y + _oy);
			_stepArray.gotoNextPoint();
			if (_someDynamicPhaseIndex == _movement->_currDynamicPhaseIndex)
				adjustSomeXY();
		} else if (_flags & 0x20) {
			_flags ^= 0x20;
			_flags |= 1;

			_movement->gotoFirstFrame();

			const Common::Point point = _movement->getCurrDynamicPhaseXY();
			const Common::Point pointS = _statics->getSomeXY();
			_movement->setOXY(_ox + point.x + _movement->_mx - pointS.x,
							  _oy + point.y + _movement->_my - pointS.y);
		}
	} else {
		if (_statics) {
			if (_messageQueueId) {
				if (_statics->_countdown) {
					_statics->_countdown--;
					return;
				}
				mqid = _messageQueueId;
				_messageQueueId = 0;
				updateGlobalMessageQueue(mqid, _id);
			}
		}
	}
}

void StaticANIObject::updateStepPos() {
	Common::Point point;

	int ox = _movement->_ox;
	int oy = _movement->_oy;

	point = _movement->calcSomeXY(1, _someDynamicPhaseIndex);
	int x = point.x;
	int y = point.y;

	point = _stepArray.getPoint(-1, _stepArray.getPointsCount());
	x += point.x;
	y += point.y;

	_statics = _movement->_staticsObj2;
	_movement = 0;

	setOXY(ox + x, oy + y);
}

Common::Point *StaticANIObject::calcNextStep(Common::Point *pRes) {
	if (!_movement) {
		pRes->x = 0;
		pRes->y = 0;

		return pRes;
	}

	Common::Point point = _movement->calcSomeXY(1, _someDynamicPhaseIndex);

	int resX = point.x;
	int resY = point.y;

	int pointN, offset;

	if (_someDynamicPhaseIndex <= 0) {
		pointN = _stepArray.getCurrPointIndex();
		offset = _stepArray.getPointsCount() - _stepArray.getCurrPointIndex();
	} else {
		pointN = _stepArray.getCurrPointIndex();
		offset = 1 - _movement->_currDynamicPhaseIndex + _someDynamicPhaseIndex;
	}

	if (pointN >= 0) {
		point = _stepArray.getPoint(pointN, offset);

		resX += point.x;
		resY += point.y;
	}

	pRes->x = resX;
	pRes->y = resY;

	return pRes;
}

void StaticANIObject::stopAnim_maybe() {
	debugC(2, kDebugAnimation, "StaticANIObject::stopAnim_maybe()");

	if (!(_flags & 1))
		return;

	_flags ^= 1;

	int oid = 0;
	int oldmqid = _messageQueueId;
	Common::Point point;

	if (_movement) {
		setOXY(_movement->_ox, _movement->_oy);

		if (_flags & 0x40) {
			if (!_movement->_currMovement) {
				if (_movement->_currDynamicPhaseIndex)
					goto L11;
L8:
				_statics = _movement->_staticsObj1;
				point = _movement->getCurrDynamicPhaseXY();
				_ox -= point.x;
				_oy -= point.y;

				_ox -= _movement->_mx;
				_oy -= _movement->_my;

				point = _statics->getSomeXY();
				if (_movement->_currMovement) {
					_oy += point.y;
					_ox -= point.x;
					_ox += _statics->getDimensions().x;
				} else {
					_ox += point.x;
					_oy += point.y;
				}
				goto L12;
			}
			if (!_movement->_currDynamicPhaseIndex)
				goto L8;
		}
L11:
		_statics = _movement->_staticsObj2;
L12:
		point = _statics->getSomeXY();

		_statics->_x = _ox - point.x;
		_statics->_y = _oy - point.y;
		oid = _movement->_id;
		_movement = 0;

		ExCommand *ex = new ExCommand(_id, 17, 24, 0, 0, 0, 1, 0, 0, 0);
		ex->_param = _odelay;
		ex->_excFlags = 2;
		ex->postMessage();
	}

	int mqid = _messageQueueId;

	if (_animExFlag) {
		_messageQueueId = 0;
		startAnimEx(oid, mqid, -1, -1);
	} else {
		if (_messageQueueId == oldmqid) {
			_messageQueueId = 0;
			if (_field_34 == 1)
				updateGlobalMessageQueue(mqid, _id);
		}
	}
}

void StaticANIObject::adjustSomeXY() {
	if (_movement) {
		Common::Point point;

		point = _movement->calcSomeXY(0, -1);

		int diff = abs(point.y) - abs(point.x);

		point = _movement->calcSomeXY(1, -1);

		if (diff > 0)
			_ox += point.x;
		else
			_oy += point.y;

		_statics = _movement->_staticsObj2;
		_movement = 0;
		_someDynamicPhaseIndex = -1;
	}
}

MessageQueue *StaticANIObject::changeStatics1(int msgNum) {
	g_fp->_aniHandler->attachObject(_id);

	MessageQueue *mq = g_fp->_aniHandler->makeQueue(this, msgNum, 0, 0, 0);

	if (!mq)
		return 0;

	if (mq->getCount() <= 0) {
		g_fp->_globalMessageQueueList->addMessageQueue(mq);

		if (_flags & 1)
			_messageQueueId = mq->_id;
	} else {
		if (!queueMessageQueue(mq)) {
			delete mq;

			return 0;
		}

		g_fp->_globalMessageQueueList->addMessageQueue(mq);
	}

	return mq;
}

void StaticANIObject::changeStatics2(int objId) {
	_animExFlag = 0;

	deleteFromGlobalMessageQueue();

	if (_movement || _statics) {
		g_fp->_aniHandler->attachObject(_id);
		g_fp->_aniHandler->putObjectToStatics(this, objId);
	} else {
		_statics = getStaticsById(objId);
	}

	if (_messageQueueId) {
		if (g_fp->_globalMessageQueueList->getMessageQueueById(_messageQueueId))
			g_fp->_globalMessageQueueList->deleteQueueById(_messageQueueId);

		_messageQueueId = 0;
	}
}

void StaticANIObject::hide() {
	if (!_messageQueueId) {
		if (_flags & 4)
			_flags ^= 4;
	}
}

void StaticANIObject::show1(int x, int y, int movId, int mqId) {
	debugC(6, kDebugAnimation, "StaticANIObject::show1(%d, %d, %d, %d)", x, y, movId, mqId);

	if (_messageQueueId)
		return;

	if (movId == -1) {
		_flags |= 4u;
		if (x != -1 && y != -1) {
			setOXY(x, y);
		}

		return;
	}

	Movement *mov = getMovementById(movId);
	if (!mov)
		return;

	if (x != -1 && y != -1) {
		setOXY(x, y);
	}

	_statics = mov->_staticsObj1;

	const Common::Point point = mov->_staticsObj1->getSomeXY();
	_statics->_x = x - point.x;
	_statics->_y = y - point.y;

	_statics->_countdown = _statics->_initialCountdown;

	_flags |= 4;
	_ox = x;
	_oy = y;
	_movement = 0;

	if (mov->_currMovement)
		_flags |= 8;
	else if (_flags & 8)
		_flags ^= 8;

	if (_flags & 1)
		_flags ^= 1;

	_messageQueueId = mqId;
}

void StaticANIObject::show2(int x, int y, int movementId, int mqId) {
	if (movementId == -1) {
		_flags |= 4u;
		return;
	}

	if (!_messageQueueId) {
		_messageQueueId = mqId;

		Movement *mov = getMovementById(movementId);

		if (mov) {
			_statics = mov->_staticsObj1;
			_movement = mov;
			mov->gotoLastFrame();
			mov->setOXY(x, y);
			mov->gotoFirstFrame();

			Common::Point point = mov->getCurrDynamicPhaseXY();
			_statics->_x = mov->_ox - point.x - mov->_mx;
			_statics->_y = mov->_oy - point.y - mov->_my;

			point = _statics->getSomeXY();
			_flags |= 4;
			_ox = _statics->_x + point.x;
			_oy = _statics->_y + point.y;

			if (mov->_currMovement) {
				_flags |= 8;
			} else {
				if (_flags & 8)
					_flags ^= 8;
			}

			if (_flags & 1)
				_flags ^= 1;

			_flags |= 0x20;
		}
	}
}

void StaticANIObject::playIdle() {
	if (isIdle())
		adjustSomeXY();
}

void StaticANIObject::startAnimSteps(int movementId, int messageQueueId, int x, int y, const PointList &points, int someDynamicPhaseIndex) {
	Movement *mov = 0;

	if (!(_flags & 0x80)) {
		if (!_messageQueueId)
			for (uint i = 0; i < _movements.size(); i++) {
				if (_movements[i]->_id == movementId) {
					mov = _movements[i];
					break;
				}
			}
	}

	if (!mov) {
		updateGlobalMessageQueue(messageQueueId, _id);

		return;
	}


	if (_movement || !_statics)
		return;

	Common::Point point = _statics->getSomeXY();

	int newx = _ox - point.x;
	int newy = _oy - point.y;

	_movement = mov;

	if (_flags & 0x40)
		_movement->gotoLastFrame();
	else
		_movement->gotoFirstFrame();

	_stepArray.clear();
	_stepArray.insertPoints(points);

	if (!(_flags & 0x40)) {
		if (!_movement->_currDynamicPhaseIndex) {
			point = _stepArray.getCurrPoint();
			newx += point.x + _movement->_mx;
			newy += point.y + _movement->_my;
			_stepArray.gotoNextPoint();

			ExCommand *ex = _movement->_currDynamicPhase->getExCommand();

			if (ex) {
				if (ex->_messageKind == 35) {
					ExCommand *newEx = ex->createClone();

					newEx->_excFlags |= 2u;
					newEx->sendMessage();
				}
			}
		}
	}

	point = _movement->getCurrDynamicPhaseXY();
	setOXY(point.x + newx, point.y + newy);

	if ((_movement->_staticsObj2->_staticsId >> 8) & 0x40)
		_flags |= 8;
	else
		_flags &= 0xFFF7;

	_flags |= 1;
	_messageQueueId = messageQueueId;
	_movement->_currDynamicPhase->_countdown = _movement->_currDynamicPhase->_initialCountdown;
	_movement->_counter = 0;
	_counter = _initialCounter;
	_someDynamicPhaseIndex = someDynamicPhaseIndex;

	ExCommand *ex = new ExCommand(_id, 17, 23, 0, 0, movementId, 1, 0, 0, 0);

	ex->_param = _odelay;
	ex->_excFlags = 2;
	ex->postMessage();
}

bool StaticANIObject::startAnimEx(int movid, int parId, int flag1, int flag2) {
	bool res = startAnim(movid, parId, -1);
	if (res)
		_animExFlag = 1;

	_someDynamicPhaseIndex = -1;
	return res;
}

bool StaticANIObject::startAnim(int movementId, int messageQueueId, int dynPhaseIdx) {
	if (_flags & 0x80)
		return false;

	debugC(4, kDebugAnimation, "StaticANIObject::startAnim(%d, %d, %d) (%s [%d]) [%d, %d]", movementId, messageQueueId, dynPhaseIdx, transCyrillic(_objectName), _id, _ox, _oy);

	if (_messageQueueId) {
		updateGlobalMessageQueue(messageQueueId, _id);
		return false;
	}

	Movement *mov = 0;

	for (uint i = 0; i < _movements.size(); i++) {
		if (_movements[i]->_id == movementId) {
			mov = _movements[i];
			break;
		}
	}

	if (!mov) {
		updateGlobalMessageQueue(messageQueueId, _id);
		return false;
	}

	if (mov == _movement) {
		_flags |= 1;
		_messageQueueId = messageQueueId;

		return true;
	}

	int newx = _ox;
	int newy = _oy;
	Common::Point point;

	if (_movement) {
		point = _movement->getCurrDynamicPhaseXY();

		newx -= point.x;
		newy -= point.y;

	} else if (_statics) {
		point = _statics->getSomeXY();

		newx -= point.x;
		newy -= point.y;
	}

	_movement = mov;

	_stepArray.clear();

	if (_flags & 0x40)
		_movement->gotoLastFrame();
	else
		_movement->gotoFirstFrame();

	if (!(_flags & 0x40)) {
		if (!_movement->_currDynamicPhaseIndex) {
			point = _stepArray.getCurrPoint();
			newx += point.x + _movement->_mx;
			newy += point.y + _movement->_my;

			_stepArray.gotoNextPoint();

			ExCommand *ex = _movement->_currDynamicPhase->getExCommand();
			if (ex) {
				if (ex->_messageKind == 35) {
					ExCommand *newex = ex->createClone();
					newex->_excFlags |= 2;
					newex->sendMessage();
				}
			}
		}
	}

	point = _movement->getCurrDynamicPhaseXY();
	setOXY(point.x + newx, point.y + newy);

	if (_movement->_staticsObj2->_staticsId & 0x4000)
		_flags |= 8;
	else
		_flags &= 0xFFF7;

	_flags |= 1;

	_messageQueueId = messageQueueId;
	_movement->_currDynamicPhase->_countdown = _movement->_currDynamicPhase->_initialCountdown;
	_movement->_counter = 0;

	_counter = _initialCounter;
	_someDynamicPhaseIndex = dynPhaseIdx;

	_stepArray.clear();

	ExCommand *newex = new ExCommand(_id, 17, 23, 0, 0, movementId, 1, 0, 0, 0);

	newex->_param = _odelay;
	newex->_excFlags = 2;

	newex->postMessage();

	return true;
}

Common::Point *StaticANIObject::calcStepLen(Common::Point *p) {
	if (_movement) {
		Common::Point point;

		point = _movement->calcSomeXY(0, _movement->_currDynamicPhaseIndex);

		p->x = point.x;
		p->y = point.y;

		int idx = _stepArray.getCurrPointIndex() - _movement->_currDynamicPhaseIndex - 1;

		if (idx >= 0) {
			point = _stepArray.getPoint(idx, _movement->_currDynamicPhaseIndex + 2);
			p->x += point.x;
			p->y += point.y;
		}
	} else {
		p->x = 0;
		p->y = 0;
	}

	return p;
}

Statics::Statics() {
	_staticsId = 0;
	_data = nullptr;
}

Statics::Statics(Statics *src, bool reverse) : DynamicPhase(src, reverse) {
	_staticsId = src->_staticsId;

	if (reverse) {
		_staticsId ^= 0x4000;
		_staticsName = sO_MirroredTo + src->_staticsName;
	} else {
		_staticsName = src->_staticsName;
	}

	_memfilename = src->_memfilename;
}

bool Statics::load(MfcArchive &file) {
	debugC(5, kDebugLoading, "Statics::load()");

	DynamicPhase::load(file);

	_staticsId = file.readUint16LE();

	_staticsName = file.readPascalString();
	debugC(6, kDebugXML, "%% <STATICS id=\"%s\" name=\"%s\" %s />",
		g_fp->gameIdToStr(_staticsId).c_str(), transCyrillic(_staticsName), DynamicPhase::toXML().c_str());

	_picture.load(file);

	return true;
}

void Statics::init() {
	Picture::init();

	if (_staticsId & 0x4000) {
		_bitmap.reset(_bitmap->reverseImage());
	}
}

Common::Point Statics::getSomeXY() const {
	return Common::Point(_someX, _someY);
}

Common::Point Statics::getCenter() const {
	Common::Rect rect(_rect);

	if (_staticsId & 0x4000) {
		const Dims dims = getDimensions();
		rect.moveTo(dims.x - _rect.right, _rect.top);
	}

	return Common::Point(rect.left + _rect.width() / 2,
						 rect.top + _rect.height() / 2);
}

Movement::Movement() {
	_lastFrameSpecialFlag = 0;
	_flipFlag = 0;
	_updateFlag1 = 0;
	_staticsObj1 = 0;
	_staticsObj2 = 0;
	_mx = 0;
	_my = 0;
	_m2x = 0;
	_m2y = 0;
	_field_50 = 1;
	_field_78 = 0;
	_field_84 = 0;
	_currDynamicPhase = 0;
	_field_8C = 0;
	_currDynamicPhaseIndex = 0;
	_field_94 = 0;
	_currMovement = 0;
	_counter = 0;
	_counterMax = 83;
}

Movement::~Movement() {
	if (!_currMovement) {
		if (_updateFlag1) {
			_dynamicPhases[0]->freePixelData();
			delete _dynamicPhases.remove_at(0);
		}

		// FIXME: At this point, the last entry in _dynamicPhases is invalid
		for (uint i = 0; i < _dynamicPhases.size() - 1; i++) {
			DynamicPhase *phase = _dynamicPhases[i];
			if (phase != _staticsObj1 && phase != _staticsObj2)
				delete phase;
			else
				_dynamicPhases[i]->freePixelData();
		}
	}
}

Movement::Movement(Movement *src, StaticANIObject *ani) {
	_lastFrameSpecialFlag = 0;
	_flipFlag = src->_flipFlag;
	_updateFlag1 = src->_updateFlag1;
	_staticsObj1 = 0;
	_staticsObj2 = 0;
	_mx = 0;
	_my = 0;
	_m2x = 0;
	_m2y = 0;

	_field_78 = 0;
	_field_84 = 0;
	_currDynamicPhase = 0;
	_field_8C = 0;
	_currDynamicPhaseIndex = src->_currDynamicPhaseIndex;
	_field_94 = 0;

	_somePoint.x = 0;
	_somePoint.y = 0;

	_currMovement = src;
	_ox = src->_ox;
	_oy = src->_oy;

	initStatics(ani);

	_counterMax = src->_counterMax;
	_counter = src->_counter;
	_field_50 = src->_field_50;

	updateCurrDynamicPhase();
}

Movement::Movement(Movement *src, int *oldIdxs, int newSize, StaticANIObject *ani) : GameObject(src) {
	_lastFrameSpecialFlag = 0;
	_updateFlag1 = 1;
	_staticsObj1 = 0;
	_staticsObj2 = 0;
	_mx = 0;
	_my = 0;
	_m2x = 0;
	_m2y = 0;

	_counter = 0;
	_counterMax = 0;

	_field_78 = 0;
	_field_84 = 0;
	_currDynamicPhase = 0;
	_field_8C = 0;
	_currDynamicPhaseIndex = 0;
	_field_94 = 0;

	_somePoint.x = 0;
	_somePoint.y = 0;

	_field_50 = src->_field_50;
	_flipFlag = src->_flipFlag;
	_currMovement = 0;
	_mx = src->_mx;
	_my = src->_my;
	_m2x = src->_m2x;
	_m2y = src->_m2y;

	if (newSize != -1) {
		if (newSize >= (int)src->_dynamicPhases.size() + 1)
			newSize = src->_dynamicPhases.size() + 1;
	} else {
		newSize = src->_dynamicPhases.size();
	}

	if (!newSize) {
		warning("Movement::Movement: newSize = 0");

		return;
	}

	_framePosOffsets.resize(newSize);

	if (oldIdxs) {
		for (int i = 0; i < newSize - 1; i++, oldIdxs++) {
			if (oldIdxs[i] == -1) {
				_dynamicPhases.push_back(src->_staticsObj1);
			} else {
				src->setDynamicPhaseIndex(oldIdxs[i]);

				_dynamicPhases.push_back(src->_currDynamicPhase);

				_framePosOffsets[i].x = src->_framePosOffsets[oldIdxs[i]].x;
				_framePosOffsets[i].y = src->_framePosOffsets[oldIdxs[i]].y;
			}
		}
		_staticsObj1 = dynamic_cast<Statics *>(_dynamicPhases.front());
		_staticsObj2 = dynamic_cast<Statics *>(_dynamicPhases.back());
	} else {
		for (int i = 0; i < newSize; i++) {
			src->setDynamicPhaseIndex(i);

			if (i < newSize - 1)
				_dynamicPhases.push_back(new DynamicPhase(src->_currDynamicPhase, 0));

			_framePosOffsets[i].x = src->_framePosOffsets[i].x;
			_framePosOffsets[i].y = src->_framePosOffsets[i].y;
		}

		_staticsObj1 = ani->getStaticsById(src->_staticsObj1->_staticsId);
		_staticsObj2 = ani->getStaticsById(src->_staticsObj2->_staticsId);

		_dynamicPhases.push_back(_staticsObj2);

		this->_updateFlag1 = src->_updateFlag1;
	}

	updateCurrDynamicPhase();
	removeFirstPhase();

	_counterMax = src->_counterMax;
	_counter = src->_counter;
}

bool Movement::load(MfcArchive &file) {
	warning("STUB: Movement::load");
	return true;
}

bool Movement::load(MfcArchive &file, StaticANIObject *ani) {
	GameObject::load(file);

	int dynCount = file.readUint16LE();

	if (dynCount != 0xffff || _id == MV_MAN_TURN_LU) {
		_framePosOffsets.resize(dynCount + 2);

		for (int i = 0; i < dynCount; i++) {
			DynamicPhase *ph = new DynamicPhase();
			ph->load(file);

			_dynamicPhases.push_back(ph);

			_framePosOffsets[i].x = ph->_x;
			_framePosOffsets[i].y = ph->_y;
		}

		int staticsid = file.readUint16LE();

		_staticsObj1 = ani->getStaticsById(staticsid);

		if (!_staticsObj1 && (staticsid & 0x4000)) {
			Statics *s = ani->getStaticsById(staticsid ^ 0x4000);
			_staticsObj1 = ani->addReverseStatics(s);
		}

		_mx = file.readSint32LE();
		_my = file.readSint32LE();

		int staticsid2 = file.readUint16LE();

		_staticsObj2 = ani->getStaticsById(staticsid2);

		if (!_staticsObj2 && (staticsid2 & 0x4000)) {
			Statics *s = ani->getStaticsById(staticsid2 ^ 0x4000);
			_staticsObj2 = ani->addReverseStatics(s);
		}

		_m2x = file.readSint32LE();
		_m2y = file.readSint32LE();

		if (_staticsObj2) {
			_dynamicPhases.push_back(_staticsObj2);

			_framePosOffsets[_dynamicPhases.size() - 1].x = _m2x;
			_framePosOffsets[_dynamicPhases.size() - 1].y = _m2y;
		}

		debugC(6, kDebugXML, "%% <MOVEMENT %s staticsId=\"%s\" mX=%d my=%d staticsId2=\"%s\" m2x=%d m2y=%d>",
			GameObject::toXML().c_str(), g_fp->gameIdToStr(staticsid).c_str(), _mx, _my, g_fp->gameIdToStr(staticsid2).c_str(), _m2x, _m2y);

		for (int i = 0; i < dynCount; i++)
			debugC(6, kDebugXML, "%% <PHASE %s />", _dynamicPhases[i]->toXML().c_str());

		debugC(6, kDebugXML, "%% </MOVEMENT>");

	} else {
		int movid = file.readUint16LE();

		_currMovement = ani->getMovementById(movid);
		_staticsObj1 = 0;
		_staticsObj2 = 0;

		initStatics(ani);
	}

	if (_staticsObj1 && _staticsObj2) {
		if ((_staticsObj1->_staticsId ^ _staticsObj2->_staticsId) & 0x4000)
			_flipFlag = 1;
	}

	if (g_fp->_gameProjectVersion >= 8)
		_field_50 = file.readUint32LE();

	if (g_fp->_gameProjectVersion < 12)
		_counterMax = 83;
	else
		_counterMax = file.readUint32LE();

	_counter = 0;
	updateCurrDynamicPhase();

	return true;
}

Common::Point Movement::getCurrDynamicPhaseXY() const {
	return Common::Point(_currDynamicPhase->_someX, _currDynamicPhase->_someY);
}

Common::Point Movement::calcSomeXY(int idx, int dynidx) {
	int oldox = _ox;
	int oldoy = _oy;
	int oldidx = _currDynamicPhaseIndex;

	int x = 0;
	int y = 0;

	if (!idx) {
		const Common::Point point = _staticsObj1->getSomeXY();
		int x1 = _mx - point.x;
		int y1 = _my - point.y;

		setDynamicPhaseIndex(0);

		x = _currDynamicPhase->_someX + x1;
		y = _currDynamicPhase->_someY + y1;
	}

	setOXY(x, y);

	while (_currDynamicPhaseIndex != dynidx && gotoNextFrame(0, 0))
		;

	Common::Point p(_ox, _oy);

	setDynamicPhaseIndex(oldidx);
	setOXY(oldox, oldoy);

	return p;
}

void Movement::setAlpha(int alpha) {
	if (_currMovement)
		for (uint i = 0; i < _currMovement->_dynamicPhases.size(); i++) {
			_currMovement->_dynamicPhases[i]->setAlpha(alpha);
		}
	else
		for (uint i = 0; i < _dynamicPhases.size(); i++) {
			_dynamicPhases[i]->setAlpha(alpha);
		}
}

Dims Movement::getDimensionsOfPhase(int phaseIndex) const {
	int idx = phaseIndex;

	if (idx == -1)
		idx = _currDynamicPhaseIndex;

	DynamicPhase *dyn;

	if (_currMovement)
		dyn = _currMovement->_dynamicPhases[idx];
	else
		dyn = _dynamicPhases[idx];

	return dyn->getDimensions();
}

void Movement::initStatics(StaticANIObject *ani) {
	if (!_currMovement)
		return;

	debugC(7, kDebugAnimation, "Movement::initStatics()");

	_staticsObj2 = ani->addReverseStatics(_currMovement->_staticsObj2);
	_staticsObj1 = ani->addReverseStatics(_currMovement->_staticsObj1);

	_mx = _currMovement->_mx;
	_my = _currMovement->_my;

	_currMovement->setDynamicPhaseIndex(_currMovement->_updateFlag1 != 0 ? 1 : 0);

	Common::Point point;

	int x1 = _currMovement->_staticsObj1->getDimensions().x - _mx;

	_mx = x1 - _currMovement->_currDynamicPhase->getDimensions().x;

	_currMovement->setDynamicPhaseIndex(_currMovement->_currDynamicPhaseIndex);

	_m2x = _currMovement->_m2x;
	_m2y = _currMovement->_m2y;
	_currMovement->gotoLastFrame();

	x1 = _currMovement->_staticsObj2->getDimensions().x;
	_m2x = _currMovement->_currDynamicPhase->getDimensions().x - _m2x - x1;
}

void Movement::updateCurrDynamicPhase() {
	debugC(7, kDebugAnimation, "Movement::updateCurrDynamicPhase()");

	if (_currMovement) {
		if (_currMovement->_dynamicPhases.size() == 0 || (uint)_currDynamicPhaseIndex >= _currMovement->_dynamicPhases.size())
			return;

		if (_currMovement->_dynamicPhases[_currDynamicPhaseIndex])
			_currDynamicPhase = _currMovement->_dynamicPhases[_currDynamicPhaseIndex];
	} else {
		if (_dynamicPhases.size() == 0 || (uint)_currDynamicPhaseIndex >= _dynamicPhases.size())
			return;

		if (_dynamicPhases[_currDynamicPhaseIndex])
			_currDynamicPhase = _dynamicPhases[_currDynamicPhaseIndex];
	}
}

int Movement::calcDuration() {
	int res = 0;

	if (_currMovement)
		for (uint i = 0; i < _currMovement->_dynamicPhases.size(); i++) {
			res += _currMovement->_dynamicPhases[i]->_initialCountdown + 1;
		}
	else
		for (uint i = 0; i < _dynamicPhases.size(); i++) {
			res += _dynamicPhases[i]->_initialCountdown + 1;
		}

	return res;
}

int Movement::countPhasesWithFlag(int maxidx, int flag) {
	int res = 0;
	int sz;

	if (_currMovement)
		sz = _currMovement->_dynamicPhases.size();
	else
		sz = _dynamicPhases.size();

	if (maxidx < 0)
		maxidx = sz;

	for (int i = 0; i < maxidx && i < sz; i++)
		if (getDynamicPhaseByIndex(i)->_dynFlags & flag)
			res++;

	return res;
}

void Movement::setDynamicPhaseIndex(int index) {
	debugC(7, kDebugAnimation, "Movement::setDynamicPhaseIndex(%d)", index);
	while (_currDynamicPhaseIndex < index)
		gotoNextFrame(0, 0);

	while (_currDynamicPhaseIndex > index)
		gotoPrevFrame();
}

DynamicPhase *Movement::getDynamicPhaseByIndex(int idx) {
	debugC(7, kDebugAnimation, "Movement::updateCurrDynamicPhase()");

	if (_currMovement) {
		if (_currMovement->_dynamicPhases.size() == 0 || (uint)idx >= _currMovement->_dynamicPhases.size())
			return 0;

		return _currMovement->_dynamicPhases[idx];
	} else {
		if (_dynamicPhases.size() == 0 || (uint)idx >= _dynamicPhases.size())
			return 0;

		return _dynamicPhases[idx];
	}
}

void Movement::loadPixelData() {
	Movement *mov = this;
	while (mov->_currMovement)
		mov = mov->_currMovement;

	for (uint i = 0; i < mov->_dynamicPhases.size(); i++) {
		if (mov->_dynamicPhases[i] != mov->_staticsObj2 || !(mov->_staticsObj2->_staticsId & 0x4000))
			mov->_dynamicPhases[i]->getPixelData();
	}

	if (!(mov->_staticsObj1->_staticsId & 0x4000))
		mov->_staticsObj1->getPixelData();
}

void Movement::freePixelData() {
	if (!_currMovement)
		for (uint i = 0; i < _dynamicPhases.size(); i++)
			_dynamicPhases[i]->freePixelData();

	if (_staticsObj1)
		_staticsObj1->freePixelData();
}

void Movement::removeFirstPhase() {
	if (_updateFlag1) {
		if (!_currDynamicPhaseIndex)
			gotoNextFrame(0, 0);

		if (!_currMovement) {
			delete _dynamicPhases.remove_at(0);

			for (uint i = 0; i < _dynamicPhases.size(); i++) {
				_framePosOffsets[i] = _framePosOffsets[i + 1];
			}
			_framePosOffsets.pop_back();
		}
		_currDynamicPhaseIndex--;
	}

	updateCurrDynamicPhase();
	_updateFlag1 = 0;
}

bool Movement::gotoNextFrame(void (*callback1)(int, Common::Point *point, int, int), void (*callback2)(int *)) {
	debugC(8, kDebugAnimation, "Movement::gotoNextFrame()");

	if (!callback2) {
		if (_currMovement) {
			if ((uint)_currDynamicPhaseIndex == _currMovement->_dynamicPhases.size() - 1
				&& !(_currMovement->_dynamicPhases.back()->_countdown)) {
				return false;
			}
		} else if ((uint)_currDynamicPhaseIndex == _dynamicPhases.size() - 1
					&& !(_dynamicPhases.back()->_countdown)) {
			return false;
		}
	}

	if (_currDynamicPhase->_countdown) {
		_currDynamicPhase->_countdown--;
		return true;
	}

	Common::Point point = getCurrDynamicPhaseXY();
	_ox -= point.x;
	_oy -= point.y;

	int deltax = 0;

	if (_currMovement)
		deltax = _currMovement->getDimensionsOfPhase(_currDynamicPhaseIndex).x;

	int oldDynIndex = _currDynamicPhaseIndex;

	if (callback2)
		callback2(&_currDynamicPhaseIndex);
	else
		_currDynamicPhaseIndex++;

	bool result = true;

	if (_currMovement) {
		if (_currMovement->_dynamicPhases.size() <= (uint)_currDynamicPhaseIndex) {
			_currDynamicPhaseIndex = _currMovement->_dynamicPhases.size() - 1;
			result = (callback2 == 0);
		}
		if (_currDynamicPhaseIndex < 0) {
			_currDynamicPhaseIndex = 0;
			result = false;
		}
		if (_currMovement->_framePosOffsets.size()) {
			if (callback1) {
				point = _currMovement->_framePosOffsets[_currDynamicPhaseIndex];
				callback1(_currDynamicPhaseIndex, &point, _ox, _oy);

				_ox += deltax - point.x;
				_oy += point.y;

				_ox -= _currMovement->getDimensionsOfPhase(_currDynamicPhaseIndex).x;
			} else if (oldDynIndex >= _currDynamicPhaseIndex) {
				while (oldDynIndex > _currDynamicPhaseIndex) {
					_ox += deltax;
					deltax = _currMovement->getDimensionsOfPhase(oldDynIndex).x;

					_ox += _currMovement->_framePosOffsets[oldDynIndex].x;
					_oy -= _currMovement->_framePosOffsets[oldDynIndex].y;
					oldDynIndex--;

					_ox -= _currMovement->getDimensionsOfPhase(oldDynIndex).x;
				}
			} else {
				for (int i = oldDynIndex + 1; i <= _currDynamicPhaseIndex; i++) {
					_ox += deltax;
					deltax = _currMovement->getDimensionsOfPhase(i).x;
					_ox -= _currMovement->_framePosOffsets[i].x;
					_oy += _currMovement->_framePosOffsets[i].y;
					_ox -= _currMovement->getDimensionsOfPhase(i).x;
				}
			}
		}
	} else {
		if (_dynamicPhases.size() <= (uint)_currDynamicPhaseIndex) {
			_currDynamicPhaseIndex = _dynamicPhases.size() - 1;
			result = (callback2 == 0);
		}
		if (_currDynamicPhaseIndex < 0) {
			_currDynamicPhaseIndex = 0;
			result = false;
		}

		if (_framePosOffsets.size()) {
			if (callback1) {
				point.x = _framePosOffsets[_currDynamicPhaseIndex].x;
				point.y = _framePosOffsets[_currDynamicPhaseIndex].y;

				callback1(_currDynamicPhaseIndex, &point, _ox, _oy);
				_ox += point.x;
				_oy += point.y;
			} else if (oldDynIndex >= _currDynamicPhaseIndex) {
				for (int i = oldDynIndex; i > _currDynamicPhaseIndex; i--) {
					_ox -= _framePosOffsets[i].x;
					_oy -= _framePosOffsets[i].y;
				}
			} else {
				for (int i = oldDynIndex + 1; i <= _currDynamicPhaseIndex; i++) {
					_ox += _framePosOffsets[i].x;
					_oy += _framePosOffsets[i].y;
				}
			}
		}
	}

	updateCurrDynamicPhase();
	point = getCurrDynamicPhaseXY();
	_ox += point.x;
	_oy += point.y;

	_currDynamicPhase->_countdown = _currDynamicPhase->_initialCountdown;

	return result;
}

bool Movement::gotoPrevFrame() {
	debugC(8, kDebugAnimation, "Movement::gotoPrevFrame()");

	if (!_currDynamicPhaseIndex) {
		gotoLastFrame();
		return false;
	}

	Common::Point point = getCurrDynamicPhaseXY();

	_ox -= point.x;
	_oy -= point.y;

	if (_currMovement) {
		if (_currMovement->_framePosOffsets.size()) {
			_ox += _currMovement->getDimensionsOfPhase(_currDynamicPhaseIndex).x;
			_ox += _currMovement->_framePosOffsets[_currDynamicPhaseIndex].x;
			_oy -= _currMovement->_framePosOffsets[_currDynamicPhaseIndex].y;
		}

		_currDynamicPhaseIndex--;
		if (_currDynamicPhaseIndex < 0)
			_currDynamicPhaseIndex = _currMovement->_dynamicPhases.size() - 1;

		_ox -= _currMovement->getDimensionsOfPhase(_currDynamicPhaseIndex).x;
	} else {
		if (_framePosOffsets.size()) {
			_ox -= _framePosOffsets[_currDynamicPhaseIndex].x;
			_oy -= _framePosOffsets[_currDynamicPhaseIndex].y;
		}

		_currDynamicPhaseIndex--;
		if (_currDynamicPhaseIndex < 0)
			_currDynamicPhaseIndex = _dynamicPhases.size() - 1;
	}

	updateCurrDynamicPhase();
	point = getCurrDynamicPhaseXY();

	_ox += point.x;
	_oy += point.y;

	return true;
}

void Movement::gotoFirstFrame() {
	while (_currDynamicPhaseIndex)
			gotoPrevFrame();
}

void Movement::gotoLastFrame() {
	if (_currMovement) {
		if ((uint)_currDynamicPhaseIndex != _currMovement->_dynamicPhases.size() - 1) {
			do {
				gotoNextFrame(0, 0);
			} while ((uint)_currDynamicPhaseIndex != _currMovement->_dynamicPhases.size() - 1);
		}
	} else {
		if ((uint)_currDynamicPhaseIndex != _dynamicPhases.size() - 1) {
			do {
				gotoNextFrame(0, 0);
			} while ((uint)_currDynamicPhaseIndex != _dynamicPhases.size() - 1);
		}
	}
}

Common::Point Movement::getCenter() const {
	Common::Rect rect(_currDynamicPhase->_rect);

	if (_currMovement) {
		const Dims dims = _currMovement->getDimensionsOfPhase(_currDynamicPhaseIndex);
		rect.moveTo(dims.x - _currDynamicPhase->_rect.right, _currDynamicPhase->_rect.top);
	}

	return Common::Point(rect.left + _currDynamicPhase->_rect.width() / 2,
						 rect.top + _currDynamicPhase->_rect.height() / 2);
}

DynamicPhase::DynamicPhase() {
	_someX = 0;
	_field_7C = 0;
	_field_7E = 0;
	_dynFlags = 0;
	_someY = 0;
	_data = nullptr;
}

DynamicPhase::DynamicPhase(DynamicPhase *src, bool reverse) {
	_field_7C = src->_field_7C;
	_field_7E = 0;

	debugC(1, kDebugAnimation, "DynamicPhase::DynamicPhase(src, %d)", reverse);

	if (reverse) {
		if (!src->_bitmap)
			src->init();

		_bitmap.reset(src->_bitmap->reverseImage());
		_dataSize = src->_dataSize;

		if (g_fp->_currArchive) {
			_mfield_14 = 0;
			_libHandle = g_fp->_currArchive;
		}

		_mflags |= 1;

		_someX = src->_someX;
		_someY = src->_someY;
	} else {
		_mfield_14 = src->_mfield_14;
		_mfield_8 = src->_mfield_8;
		_mflags = src->_mflags;

		_memfilename = src->_memfilename;
		_dataSize = src->_dataSize;
		_mfield_10 = src->_mfield_10;
		_libHandle = src->_libHandle;

		if (src->_bitmap) {
			_field_54 = 1;
			_bitmap.reset(src->_bitmap->reverseImage(false));
		}

		_someX = src->_someX;
		_someY = src->_someY;
	}

	_rect = src->_rect;

	_width = src->_width;
	_height = src->_height;
	_field_7C = src->_field_7C;

	if (src->getExCommand())
		_exCommand.reset(src->getExCommand()->createClone());
	else
		_exCommand.reset();

	_initialCountdown = src->_initialCountdown;
	_field_6A = src->_field_6A;
	_dynFlags = src->_dynFlags;

	setPaletteData(src->getPaletteData());

	copyMemoryObject2(*src);
}

Common::String DynamicPhase::toXML() {
	return Common::String::format("f7c=%d left=%d top=%d right=%d bottom=%d sX=%d sY=%d dynFlags=%d %s",
			_field_7C, _rect.left, _rect.top, _rect.right, _rect.bottom, _someX, _someY, _dynFlags,
			StaticPhase::toXML().c_str());
}

bool DynamicPhase::load(MfcArchive &file) {
	debugC(5, kDebugLoading, "DynamicPhase::load()");

	StaticPhase::load(file);

	_field_7C = file.readUint16LE();
	_rect.left = file.readSint32LE();
	_rect.top = file.readSint32LE();
	_rect.right = file.readSint32LE();
	_rect.bottom = file.readSint32LE();

	assert(g_fp->_gameProjectVersion >= 1);

	_someX = file.readSint32LE();
	_someY = file.readSint32LE();

	assert(g_fp->_gameProjectVersion >= 12);

	_dynFlags = file.readUint32LE();

	return true;
}

StaticPhase::StaticPhase() {
	_field_6A = 1;
	_initialCountdown = 0;
	_countdown = 0;
	_field_68 = 0;
}

Common::String StaticPhase::toXML() {
	return Common::String::format("countdown=%d f6a=%d", _initialCountdown, _field_6A);
}

bool StaticPhase::load(MfcArchive &file) {
	debugC(5, kDebugLoading, "StaticPhase::load()");

	Picture::load(file);

	_initialCountdown = file.readUint16LE();
	_field_6A = file.readUint16LE();

	assert(g_fp->_gameProjectVersion >= 12);

	_exCommand.reset(file.readClass<ExCommand>());

	return true;
}

} // End of namespace Fullpipe
