/* 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 "sherlock/scalpel/scalpel_scene.h"
#include "sherlock/scalpel/scalpel_map.h"
#include "sherlock/scalpel/scalpel_people.h"
#include "sherlock/scalpel/scalpel_user_interface.h"
#include "sherlock/scalpel/scalpel.h"
#include "sherlock/events.h"
#include "sherlock/people.h"
#include "sherlock/screen.h"
#include "sherlock/sherlock.h"

namespace Sherlock {

namespace Scalpel {

const int FS_TRANS[8] = {
	STOP_UP, STOP_UPRIGHT, STOP_RIGHT, STOP_DOWNRIGHT, STOP_DOWN, STOP_DOWNLEFT, STOP_LEFT, STOP_UPLEFT
};

/*----------------------------------------------------------------*/

ScalpelScene::~ScalpelScene() {
	for (uint idx = 0; idx < _canimShapes.size(); ++idx)
		delete _canimShapes[idx];
}

bool ScalpelScene::loadScene(const Common::String &filename) {
	ScalpelMap &map = *(ScalpelMap *)_vm->_map;
	bool result = Scene::loadScene(filename);

	if (!_vm->isDemo()) {
		// Reset the previous map location and position on overhead map
		map._oldCharPoint = _currentScene;

		map._overPos.x = (map[_currentScene].x - 6) * FIXED_INT_MULTIPLIER;
		map._overPos.y = (map[_currentScene].y + 9) * FIXED_INT_MULTIPLIER;

	}

	return result;
}

void ScalpelScene::drawAllShapes() {
	People &people = *_vm->_people;
	Screen &screen = *_vm->_screen;

	// Restrict drawing window
	screen.setDisplayBounds(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT));

	// Draw all active shapes which are behind the person
	for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
		if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE && _bgShapes[idx]._misc == BEHIND)
			screen.getBackBuffer()->SHtransBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, _bgShapes[idx]._flags & OBJ_FLIPPED);
	}

	// Draw all canimations which are behind the person
	for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
		if (_canimShapes[idx]->_type == ACTIVE_BG_SHAPE && _canimShapes[idx]->_misc == BEHIND)
			screen.getBackBuffer()->SHtransBlitFrom(*_canimShapes[idx]->_imageFrame,
			_canimShapes[idx]->_position, _canimShapes[idx]->_flags & OBJ_FLIPPED);
	}

	// Draw all active shapes which are normal and behind the person
	for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
		if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE && _bgShapes[idx]._misc == NORMAL_BEHIND)
			screen.getBackBuffer()->SHtransBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, _bgShapes[idx]._flags & OBJ_FLIPPED);
	}

	// Draw all canimations which are normal and behind the person
	for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
		if (_canimShapes[idx]->_type == ACTIVE_BG_SHAPE && _canimShapes[idx]->_misc == NORMAL_BEHIND)
			screen.getBackBuffer()->SHtransBlitFrom(*_canimShapes[idx]->_imageFrame, _canimShapes[idx]->_position,
			_canimShapes[idx]->_flags & OBJ_FLIPPED);
	}

	// Draw any active characters
	for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
		Person &p = people[idx];
		if (p._type == CHARACTER && p._walkLoaded) {
			bool flipped = IS_SERRATED_SCALPEL && (
				p._sequenceNumber == WALK_LEFT || p._sequenceNumber == STOP_LEFT ||
				p._sequenceNumber == WALK_UPLEFT || p._sequenceNumber == STOP_UPLEFT ||
				p._sequenceNumber == WALK_DOWNRIGHT || p._sequenceNumber == STOP_DOWNRIGHT);

			screen.getBackBuffer()->SHtransBlitFrom(*p._imageFrame, Common::Point(p._position.x / FIXED_INT_MULTIPLIER,
				p._position.y / FIXED_INT_MULTIPLIER - p.frameHeight()), flipped);
		}
	}

	// Draw all static and active shapes that are NORMAL and are in front of the player
	for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
		if ((_bgShapes[idx]._type == ACTIVE_BG_SHAPE || _bgShapes[idx]._type == STATIC_BG_SHAPE) &&
			_bgShapes[idx]._misc == NORMAL_FORWARD)
			screen.getBackBuffer()->SHtransBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position,
			_bgShapes[idx]._flags & OBJ_FLIPPED);
	}

	// Draw all static and active canimations that are NORMAL and are in front of the player
	for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
		if ((_canimShapes[idx]->_type == ACTIVE_BG_SHAPE || _canimShapes[idx]->_type == STATIC_BG_SHAPE) &&
			_canimShapes[idx]->_misc == NORMAL_FORWARD)
			screen.getBackBuffer()->SHtransBlitFrom(*_canimShapes[idx]->_imageFrame, _canimShapes[idx]->_position,
			_canimShapes[idx]->_flags & OBJ_FLIPPED);
	}

	// Draw all static and active shapes that are FORWARD
	for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
		_bgShapes[idx]._oldPosition = _bgShapes[idx]._position;
		_bgShapes[idx]._oldSize = Common::Point(_bgShapes[idx].frameWidth(),
			_bgShapes[idx].frameHeight());

		if ((_bgShapes[idx]._type == ACTIVE_BG_SHAPE || _bgShapes[idx]._type == STATIC_BG_SHAPE) &&
			_bgShapes[idx]._misc == FORWARD)
			screen.getBackBuffer()->SHtransBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position,
			_bgShapes[idx]._flags & OBJ_FLIPPED);
	}

	// Draw all static and active canimations that are forward
	for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
		if ((_canimShapes[idx]->_type == ACTIVE_BG_SHAPE || _canimShapes[idx]->_type == STATIC_BG_SHAPE) &&
			_canimShapes[idx]->_misc == FORWARD)
			screen.getBackBuffer()->SHtransBlitFrom(*_canimShapes[idx]->_imageFrame, _canimShapes[idx]->_position,
			_canimShapes[idx]->_flags & OBJ_FLIPPED);
	}

	screen.resetDisplayBounds();
}

