/* 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/util.h"
#include "sherlock/sherlock.h"
#include "sherlock/objects.h"
#include "sherlock/people.h"
#include "sherlock/scene.h"
#include "sherlock/scalpel/scalpel_map.h"
#include "sherlock/scalpel/scalpel_people.h"

namespace Sherlock {

#define START_FRAME 0

#define UPPER_LIMIT 0
#define LOWER_LIMIT (IS_SERRATED_SCALPEL ? CONTROLS_Y : SHERLOCK_SCREEN_HEIGHT)
#define LEFT_LIMIT 0
#define RIGHT_LIMIT SHERLOCK_SCREEN_WIDTH
#define NUM_ADJUSTED_WALKS 21

// Distance to walk around WALK_AROUND boxes
#define CLEAR_DIST_X 5
#define CLEAR_DIST_Y 0

struct AdjustWalk {
	char _vgsName[9];
	int _xAdjust;
	int _flipXAdjust;
	int _yAdjust;
} ;

#define NUM_ADJUSTED_WALKS 21
static const AdjustWalk ADJUST_WALKS[NUM_ADJUSTED_WALKS] = {
	{ "TUPRIGHT", -7, -19, 6 },
	{ "TRIGHT", 8, -14, 0 },
	{ "TDOWNRG", 14, -12, 0 },
	{ "TWUPRIGH", 12, 4, 2 },
	{ "TWRIGHT", 31, -14, 0 },
	{ "TWDOWNRG", 6, -24, 0 },
	{ "HTUPRIGH", 2, -20, 0 },
	{ "HTRIGHT", 28, -20, 0 },
	{ "HTDOWNRG", 8, -2, 0 },
	{ "GTUPRIGH", 4, -12, 0 },
	{ "GTRIGHT", 12, -16, 0 },
	{ "GTDOWNRG", 10, -18, 0 },
	{ "JTUPRIGH", 8, -10, 0 },
	{ "JTRIGHT", 22, -6, 0 },
	{ "JTDOWNRG", 4, -20, 0 },
	{ "CTUPRIGH", 10, 0, 0 },
	{ "CTRIGHT", 26, -22, 0 },
	{ "CTDOWNRI", 16, 4, 0 },
	{ "ITUPRIGH", 0, 0, 0 },
	{ "ITRIGHT", 20, 0, 0 },
	{ "ITDOWNRG", 8, 0, 0 }
};

SherlockEngine *Sprite::_vm;

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

BaseObject::BaseObject() {
	_type = INVALID;
	_sequences = nullptr;
	_images = nullptr;
	_imageFrame = nullptr;
	_walkCount = 0;
	_allow = 0;
	_frameNumber = 0;
	_lookFlag = 0;
	_requiredFlag = 0;
	_status = 0;
	_misc = 0;
	_maxFrames = 0;
	_flags = 0;
	_aType = OBJECT;
	_lookFrames = 0;
	_seqCounter = 0;
	_lookFacing = 0;
	_lookcAnim = 0;
	_seqStack = 0;
	_seqTo = 0;
	_descOffset = 0;
	_seqCounter2 = 0;
	_seqSize = 0;
	_quickDraw = 0;
	_scaleVal = 0;
	_requiredFlags1 = 0;
	_gotoSeq = 0;
	_talkSeq = 0;
	_restoreSlot = 0;
}

bool BaseObject::hasAborts() const {
	int seqNum = _talkSeq;

	// See if the object is in it's regular sequence
	bool startChecking = !seqNum || _type == CHARACTER;

	uint idx = 0;
	do
	{
		// Get the Frame value
		int v = _sequences[idx++];

		// See if we found an Allow Talk Interrupt Code
		if (startChecking && v == ALLOW_TALK_CODE)
			return true;

		// If we've started checking and we've encountered another Talk or Listen Sequence Code,
		// then we're done checking this sequence because this is where it would repeat
		if (startChecking && (v == TALK_SEQ_CODE || v == TALK_LISTEN_CODE))
			return false;

		// See if we've found the beginning of a Talk Sequence
		if ((v == TALK_SEQ_CODE && seqNum < 128) || (v == TALK_LISTEN_CODE && seqNum >= 128)) {
			// If checking was already on and we came across one of these codes, then there couldn't
			// have been an Allow Talk Interrupt code in the sequence we were checking, so we're done.
			if (startChecking)
				return false;

			seqNum--;
			// See if we're at the correct Talk Sequence Number
			if (!(seqNum & 127))
			{
				// Correct Sequence, Start Checking Now
				startChecking = true;
			}
		} else {
			// Move ahead any extra because of special control codes
			switch (v) {
			case 0:				idx++; break;
			case MOVE_CODE:
			case TELEPORT_CODE:	idx += 4; break;
			case CALL_TALK_CODE:idx += 8; break;
			case HIDE_CODE:		idx += 2; break;
			}
		}
	} while (idx < _seqSize);

	return true;
}

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

void Sprite::clear() {
	_name = "";
	_description = "";
	_examine.clear();
	_pickUp = "";
	_walkSequences.clear();
	_sequences = nullptr;
	_images = nullptr;
	_imageFrame = nullptr;
	_walkCount = 0;
	_allow = 0;
	_frameNumber = _sequenceNumber = 0;
	_position.x = _position.y = 0;
	_delta.x = _delta.y = 0;
	_oldPosition.x = _oldPosition.y = 0;
	_oldSize.x = _oldSize.y = 0;
	_goto.x = _goto.y = 0;
	_type = INVALID;
	_pickUp.clear();
	_noShapeSize.x = _noShapeSize.y = 0;
	_status = 0;
	_misc = 0;
	_numFrames = 0;
	_altImages = nullptr;
	_altSeq = 0;
	Common::fill(&_stopFrames[0], &_stopFrames[8], (ImageFrame *)nullptr);
}

void Sprite::setImageFrame() {
	int frameNum = MAX(_frameNumber, 0);
	int imageNumber = _walkSequences[_sequenceNumber][frameNum];
	
	if (IS_SERRATED_SCALPEL)
		imageNumber = imageNumber + _walkSequences[_sequenceNumber][0] - 2;
	else if (imageNumber > _numFrames)
		imageNumber = 1;

	// Get the images to use
	ImageFile *images = _altSeq ? _altImages : _images;
	assert(images);

	// Set the frame pointer
	_imageFrame = &(*images)[imageNumber];
}

void Sprite::adjustSprite() {
	Map &map = *_vm->_map;
	People &people = *_vm->_people;
	Scene &scene = *_vm->_scene;
	Talk &talk = *_vm->_talk;

	if (_type == INVALID || (_type == CHARACTER && scene._animating))
		return;

	if (!talk._talkCounter && _type == CHARACTER && _walkCount) {
		// Handle active movement for the sprite
		_position += _delta;
		--_walkCount;

		if (!_walkCount) {
			// If there any points left for the character to walk to along the
			// route to a destination, then move to the next point
			if (!people._walkTo.empty()) {
				people._walkDest = people._walkTo.pop();
				people.setWalking();
			} else {
				people.gotoStand(*this);
			}
		}
	}

	if (_type == CHARACTER && !map._active) {
		if ((_position.y / FIXED_INT_MULTIPLIER) > LOWER_LIMIT) {
			_position.y = LOWER_LIMIT * FIXED_INT_MULTIPLIER;
			people.gotoStand(*this);
		}

		if ((_position.y / FIXED_INT_MULTIPLIER) < UPPER_LIMIT) {
			_position.y = UPPER_LIMIT * FIXED_INT_MULTIPLIER;
			people.gotoStand(*this);
		}

		if ((_position.x / FIXED_INT_MULTIPLIER) < LEFT_LIMIT) {
			_position.x = LEFT_LIMIT * FIXED_INT_MULTIPLIER;
			people.gotoStand(*this);
		}

		if ((_position.x / FIXED_INT_MULTIPLIER) > RIGHT_LIMIT) {
			_position.x = RIGHT_LIMIT * FIXED_INT_MULTIPLIER;
			people.gotoStand(*this);
		}
	} else if (!map._active) {
		_position.y = CLIP((int)_position.y, (int)UPPER_LIMIT, (int)LOWER_LIMIT);
		_position.x = CLIP((int)_position.x, (int)LEFT_LIMIT, (int)RIGHT_LIMIT);
	}

	if (!map._active || (map._frameChangeFlag = !map._frameChangeFlag))
		++_frameNumber;

	if (_frameNumber >= (int)_walkSequences[_sequenceNumber]._sequences.size() ||
			_walkSequences[_sequenceNumber][_frameNumber] == 0) {
		switch (_sequenceNumber) {
		case Scalpel::STOP_UP:
		case Scalpel::STOP_DOWN:
		case Scalpel::STOP_LEFT:
		case Scalpel::STOP_RIGHT:
		case Scalpel::STOP_UPRIGHT:
		case Scalpel::STOP_UPLEFT:
		case Scalpel::STOP_DOWNRIGHT:
		case Scalpel::STOP_DOWNLEFT:
			// We're in a stop sequence, so reset back to the last frame, so
			// the character is shown as standing still
			--_frameNumber;
			break;

		default:
			// Move 1 past the first frame - we need to compensate, since we
			// already passed the frame increment
			_frameNumber = 1;
			break;
		}
	}

	// Update the _imageFrame to point to the new frame's image
	setImageFrame();

	// Check to see if character has entered an exit zone
	if (!_walkCount && scene._walkedInScene && scene._goToScene == -1) {
		Common::Rect charRect(_position.x / FIXED_INT_MULTIPLIER - 5, _position.y / FIXED_INT_MULTIPLIER - 2,
			_position.x / FIXED_INT_MULTIPLIER + 5, _position.y / FIXED_INT_MULTIPLIER + 2);
		Exit *exit = scene.checkForExit(charRect);

		if (exit) {
			scene._goToScene = exit->_scene;

			if (exit->_people.x != 0) {
				people._hSavedPos = exit->_people;
				people._hSavedFacing = exit->_peopleDir;

				if (people._hSavedFacing > 100 && people._hSavedPos.x < 1)
					people._hSavedPos.x = 100;
			}
		}
	}
}

void Sprite::checkSprite() {
	Events &events = *_vm->_events;
	People &people = *_vm->_people;
	Scene &scene = *_vm->_scene;
	Screen &screen = *_vm->_screen;
	Talk &talk = *_vm->_talk;
	Point32 pt;
	Common::Rect objBounds;
	Common::Point spritePt(_position.x / FIXED_INT_MULTIPLIER, _position.y / FIXED_INT_MULTIPLIER);

	if (!talk._talkCounter && _type == CHARACTER) {
		pt = _walkCount ? _position + _delta : _position;
		pt.x /= 100;
		pt.y /= 100;

		for (uint idx = 0; idx < scene._bgShapes.size() && !talk._talkToAbort; ++idx) {
			Object &obj = scene._bgShapes[idx];
			if (obj._aType <= PERSON || obj._type == INVALID || obj._type == HIDDEN)
				continue;

			if (obj._type == NO_SHAPE) {
				objBounds = Common::Rect(obj._position.x, obj._position.y,
					obj._position.x + obj._noShapeSize.x + 1, obj._position.y + obj._noShapeSize.y + 1);
			} else {
				int xp = obj._position.x + obj._imageFrame->_offset.x;
				int yp = obj._position.y + obj._imageFrame->_offset.y;
				objBounds = Common::Rect(xp, yp,
					xp + obj._imageFrame->_frame.w + 1, yp + obj._imageFrame->_frame.h + 1);
			}

			if (objBounds.contains(pt)) {
				if (objBounds.contains(spritePt)) {
					// Current point is already inside the the bounds, so impact occurred
					// on a previous call. So simply do nothing until we're clear of the box
					switch (obj._aType) {
					case TALK_MOVE:
						if (_walkCount) {
							// Holmes is moving
							obj._type = HIDDEN;
							obj.setFlagsAndToggles();
							talk.talkTo(obj._use[0]._target);
						}
						break;

					case PAL_CHANGE:
					case PAL_CHANGE2:
						if (_walkCount) {
							int palStart = atoi(obj._use[0]._names[0].c_str()) * 3;
							int palLength = atoi(obj._use[0]._names[1].c_str()) * 3;
							int templ = atoi(obj._use[0]._names[2].c_str()) * 3;
							if (templ == 0)
								templ = 100;

							// Ensure only valid palette change data found
							if (palLength > 0) {
								// Figure out how far into the shape Holmes is so that we
								// can figure out what percentage of the original palette
								// to set the current palette to
								int palPercent = (pt.x - objBounds.left) * 100 / objBounds.width();
								palPercent = palPercent * templ / 100;
								if (obj._aType == PAL_CHANGE)
									// Invert percentage
									palPercent = 100 - palPercent;

								for (int i = palStart; i < (palStart + palLength); ++i)
									screen._sMap[i] = screen._cMap[i] * palPercent / 100;

								events.pollEvents();
								screen.setPalette(screen._sMap);
							}
						}
						break;

					case TALK:
					case TALK_EVERY:
						obj._type = HIDDEN;
						obj.setFlagsAndToggles();
						talk.talkTo(obj._use[0]._target);
						break;

					default:
						break;
					}
				} else {
					// New impact just occurred
					switch (obj._aType) {
					case BLANK_ZONE:
						// A blank zone masks out all other remaining zones underneath it.
						// If this zone is hit, exit the outer loop so we do not check anymore
						return;

					case SOLID:
					case TALK:
						// Stop walking
						if (obj._aType == TALK) {
							obj.setFlagsAndToggles();
							talk.talkTo(obj._use[0]._target);
						} else {
							people.gotoStand(*this);
						}
						break;

					case TALK_EVERY:
						if (obj._aType == TALK_EVERY) {
							obj._type = HIDDEN;
							obj.setFlagsAndToggles();
							talk.talkTo(obj._use[0]._target);
						} else {
							people.gotoStand(*this);
						}
						break;

					case FLAG_SET:
						obj.setFlagsAndToggles();
						obj._type = HIDDEN;
						break;

					case WALK_AROUND:
						if (objBounds.contains(people._walkTo.front())) {
							// Reached zone
							people.gotoStand(*this);
						} else {
							// Destination not within box, walk to best corner
							Common::Point walkPos;

							if (spritePt.x >= objBounds.left && spritePt.x < objBounds.right) {
								// Impact occurred due to vertical movement. Determine whether to
								// travel to the left or right side
								if (_delta.x > 0)
									// Go to right side
									walkPos.x = objBounds.right + CLEAR_DIST_X;
								else if (_delta.x < 0) {
									// Go to left side
									walkPos.x = objBounds.left - CLEAR_DIST_X;
								} else {
									// Going straight up or down. So choose best side
									if (spritePt.x >= (objBounds.left + objBounds.width() / 2))
										walkPos.x = objBounds.right + CLEAR_DIST_X;
									else
										walkPos.x = objBounds.left - CLEAR_DIST_X;
								}

								walkPos.y = (_delta.y >= 0) ? objBounds.top - CLEAR_DIST_Y :
									objBounds.bottom + CLEAR_DIST_Y;
							} else {
								// Impact occurred due to horizontal movement
								if (_delta.y > 0)
									// Go to bottom of box
									walkPos.y = objBounds.bottom + CLEAR_DIST_Y;
								else if (_delta.y < 0)
									// Go to top of box
									walkPos.y = objBounds.top - CLEAR_DIST_Y;
								else {
									// Going straight horizontal, so choose best side
									if (spritePt.y >= (objBounds.top + objBounds.height() / 2))
										walkPos.y = objBounds.bottom + CLEAR_DIST_Y;
									else
										walkPos.y = objBounds.top - CLEAR_DIST_Y;
								}

								walkPos.x = (_delta.x >= 0) ? objBounds.left - CLEAR_DIST_X :
									objBounds.right + CLEAR_DIST_X;
							}

							walkPos.x += people[AL]._imageFrame->_frame.w / 2;
							people._walkDest = walkPos;
							people._walkTo.push(walkPos);
							people.setWalking();
						}
						break;

					case DELTA:
						_position.x += 200;
						break;

					default:
						break;
					}
				}
			}
		}
	}
}

const Common::Rect Sprite::getOldBounds() const {
	return Common::Rect(_oldPosition.x, _oldPosition.y, _oldPosition.x + _oldSize.x, _oldPosition.y + _oldSize.y);
}

void Sprite::setObjTalkSequence(int seq) {
	assert(seq != -1 && _type == CHARACTER);

	if (_seqTo) {
		// reset to previous value
		_walkSequences[_sequenceNumber]._sequences[_frameNumber] = _seqTo;
		_seqTo = 0;
	}

	_sequenceNumber = _gotoSeq;
	_frameNumber = 0;
	checkWalkGraphics();
}

void Sprite::checkWalkGraphics() {
	People &people = *_vm->_people;
	int npcNum = -1;

	if (_images == nullptr) {
		freeAltGraphics();
		return;
	}

	Common::String filename = Common::String::format("%s.vgs", _walkSequences[_sequenceNumber]._vgsName.c_str());

	// Set the adjust depending on if we have to fine tune the x position of this particular graphic
	_adjust.x = _adjust.y = 0;

	for (int idx = 0; idx < NUM_ADJUSTED_WALKS; ++idx) {
		if (!scumm_strnicmp(_walkSequences[_sequenceNumber]._vgsName.c_str(), ADJUST_WALKS[idx]._vgsName, 
				strlen(ADJUST_WALKS[idx]._vgsName))) {
			if (_walkSequences[_sequenceNumber]._horizFlip)
				_adjust.x = ADJUST_WALKS[idx]._flipXAdjust;
			else
				_adjust.x = ADJUST_WALKS[idx]._xAdjust;

			_adjust.y = ADJUST_WALKS[idx]._yAdjust;
			break;
		}
	}

	// See if we're already using Alternate Graphics
	if (_altSeq) {
		// See if the VGS file called for is different than the alternate graphics already loaded
		if (!_walkSequences[_sequenceNumber]._vgsName.compareToIgnoreCase(_walkSequences[_altSeq - 1]._vgsName)) {
			// Different AltGraphics, Free the old ones
			freeAltGraphics();
		}
	}

	// If there is no Alternate Sequence set, see if we need to load a new one
	if (!_altSeq) {
		// Find which NPC this is so we can check the name of the graphics loaded
		for (int idx = 0; idx < MAX_CHARACTERS; ++idx) {
			if (this == &people[idx]) {
				npcNum = idx;
				break;
			}
		}

		if (npcNum != -1) {
			// See if the VGS file called for is different than the main graphics which are already loaded
			if (!filename.compareToIgnoreCase(people[npcNum]._walkVGSName)) {
				// See if this is one of the more used Walk Graphics stored in WALK.LIB
				for (int idx = 0; idx < NUM_IN_WALK_LIB; ++idx) {
					if (!scumm_stricmp(filename.c_str(), WALK_LIB_NAMES[idx])) {
						people._useWalkLib = true;
						break;
					}
				}

				_altImages = new ImageFile(filename);
				people._useWalkLib = false;

				_altSeq = _sequenceNumber + 1;
			}
		}
	}

	// If this is a different seqeunce from the current sequence, reset the appropriate variables
	if (_sequences != &_walkSequences[_sequenceNumber]._sequences[0]) {		
		_seqTo = _seqCounter = _seqCounter2 = _seqStack = _startSeq = 0;
		_sequences = &_walkSequences[_sequenceNumber]._sequences[0];
		_seqSize = _walkSequences[_sequenceNumber]._sequences.size();
	}

	setImageFrame();
}

void Sprite::freeAltGraphics() {
	if (_altImages != nullptr) {
		delete _altImages;
		_altImages = nullptr;
	}

	_altSeq = 0;
}

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

void WalkSequence::load(Common::SeekableReadStream &s) {
	char buffer[9];
	s.read(buffer, 9);
	_vgsName = Common::String(buffer);
	_horizFlip = s.readByte() != 0;

	_sequences.resize(s.readUint16LE());
	s.skip(4);		// Skip over pointer field of structure

	s.read(&_sequences[0], _sequences.size());
}

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

WalkSequences &WalkSequences::operator=(const WalkSequences &src) {
	resize(src.size());
	for (uint idx = 0; idx < size(); ++idx) {
		const WalkSequence &wSrc = src[idx];
		WalkSequence &wDest = (*this)[idx];
		wDest._horizFlip = wSrc._horizFlip;

		wDest._sequences.resize(wSrc._sequences.size());
		Common::copy(&wSrc._sequences[0], &wSrc._sequences[0] + wSrc._sequences.size(), &wDest._sequences[0]);
	}

	return *this;
}

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

void ActionType::load(Common::SeekableReadStream &s) {
	char buffer[12];

	_cAnimNum = s.readByte();
	_cAnimSpeed = s.readByte();
	if (_cAnimSpeed & 0x80)
		_cAnimSpeed = -(_cAnimSpeed & 0x7f);

	for (int idx = 0; idx < NAMES_COUNT; ++idx) {
		s.read(buffer, 12);
		_names[idx] = Common::String(buffer);
	}
}

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

UseType::UseType() {
	_cAnimNum = _cAnimSpeed = 0;
	_useFlag = 0;
}

void UseType::load(Common::SeekableReadStream &s, bool isRoseTattoo) {
	char buffer[12];

	if (isRoseTattoo) {
		s.read(buffer, 12);
		_verb = Common::String(buffer);
	}

	_cAnimNum = s.readByte();
	_cAnimSpeed = s.readByte();
	if (_cAnimSpeed & 0x80)
		_cAnimSpeed = -(_cAnimSpeed & 0x7f);

	for (int idx = 0; idx < NAMES_COUNT; ++idx) {
		s.read(buffer, 12);
		_names[idx] = Common::String(buffer);
	}

	_useFlag = s.readSint16LE();

	if (!isRoseTattoo)
		s.skip(6);

	s.read(buffer, 12);
	_target = Common::String(buffer);
}

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

SherlockEngine *Object::_vm;
bool Object::_countCAnimFrames;

void Object::setVm(SherlockEngine *vm) {
	_vm = vm;
	_countCAnimFrames = false;
}

Object::Object(): BaseObject() {
	_sequenceNumber = 0;
	_sequenceOffset = 0;
	_pickup = 0;
	_defaultCommand = 0;
	_pickupFlag = 0;
}

void Object::load(Common::SeekableReadStream &s, bool isRoseTattoo) {
	char buffer[41];
	s.read(buffer, 12);
	_name = Common::String(buffer);
	s.read(buffer, 41);
	_description = Common::String(buffer);

	_examine.clear();
	_sequences = nullptr;
	_images = nullptr;
	_imageFrame = nullptr;

	s.skip(4);
	_sequenceOffset = s.readUint16LE();
	s.seek(10, SEEK_CUR);

	_walkCount = s.readByte();
	_allow = s.readByte();
	_frameNumber = s.readSint16LE();
	_sequenceNumber = s.readSint16LE();
	_position.x = s.readSint16LE();
	_position.y = s.readSint16LE();
	_delta.x = s.readSint16LE();
	_delta.y = s.readSint16LE();
	_type = (SpriteType)s.readUint16LE();
	_oldPosition.x = s.readSint16LE();
	_oldPosition.y = s.readSint16LE();
	_oldSize.x = s.readUint16LE();
	_oldSize.y = s.readUint16LE();
	
	_goto.x = s.readSint16LE();
	_goto.y = s.readSint16LE();
	if (!isRoseTattoo) {
		_goto.x = _goto.x * FIXED_INT_MULTIPLIER / 100;
		_goto.y = _goto.y * FIXED_INT_MULTIPLIER / 100;
	}

	_pickup = isRoseTattoo ? 0 : s.readByte();
	_defaultCommand = isRoseTattoo ? 0 : s.readByte();
	_lookFlag = s.readSint16LE();
	_pickupFlag = isRoseTattoo ? 0 : s.readSint16LE();
	_requiredFlag = s.readSint16LE();
	_noShapeSize.x = s.readUint16LE();
	_noShapeSize.y = s.readUint16LE();
	_status = s.readUint16LE();
	_misc = s.readByte();
	_maxFrames = s.readUint16LE();
	_flags = s.readByte();

	if (!isRoseTattoo)
		_aOpen.load(s);

	_aType = (AType)s.readByte();
	_lookFrames = s.readByte();
	_seqCounter = s.readByte();
	_lookPosition.x = s.readUint16LE() * FIXED_INT_MULTIPLIER / 100;
	_lookPosition.y = (isRoseTattoo ? s.readSint16LE() : s.readByte()) * FIXED_INT_MULTIPLIER;
	_lookFacing = s.readByte();
	_lookcAnim = s.readByte();

	if (!isRoseTattoo)
		_aClose.load(s);

	_seqStack = s.readByte();
	_seqTo = s.readByte();
	_descOffset = s.readUint16LE();
	_seqCounter2 = s.readByte();
	_seqSize = s.readUint16LE();

	if (isRoseTattoo) {
		for (int idx = 0; idx < 6; ++idx)
			_use[idx].load(s, true);

		_quickDraw = s.readByte();
		_scaleVal = s.readUint16LE();
		_requiredFlags1 = s.readSint16LE();
		_gotoSeq = s.readByte();
		_talkSeq = s.readByte();
		_restoreSlot = s.readByte();
	} else {
		s.skip(1);
		_aMove.load(s);
		s.skip(8);

		for (int idx = 0; idx < 4; ++idx)
			_use[idx].load(s, false);
	}
}

void Object::toggleHidden() {
	if (_type != HIDDEN && _type != HIDE_SHAPE && _type != INVALID) {
		if (_seqTo != 0)
			_sequences[_frameNumber] = _seqTo + SEQ_TO_CODE + 128;
		_seqTo = 0;

		if (_images == nullptr || _images->size() == 0)
			// No shape to erase, so flag as hidden
			_type = HIDDEN;
		else
			// Otherwise, flag it to be hidden after it gets erased
			_type = HIDE_SHAPE;
	} else if (_type != INVALID) {
		if (_seqTo != 0)
			_sequences[_frameNumber] = _seqTo + SEQ_TO_CODE + 128;
		_seqTo = 0;

		_seqCounter = _seqCounter2 = 0;
		_seqStack = 0;
		_frameNumber = -1;

		if (_images == nullptr || _images->size() == 0) {
			_type = NO_SHAPE;
		} else {
			_type = ACTIVE_BG_SHAPE;
			int idx = _sequences[0];
			if (idx >= _maxFrames)
				// Turn on: set up first frame
				idx = 0;

			_imageFrame = &(*_images)[idx];
		}
	}
}

void Object::checkObject() {
	Scene &scene = *_vm->_scene;
	Sound &sound = *_vm->_sound;
	Talk &talk = *_vm->_talk;
	int checkFrame = _allow ? MAX_FRAME : FRAMES_END;
	bool codeFound;

	if (_seqTo) {
		byte *ptr = &_sequences[_frameNumber];
		if (*ptr == _seqTo) {
			// The sequence is completed
			*ptr = _seqTo + SEQ_TO_CODE + 128;	// Reset to normal
			_seqTo = 0;
		} else {
			// Continue doing sequence
			if (*ptr > _seqTo)
				*ptr -= 1;
			else
				*ptr += 1;

			return;
		}
	}

	++_frameNumber;

	do {
		// Check for end of sequence
		codeFound = checkEndOfSequence();

		if (_sequences[_frameNumber] >= 128 && _frameNumber < checkFrame) {
			codeFound = true;
			int v = _sequences[_frameNumber];

			// Check for a Talk or Listen Sequence
			if (IS_ROSE_TATTOO && v == ALLOW_TALK_CODE) {
				if (_gotoSeq) {
					setObjTalkSequence(_gotoSeq);
				} else {
					++_frameNumber;
				}
			} else if (IS_ROSE_TATTOO && (v == TALK_SEQ_CODE || v == TALK_LISTEN_CODE)) {
				if (_talkSeq)
					setObjTalkSequence(_talkSeq);
				else
					setObjSequence(0, false);
			} else  if (v >= GOTO_CODE) {
				// Goto code found
				v -= GOTO_CODE;
				_seqCounter2 = _seqCounter;
				_seqStack = _frameNumber + 1;
				setObjSequence(v, false);
			} else if (v >= SOUND_CODE && (v < (SOUND_CODE + 30))) {
				codeFound = true;
				++_frameNumber;
				v -= SOUND_CODE + (IS_SERRATED_SCALPEL ? 1 : 0);

				if (sound._soundOn && !_countCAnimFrames) {
					if (!scene._sounds[v]._name.empty() && sound._digitized)
						sound.playLoadedSound(v, WAIT_RETURN_IMMEDIATELY);
				}
			} else if (v >= FLIP_CODE && v < (FLIP_CODE + 3)) {
				// Flip code
				codeFound = true;
				++_frameNumber;
				v -= FLIP_CODE;

				// Alter the flipped status
				switch (v) {
				case 0:
					// Clear the flag
					_flags &= ~OBJ_FLIPPED;
					break;
				case 1:
					// Set the flag
					_flags |= OBJ_FLIPPED;
					break;
				case 2:
					// Toggle the flag
					_flags ^= OBJ_FLIPPED;
					break;
				default:
					break;
				}
			} else if (IS_ROSE_TATTOO && v == TELEPORT_CODE) {
				_position.x = READ_LE_UINT16(&_sequences[_frameNumber + 1]);
				_position.y = READ_LE_UINT16(&_sequences[_frameNumber + 3]);

				_frameNumber += 5;
			} else if (IS_ROSE_TATTOO && v == CALL_TALK_CODE) {
				Common::String filename;
				for (int idx = 0; idx < 8; ++idx) {
					if (_sequences[_frameNumber + 1 + idx] != 1)
						filename += (char)_sequences[_frameNumber + 1 + idx];
					else
						break;
				}

				_frameNumber += 8;
				talk.talkTo(filename);

			} else if (IS_ROSE_TATTOO && v == HIDE_CODE) {
				switch (_sequences[_frameNumber + 2]) {
				case 1:
					// Hide Object
					if (scene._bgShapes[_sequences[_frameNumber + 1] - 1]._type != HIDDEN)
						scene._bgShapes[_sequences[_frameNumber + 1] - 1].toggleHidden();
					break;

				case 2:
					// Activate Object
					if (scene._bgShapes[_sequences[_frameNumber + 1] - 1]._type == HIDDEN)
						scene._bgShapes[_sequences[_frameNumber + 1] - 1].toggleHidden();
					break;

				case 3:
					// Toggle Object
					scene._bgShapes[_sequences[_frameNumber + 1] - 1].toggleHidden();
					break;

				default:
					break;
				}
				_frameNumber += 3;
			
			} else {
				v -= 128;

				// 68-99 is a squence code
				if (v > SEQ_TO_CODE) {
					byte *p = &_sequences[_frameNumber];
					v -= SEQ_TO_CODE;	// # from 1-32
					_seqTo = v;
					*p = *(p - 1);

					if (*p > 128)
						// If the high bit is set, convert to a real frame
						*p -= (byte)(SEQ_TO_CODE - 128);

					if (*p > _seqTo)
						*p -= 1;
					else
						*p += 1;

					// Will be incremented below to return back to original value
					--_frameNumber;
					v = 0;

				} else if (IS_ROSE_TATTOO && v == 10) {
					// Set delta for objects
					_delta = Common::Point(READ_LE_UINT16(&_sequences[_frameNumber + 1]), 
						READ_LE_UINT16(&_sequences[_frameNumber + 3]));
					_noShapeSize = Common::Point(0, 0);
					_frameNumber += 4;

				} else if (v == 10) {
					// Set delta for objects
					Common::Point pt(_sequences[_frameNumber + 1], _sequences[_frameNumber + 2]);
					if (pt.x > 128)
						pt.x = (pt.x - 128) *  -1;
					else
						pt.x--;

					if (pt.y > 128)
						pt.y = (pt.y - 128) * -1;
					else
						pt.y--;

					_delta = pt;
					_frameNumber += 2;

				} else if (v < USE_COUNT) {
					for (int idx = 0; idx < NAMES_COUNT; ++idx) {
						checkNameForCodes(_use[v]._names[idx], nullptr);
					}

					if (_use[v]._useFlag)
						_vm->setFlags(_use[v]._useFlag);
				}

				++_frameNumber;
			}
		}
	} while (codeFound);
}

bool Object::checkEndOfSequence() {
	Screen &screen = *_vm->_screen;
	int checkFrame = _allow ? MAX_FRAME : FRAMES_END;
	bool result = false;

	if (_type == REMOVE || _type == INVALID)
		return false;

	if (_sequences[_frameNumber] == 0 || _frameNumber >= checkFrame) {
		result = true;

		if (_frameNumber >= (checkFrame - 1)) {
			_frameNumber = START_FRAME;
		}  else {
			// Determine next sequence to use
			int seq = _sequences[_frameNumber + 1];

			if (seq == 99) {
				--_frameNumber;
				screen._backBuffer1.transBlitFrom(*_imageFrame, _position);
				screen._backBuffer2.transBlitFrom(*_imageFrame, _position);
				_type = INVALID;
			} else if (IS_ROSE_TATTOO && _talkSeq && seq == 0) {
				setObjTalkSequence(_talkSeq);
			} else {
				setObjSequence(seq, false);
			}
		}

		if (_allow && _frameNumber == 0) {
			// canimation just ended
			if (_type != NO_SHAPE && _type != REMOVE) {
				_type = REMOVE;

				if (!_countCAnimFrames) {
					// Save details before shape is removed
					_delta.x = _imageFrame->_frame.w;
					_delta.y = _imageFrame->_frame.h;
					_position += _imageFrame->_offset;

					// Free the images
					delete _images;
					_images = nullptr;
					_imageFrame = nullptr;
				}
			} else {
				_type = INVALID;
			}
		}
	}

	return result;
}

void Object::setObjSequence(int seq, bool wait) {
	Scene &scene = *_vm->_scene;
	int checkFrame = _allow ? MAX_FRAME : FRAMES_END;

	if (seq >= 128) {
		// Loop the sequence until the count exceeded
		seq -= 128;

		++_seqCounter;
		if (_seqCounter >= seq) {
			// Go to next sequence
			if (_seqStack) {
				_frameNumber = _seqStack;
				_seqStack = 0;
				_seqCounter = _seqCounter2;
				_seqCounter2 = 0;
				if (_frameNumber >= checkFrame)
					_frameNumber = START_FRAME;

				return;
			}

			_frameNumber += 2;
			if (_frameNumber >= checkFrame)
				_frameNumber = 0;

			_seqCounter = 0;
			if (_sequences[_frameNumber] == 0)
				seq = _sequences[_frameNumber + 1];
			else
				return;
		} else {
			// Find beginning of sequence
			do {
				--_frameNumber;
			} while (_frameNumber > 0 && _sequences[_frameNumber] != 0);

			if (_frameNumber != 0)
				_frameNumber += 2;

			return;
		}
	} else {
		// Reset sequence counter
		_seqCounter = 0;
	}

	int idx = 0;
	int seqCc = 0;

	while (seqCc < seq && idx < checkFrame) {
		++idx;
		if (_sequences[idx] == 0) {
			++seqCc;
			idx += 2;
		}
	}

	if (idx >= checkFrame)
		idx = 0;
	_frameNumber = idx;

	if (wait) {
		seqCc = idx;
		while (_sequences[idx] != 0)
			++idx;

		idx = idx - seqCc + 2;
		for (; idx > 0; --idx)
			scene.doBgAnim();
	}
}

void Object::setObjTalkSequence(int seq) {
	Talk &talk = *_vm->_talk;

	// See if we're supposed to restore the object's sequence from the talk sequence stack
	if (seq == -1) {
		TalkSequence &ts = talk._talkSequenceStack[_restoreSlot];
		if (_seqTo != 0)
			_sequences[_frameNumber] = _seqTo;
		_frameNumber = ts._frameNumber;
		_sequenceNumber = ts._sequenceNumber;
		_seqStack = ts._seqStack;
		_seqTo = ts._seqTo;
		_seqCounter = ts._seqCounter;
		_seqCounter2 = ts._seqCounter2;
		_talkSeq = 0;

		// Flag this slot as free again
		ts._obj = nullptr;

		return;
	}

	assert(_type != CHARACTER);

	// If the object passed in is an NPC, set it's sequence through the sequence number rather
	// than adjusting the frame number to a specific sub-sequence
	/*	
		s = (SpriteType *)bg;
		if (s->seqto)
		{
			// reset to previous value
			s->WalkSeqs[s->fs]->Seq[s->fn] = s->seqto;
			s->seqto = 0;
		}
		s->fs = s->GotoSeq;
		s->fn = 0;
		CheckWalkGraphics(s);
	*/
	
	talk.pushTalkSequence(this);
	int talkSeqNum = seq;

	// Find where the talk sequence data begins in the object
	int idx = 0;
	for (;;) {
		// Get the Frame value
		byte f = _sequences[idx++];

		// See if we've found the beginning of a Talk Sequence
		if ((f == TALK_SEQ_CODE && seq < 128) || (f == TALK_LISTEN_CODE && seq > 128)) {
			--seq;

			// See if we're at the correct Talk Sequence Number
			if (!(seq & 127))
			{
				// Correct Sequence, Start Talking Here
				if (_seqTo != 0)
					_sequences[_frameNumber] = _seqTo;
				_frameNumber = idx;
				_seqTo = 0;
				_seqStack = 0;
				_seqCounter = 0;
				_seqCounter2 = 0;
				_talkSeq = talkSeqNum;
				break;
			}
		} else {
			// Move ahead any extra because of special control codes
			switch (f) {
			case 0: idx++; break;
			case MOVE_CODE:
			case TELEPORT_CODE: idx += 4; break;
			case CALL_TALK_CODE: idx += 8; break;
			case HIDE_CODE: idx += 2; break;
			}
		}

		// See if we're out of sequence data
		if (idx >= (int)_seqSize)
			break;
	}
}

