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

/*
 * Based on
 * WebVenture (c) 2010, Sean Kasun
 * https://github.com/mrkite/webventure, http://seancode.com/webventure/
 *
 * Used with explicit permission from the author
 */

#include "macventure/world.h"
#include "macventure/macventure.h"

#include "common/file.h"

namespace MacVenture {

World::World(MacVentureEngine *engine, Common::MacResManager *resMan) {
	_resourceManager = resMan;
	_engine = engine;
	_saveGame = NULL;
	_gameText = NULL;

	startNewGame();

	_objectConstants = new Container(_engine->getFilePath(kObjectPathID));
	calculateObjectRelations();

	_gameText = new Container(_engine->getFilePath(kTextPathID));
}


World::~World()	{

	if (_saveGame)
		delete _saveGame;

	if (_objectConstants)
		delete _objectConstants;

	if (_gameText)
		delete _gameText;
}

void World::startNewGame() {
	if (_saveGame)
		delete _saveGame;

	if ((_startGameFileName = _engine->getStartGameFileName()) == "")
		error("WORLD: Could not load initial game configuration");

	Common::File saveGameFile;
	if (!saveGameFile.open(_startGameFileName))
		error("WORLD: Could not load initial game configuration");

	debugC(2, kMVDebugMain, "Loading save game state from %s", _startGameFileName.c_str());
	Common::SeekableReadStream *saveGameRes = saveGameFile.readStream(saveGameFile.size());

	_saveGame = new SaveGame(_engine, saveGameRes);

	calculateObjectRelations();

	delete saveGameRes;
	saveGameFile.close();
}

uint32 World::getObjAttr(ObjID objID, uint32 attrID) {
	uint res;
	uint32 index = _engine->getGlobalSettings()._attrIndices[attrID];
	// HACK, but if I try to initialize it in the else clause, it goes out of scope and segfaults
	Common::SeekableReadStream *objStream = _objectConstants->getItem(objID);
	if (!(index & 0x80)) { // It's not a constant
		res = _saveGame->getAttr(objID, index);
	} else {
		index &= 0x7F;
		if (objStream->size() == 0) {
			return 0;
		}
		// Look for the right attribute inside the object
		objStream->skip(index * 2);
		res = objStream->readByte() << 8;
		res |= objStream->readByte();
	}
	res &= _engine->getGlobalSettings()._attrMasks[attrID];
	res >>= _engine->getGlobalSettings()._attrShifts[attrID];
	if (res & 0x8000)
		res = -((res ^ 0xffff) + 1);
	debugC(5, kMVDebugMain, "Attribute %x from object %x is %x", attrID, objID, res);
	delete objStream;
	return res;
}

void World::setObjAttr(ObjID objID, uint32 attrID, Attribute value) {
	if (attrID == kAttrPosX || attrID == kAttrPosY) {
		// Round to scale
		// Intentionally empty, we don't seem to require this functionality
	}

	if (attrID == kAttrParentObject)
		setParent(objID, value);

	if (attrID < kAttrOtherDoor)
		_engine->enqueueObject(kUpdateObject, objID);

	uint32 idx = _engine->getGlobalSettings()._attrIndices[attrID];
	value <<= _engine->getGlobalSettings()._attrShifts[attrID];
	value &= _engine->getGlobalSettings()._attrMasks[attrID];
	Attribute oldVal = _saveGame->getAttr(objID, idx);
	oldVal &= ~_engine->getGlobalSettings()._attrMasks[attrID];
	_saveGame->setAttr(idx, objID, (value | oldVal));
	_engine->gameChanged();
}

bool MacVenture::World::isObjActive(ObjID obj) {
	ObjID destObj = _engine->getDestObject();
	Common::Point p = _engine->getDeltaPoint();
	ControlAction selectedControl = _engine->getSelectedControl();
	if (!getAncestor(obj)) {
		return false; // If our ancestor is the garbage (obj 0), we're inactive
	}
	if (_engine->getInvolvedObjects() >= 2 &&	// If (we need > 1 objs for the command) &&
		destObj > 0 &&			// we have a destination object &&
		!getAncestor(destObj)) {	// but that destination object is in the garbage
		return false;
	}
	if (selectedControl != kMoveObject) {
		return true; // We only need one
	}
	// Handle move object
	if (!isObjDraggable(obj)) {
		return false; // We can't move it
	}
	if (getObjAttr(1, kAttrParentObject) != destObj) {
		return true; // if the target is not the player's parent, we can go
	}
	Common::Rect rect(kScreenWidth, kScreenHeight);
	rect.top -= getObjAttr(obj, kAttrPosY) + p.y;
	rect.left -= getObjAttr(obj, kAttrPosX) + p.x;
	return intersects(obj, rect);
}

ObjID World::getAncestor(ObjID objID) {
	ObjID root = getObjAttr(1, kAttrParentObject);
	while (objID != 0 && objID != 1 && objID != root) {
		objID = getObjAttr(objID, kAttrParentObject);
	}
	return objID;
}

Common::Array<ObjID> World::getFamily(ObjID objID, bool recursive) {
	Common::Array<ObjID> res;
	res.push_back(objID);
	res.push_back(getChildren(objID, recursive));
	return res;
}

Common::Array<ObjID> World::getChildren(ObjID objID, bool recursive) {
	Common::Array<ObjID> res;
	ObjID child = _relations[objID * 2];
	while (child) {
		res.push_back(child);
		if (!recursive)
			res.push_back(getChildren(child, false));
		child = _relations[child * 2 + 1];
	}
	return res;
}

Attribute World::getGlobal(uint32 attrID) {
	return _saveGame->getGlobals()[attrID];
}

void World::setGlobal(uint32 attrID, Attribute value) {
	_saveGame->setGlobal(attrID, value);
}

void World::updateObj(ObjID objID) {
	WindowReference win;
	if (getObjAttr(1, kAttrParentObject) == objID) {
		win = kMainGameWindow;
	} else {
		win = _engine->getObjWindow(objID);
	}
	if (win) {
		_engine->focusObjWin(objID);
		_engine->runObjQueue();
		_engine->updateWindow(win);
	}
}

void World::captureChildren(ObjID objID) {
	warning("Capture children unimplemented!");
}

void World::releaseChildren(ObjID objID) {
	warning("Release children unimplemented!");
}

Common::String World::getText(ObjID objID, ObjID source, ObjID target) {
	if (objID & 0x8000) {
		return _engine->getUserInput();
	}
	TextAsset text = TextAsset(_engine, objID, source, target, _gameText, _engine->isOldText(), _engine->getDecodingHuffman());

	return *text.decode();
}


bool World::isObjDraggable(ObjID objID) {
	return (getObjAttr(objID, kAttrInvisible) == 0 &&
			getObjAttr(objID, kAttrUnclickable) == 0 &&
			getObjAttr(objID, kAttrUndraggable) == 0);
}

bool World::intersects(ObjID objID, Common::Rect rect) {
	return _engine->getObjBounds(objID).intersects(rect);
}

void World::calculateObjectRelations() {
	_relations.clear();
	ObjID val, next;
	uint32 numObjs = _engine->getGlobalSettings()._numObjects;
	const AttributeGroup &parents = *_saveGame->getGroup(0);
	for (uint i = 0; i < numObjs * 2; i++) {
		_relations.push_back(0);
	}
	for (uint i = numObjs - 1; i > 0; i--) {
		val = parents[i];
		next = _relations[val * 2];
		if (next) {
			_relations[i * 2 + 1] = next;
		}
		_relations[val * 2] = i;
	}
}

void World::setParent(ObjID child, ObjID newParent) {
	ObjID old = _saveGame->getAttr(child, kAttrParentObject);
	if (newParent == child)
		return;

	ObjID oldNdx = old * 2;
	old = _relations[oldNdx];
	while (old != child) {
		oldNdx = (old * 2) + 1;
		old = _relations[oldNdx];
	}
	_relations[oldNdx] = _relations[(old * 2) + 1];
	oldNdx = newParent * 2;
	old = _relations[oldNdx];
	while (old && old <= child) {
		oldNdx = (old * 2) + 1;
		old = _relations[oldNdx];
	}
	_relations[child * 2 + 1] = old;
	_relations[oldNdx] = child;
}

void World::loadGameFrom(Common::InSaveFile *file) {
	if (_saveGame) {
		delete _saveGame;
	}
	_saveGame = new SaveGame(_engine, file);
	calculateObjectRelations();
}

void World::saveGameInto(Common::OutSaveFile *file) {
	_saveGame->saveInto(file);
}

// SaveGame
SaveGame::SaveGame(MacVentureEngine *engine, Common::SeekableReadStream *res) {
	_groups = Common::Array<AttributeGroup>();
	loadGroups(engine, res);
	_globals = Common::Array<uint16>();
	loadGlobals(engine, res);
	_text = Common::String();
	loadText(engine, res);
}

SaveGame::~SaveGame() {
}


Attribute SaveGame::getAttr(ObjID objID, uint32 attrID) {
	return _groups[attrID][objID];
}

void SaveGame::setAttr(uint32 attrID, ObjID objID, Attribute value) {
	_groups[attrID][objID] = value;
}

const Common::Array<AttributeGroup> &MacVenture::SaveGame::getGroups() {
	return _groups;
}

const AttributeGroup *SaveGame::getGroup(uint32 groupID) {
	assert(groupID < _groups.size());
	return &(_groups[groupID]);
}

void SaveGame::setGlobal(uint32 attrID, Attribute value) {
	_globals[attrID] = value;
}

const Common::Array<uint16> &SaveGame::getGlobals() {
	return _globals;
}

const Common::String &SaveGame::getText() {
	return _text;
}

void SaveGame::saveInto(Common::OutSaveFile *file) {
	warning("Saving the game not yet tested!");
	// Save attibutes
	Common::Array<AttributeGroup>::const_iterator itg;
	for (itg = _groups.begin(); itg != _groups.end(); itg++) {
		Common::Array<Attribute>::const_iterator ita;
		for (ita = itg->begin(); ita != itg->end(); ita++) {
			file->writeUint16BE((*ita));
		}
	}
	// Save globals
	Common::Array<uint16>::const_iterator global;
	for (global = _globals.begin(); global != _globals.end(); global++) {
		file->writeUint16BE((*global));
	}
	// Save text
	// TODO: Insert text from GUI console
	_text = "Hello";
	file->write(_text.c_str(), _text.size());
}

void SaveGame::loadGroups(MacVentureEngine *engine, Common::SeekableReadStream *res) {
	GlobalSettings settings = engine->getGlobalSettings();
	for (int i = 0; i < settings._numGroups; ++i) {
		AttributeGroup g;
		for (int j = 0; j < settings._numObjects; ++j) {
			g.push_back(res->readUint16BE());
		}

		_groups.push_back(g);
	}
}

void SaveGame::loadGlobals(MacVentureEngine *engine, Common::SeekableReadStream *res) {
	GlobalSettings settings = engine->getGlobalSettings();
	for (int i = 0; i < settings._numGlobals; ++i) {
		_globals.push_back(res->readUint16BE());
	}
}

void SaveGame::loadText(MacVentureEngine *engine, Common::SeekableReadStream *res) {
	// TODO: Load console text. For now, the GUI doesn't even look at this.
	_text = "Placeholder Console Text";
}


} // End of namespace MacVenture