void ScalpelScene::checkBgShapes() {
	People &people = *_vm->_people;
	Person &holmes = people[HOLMES];
	Common::Point pt(holmes._position.x / FIXED_INT_MULTIPLIER, holmes._position.y / FIXED_INT_MULTIPLIER);

	// Call the base scene method to handle bg shapes
	Scene::checkBgShapes();

	// Iterate through the canim list
	for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
		Object &obj = *_canimShapes[idx];
		if (obj._type == STATIC_BG_SHAPE || obj._type == ACTIVE_BG_SHAPE) {
			if ((obj._flags & 5) == 1) {
				obj._misc = (pt.y < (obj._position.y + obj._imageFrame->_frame.h - 1)) ?
				NORMAL_FORWARD : NORMAL_BEHIND;
			} else if (!(obj._flags & 1)) {
				obj._misc = BEHIND;
			} else if (obj._flags & 4) {
				obj._misc = FORWARD;
			}
		}
	}
}

void ScalpelScene::doBgAnimCheckCursor() {
	Inventory &inv = *_vm->_inventory;
	Events &events = *_vm->_events;
	UserInterface &ui = *_vm->_ui;
	Common::Point mousePos = events.mousePos();
	events.animateCursorIfNeeded();

	if (ui._menuMode == LOOK_MODE) {
		if (mousePos.y > CONTROLS_Y1)
			events.setCursor(ARROW);
		else if (mousePos.y < CONTROLS_Y)
			events.setCursor(MAGNIFY);
	}

	// Check for setting magnifying glass cursor
	if (ui._menuMode == INV_MODE || ui._menuMode == USE_MODE || ui._menuMode == GIVE_MODE) {
		if (inv._invMode == INVMODE_LOOK) {
			// Only show Magnifying glass cursor if it's not on the inventory command line
			if (mousePos.y < CONTROLS_Y || mousePos.y >(CONTROLS_Y1 + 13))
				events.setCursor(MAGNIFY);
			else
				events.setCursor(ARROW);
		} else {
			events.setCursor(ARROW);
		}
	}
}