int Object::checkNameForCodes(const Common::String &name, const char *const messages[]) {
	People &people = *_vm->_people;
	Scene &scene = *_vm->_scene;
	Screen &screen = *_vm->_screen;
	Talk &talk = *_vm->_talk;
	UserInterface &ui = *_vm->_ui;
	bool printed = false;

	scene.toggleObject(name);

	if (name.hasPrefix("*")) {
		// A code was found
		printed = true;
		char ch = (name == "*") ? 0 : toupper(name[1]);

		switch (ch) {
		case 'C':
			talk.talkTo(name.c_str() + 2);
			break;

		case 'T':
		case 'B':
		case 'F':
		case 'W':
			// Nothing: action was already done before canimation
			break;

		case 'G':
		case 'A': {
			// G: Have object go somewhere
			// A: Add onto existing co-ordinates
			Common::String sx(name.c_str() + 2, name.c_str() + 5);
			Common::String sy(name.c_str() + 6, name.c_str() + 9);

			if (ch == 'G')
				_position = Common::Point(atoi(sx.c_str()), atoi(sy.c_str()));
			else
				_position += Common::Point(atoi(sx.c_str()), atoi(sy.c_str()));
			break;
		}

		default:
			if (ch >= '0' && ch <= '9') {
				scene._goToScene = atoi(name.c_str() + 1);

				if (IS_SERRATED_SCALPEL && scene._goToScene < 97) {
					Scalpel::ScalpelMap &map = *(Scalpel::ScalpelMap *)_vm->_map;
					if (map[scene._goToScene].x) {
						map._overPos.x = (map[scene._goToScene].x - 6) * FIXED_INT_MULTIPLIER;
						map._overPos.y = (map[scene._goToScene].y + 9) * FIXED_INT_MULTIPLIER;
					}
				}

				const char *p;
				if ((p = strchr(name.c_str(), ',')) != nullptr) {
					++p;

					Common::String s(p, p + 3);
					people._hSavedPos.x = atoi(s.c_str());

					s = Common::String(p + 3, p + 6);
					people._hSavedPos.y = atoi(s.c_str());

					s = Common::String(p + 6, p + 9);
					people._hSavedFacing = atoi(s.c_str());
					if (people._hSavedFacing == 0)
						people._hSavedFacing = 10;
				} else if ((p = strchr(name.c_str(), '/')) != nullptr) {
					people._hSavedPos = Common::Point(1, 0);
					people._hSavedFacing = 100 + atoi(p + 1);
				}
			} else {
				scene._goToScene = 100;
			}

			people[AL]._position = Point32(0, 0);
			break;
		}
	} else if (name.hasPrefix("!")) {
		// Message attached to canimation
		int messageNum = atoi(name.c_str() + 1);
		ui._infoFlag = true;
		ui.clearInfo();
		screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", messages[messageNum]);
		ui._menuCounter = 25;
	} else if (name.hasPrefix("@")) {
		// Message attached to canimation
		ui._infoFlag = true;
		ui.clearInfo();
		screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", name.c_str() + 1);
		printed = true;
		ui._menuCounter = 25;
	}

	return printed;
}

