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

/*
 * This file is based on WME Lite.
 * http://dead-code.org/redir.php?target=wmelite
 * Copyright (c) 2011 Jan Nedoma
 */

#include "engines/wintermute/base/base_sprite.h"
#include "engines/wintermute/utils/path_util.h"
#include "engines/wintermute/base/base_parser.h"
#include "engines/wintermute/base/base_dynamic_buffer.h"
#include "engines/wintermute/base/gfx/base_surface.h"
#include "engines/wintermute/base/base_game.h"
#include "engines/wintermute/base/base_engine.h"
#include "engines/wintermute/base/base_frame.h"
#include "engines/wintermute/base/sound/base_sound.h"
#include "engines/wintermute/base/base_sub_frame.h"
#include "engines/wintermute/base/base_file_manager.h"
#include "engines/wintermute/platform_osystem.h"
#include "engines/wintermute/base/scriptables/script_value.h"
#include "engines/wintermute/base/scriptables/script.h"
#include "engines/wintermute/base/scriptables/script_stack.h"
#include "engines/wintermute/game_description.h"

namespace Wintermute {

IMPLEMENT_PERSISTENT(BaseSprite, false)

//////////////////////////////////////////////////////////////////////
BaseSprite::BaseSprite(BaseGame *inGame, BaseObject *Owner) : BaseScriptHolder(inGame) {
	_editorAllFrames = false;
	_owner = Owner;
	setDefaults();
}


//////////////////////////////////////////////////////////////////////
BaseSprite::~BaseSprite() {
	cleanup();
}


//////////////////////////////////////////////////////////////////////////
void BaseSprite::setDefaults() {
	_currentFrame = -1;
	_looping = false;
	_lastFrameTime = 0;
	setFilename(nullptr);
	_finished = false;
	_changed = false;
	_paused = false;
	_continuous = false;
	_moveX = _moveY = 0;

	_editorMuted = false;
	_editorBgFile = nullptr;
	_editorBgOffsetX = _editorBgOffsetY = 0;
	_editorBgAlpha = 0xFF;
	_streamed = false;
	_streamedKeepLoaded = false;

	setName("");

	_precise = true;
}


//////////////////////////////////////////////////////////////////////////
void BaseSprite::cleanup() {
	BaseScriptHolder::cleanup();

	for (uint32 i = 0; i < _frames.size(); i++) {
		delete _frames[i];
	}
	_frames.clear();

	delete[] _editorBgFile;
	_editorBgFile = nullptr;

	setDefaults();
}


//////////////////////////////////////////////////////////////////////////
bool BaseSprite::draw(int x, int y, BaseObject *registerOwner, float zoomX, float zoomY, uint32 alpha) {
	getCurrentFrame(zoomX, zoomY);
	if (_currentFrame < 0 || _currentFrame >= (int32)_frames.size()) {
		return STATUS_OK;
	}

	// move owner if allowed to
	if (_changed && _owner && _owner->_movable) {
		_owner->_posX += _moveX;
		_owner->_posY += _moveY;
		_owner->afterMove();

		x = _owner->_posX;
		y = _owner->_posY;
	}

	// draw frame
	return display(x, y, registerOwner, zoomX, zoomY, alpha);
}

bool BaseSprite::isChanged() {
	return _changed;
}

bool BaseSprite::isFinished() {
	return _finished;
}

//////////////////////////////////////////////////////////////////////
bool BaseSprite::loadFile(const Common::String &filename, int lifeTime, TSpriteCacheType cacheType) {
	Common::SeekableReadStream *file = BaseFileManager::getEngineInstance()->openFile(filename);
	if (!file) {
		BaseEngine::LOG(0, "BaseSprite::LoadFile failed for file '%s'", filename.c_str());
		if (_gameRef->_debugDebugMode) {
			return loadFile("invalid_debug.bmp", lifeTime, cacheType);
		} else {
			return loadFile("invalid.bmp", lifeTime, cacheType);
		}
	} else {
		BaseFileManager::getEngineInstance()->closeFile(file);
		file = nullptr;
	}

	bool ret = STATUS_FAILED;

	AnsiString filePrefix = filename;
	AnsiString ext = PathUtil::getExtension(filename);
	ext.toLowercase();
	filePrefix.toLowercase();
	if (filePrefix.hasPrefix("savegame:") || (ext == "bmp") || (ext == "tga") || (ext == "png") || (ext == "jpg")) {
		BaseFrame *frame = new BaseFrame(_gameRef);
		BaseSubFrame *subframe = new BaseSubFrame(_gameRef);
		subframe->setSurface(filename, true, 0, 0, 0, lifeTime, true);
		if (subframe->_surface == nullptr) {
			BaseEngine::LOG(0, "Error loading simple sprite '%s'", filename.c_str());
			ret = STATUS_FAILED;
			delete frame;
			delete subframe;
		} else {
			subframe->setDefaultRect();
			frame->_subframes.add(subframe);
			_frames.add(frame);
			_currentFrame = 0;
			ret = STATUS_OK;
		}
	} else {
		char *buffer = (char *)BaseFileManager::getEngineInstance()->readWholeFile(filename);
		if (buffer) {
			if (DID_FAIL(ret = loadBuffer(buffer, true, lifeTime, cacheType))) {
				BaseEngine::LOG(0, "Error parsing SPRITE file '%s'", filename.c_str());
			} else {
				ret = STATUS_OK;
			}
			delete[] buffer;
		}
	}

	setFilename(filename.c_str());

	return ret;
}



TOKEN_DEF_START
TOKEN_DEF(CONTINUOUS)
TOKEN_DEF(SPRITE)
TOKEN_DEF(LOOPING)
TOKEN_DEF(FRAME)
TOKEN_DEF(NAME)
TOKEN_DEF(PRECISE)
TOKEN_DEF(EDITOR_MUTED)
TOKEN_DEF(STREAMED_KEEP_LOADED)
TOKEN_DEF(STREAMED)
TOKEN_DEF(SCRIPT)
TOKEN_DEF(EDITOR_BG_FILE)
TOKEN_DEF(EDITOR_BG_OFFSET_X)
TOKEN_DEF(EDITOR_BG_OFFSET_Y)
TOKEN_DEF(EDITOR_BG_ALPHA)
TOKEN_DEF(EDITOR_PROPERTY)
TOKEN_DEF_END
//////////////////////////////////////////////////////////////////////
bool BaseSprite::loadBuffer(char *buffer, bool complete, int lifeTime, TSpriteCacheType cacheType) {
	TOKEN_TABLE_START(commands)
	TOKEN_TABLE(CONTINUOUS)
	TOKEN_TABLE(SPRITE)
	TOKEN_TABLE(LOOPING)
	TOKEN_TABLE(FRAME)
	TOKEN_TABLE(NAME)
	TOKEN_TABLE(PRECISE)
	TOKEN_TABLE(EDITOR_MUTED)
	TOKEN_TABLE(STREAMED_KEEP_LOADED)
	TOKEN_TABLE(STREAMED)
	TOKEN_TABLE(SCRIPT)
	TOKEN_TABLE(EDITOR_BG_FILE)
	TOKEN_TABLE(EDITOR_BG_OFFSET_X)
	TOKEN_TABLE(EDITOR_BG_OFFSET_Y)
	TOKEN_TABLE(EDITOR_BG_ALPHA)
	TOKEN_TABLE(EDITOR_PROPERTY)
	TOKEN_TABLE_END

	char *params;
	int cmd;
	BaseParser parser;

	cleanup();


	if (complete) {
		if (parser.getCommand(&buffer, commands, &params) != TOKEN_SPRITE) {
			BaseEngine::LOG(0, "'SPRITE' keyword expected.");
			return STATUS_FAILED;
		}
		buffer = params;
	}

	int frameCount = 1;
	BaseFrame *frame;
	while ((cmd = parser.getCommand(&buffer, commands, &params)) > 0) {
		switch (cmd) {
		case TOKEN_CONTINUOUS:
			parser.scanStr(params, "%b", &_continuous);
			break;

		case TOKEN_EDITOR_MUTED:
			parser.scanStr(params, "%b", &_editorMuted);
			break;

		case TOKEN_SCRIPT:
			addScript(params);
			break;

		case TOKEN_LOOPING:
			parser.scanStr(params, "%b", &_looping);
			break;

		case TOKEN_PRECISE:
			parser.scanStr(params, "%b", &_precise);
			break;

		case TOKEN_STREAMED:
			parser.scanStr(params, "%b", &_streamed);
			if (_streamed && lifeTime == -1) {
				lifeTime = 500;
				cacheType = CACHE_ALL;
			}
			break;

		case TOKEN_STREAMED_KEEP_LOADED:
			parser.scanStr(params, "%b", &_streamedKeepLoaded);
			break;

		case TOKEN_NAME:
			setName(params);
			break;

		case TOKEN_EDITOR_BG_FILE:
			if (_gameRef->_editorMode) {
				delete[] _editorBgFile;
				_editorBgFile = new char[strlen(params) + 1];
				if (_editorBgFile) {
					strcpy(_editorBgFile, params);
				}
			}
			break;

		case TOKEN_EDITOR_BG_OFFSET_X:
			parser.scanStr(params, "%d", &_editorBgOffsetX);
			break;

		case TOKEN_EDITOR_BG_OFFSET_Y:
			parser.scanStr(params, "%d", &_editorBgOffsetY);
			break;

		case TOKEN_EDITOR_BG_ALPHA:
			parser.scanStr(params, "%d", &_editorBgAlpha);
			_editorBgAlpha = MIN<int32>(_editorBgAlpha, 255);
			_editorBgAlpha = MAX<int32>(_editorBgAlpha, 0);
			break;

		case TOKEN_FRAME: {
			int frameLifeTime = lifeTime;
			if (cacheType == CACHE_HALF && frameCount % 2 != 1) {
				frameLifeTime = -1;
			}

			frame = new BaseFrame(_gameRef);

			if (DID_FAIL(frame->loadBuffer(params, frameLifeTime, _streamedKeepLoaded))) {
				delete frame;
				BaseEngine::LOG(0, "Error parsing frame %d", frameCount);
				return STATUS_FAILED;
			}

			_frames.add(frame);
			frameCount++;
			if (_currentFrame == -1) {
				_currentFrame = 0;
			}
		}
		break;

		case TOKEN_EDITOR_PROPERTY:
			parseEditorProperty(params, false);
			break;

		default:
			break;
		}
	}

	if (cmd == PARSERR_TOKENNOTFOUND) {
		BaseEngine::LOG(0, "Syntax error in SPRITE definition");
		return STATUS_FAILED;
	}
	_canBreak = !_continuous;

	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////
void BaseSprite::reset() {
	if (_frames.size() > 0) {
		_currentFrame = 0;
	} else {
		_currentFrame = -1;
	}
	if (BaseEngine::instance().getTargetExecutable() >= WME_1_8_6) {
		/*
		* This was added in WME 1.8.6
		*
		* 5MA and possibly other games ship with pre-1.8.6 WME, and
		* depends (e.g.: menu sounds, etc) on this not being triggered.
		*
		* See bug #6647
		*/
		killAllSounds();
	}
	_lastFrameTime = 0;
	_finished = false;
	_moveX = _moveY = 0;
}


//////////////////////////////////////////////////////////////////////
bool BaseSprite::getCurrentFrame(float zoomX, float zoomY) {
	//if (_owner && _owner->_freezable && _gameRef->_state == GAME_FROZEN) return true;

	if (_currentFrame == -1) {
		return false;
	}

	uint32 timer;
	if (_owner && _owner->_freezable) {
		timer = _gameRef->getTimer()->getTime();
	} else {
		timer = _gameRef->getLiveTimer()->getTime();
	}

	int lastFrame = _currentFrame;

	// get current frame
	if (!_paused && !_finished && timer >= _lastFrameTime + _frames[_currentFrame]->_delay && _lastFrameTime != 0) {
		if (_currentFrame < (int32)_frames.size() - 1) {
			_currentFrame++;
			if (_continuous) {
				_canBreak = (_currentFrame == (int32)_frames.size() - 1);
			}
		} else {
			if (_looping) {
				_currentFrame = 0;
				_canBreak = true;
			} else {
				_finished = true;
				_canBreak = true;
			}
		}

		_lastFrameTime = timer;
	}

	_changed = (lastFrame != _currentFrame || (_looping && (int32)_frames.size() == 1));

	if (_lastFrameTime == 0) {
		_lastFrameTime = timer;
		_changed = true;
		if (_continuous) {
			_canBreak = (_currentFrame == (int32)_frames.size() - 1);
		}
	}

	if (_changed) {
		_moveX = _frames[_currentFrame]->_moveX;
		_moveY = _frames[_currentFrame]->_moveY;

		if (zoomX != 100 || zoomY != 100) {
			_moveX = (int)((float)_moveX * (float)(zoomX / 100.0f));
			_moveY = (int)((float)_moveY * (float)(zoomY / 100.0f));
		}
	}

	return _changed;
}


//////////////////////////////////////////////////////////////////////
bool BaseSprite::display(int x, int y, BaseObject *registerVal, float zoomX, float zoomY, uint32 alpha, float rotate, Graphics::TSpriteBlendMode blendMode) {
	if (_currentFrame < 0 || _currentFrame >= (int32)_frames.size()) {
		return STATUS_OK;
	}

	// on change...
	if (_changed) {
		if (_frames[_currentFrame]->_killSound) {
			killAllSounds();
		}
		applyEvent("FrameChanged");
		_frames[_currentFrame]->oneTimeDisplay(_owner, _gameRef->_editorMode && _editorMuted);
	}

	// draw frame
	return _frames[_currentFrame]->draw(x - _gameRef->_offsetX, y - _gameRef->_offsetY, registerVal, zoomX, zoomY, _precise, alpha, _editorAllFrames, rotate, blendMode);
}


//////////////////////////////////////////////////////////////////////////
BaseSurface *BaseSprite::getSurface() {
	// only used for animated textures for 3D models
	if (_currentFrame < 0 || _currentFrame >= (int32)_frames.size()) {
		return nullptr;
	}
	BaseFrame *frame = _frames[_currentFrame];
	if (frame && frame->_subframes.size() > 0) {
		BaseSubFrame *subframe = frame->_subframes[0];
		if (subframe) {
			return subframe->_surface;
		} else {
			return nullptr;
		}
	} else {
		return nullptr;
	}
}

//////////////////////////////////////////////////////////////////////////
bool BaseSprite::getBoundingRect(Rect32 *rect, int x, int y, float scaleX, float scaleY) {
	if (!rect) {
		return false;
	}

	rect->setEmpty();
	for (uint32 i = 0; i < _frames.size(); i++) {
		Rect32 frame;
		Rect32 temp;
		BasePlatform::copyRect(&temp, rect);
		_frames[i]->getBoundingRect(&frame, x, y, scaleX, scaleY);
		BasePlatform::unionRect(rect, &temp, &frame);
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
bool BaseSprite::saveAsText(BaseDynamicBuffer *buffer, int indent) {
	buffer->putTextIndent(indent, "SPRITE {\n");
	buffer->putTextIndent(indent + 2, "NAME=\"%s\"\n", getName());
	buffer->putTextIndent(indent + 2, "LOOPING=%s\n", _looping ? "TRUE" : "FALSE");
	buffer->putTextIndent(indent + 2, "CONTINUOUS=%s\n", _continuous ? "TRUE" : "FALSE");
	buffer->putTextIndent(indent + 2, "PRECISE=%s\n", _precise ? "TRUE" : "FALSE");
	if (_streamed) {
		buffer->putTextIndent(indent + 2, "STREAMED=%s\n", _streamed ? "TRUE" : "FALSE");

		if (_streamedKeepLoaded) {
			buffer->putTextIndent(indent + 2, "STREAMED_KEEP_LOADED=%s\n", _streamedKeepLoaded ? "TRUE" : "FALSE");
		}
	}

	if (_editorMuted) {
		buffer->putTextIndent(indent + 2, "EDITOR_MUTED=%s\n", _editorMuted ? "TRUE" : "FALSE");
	}

	if (_editorBgFile) {
		buffer->putTextIndent(indent + 2, "EDITOR_BG_FILE=\"%s\"\n", _editorBgFile);
		buffer->putTextIndent(indent + 2, "EDITOR_BG_OFFSET_X=%d\n", _editorBgOffsetX);
		buffer->putTextIndent(indent + 2, "EDITOR_BG_OFFSET_Y=%d\n", _editorBgOffsetY);
		buffer->putTextIndent(indent + 2, "EDITOR_BG_ALPHA=%d\n", _editorBgAlpha);
	}

	BaseScriptHolder::saveAsText(buffer, indent + 2);

	// scripts
	for (uint32 i = 0; i < _scripts.size(); i++) {
		buffer->putTextIndent(indent + 2, "SCRIPT=\"%s\"\n", _scripts[i]->_filename);
	}

	for (uint32 i = 0; i < _frames.size(); i++) {
		_frames[i]->saveAsText(buffer, indent + 2);
	}

	buffer->putTextIndent(indent, "}\n\n");

	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
bool BaseSprite::persist(BasePersistenceManager *persistMgr) {
	BaseScriptHolder::persist(persistMgr);

	persistMgr->transferBool(TMEMBER(_canBreak));
	persistMgr->transferBool(TMEMBER(_changed));
	persistMgr->transferBool(TMEMBER(_paused));
	persistMgr->transferBool(TMEMBER(_continuous));
	persistMgr->transferSint32(TMEMBER(_currentFrame));
	persistMgr->transferBool(TMEMBER(_editorAllFrames));
	persistMgr->transferSint32(TMEMBER(_editorBgAlpha));
	persistMgr->transferCharPtr(TMEMBER(_editorBgFile));
	persistMgr->transferSint32(TMEMBER(_editorBgOffsetX));
	persistMgr->transferSint32(TMEMBER(_editorBgOffsetY));
	persistMgr->transferBool(TMEMBER(_editorMuted));
	persistMgr->transferBool(TMEMBER(_finished));

	_frames.persist(persistMgr);

	persistMgr->transferUint32(TMEMBER(_lastFrameTime));
	persistMgr->transferBool(TMEMBER(_looping));
	persistMgr->transferSint32(TMEMBER(_moveX));
	persistMgr->transferSint32(TMEMBER(_moveY));
	persistMgr->transferPtr(TMEMBER_PTR(_owner));
	persistMgr->transferBool(TMEMBER(_precise));
	persistMgr->transferBool(TMEMBER(_streamed));
	persistMgr->transferBool(TMEMBER(_streamedKeepLoaded));


	return STATUS_OK;
}


//////////////////////////////////////////////////////////////////////////
// high level scripting interface
//////////////////////////////////////////////////////////////////////////
bool BaseSprite::scCallMethod(ScScript *script, ScStack *stack, ScStack *thisStack, const char *name) {
	//////////////////////////////////////////////////////////////////////////
	// GetFrame
	//////////////////////////////////////////////////////////////////////////
	if (strcmp(name, "GetFrame") == 0) {
		stack->correctParams(1);
		int index = stack->pop()->getInt(-1);
		if (index < 0 || index >= (int32)_frames.size()) {
			script->runtimeError("Sprite.GetFrame: Frame index %d is out of range.", index);
			stack->pushNULL();
		} else {
			stack->pushNative(_frames[index], true);
		}
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// DeleteFrame
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "DeleteFrame") == 0) {
		stack->correctParams(1);
		ScValue *val = stack->pop();
		if (val->isInt()) {
			int index = val->getInt(-1);
			if (index < 0 || index >= (int32)_frames.size()) {
				script->runtimeError("Sprite.DeleteFrame: Frame index %d is out of range.", index);
			}
		} else {
			BaseFrame *frame = (BaseFrame *)val->getNative();
			for (uint32 i = 0; i < _frames.size(); i++) {
				if (_frames[i] == frame) {
					if (i == (uint32)_currentFrame) {
						_lastFrameTime = 0;
					}
					delete _frames[i];
					_frames.remove_at(i);
					break;
				}
			}
		}
		stack->pushNULL();
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// Reset
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "Reset") == 0) {
		stack->correctParams(0);
		reset();
		stack->pushNULL();
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// AddFrame
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "AddFrame") == 0) {
		stack->correctParams(1);
		ScValue *val = stack->pop();
		const char *filename = nullptr;
		if (!val->isNULL()) {
			filename = val->getString();
		}

		BaseFrame *frame = new BaseFrame(_gameRef);
		if (filename != nullptr) {
			BaseSubFrame *sub = new BaseSubFrame(_gameRef);
			if (DID_SUCCEED(sub->setSurface(filename))) {
				sub->setDefaultRect();
				frame->_subframes.add(sub);
			} else {
				delete sub;
			}
		}
		_frames.add(frame);

		stack->pushNative(frame, true);
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// InsertFrame
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "InsertFrame") == 0) {
		stack->correctParams(2);
		int index = stack->pop()->getInt();
		if (index < 0) {
			index = 0;
		}

		ScValue *val = stack->pop();
		const char *filename = nullptr;
		if (!val->isNULL()) {
			filename = val->getString();
		}

		BaseFrame *frame = new BaseFrame(_gameRef);
		if (filename != nullptr) {
			BaseSubFrame *sub = new BaseSubFrame(_gameRef);
			if (DID_SUCCEED(sub->setSurface(filename))) {
				frame->_subframes.add(sub);
			} else {
				delete sub;
			}
		}

		if (index >= (int32)_frames.size()) {
			_frames.add(frame);
		} else {
			_frames.insert_at(index, frame);
		}

		stack->pushNative(frame, true);
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// Pause
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "Pause") == 0) {
		stack->correctParams(0);
		_paused = true;
		stack->pushNULL();
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// Play
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "Play") == 0) {
		stack->correctParams(0);
		_paused = false;
		stack->pushNULL();
		return STATUS_OK;
	} else {
		return BaseScriptHolder::scCallMethod(script, stack, thisStack, name);
	}
}