void ScalpelScene::doBgAnim() {
	ScalpelEngine &vm = *((ScalpelEngine *)_vm);
	Events &events = *_vm->_events;
	People &people = *_vm->_people;
	Screen &screen = *_vm->_screen;
	Talk &talk = *_vm->_talk;

	doBgAnimCheckCursor();

	screen.setDisplayBounds(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT));
	talk._talkToAbort = false;

	if (_restoreFlag) {
		for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
			if (people[idx]._type == CHARACTER)
				people[idx].checkSprite();
		}

		for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
			if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE)
				_bgShapes[idx].checkObject();
		}

		if (people._portraitLoaded && people._portrait._type == ACTIVE_BG_SHAPE)
			people._portrait.checkObject();

		for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
			if (_canimShapes[idx]->_type != INVALID && _canimShapes[idx]->_type != REMOVE)
				_canimShapes[idx]->checkObject();
		}

		if (_currentScene == DRAWING_ROOM)
			vm.eraseBrumwellMirror();

		// Restore the back buffer from the back buffer 2 in the changed area
		Common::Rect bounds(people[HOLMES]._oldPosition.x, people[HOLMES]._oldPosition.y,
			people[HOLMES]._oldPosition.x + people[HOLMES]._oldSize.x,
			people[HOLMES]._oldPosition.y + people[HOLMES]._oldSize.y);
		Common::Point pt(bounds.left, bounds.top);

		if (people[HOLMES]._type == CHARACTER)
			screen.restoreBackground(bounds);
		else if (people[HOLMES]._type == REMOVE)
			screen.getBackBuffer()->SHblitFrom(screen._backBuffer2, pt, bounds);

		for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
			Object &o = _bgShapes[idx];
			if (o._type == ACTIVE_BG_SHAPE || o._type == HIDE_SHAPE || o._type == REMOVE)
				screen.restoreBackground(o.getOldBounds());
		}

		if (people._portraitLoaded)
			screen.restoreBackground(Common::Rect(
				people._portrait._oldPosition.x, people._portrait._oldPosition.y,
				people._portrait._oldPosition.x + people._portrait._oldSize.x,
				people._portrait._oldPosition.y + people._portrait._oldSize.y
			));

		for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
			Object &o = _bgShapes[idx];
			if (o._type == NO_SHAPE && ((o._flags & OBJ_BEHIND) == 0)) {
				// Restore screen area
				screen.getBackBuffer()->SHblitFrom(screen._backBuffer2, o._position,
					Common::Rect(o._position.x, o._position.y,
					o._position.x + o._noShapeSize.x, o._position.y + o._noShapeSize.y));

				o._oldPosition = o._position;
				o._oldSize = o._noShapeSize;
			}
		}

		for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
			Object &o = *_canimShapes[idx];
			if (o._type == ACTIVE_BG_SHAPE || o._type == HIDE_SHAPE || o._type == REMOVE)
				screen.restoreBackground(Common::Rect(o._oldPosition.x, o._oldPosition.y,
					o._oldPosition.x + o._oldSize.x, o._oldPosition.y + o._oldSize.y));
		}
	}

	//
	// Update the background objects and canimations
	//

	for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
		Object &o = _bgShapes[idx];
		if (o._type == ACTIVE_BG_SHAPE || o._type == NO_SHAPE)
			o.adjustObject();
	}

	if (people._portraitLoaded && people._portrait._type == ACTIVE_BG_SHAPE)
		people._portrait.adjustObject();

	for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
		if (_canimShapes[idx]->_type != INVALID)
			_canimShapes[idx]->adjustObject();
	}

	if (people[HOLMES]._type == CHARACTER && people._holmesOn)
		people[HOLMES].adjustSprite();

	// Flag the bg shapes which need to be redrawn
	checkBgShapes();

	if (_currentScene == DRAWING_ROOM)
		vm.doBrumwellMirror();

	// Draw all active shapes which are behind the person
	for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
		Object &o = _bgShapes[idx];
		if (o._type == ACTIVE_BG_SHAPE && o._misc == BEHIND)
			screen.getBackBuffer()->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
	}

	// Draw all canimations which are behind the person
	for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
		Object &o = *_canimShapes[idx];
		if (o._type == ACTIVE_BG_SHAPE && o._misc == BEHIND) {
			screen.getBackBuffer()->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
		}
	}

	// Draw all active shapes which are HAPPEN and behind the person
	for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
		Object &o = _bgShapes[idx];
		if (o._type == ACTIVE_BG_SHAPE && o._misc == NORMAL_BEHIND)
			screen.getBackBuffer()->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
	}

	// Draw all canimations which are NORMAL and behind the person
	for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
		Object &o = *_canimShapes[idx];
		if (o._type == ACTIVE_BG_SHAPE && o._misc == NORMAL_BEHIND) {
			screen.getBackBuffer()->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
		}
	}

	// Draw the player if he's active and his walk has been loaded into memory
	if (people[HOLMES]._type == CHARACTER && people[HOLMES]._walkLoaded && people._holmesOn) {
		// If Holmes is too far to the right, move him back so he's on-screen
		int xRight = SHERLOCK_SCREEN_WIDTH - 2 - people[HOLMES]._imageFrame->_frame.w;
		int tempX = MIN(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER, xRight);

		bool flipped = people[HOLMES]._sequenceNumber == WALK_LEFT || people[HOLMES]._sequenceNumber == STOP_LEFT ||
			people[HOLMES]._sequenceNumber == WALK_UPLEFT || people[HOLMES]._sequenceNumber == STOP_UPLEFT ||
			people[HOLMES]._sequenceNumber == WALK_DOWNRIGHT || people[HOLMES]._sequenceNumber == STOP_DOWNRIGHT;
		screen.getBackBuffer()->SHtransBlitFrom(*people[HOLMES]._imageFrame,
			Common::Point(tempX, people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES]._imageFrame->_frame.h), flipped);
	}

	// Draw all static and active shapes are NORMAL and are in front of the person
	for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
		Object &o = _bgShapes[idx];
		if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == NORMAL_FORWARD)
			screen.getBackBuffer()->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
	}

	// Draw all static and active canimations that are NORMAL and are in front of the person
	for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
		Object &o = *_canimShapes[idx];
		if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == NORMAL_FORWARD) {
			screen.getBackBuffer()->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
		}
	}

	// Draw all static and active shapes that are in front of the person
	for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
		Object &o = _bgShapes[idx];
		if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == FORWARD)
			screen.getBackBuffer()->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
	}

	// Draw any active portrait
	if (people._portraitLoaded && people._portrait._type == ACTIVE_BG_SHAPE)
		screen.getBackBuffer()->SHtransBlitFrom(*people._portrait._imageFrame,
			people._portrait._position, people._portrait._flags & OBJ_FLIPPED);

	// Draw all static and active canimations that are in front of the person
	for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
		Object &o = *_canimShapes[idx];
		if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == FORWARD) {
			screen.getBackBuffer()->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
		}
	}

	// Draw all NO_SHAPE shapes which have flag bit 0 clear
	for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
		Object &o = _bgShapes[idx];
		if (o._type == NO_SHAPE && (o._flags & OBJ_BEHIND) == 0)
			screen.getBackBuffer()->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED);
	}

	// Bring the newly built picture to the screen
	if (_animating == 2) {
		_animating = 0;
		screen.slamRect(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT));
	} else {
		if (people[HOLMES]._type != INVALID && ((_goToScene == -1 || _canimShapes.empty()))) {
			if (people[HOLMES]._type == REMOVE) {
				screen.slamRect(Common::Rect(
					people[HOLMES]._oldPosition.x, people[HOLMES]._oldPosition.y,
					people[HOLMES]._oldPosition.x + people[HOLMES]._oldSize.x,
					people[HOLMES]._oldPosition.y + people[HOLMES]._oldSize.y
				));
				people[HOLMES]._type = INVALID;
			} else {
				screen.flushImage(people[HOLMES]._imageFrame,
					Common::Point(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER,
						people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES].frameHeight()),
					&people[HOLMES]._oldPosition.x, &people[HOLMES]._oldPosition.y,
					&people[HOLMES]._oldSize.x, &people[HOLMES]._oldSize.y);
			}
		}

		if (_currentScene == DRAWING_ROOM)
			vm.flushBrumwellMirror();

		for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
			Object &o = _bgShapes[idx];
			if ((o._type == ACTIVE_BG_SHAPE || o._type == REMOVE) && _goToScene == -1) {
				screen.flushImage(o._imageFrame, o._position,
					&o._oldPosition.x, &o._oldPosition.y, &o._oldSize.x, &o._oldSize.y);
			}
		}

		if (people._portraitLoaded) {
			if (people._portrait._type == REMOVE)
				screen.slamRect(Common::Rect(
					people._portrait._position.x, people._portrait._position.y,
					people._portrait._position.x + people._portrait._delta.x,
					people._portrait._position.y + people._portrait._delta.y
				));
			else
				screen.flushImage(people._portrait._imageFrame, people._portrait._position,
					&people._portrait._oldPosition.x, &people._portrait._oldPosition.y,
					&people._portrait._oldSize.x, &people._portrait._oldSize.y);

			if (people._portrait._type == REMOVE)
				people._portrait._type = INVALID;
		}

		if (_goToScene == -1) {
			for (uint idx = 0; idx < _bgShapes.size(); ++idx) {
				Object &o = _bgShapes[idx];
				if (o._type == NO_SHAPE && (o._flags & OBJ_BEHIND) == 0) {
					screen.slamArea(o._position.x, o._position.y, o._oldSize.x, o._oldSize.y);
					screen.slamArea(o._oldPosition.x, o._oldPosition.y, o._oldSize.x, o._oldSize.y);
				} else if (o._type == HIDE_SHAPE) {
					// Hiding shape, so flush it out and mark it as hidden
					screen.flushImage(o._imageFrame, o._position,
						&o._oldPosition.x, &o._oldPosition.y, &o._oldSize.x, &o._oldSize.y);
					o._type = HIDDEN;
				}
			}
		}

		for (int idx = _canimShapes.size() - 1; idx >= 0; --idx) {
			Object &o = *_canimShapes[idx];

			if (o._type == INVALID) {
				// Anim shape was invalidated by checkEndOfSequence, so at this point we can remove it
				delete _canimShapes[idx];
				_canimShapes.remove_at(idx);
			} else  if (o._type == REMOVE) {
				if (_goToScene == -1)
					screen.slamArea(o._position.x, o._position.y, o._delta.x, o._delta.y);

				// Shape for an animation is no longer needed, so remove it completely
				delete _canimShapes[idx];
				_canimShapes.remove_at(idx);
			} else if (o._type == ACTIVE_BG_SHAPE) {
				screen.flushImage(o._imageFrame, o._position,
					&o._oldPosition.x, &o._oldPosition.y, &o._oldSize.x, &o._oldSize.y);
			}
		}
	}

	_restoreFlag = true;
	_doBgAnimDone = true;

	events.wait(3);
	screen.resetDisplayBounds();

	// Check if the method was called for calling a portrait, and a talk was
	// interrupting it. This talk file would not have been executed at the time,
	// since we needed to finish the 'doBgAnim' to finish clearing the portrait
	if (people._clearingThePortrait && talk._scriptMoreFlag == 3) {
		// Reset the flags and call to talk
		people._clearingThePortrait = false;
		talk._scriptMoreFlag = 0;
		talk.talkTo(talk._scriptName);
	}
}