void Object::setFlagsAndToggles() {
	Scene &scene = *_vm->_scene;
	Talk &talk = *_vm->_talk;

	for (int useIdx = 0; useIdx < USE_COUNT; ++useIdx) {
		if (_use[useIdx]._useFlag) {
			if (!_vm->readFlags(_use[useIdx]._useFlag))
				_vm->setFlags(_use[useIdx]._useFlag);
		}

		if (_use[useIdx]._cAnimSpeed) {
			if (_use[useIdx]._cAnimNum == 0)
				// 0 is really a 10
				scene.startCAnim(9, _use[useIdx]._cAnimSpeed);
			else
				scene.startCAnim(_use[useIdx]._cAnimNum - 1, _use[useIdx]._cAnimSpeed);
		}

		if (!talk._talkToAbort) {
			for (int idx = 0; idx < NAMES_COUNT; ++idx)
				scene.toggleObject(_use[useIdx]._names[idx]);
		}
	}
}

void Object::adjustObject() {
	if (_type == REMOVE)
		return;

	if (IS_ROSE_TATTOO && (_delta.x || _delta.y)) {
		// The shape position is in pixels, and the delta is in fixed integer amounts
		int t;
		_noShapeSize.x += _delta.x;
		t = _noShapeSize.x / (FIXED_INT_MULTIPLIER / 10);
		_noShapeSize.x -= t * (FIXED_INT_MULTIPLIER / 10);
		_position.x += t;

		_noShapeSize.y += _delta.y;
		t = _noShapeSize.y / (FIXED_INT_MULTIPLIER / 10);
		_noShapeSize.y -= t * (FIXED_INT_MULTIPLIER / 10);
		_position.y += t;
	} else if (IS_SERRATED_SCALPEL) {
		// The delta is in whole pixels, so simply adjust the position with it
		_position += _delta;
	}

	if (_position.y > LOWER_LIMIT)
		_position.y = LOWER_LIMIT;

	if (_type != NO_SHAPE) {
		int frame = _frameNumber;
		if (frame == -1)
			frame = 0;

		int imgNum = _sequences[frame];
		if (imgNum > _maxFrames)
			imgNum = 1;

		_imageFrame = &(*_images)[imgNum - 1];
	}
}

