/* 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 "illusions/actor.h"
#include "illusions/illusions.h"
#include "illusions/resources/actorresource.h"
#include "illusions/dictionary.h"

namespace Illusions {

// ActorResourceLoader

void ActorResourceLoader::load(Resource *resource) {
	resource->_instance = _vm->_actorInstances->createActorInstance(resource);
}

bool ActorResourceLoader::isFlag(int flag) {
	return
		flag == kRlfLoadFile;
}

// Frame

void Frame::load(byte *dataStart, Common::SeekableReadStream &stream) {
	_flags = stream.readUint16LE();
	stream.skip(2); // Skip padding
	uint32 pointsConfigOffs = stream.readUint32LE();
	_surfInfo.load(stream);
	uint32 compressedPixelsOffs = stream.readUint32LE();
	_compressedPixels = dataStart + compressedPixelsOffs;
	_pointsConfig = dataStart + pointsConfigOffs;
	debug(5, "Frame::load() compressedPixelsOffs: %08X",
		compressedPixelsOffs);
}

// Sequence

void Sequence::load(byte *dataStart, Common::SeekableReadStream &stream) {
	_sequenceId = stream.readUint32LE();
	_unk4 = stream.readUint32LE();
	uint32 sequenceCodeOffs = stream.readUint32LE();
	_sequenceCode = dataStart + sequenceCodeOffs;
	debug(5, "Sequence::load() _sequenceId: %08X; _unk4: %d; sequenceCodeOffs: %08X",
		_sequenceId, _unk4, sequenceCodeOffs);
}

// ActorType

void ActorType::load(byte *dataStart, Common::SeekableReadStream &stream) {
	_actorTypeId = stream.readUint32LE();
	_surfInfo.load(stream);
	uint32 pointsConfigOffs = stream.readUint32LE();
	uint namedPointsCount = stream.readUint16LE();
	stream.skip(2); // Skip padding
	uint32 namedPointsOffs = stream.readUint32LE();
	_color.r = stream.readByte();
	_color.g = stream.readByte();
	_color.b = stream.readByte();
	stream.readByte(); // Skip padding
	_scale = stream.readByte();
	_priority = stream.readByte();
	_value1E = stream.readUint16LE();
	_pathWalkPointsIndex = stream.readUint16LE();
	_scaleLayerIndex = stream.readUint16LE();
	_pathWalkRectIndex = stream.readUint16LE();
	_priorityLayerIndex = stream.readUint16LE();
	_regionLayerIndex = stream.readUint16LE();
	_flags = stream.readUint16LE();
	_pointsConfig = dataStart + pointsConfigOffs;
	stream.seek(namedPointsOffs);
	_namedPoints.load(namedPointsCount, stream);
	debug(5, "ActorType::load() _actorTypeId: %08X; _color(%d,%d,%d); _scale: %d; _priority: %d; _value1E: %d",
		_actorTypeId, _color.r, _color.g, _color.b, _scale, _priority, _value1E);
	debug(5, "ActorType::load() _pathWalkPointsIndex: %d; _scaleLayerIndex: %d; _pathWalkRectIndex: %d",
		_pathWalkPointsIndex, _scaleLayerIndex, _pathWalkRectIndex);
	debug(5, "ActorType::load() _priorityLayerIndex: %d; _regionLayerIndex: %d; _flags: %04X",
		_priorityLayerIndex, _regionLayerIndex,_flags);
}

// ActorResource

ActorResource::ActorResource() {
}

ActorResource::~ActorResource() {
}

void ActorResource::load(Resource *resource) {
	byte *data = resource->_data;
	uint32 dataSize = resource->_dataSize;
	Common::MemoryReadStream stream(data, dataSize, DisposeAfterUse::NO);

	_totalSize = stream.readUint32LE();

	// Load actor types
	stream.seek(0x06);
	uint actorTypesCount = stream.readUint16LE();
	stream.seek(0x10);
	uint32 actorTypesOffs = stream.readUint32LE();
	_actorTypes.reserve(actorTypesCount);
	for (uint i = 0; i < actorTypesCount; ++i) {
		ActorType actorType;
		stream.seek(actorTypesOffs + i * 0x2C);
		actorType.load(data, stream);
		_actorTypes.push_back(actorType);
	}

	// Load sequences
	stream.seek(0x08);
	uint sequencesCount = stream.readUint16LE();
	stream.seek(0x14);
	uint32 sequencesOffs = stream.readUint32LE();
	stream.seek(sequencesOffs);
	_sequences.reserve(sequencesCount);
	for (uint i = 0; i < sequencesCount; ++i) {
		Sequence sequence;
		sequence.load(data, stream);
		_sequences.push_back(sequence);
	}

	// Load frames
	stream.seek(0x0A);
	uint framesCount = stream.readUint16LE();
	stream.seek(0x18);
	uint32 framesOffs = stream.readUint32LE();
	stream.seek(framesOffs);
	_frames.reserve(framesCount);
	for (uint i = 0; i < framesCount; ++i) {
		Frame frame;
		frame.load(data, stream);
		_frames.push_back(frame);
	}

	// Load named points
	if (resource->_gameId == kGameIdBBDOU) {
		// The count isn't stored explicitly so calculate it
		uint namedPointsCount = (actorTypesOffs - 0x20) / 8;
		stream.seek(0x20);
		_namedPoints.load(namedPointsCount, stream);
	}

	debug(1, "ActorResource(%08X) framesCount: %d", resource->_resId, framesCount);
}

bool ActorResource::containsSequence(Sequence *sequence) {
	for (uint i = 0; i < _sequences.size(); ++i) {
		if (sequence == &_sequences[i])
			return true;
	}
	return false;
}

bool ActorResource::findNamedPoint(uint32 namedPointId, Common::Point &pt) {
	return _namedPoints.findNamedPoint(namedPointId, pt);
}

// ActorInstance

ActorInstance::ActorInstance(IllusionsEngine *vm)
	: _vm(vm) {
}

void ActorInstance::load(Resource *resource) {
	_actorResource = new ActorResource();
	_actorResource->load(resource);
	_sceneId = resource->_sceneId;
	_pauseCtr = 0;
	initActorTypes(resource->_gameId);
}

void ActorInstance::unload() {
	if (_pauseCtr <= 0)
		unregisterResources();
	_vm->_actorInstances->removeActorInstance(this);
	delete _actorResource;
}

void ActorInstance::pause() {
	++_pauseCtr;
	if (_pauseCtr == 1)
		unregisterResources();
}

void ActorInstance::unpause() {
	--_pauseCtr;
	if (_pauseCtr == 0)
		registerResources();
}

void ActorInstance::initActorTypes(int gameId) {
	for (uint i = 0; i < _actorResource->_actorTypes.size(); ++i) {
		ActorType *actorType = &_actorResource->_actorTypes[i];
		ActorType *actorType2 = _vm->_dict->findActorType(actorType->_actorTypeId);
		if (actorType2) {
			actorType->_surfInfo._dimensions._width = MAX(actorType->_surfInfo._dimensions._width,
				actorType2->_surfInfo._dimensions._width);
			actorType->_surfInfo._dimensions._height = MAX(actorType->_surfInfo._dimensions._height,
				actorType2->_surfInfo._dimensions._height);
			if (actorType->_color.r == 255 && actorType->_color.g == 255 && actorType->_color.b == 255)
				actorType->_color = actorType2->_color;
			if (actorType->_value1E == 0)
				actorType->_value1E = actorType2->_value1E;
		}
		_vm->_dict->addActorType(actorType->_actorTypeId, actorType);
	}
	for (uint i = 0; i < _actorResource->_sequences.size(); ++i) {
		Sequence *sequence = &_actorResource->_sequences[i];
		_vm->_dict->addSequence(sequence->_sequenceId, sequence);
		if (gameId == kGameIdDuckman && sequence->_sequenceId == 0x60101) {
			// TODO check that this is the correct location for this logic.
			_vm->_controls->placeActor(0x50023, Common::Point(0,0), sequence->_sequenceId, 0x400d7, 0);
		}
	}
}

void ActorInstance::registerResources() {
	for (uint i = 0; i < _actorResource->_actorTypes.size(); ++i) {
		ActorType *actorType = &_actorResource->_actorTypes[i];
		_vm->_dict->addActorType(actorType->_actorTypeId, actorType);
	}
	for (uint i = 0; i < _actorResource->_sequences.size(); ++i) {
		Sequence *sequence = &_actorResource->_sequences[i];
		_vm->_dict->addSequence(sequence->_sequenceId, sequence);
	}
}

void ActorInstance::unregisterResources() {
	for (uint i = 0; i < _actorResource->_actorTypes.size(); ++i) {
		_vm->_dict->removeActorType(_actorResource->_actorTypes[i]._actorTypeId);
	}
	for (uint i = 0; i < _actorResource->_sequences.size(); ++i) {
		_vm->_dict->removeSequence(_actorResource->_sequences[i]._sequenceId);
	}
}

// ActorInstanceList

ActorInstanceList::ActorInstanceList(IllusionsEngine *vm)
	: _vm(vm) {
}

ActorInstanceList::~ActorInstanceList() {
}

ActorInstance *ActorInstanceList::createActorInstance(Resource *resource) {
	ActorInstance *actorInstance = new ActorInstance(_vm);
	actorInstance->load(resource);
	_items.push_back(actorInstance);
	return actorInstance;
}

void ActorInstanceList::removeActorInstance(ActorInstance *actorInstance) {
	_items.remove(actorInstance);
}

void ActorInstanceList::pauseBySceneId(uint32 sceneId) {
	for (auto &it : _items) {
		if (it->_sceneId == sceneId)
			it->pause();
	}
}

void ActorInstanceList::unpauseBySceneId(uint32 sceneId) {
	for (auto &it : _items) {
		if (it->_sceneId == sceneId)
			it->unpause();
	}
}

FramesList *ActorInstanceList::findSequenceFrames(Sequence *sequence) {
	for (auto &actorInstance : _items) {
		if (actorInstance->_pauseCtr <= 0 && actorInstance->_actorResource->containsSequence(sequence))
			return &actorInstance->_actorResource->_frames;
	}
	return nullptr;
}

ActorInstance *ActorInstanceList::findActorByResource(ActorResource *actorResource) {
	for (auto &it : _items) {
		if (it->_actorResource == actorResource)
			return (it);
	}
	return nullptr;
}

bool ActorInstanceList::findNamedPoint(uint32 namedPointId, Common::Point &pt) {
	for (auto &actorInstance : _items) {
		if (actorInstance->_pauseCtr == 0 && actorInstance->_actorResource->findNamedPoint(namedPointId, pt))
			return true;
	}
	return false;
}

} // End of namespace Illusions