//////////////////////////////////////////////////////////////////////////
ScValue *BaseSprite::scGetProperty(const Common::String &name) {
	_scValue->setNULL();

	//////////////////////////////////////////////////////////////////////////
	// Type
	//////////////////////////////////////////////////////////////////////////
	if (name == "Type") {
		_scValue->setString("sprite");
		return _scValue;
	}

	//////////////////////////////////////////////////////////////////////////
	// NumFrames (RO)
	//////////////////////////////////////////////////////////////////////////
	else if (name == "NumFrames") {
		_scValue->setInt(_frames.size());
		return _scValue;
	}

	//////////////////////////////////////////////////////////////////////////
	// CurrentFrame
	//////////////////////////////////////////////////////////////////////////
	else if (name == "CurrentFrame") {
		_scValue->setInt(_currentFrame);
		return _scValue;
	}

	//////////////////////////////////////////////////////////////////////////
	// PixelPerfect
	//////////////////////////////////////////////////////////////////////////
	else if (name == "PixelPerfect") {
		_scValue->setBool(_precise);
		return _scValue;
	}

	//////////////////////////////////////////////////////////////////////////
	// Looping
	//////////////////////////////////////////////////////////////////////////
	else if (name == "Looping") {
		_scValue->setBool(_looping);
		return _scValue;
	}

	//////////////////////////////////////////////////////////////////////////
	// Owner (RO)
	//////////////////////////////////////////////////////////////////////////
	else if (name == "Owner") {
		if (_owner == nullptr) {
			_scValue->setNULL();
		} else {
			_scValue->setNative(_owner, true);
		}
		return _scValue;
	}

	//////////////////////////////////////////////////////////////////////////
	// Finished (RO)
	//////////////////////////////////////////////////////////////////////////
	else if (name == "Finished") {
		_scValue->setBool(_finished);
		return _scValue;
	}

	//////////////////////////////////////////////////////////////////////////
	// Paused (RO)
	//////////////////////////////////////////////////////////////////////////
	else if (name == "Paused") {
		_scValue->setBool(_paused);
		return _scValue;
	} else {
		return BaseScriptHolder::scGetProperty(name);
	}
}