int Object::pickUpObject(const char *const messages[]) {
	Inventory &inv = *_vm->_inventory;
	People &people = *_vm->_people;
	Scene &scene = *_vm->_scene;
	Screen &screen = *_vm->_screen;
	Talk &talk = *_vm->_talk;
	UserInterface &ui = *_vm->_ui;
	int pickup = _pickup & 0x7f;
	bool printed = false;
	int numObjects = 0;

	if (pickup == 99) {
		for (int idx = 0; idx < NAMES_COUNT && !talk._talkToAbort; ++idx) {
			if (checkNameForCodes(_use[0]._names[idx], nullptr)) {
				if (!talk._talkToAbort)
					printed = true;
			}
		}

		return 0;
	}

	if (!pickup || (pickup > 50 && pickup <= 80)) {
		int message = _pickup;
		if (message > 50)
			message -= 50;

		ui._infoFlag = true;
		ui.clearInfo();
		screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", messages[message]);
		ui._menuCounter = 30;
	} else {
		// Pick it up
		bool takeFlag = true;
		if ((_pickup & 0x80) == 0) {
			// Play an animation
			if (pickup > 80) {
				takeFlag = false;		// Don't pick it up
				scene.startCAnim(pickup - 81, 1);
				if (_pickupFlag)
					_vm->setFlags(_pickupFlag);
			} else {
				scene.startCAnim(pickup - 1, 1);
				if (!talk._talkToAbort) {
					// Erase the shape
					_type = _type == NO_SHAPE ? INVALID : REMOVE;
				}
			}

			if (talk._talkToAbort)
				return 0;
		} else {
			// Play generic pickup sequence
			// Original moved cursor position here
			people.goAllTheWay();
			ui._menuCounter = 25;
			ui._temp1 = 1;
		}

		for (int idx = 0; idx < NAMES_COUNT && !talk._talkToAbort; ++idx) {
			if (checkNameForCodes(_use[0]._names[idx], nullptr)) {
				if (!talk._talkToAbort)
					printed = true;
			}
		}
		if (talk._talkToAbort)
			return 0;

		// Add the item to the player's inventory
		if (takeFlag)
			numObjects = inv.putItemInInventory(*this);

		if (!printed) {
			ui._infoFlag = true;
			ui.clearInfo();

			Common::String itemName = _description;
			itemName.setChar(tolower(itemName[0]), 0);
			screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "Picked up %s", itemName.c_str());
			ui._menuCounter = 25;
		}
	}

	return numObjects;
}

