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

#include "mohawk/myst_areas.h"
#include "mohawk/myst_graphics.h"

#include "mohawk/resource.h"

namespace Mohawk {

MystCard::MystCard(MohawkEngine_Myst *vm, uint16 id) :
		_vm(vm),
		_id(id),
		_hoverResource(nullptr),
		_activeResource(nullptr),
		_clickedResource(nullptr) {

	loadView();
	loadResources();
	loadCursorHints();
}

void MystCard::enter() {
	// Handle images
	drawBackground();

	// Handle sound
	_vm->applySoundBlock(_soundBlock);

	if (_flags & kMystZipDestination)
		_vm->_gameState->addZipDest(_vm->_stack->getStackId(), _id);

	// Run the entrance script (if present)
	runInitScript();

	// Update the images of each area too
	drawResourceImages();

	for (uint16 i = 0; i < _resources.size(); i++)
		_resources[i]->handleCardChange();
}

void MystCard::leave() {
	runExitScript();
}

MystCard::~MystCard() {
	for (uint32 i = 0; i < _resources.size(); i++)
		delete _resources[i];
}

uint16 MystCard::getId() const {
	return _id;
}

void MystCard::loadView() {
	debugC(kDebugView, "Loading Card View: %d", _id);

	Common::SeekableReadStream *viewStream = _vm->getResource(ID_VIEW, _id);

	// Card Flags
	_flags = viewStream->readUint16LE();
	debugC(kDebugView, "Flags: 0x%04X", _flags);

	// The Image Block (Reminiscent of Riven PLST resources)
	uint16 conditionalImageCount = viewStream->readUint16LE();
	debugC(kDebugView, "Conditional Image Count: %d", conditionalImageCount);
	if (conditionalImageCount != 0) {
		for (uint16 i = 0; i < conditionalImageCount; i++) {
			MystCondition conditionalImage;

			debugC(kDebugView, "\tImage %d:", i);
			conditionalImage.var = viewStream->readUint16LE();
			debugC(kDebugView, "\t\tVar: %d", conditionalImage.var);
			uint16 numStates = viewStream->readUint16LE();
			debugC(kDebugView, "\t\tNumber of States: %d", numStates);
			for (uint16 j = 0; j < numStates; j++) {
				conditionalImage.values.push_back(viewStream->readUint16LE());
				debugC(kDebugView, "\t\tState %d -> Value %d", j, conditionalImage.values[j]);
			}

			_conditionalImages.push_back(conditionalImage);
		}
		_mainImage = 0;
	} else {
		_mainImage = viewStream->readUint16LE();
		debugC(kDebugView, "Main Image: %d", _mainImage);
	}

	// The Sound Block (Reminiscent of Riven SLST resources)
	_soundBlock = _vm->readSoundBlock(viewStream);

	// Resources that scripts can call upon
	uint16 scriptResCount = viewStream->readUint16LE();
	debugC(kDebugView, "Script Resource Count: %d", scriptResCount);
	for (uint16 i = 0; i < scriptResCount; i++) {
		ScriptResource scriptResource;

		debugC(kDebugView, "\tResource %d:", i);
		scriptResource.type = (ScriptResourceType) viewStream->readUint16LE();
		debugC(kDebugView, "\t\t Type: %d", scriptResource.type);

		switch (scriptResource.type) {
			case kResourceImage:
				debugC(kDebugView, "\t\t\t\t= Image");
				break;
			case kResourceSound:
				debugC(kDebugView, "\t\t\t\t= Sound");
				break;
			case kResourceSwitch:
				debugC(kDebugView, "\t\t\t\t= Resource Switch");
				break;
			case kResourceImageNoCache:
				debugC(kDebugView, "\t\t\t\t= Image - Caching disabled");
				break;
			case kResourceSoundNoCache:
				debugC(kDebugView, "\t\t\t\t= Sound - Caching disabled");
				break;
			default:
				debugC(kDebugView, "\t\t\t\t= Unknown");
				warning("Unknown script resource type '%d' in card '%d'", scriptResource.type, _id);
				break;
		}

		if (scriptResource.type == kResourceSwitch) {
			scriptResource.switchVar = viewStream->readUint16LE();
			debugC(kDebugView, "\t\t Var: %d", scriptResource.switchVar);
			uint16 count = viewStream->readUint16LE();
			debugC(kDebugView, "\t\t Resource List Count: %d", count);
			scriptResource.switchResourceType = (ScriptResourceType) viewStream->readUint16LE();
			debugC(kDebugView, "\t\t u0: %d", scriptResource.switchResourceType);

			for (uint16 j = 0; j < count; j++) {
				scriptResource.switchResourceIds.push_back(viewStream->readSint16LE());
				debugC(kDebugView, "\t\t Resource List %d: %d", j, scriptResource.switchResourceIds[j]);
			}
		} else {
			scriptResource.id = viewStream->readUint16LE();
			debugC(kDebugView, "\t\t Id: %d", scriptResource.id);
		}

		_scriptResources.push_back(scriptResource);
	}

	// Identifiers for other resources. 0 if non existent. There is always an RLST.
	_resourceListId = viewStream->readUint16LE();
	if (!_resourceListId)
		error("RLST Index missing");

	_hintResourceId = viewStream->readUint16LE();
	_initScriptId = viewStream->readUint16LE();
	_exitScriptId = viewStream->readUint16LE();

	delete viewStream;

	// Precache Card Resources
	uint32 cacheImageType;
	if (_vm->getFeatures() & GF_ME)
		cacheImageType = ID_PICT;
	else
		cacheImageType = ID_WDIB;

	// Precache Image Block data
	if (!_conditionalImages.empty()) {
		for (uint16 i = 0; i < _conditionalImages.size(); i++) {
			uint16 value = _vm->_stack->getVar(_conditionalImages[i].var);
			_vm->cachePreload(cacheImageType, _conditionalImages[i].values[value]);
		}
	} else {
		_vm->cachePreload(cacheImageType, _mainImage);
	}

	// Precache Sound Block data
	if (_soundBlock.sound > 0)
		_vm->cachePreload(ID_MSND, _soundBlock.sound);
	else if (_soundBlock.sound == kMystSoundActionConditional) {
		uint16 value = _vm->_stack->getVar(_soundBlock.soundVar);
		if (_soundBlock.soundList[value].action > 0) {
			_vm->cachePreload(ID_MSND, _soundBlock.soundList[value].action);
		}
	}

	// Precache Script Resources
	for (uint16 i = 0; i < _scriptResources.size(); i++) {
		ScriptResourceType type;
		int16 id;
		if (_scriptResources[i].type == kResourceSwitch) {
			type = _scriptResources[i].switchResourceType;
			uint16 value = _vm->_stack->getVar(_scriptResources[i].switchVar);
			id = _scriptResources[i].switchResourceIds[value];
		} else {
			type = _scriptResources[i].type;
			id = _scriptResources[i].id;
		}

		if (id < 0) continue;

		switch (type) {
			case kResourceImage:
				_vm->cachePreload(cacheImageType, id);
				break;
			case kResourceSound:
				_vm->cachePreload(ID_MSND, id);
				break;
			default:
				// The other resource types should not be cached
				break;
		}
	}
}

void MystCard::loadCursorHints() {
	if (!_hintResourceId) {
		debugC(kDebugHint, "No HINT Present");
		return;
	}

	debugC(kDebugHint, "Loading Cursor Hints:");

	Common::SeekableReadStream *hintStream = _vm->getResource(ID_HINT, _id);
	uint16 cursorHintCount = hintStream->readUint16LE();
	debugC(kDebugHint, "Cursor Hint Count: %d", cursorHintCount);

	for (uint16 i = 0; i < cursorHintCount; i++) {
		MystCursorHint hint;

		debugC(kDebugHint, "Cursor Hint %d:", i);
		hint.id = hintStream->readUint16LE();
		debugC(kDebugHint, "\tId: %d", hint.id);
		hint.cursor = hintStream->readSint16LE();
		debugC(kDebugHint, "\tCursor: %d", hint.cursor);

		if (hint.cursor == -1) {
			debugC(kDebugHint, "\tConditional Cursor Hints:");
			hint.variableHint.var = hintStream->readUint16LE();
			debugC(kDebugHint, "\tVar: %d", hint.variableHint.var);
			uint16 numStates = hintStream->readUint16LE();
			debugC(kDebugHint, "\tNumber of States: %d", numStates);
			for (uint16 j = 0; j < numStates; j++) {
				hint.variableHint.values.push_back(hintStream->readUint16LE());
				debugC(kDebugHint, "\t\t State %d: Cursor %d", j, hint.variableHint.values[j]);
			}
		} else {
			hint.variableHint.var = 0;
		}

		_cursorHints.push_back(hint);
	}

	delete hintStream;
}

void MystCard::loadResources() {
	if (!_resourceListId) {
		debugC(kDebugResource, "No RLST present");
		return;
	}

	Common::SeekableReadStream *rlstStream = _vm->getResource(ID_RLST, _resourceListId);
	uint16 resourceCount = rlstStream->readUint16LE();
	debugC(kDebugResource, "RLST Resource Count: %d", resourceCount);

	for (uint16 i = 0; i < resourceCount; i++) {
		debugC(kDebugResource, "Resource #%d:", i);
		_resources.push_back(_vm->loadResource(rlstStream, nullptr));
	}

	delete rlstStream;
}

uint16 MystCard::getBackgroundImageId() {
	uint16 imageToDraw = 0;

	if (_conditionalImages.empty())
		imageToDraw = _mainImage;
	else {
		for (uint16 i = 0; i < _conditionalImages.size(); i++) {
			uint16 varValue = _vm->_stack->getVar(_conditionalImages[i].var);
			if (varValue < _conditionalImages[i].values.size())
				imageToDraw = _conditionalImages[i].values[varValue];
		}
	}

	return imageToDraw;
}

void MystCard::drawBackground() {
	_vm->_gfx->copyImageToBackBuffer(getBackgroundImageId(), Common::Rect(0, 0, 544, 332));
}

void MystCard::runInitScript() {
	if (!_initScriptId) {
		debugC(kDebugINIT, "No INIT Present");
		return;
	}

	debugC(kDebugINIT, "Running INIT script");

	Common::SeekableReadStream *initStream = _vm->getResource(ID_INIT, _initScriptId);
	MystScript script = _vm->_stack->readScript(initStream, kMystScriptInit);
	delete initStream;

	_vm->_stack->runScript(script);
}

void MystCard::runExitScript() {
	if (!_exitScriptId) {
		debugC(kDebugEXIT, "No EXIT Present");
		return;
	}

	debugC(kDebugEXIT, "Running EXIT script");

	Common::SeekableReadStream *exitStream = _vm->getResource(ID_EXIT, _exitScriptId);
	MystScript script = _vm->_stack->readScript(exitStream, kMystScriptExit);
	delete exitStream;

	_vm->_stack->runScript(script);
}

void MystCard::drawResourceRects() {
	for (uint16 i = 0; i < _resources.size(); i++) {
		_resources[i]->getRect().debugPrint(0);
		_resources[i]->drawBoundingRect();
	}
}

void MystCard::updateActiveResource(const Common::Point &mouse) {
	_activeResource = nullptr;
	for (uint16 i = 0; i < _resources.size(); i++) {
		if (_resources[i]->contains(mouse) && _resources[i]->canBecomeActive()) {
			_activeResource = _resources[i];
			break;
		}
	}
}

MystArea *MystCard::forceUpdateClickedResource(const Common::Point &mouse) {
	updateActiveResource(mouse);

	_clickedResource = _activeResource;

	return _clickedResource;
}

void MystCard::updateResourcesForInput(const Common::Point &mouse, bool mouseClicked, bool mouseMoved) {
	// Tell previous resource the mouse is no longer hovering it
	if (_hoverResource && !_hoverResource->contains(mouse)) {
		_hoverResource->handleMouseLeave();
		_hoverResource = nullptr;
	}

	for (uint16 i = 0; i < _resources.size(); i++) {
		if (_resources[i]->contains(mouse) && _resources[i]->hasType(kMystAreaHover)
		    && _hoverResource != _resources[i]) {
			_hoverResource = static_cast<MystAreaHover *>(_resources[i]);
			_hoverResource->handleMouseEnter();
		}
	}

	if (!mouseClicked && _clickedResource) {
		if (_clickedResource->isEnabled()) {
			_clickedResource->handleMouseUp();
		}
		_clickedResource = nullptr;
	} else if (mouseMoved && _clickedResource) {
		if (_clickedResource->isEnabled()) {
			_clickedResource->handleMouseDrag();
		}
	} else if (mouseClicked && !_clickedResource) {
		if (_activeResource && _activeResource->isEnabled()) {
			_clickedResource = _activeResource;
			_clickedResource->handleMouseDown();
		}
	}
}

int16 MystCard::getActiveResourceCursor() {
	if (!_hintResourceId) {
		// Default to the main cursor when no hints are present
		return -1;
	}

	// Check all the cursor hints to see if we're in a hotspot that contains a hint.
	for (uint16 i = 0; i < _cursorHints.size(); i++) {
		if (_activeResource && _resources[_cursorHints[i].id] == _activeResource && _activeResource->isEnabled()) {
			if (_cursorHints[i].cursor == -1) {
				uint16 var_value = _vm->_stack->getVar(_cursorHints[i].variableHint.var);

				if (var_value >= _cursorHints[i].variableHint.values.size())
					warning("Variable %d Out of Range in variable HINT Resource %d", _cursorHints[i].variableHint.var,
					        i);
				else {
					uint16 cursor = _cursorHints[i].variableHint.values[var_value];
					if (cursor == 0)
						return -1;
					else
						return cursor;
				}
			} else {
				if (_cursorHints[i].cursor == 0)
					return -1;
				else
					return _cursorHints[i].cursor;
			}
		}
	}

	return -1;
}

void MystCard::setResourceEnabled(uint16 resourceIndex, bool enable) {
	if (resourceIndex < _resources.size()) {
		_resources[resourceIndex]->setEnabled(enable);
	} else
		warning("Attempt to change unknown resource enable state");
}

void MystCard::drawResourceImages() {
	for (uint16 i = 0; i < _resources.size(); i++)
		if (_resources[i]->isDrawSubimages())
			_resources[i]->drawDataToScreen();
}

void MystCard::redrawArea(uint16 var, bool updateScreen) {
	for (uint16 i = 0; i < _resources.size(); i++)
		if (_resources[i]->hasType(kMystAreaImageSwitch) && _resources[i]->getImageSwitchVar() == var)
			_vm->redrawResource(static_cast<MystAreaImageSwitch *>(_resources[i]), updateScreen);
}

bool MystCard::isDraggingResource() const {
	return _clickedResource != nullptr;
}

void MystCard::resetClickedResource() {
	_clickedResource = nullptr;
}

} // End of namespace Mohawk