//////////////////////////////////////////////////////////////////////////
bool BaseSprite::scSetProperty(const char *name, ScValue *value) {
	//////////////////////////////////////////////////////////////////////////
	// CurrentFrame
	//////////////////////////////////////////////////////////////////////////
	if (strcmp(name, "CurrentFrame") == 0) {
		_currentFrame = value->getInt(0);
		if (_currentFrame >= (int32)_frames.size() || _currentFrame < 0) {
			_currentFrame = -1;
		}
		_lastFrameTime = 0;
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// PixelPerfect
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "PixelPerfect") == 0) {
		_precise = value->getBool();
		return STATUS_OK;
	}

	//////////////////////////////////////////////////////////////////////////
	// Looping
	//////////////////////////////////////////////////////////////////////////
	else if (strcmp(name, "Looping") == 0) {
		_looping = value->getBool();
		return STATUS_OK;
	} else {
		return BaseScriptHolder::scSetProperty(name, value);
	}
}


//////////////////////////////////////////////////////////////////////////
const char *BaseSprite::scToString() {
	return "[sprite]";
}


//////////////////////////////////////////////////////////////////////////
bool BaseSprite::killAllSounds() {
	for (uint32 i = 0; i < _frames.size(); i++) {
		_frames[i]->stopSound();
	}
	return STATUS_OK;
}

Common::String BaseSprite::debuggerToString() const {
	return Common::String::format("%p: Sprite \"%s\"", (const void *)this, getName());
}
} // End of namespace Wintermute