const Common::Rect Object::getNewBounds() const {
	Point32 pt = _position;
	if (_imageFrame)
		pt += _imageFrame->_offset;

	return Common::Rect(pt.x, pt.y, pt.x + frameWidth(), pt.y + frameHeight());
}

const Common::Rect Object::getNoShapeBounds() const {
	return Common::Rect(_position.x, _position.y,
		_position.x + _noShapeSize.x, _position.y + _noShapeSize.y);
}

const Common::Rect Object::getOldBounds() const {
	return Common::Rect(_oldPosition.x, _oldPosition.y,
		_oldPosition.x + _oldSize.x, _oldPosition.y + _oldSize.y);
}

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

void CAnim::load(Common::SeekableReadStream &s, bool isRoseTattoo) {
	char buffer[12];
	s.read(buffer, 12);
	_name = Common::String(buffer);

	if (isRoseTattoo) {
		Common::fill(&_sequences[0], &_sequences[30], 0);
		_size = s.readUint32LE();
	} else {
		s.read(_sequences, 30);
	}

	_position.x = s.readSint16LE();
	_position.y = s.readSint16LE();
	
	if (isRoseTattoo) {
		_flags = s.readByte();
		_scaleVal = s.readSint16LE();
	} else {
		_size = s.readUint32LE();
		_type = (SpriteType)s.readUint16LE();
		_flags = s.readByte();
	}

	_goto.x = s.readSint16LE();
	_goto.y = s.readSint16LE();
	_gotoDir = s.readSint16LE();
	_teleportPos.x = s.readSint16LE();
	_teleportPos.y = s.readSint16LE();
	if (!isRoseTattoo) {
		_goto.x = _goto.x * FIXED_INT_MULTIPLIER / 100;
		_goto.y = _goto.y * FIXED_INT_MULTIPLIER / 100;
		_teleportPos.x = _teleportPos.x * FIXED_INT_MULTIPLIER / 100;
		_teleportPos.y = _teleportPos.y * FIXED_INT_MULTIPLIER / 100;
	}

	_teleportDir = s.readSint16LE();
}

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

CAnimStream::CAnimStream() {
	_stream = nullptr;
	_frameSize = 0;
	_images = nullptr;
	_imageFrame = nullptr;
	_flags = 0;
	_scaleVal = 0;
	_zPlacement = 0;
}

void CAnimStream::getNextFrame() {
	// TODO
}

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

SceneImage::SceneImage() {
	_images = nullptr;
	_maxFrames = 0;
	_filesize = 0;
}

} // End of namespace Sherlock