int ScalpelScene::startCAnim(int cAnimNum, int playRate) {
	Events &events = *_vm->_events;
	ScalpelMap &map = *(ScalpelMap *)_vm->_map;
	People &people = *_vm->_people;
	Resources &res = *_vm->_res;
	Talk &talk = *_vm->_talk;
	ScalpelUserInterface &ui = *(ScalpelUserInterface *)_vm->_ui;
	Point32 tpPos, walkPos;
	int tpDir, walkDir;
	int tFrames = 0;
	int gotoCode = -1;
	Object *cObj;

	// Validation
	if (cAnimNum >= (int)_cAnim.size())
		// number out of bounds
		return -1;
	if (_canimShapes.size() >= 3 || playRate == 0)
		// Too many active animations, or invalid play rate
		return 0;

	CAnim &cAnim = _cAnim[cAnimNum];
	if (playRate < 0) {
		// Reverse direction
		walkPos = cAnim._teleport[0];
		walkDir = cAnim._teleport[0]._facing;
		tpPos = cAnim._goto[0];
		tpDir = cAnim._goto[0]._facing;
	} else {
		// Forward direction
		walkPos = cAnim._goto[0];
		walkDir = cAnim._goto[0]._facing;
		tpPos = cAnim._teleport[0];
		tpDir = cAnim._teleport[0]._facing;
	}

	CursorId oldCursor = events.getCursor();
	events.setCursor(WAIT);

	if (walkPos.x != -1) {
		// Holmes must walk to the walk point before the cAnimation is started
		if (people[HOLMES]._position != walkPos)
			people[HOLMES].walkToCoords(walkPos, walkDir);
	}

	if (talk._talkToAbort)
		return 1;

	// Add new anim shape entry for displaying the animation
	cObj = new Object();
	_canimShapes.push_back(cObj);

	// Copy the canimation into the bgShapes type canimation structure so it can be played
	cObj->_allow = cAnimNum + 1;				// Keep track of the parent structure
	cObj->_name = _cAnim[cAnimNum]._name;	// Copy name

	// Remove any attempt to draw object frame
	if (cAnim._type == NO_SHAPE && cAnim._sequences[0] < 100)
		cAnim._sequences[0] = 0;

	cObj->_sequences = cAnim._sequences;
	cObj->_images = nullptr;
	cObj->_position = cAnim._position;
	cObj->_delta = Common::Point(0, 0);
	cObj->_type = cAnim._type;
	cObj->_flags = cAnim._flags;

	cObj->_maxFrames = 0;
	cObj->_frameNumber = -1;
	cObj->_sequenceNumber = cAnimNum;
	cObj->_oldPosition = Common::Point(0, 0);
	cObj->_oldSize = Common::Point(0, 0);
	cObj->_goto = Common::Point(0, 0);
	cObj->_status = 0;
	cObj->_misc = 0;
	cObj->_imageFrame = nullptr;

	if (cAnim._name.size() > 0 && cAnim._type != NO_SHAPE) {
		if (tpPos.x != -1)
			people[HOLMES]._type = REMOVE;

		Common::String fname = cAnim._name + ".vgs";
		if (!res.isInCache(fname)) {
			// Set up RRM scene data
			Common::SeekableReadStream *roomStream = res.load(_roomFilename);
			roomStream->seek(cAnim._dataOffset);
			//rrmStream->seek(44 + cAnimNum * 4);
			//rrmStream->seek(rrmStream->readUint32LE());

			// Load the canimation into the cache
			Common::SeekableReadStream *imgStream = !_compressed ? roomStream->readStream(cAnim._dataSize) :
				Resources::decompressLZ(*roomStream, cAnim._dataSize);
			res.addToCache(fname, *imgStream);

			delete imgStream;
			delete roomStream;
		}

		// Now load the resource as an image
		if (!IS_3DO) {
			cObj->_images = new ImageFile(fname);
		} else {
			cObj->_images = new ImageFile3DO(fname, kImageFile3DOType_RoomFormat);
		}
		cObj->_imageFrame = &(*cObj->_images)[0];
		cObj->_maxFrames = cObj->_images->size();

		int frames = 0;
		if (playRate < 0) {
			// Reverse direction
			// Count number of frames
			while (frames < MAX_FRAME && cObj->_sequences[frames])
				++frames;
		} else {
			// Forward direction
			BaseObject::_countCAnimFrames = true;

			while (cObj->_type == ACTIVE_BG_SHAPE) {
				cObj->checkObject();
				++frames;

				if (frames >= 1000)
					error("CAnim has infinite loop sequence");
			}

			if (frames > 1)
				--frames;

			BaseObject::_countCAnimFrames = false;

			cObj->_type = cAnim._type;
			cObj->_frameNumber = -1;
			cObj->_position = cAnim._position;
			cObj->_delta = Common::Point(0, 0);
		}

		// Return if animation has no frames in it
		if (frames == 0)
			return -2;

		++frames;
		int repeat = ABS(playRate);
		int dir;

		if (playRate < 0) {
			// Play in reverse
			dir = -2;
			cObj->_frameNumber = frames - 3;
		} else {
			dir = 0;
		}

		tFrames = frames - 1;
		int pauseFrame = (_cAnimFramePause) ? frames - _cAnimFramePause : -1;

		while (--frames) {
			if (frames == pauseFrame)
				ui.printObjectDesc();

			doBgAnim();

			// Repeat same frame
			int temp = repeat;
			while (--temp > 0) {
				cObj->_frameNumber--;
				doBgAnim();

				if (_vm->shouldQuit())
					return 0;
			}

			cObj->_frameNumber += dir;
		}

		people[HOLMES]._type = CHARACTER;
	}

	// Teleport to ending coordinates if necessary
	if (tpPos.x != -1) {
		people[HOLMES]._position = tpPos;	// Place the player
		people[HOLMES]._sequenceNumber = tpDir;
		people[HOLMES].gotoStand();
	}

	if (playRate < 0)
		// Reverse direction - set to end sequence
		cObj->_frameNumber = tFrames - 1;

	if (cObj->_frameNumber <= 26)
		gotoCode = cObj->_sequences[cObj->_frameNumber + 3];

	// Unless anim shape has already been removed, do a final check to allow it to become REMOVEd
	for (uint idx = 0; idx < _canimShapes.size(); ++idx) {
		if (_canimShapes[idx] == cObj) {
			cObj->checkObject();
			break;
		}
	}

	if (gotoCode > 0 && !talk._talkToAbort) {
		_goToScene = gotoCode;

		if (_goToScene < 97 && map[_goToScene].x) {
			map._overPos = map[_goToScene];
		}
	}

	people.loadWalk();

	if (tpPos.x != -1 && !talk._talkToAbort) {
		// Teleport to ending coordinates
		people[HOLMES]._position = tpPos;
		people[HOLMES]._sequenceNumber = tpDir;

		people[HOLMES].gotoStand();
	}

	events.setCursor(oldCursor);

	return 1;
}

int ScalpelScene::closestZone(const Common::Point &pt) {
	int dist = 1000;
	int zone = -1;

	for (uint idx = 0; idx < _zones.size(); ++idx) {
		Common::Point zc((_zones[idx].left + _zones[idx].right) / 2,
			(_zones[idx].top + _zones[idx].bottom) / 2);
		int d = ABS(zc.x - pt.x) + ABS(zc.y - pt.y);

		if (d < dist) {
			// Found a closer zone
			dist = d;
			zone = idx;
		}
	}

	return zone;
}

int ScalpelScene::findBgShape(const Common::Point &pt) {
	if (!_doBgAnimDone)
		// New frame hasn't been drawn yet
		return -1;

	for (int idx = (int)_bgShapes.size() - 1; idx >= 0; --idx) {
		Object &o = _bgShapes[idx];
		if (o._type != INVALID && o._type != NO_SHAPE && o._type != HIDDEN
				&& o._type != REMOVE && o._aType <= PERSON) {
			if (o.getNewBounds().contains(pt))
				return idx;
		} else if (o._type == NO_SHAPE) {
			if (o.getNoShapeBounds().contains(pt))
				return idx;
		}
	}

	return -1;
}

} // End of namespace Scalpel

} // End of namespace Sherlock
