/* 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 "common/file.h"
#include "common/textconsole.h"

#include "agos/intern.h"
#include "agos/agos.h"
#include "agos/vga.h"

namespace AGOS {

Child *AGOSEngine::allocateChildBlock(Item *i, uint type, uint size) {
	Child *child = (Child *)allocateItem(size);
	child->next = i->children;
	i->children = child;
	child->type = type;
	return child;
}

void *AGOSEngine::allocateItem(uint size) {
	byte *item = new byte[size]();
	_itemHeap.push_back(item);
	return item;
}

void AGOSEngine::allocItemHeap() {
	_itemHeap.clear();
}

bool AGOSEngine_Elvira2::hasIcon(Item *item) {
	SubObject *child = (SubObject *)findChildOfType(item, kObjectType);
	return (child && (child->objectFlags & kOFIcon) != 0);
}

bool AGOSEngine::hasIcon(Item *item) {
	return (getUserFlag(item, 7) != 0);
}

uint AGOSEngine_Elvira2::itemGetIconNumber(Item *item) {
	SubObject *child = (SubObject *)findChildOfType(item, kObjectType);
	uint offs;

	if (child == nullptr || !(child->objectFlags & kOFIcon))
		return 0;

	offs = getOffsetOfChild2Param(child, 0x10);
	return child->objectFlagValue[offs];
}

uint AGOSEngine::itemGetIconNumber(Item *item) {
	return getUserFlag(item, 7);
}

void AGOSEngine::setItemState(Item *item, int value) {
	item->state = value;
}

void AGOSEngine::createPlayer() {
	SubPlayer *p;

	_currentPlayer = _itemArrayPtr[1];
	_currentPlayer->adjective = -1;
	_currentPlayer->noun = 10000;

	p = (SubPlayer *)allocateChildBlock(_currentPlayer, kPlayerType, sizeof(SubPlayer));
	if (p == nullptr)
		error("createPlayer: player create failure");

	p->size = 0;
	p->weight = 0;
	p->strength = 6000;
	p->flags = 1; // Male
	p->level = 1;
	p->score = 0;

	setUserFlag(_currentPlayer, 0, 0);
}

Child *AGOSEngine::findChildOfType(Item *i, uint type) {
	Item *b = nullptr;
	Child *child = i->children;

	for (; child; child = child->next) {
		if (child->type == type)
			return child;
		if (child->type == 255)
			b = derefItem(((SubInherit *)(child))->inMaster);
	}
	if (b) {
		child = b->children;
		for (; child; child = child->next) {
			if (child->type == type)
				return child;
		}
	}

	return nullptr;
}

int AGOSEngine::getUserFlag(Item *item, int a) {
	SubUserFlag *subUserFlag;

	subUserFlag = (SubUserFlag *)findChildOfType(item, kUserFlagType);
	if (subUserFlag == nullptr)
		return 0;

	int max = (getGameType() == GType_ELVIRA1) ? 7 : 3;
	if (a < 0 || a > max)
		return 0;

	return subUserFlag->userFlags[a];
}

int AGOSEngine::getUserFlag1(Item *item, int a) {
	SubUserFlag *subUserFlag;

	if (item == nullptr || item == _dummyItem2 || item == _dummyItem3)
		return -1;

	subUserFlag = (SubUserFlag *)findChildOfType(item, kUserFlagType);
	if (subUserFlag == nullptr)
		return 0;

	if (a < 0 || a > 7)
		return 0;

	return	subUserFlag->userFlags[a];
}

void AGOSEngine::setUserFlag(Item *item, int a, int b) {
	SubUserFlag *subUserFlag;

	subUserFlag = (SubUserFlag *)findChildOfType(item, kUserFlagType);
	if (subUserFlag == nullptr) {
		subUserFlag = (SubUserFlag *)allocateChildBlock(item, kUserFlagType, sizeof(SubUserFlag));
	}

	if (a < 0 || a > 7)
		return;

	subUserFlag->userFlags[a] = b;
}

int AGOSEngine::getUserItem(Item *item, int n) {
	SubUserFlag *subUserFlag;

	subUserFlag = (SubUserFlag *)findChildOfType(item, kUserFlagType);
	if (subUserFlag == nullptr)
		return 0;

	if (n < 0 || n > 0)
		return 0;

	return	subUserFlag->userItems[n];
}

void AGOSEngine::setUserItem(Item *item, int n, int m) {
	SubUserFlag *subUserFlag;

	subUserFlag = (SubUserFlag *)findChildOfType(item, kUserFlagType);
	if (subUserFlag == nullptr) {
		subUserFlag = (SubUserFlag *)allocateChildBlock(item, kUserFlagType, sizeof(SubUserFlag));
	}

	if (n == 0)
		subUserFlag->userItems[n] = m;
}

bool AGOSEngine::isRoom(Item *item) {
	return findChildOfType(item, kRoomType) != nullptr;
}

bool AGOSEngine::isObject(Item *item) {
	return findChildOfType(item, kObjectType) != nullptr;
}

bool AGOSEngine::isPlayer(Item *item) {
	return findChildOfType(item, kPlayerType) != nullptr;
}

uint AGOSEngine::getOffsetOfChild2Param(SubObject *child, uint prop) {
	uint m = 1;
	uint offset = 0;
	while (m != prop) {
		if (child->objectFlags & m)
			offset++;
		m *= 2;
	}
	return offset;
}

Item *AGOSEngine::me() {
	if (_currentPlayer)
		return _currentPlayer;
	return _dummyItem1;
}

Item *AGOSEngine::actor() {
	error("actor: is this code ever used?");
	//if (_actorPlayer)
	//	return _actorPlayer;
	return _dummyItem1;	// for compilers that don't support NORETURN
}

Item *AGOSEngine::getNextItemPtr() {
	int a = getNextWord();
	switch (a) {
	case -1:
		return _subjectItem;
	case -3:
		return _objectItem;
	case -5:
		return me();
	case -7:
		return actor();
	case -9:
		return derefItem(me()->parent);
	default:
		return derefItem(a);
	}
}

Item *AGOSEngine::getNextItemPtrStrange() {
	int a = getNextWord();
	switch (a) {
	case -1:
		return _subjectItem;
	case -3:
		return _objectItem;
	case -5:
		return _dummyItem2;
	case -7:
		return nullptr;
	case -9:
		return _dummyItem3;
	default:
		return derefItem(a);
	}
}

uint AGOSEngine::getNextItemID() {
	int a = getNextWord();
	switch (a) {
	case -1:
		return itemPtrToID(_subjectItem);
	case -3:
		return itemPtrToID(_objectItem);
	case -5:
		return getItem1ID();
	case -7:
		return 0;
	case -9:
		return me()->parent;
	default:
		return a;
	}
}

void AGOSEngine::setItemParent(Item *item, Item *parent) {
	Item *old_parent = derefItem(item->parent);

	if (item == parent)
		error("setItemParent: Trying to set item as its own parent");

	// unlink it if it has a parent
	if (old_parent)
		unlinkItem(item);
	itemChildrenChanged(old_parent);
	linkItem(item, parent);
	itemChildrenChanged(parent);
}

void AGOSEngine::itemChildrenChanged(Item *item) {
	int i;
	WindowBlock *window;

	if (_noParentNotify)
		return;

	mouseOff();

	for (i = 0; i != 8; i++) {
		window = _windowArray[i];
		if (window && window->iconPtr && window->iconPtr->itemRef == item) {
			if (_fcsData1[i]) {
				_fcsData2[i] = true;
			} else {
				_fcsData2[i] = false;
				drawIconArray(i, item, window->iconPtr->line, window->iconPtr->classMask);
			}
		}
	}

	mouseOn();
}

void AGOSEngine::unlinkItem(Item *item) {
	Item *first, *parent, *next;

	// can't unlink item without parent
	if (item->parent == 0)
		return;

	// get parent and first child of parent
	parent = derefItem(item->parent);
	first = derefItem(parent->child);

	// the node to remove is first in the parent's children?
	if (first == item) {
		parent->child = item->next;
		item->parent = 0;
		item->next = 0;
		return;
	}

	for (;;) {
		if (!first)
			error("unlinkItem: parent empty");
		if (first->next == 0)
			error("unlinkItem: parent does not contain child");

		next = derefItem(first->next);
		if (next == item) {
			first->next = next->next;
			item->parent = 0;
			item->next = 0;
			return;
		}
		first = next;
	}
}

void AGOSEngine::linkItem(Item *item, Item *parent) {
	uint id;
	// Don't allow that an item that is already linked is relinked
	if (item->parent)
		return;

	id = itemPtrToID(parent);
	item->parent = id;

	if (parent != nullptr) {
		item->next = parent->child;
		parent->child = itemPtrToID(item);
	} else {
		item->next = 0;
	}
}

int AGOSEngine::wordMatch(Item *item, int16 a, int16 n) {
	if (getGameType() == GType_ELVIRA2 || getGameType() == GType_WW) {
		if (a == -1 && n == -1)
			return 1;
	}
	if (a == -1 && n == item->noun)
		return 1;
	if (a == item->adjective && n == item->noun)
		return 1;

	return 0;
}

Item *AGOSEngine::derefItem(uint item) {
	if (item >= _itemArraySize)
		error("derefItem: invalid item %d", item);
	return _itemArrayPtr[item];
}

Item *AGOSEngine::findInByClass(Item *i, int16 m) {
	i = derefItem(i->child);
	while (i) {
		if (i->classFlags & m) {
			_findNextPtr = derefItem(i->next);
			return i;
		}
		if (m == 0) {
			_findNextPtr = derefItem(i->next);
			return i;
		}
		i = derefItem(i->next);
	}
	return nullptr;
}

Item *AGOSEngine::nextInByClass(Item *i, int16 m) {
	i = _findNextPtr;
	while (i) {
		if (i->classFlags & m) {
			_findNextPtr = derefItem(i->next);
			return i;
		}
		if (m == 0) {
			_findNextPtr = derefItem(i->next);
			return i;
		}
		i = derefItem(i->next);
	}
	return nullptr;
}

Item *AGOSEngine::findMaster(int16 a, int16 n) {
	uint j;

	for (j = 1; j < _itemArraySize; j++) {
		Item *item = derefItem(j);
		if (item == nullptr)
			continue;

		if (wordMatch(item, a, n))
			return item;
	}

	return nullptr;
}

Item *AGOSEngine::nextMaster(Item *i, int16 a, int16 n) {
	uint j;
	uint first = itemPtrToID(i) + 1;

	for (j = first; j < _itemArraySize; j++) {
		Item *item = derefItem(j);
		if (item == nullptr)
			continue;

		if (wordMatch(item, a, n))
			return item;
	}

	return nullptr;
}

uint AGOSEngine::itemPtrToID(Item *id) {
	uint i;
	for (i = 0; i != _itemArraySize; i++)
		if (_itemArrayPtr[i] == id)
			return i;
	error("itemPtrToID: not found");
	return 0;	// for compilers that don't support NORETURN
}

} // End of namespace AGOS
