/* 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 "hdb/hdb.h"
#include "hdb/ai.h"
#include "hdb/file-manager.h"
#include "hdb/gfx.h"
#include "hdb/map.h"

namespace HDB {

Map::Map() {

	if (g_hdb->isPPC()) {
		_screenXTiles = 9;
		_screenYTiles = 11;
		_screenTileWidth = 8;
		_screenTileHeight = 10;
	} else {
		_screenXTiles = 17;
		_screenYTiles = 16;
		_screenTileWidth = 16;
		_screenTileHeight = 16;
	}

	_mapLoaded = false;

	_animCycle = 0;

	_numForegrounds = _numGratings = 0;

	_mapExplosions = nullptr;
	_mapExpBarrels = nullptr;
	_mapLaserBeams = nullptr;

	_background = nullptr;
	_foreground = nullptr;
	_iconList = nullptr;

	_width = 0;
	_height = 0;
	_mapX = 0;
	_mapY = 0;
	_mapTileX = 0;
	_mapTileY = 0;
	_mapTileXOff = 0;
	_mapTileYOff = 0;
	_backgroundOffset = 0;
	_foregroundOffset = 0;
	_iconNum = 0;
	_iconListOffset = 0;
	_infoNum = 0;
	_infoListOffset = 0;
}

Map::~Map() {
	delete[] _background;
	delete[] _foreground;
	delete[] _iconList;

	free(_mapExplosions);
	free(_mapExpBarrels);
	free(_mapLaserBeams);
}

void Map::save(Common::OutSaveFile *out) {

	int i;

	// Save Map Dimensions and Offsets
	out->writeSint32LE(_mapX);
	out->writeSint32LE(_mapY);
	out->writeSint32LE(_width);
	out->writeSint32LE(_height);
	out->writeSint32LE(_mapTileX);
	out->writeSint32LE(_mapTileY);
	out->writeSint32LE(_mapTileXOff);
	out->writeSint32LE(_mapTileYOff);

	// Save out all Level2 Gratings and AnimCycle

	out->writeSint32LE(_numGratings);
	for (i = 0; i < _numGratings; i++) {
		out->writeUint16LE(_gratings[i].x);
		out->writeUint16LE(_gratings[i].y);
		out->writeUint16LE(_gratings[i].tile);
	}

	out->writeSint32LE(_animCycle);

	// find out how many SLOW, MEDIUM & FAST bg tile anims there are and save them all out
	out->writeUint32LE(_listBGAnimSlow.size());
	for (i = 0; (uint)i < _listBGAnimSlow.size(); i++) {
		out->writeUint32LE(_listBGAnimSlow[i]);
	}

	out->writeUint32LE(_listBGAnimMedium.size());
	for (i = 0; (uint)i < _listBGAnimMedium.size(); i++) {
		out->writeUint32LE(_listBGAnimMedium[i]);
	}

	out->writeUint32LE(_listBGAnimFast.size());
	for (i = 0; (uint)i < _listBGAnimFast.size(); i++) {
		out->writeUint32LE(_listBGAnimFast[i]);
	}

	// find out how many SLOW, MEDIUM & FAST fg tile anims there are and save them all out

	out->writeUint32LE(_listFGAnimSlow.size());
	for (i = 0; (uint)i < _listFGAnimSlow.size(); i++) {
		out->writeUint32LE(_listFGAnimSlow[i]);
	}

	out->writeUint32LE(_listFGAnimMedium.size());
	for (i = 0; (uint)i < _listFGAnimMedium.size(); i++) {
		out->writeUint32LE(_listFGAnimMedium[i]);
	}

	out->writeUint32LE(_listFGAnimFast.size());
	for (i = 0; (uint)i < _listFGAnimFast.size(); i++) {
		out->writeUint32LE(_listFGAnimFast[i]);
	}

	// save map data
	for (i = 0; i < _width * _height; i++)
		out->writeSint32LE(_background[i]);
	for (i = 0; i < _width * _height; i++)
		out->writeSint32LE(_foreground[i]);
	for (i = 0; i < _width * _height; i++)
		out->writeByte(_mapExplosions[i]);
	for (i = 0; i < _width * _height; i++)
		out->writeByte(_mapExpBarrels[i]);
	for (i = 0; i < _width * _height; i++)
		out->writeByte(_mapLaserBeams[i]);
}

void Map::loadSaveFile(Common::InSaveFile *in) {
	restartSystem();

	// Load Map Dimensions and Offsets
	_mapX = in->readSint32LE();
	_mapY = in->readSint32LE();
	_width = in->readUint32LE();
	_height = in->readUint32LE();
	_mapTileX = in->readSint32LE();
	_mapTileY = in->readSint32LE();
	_mapTileXOff = in->readSint32LE();
	_mapTileYOff = in->readSint32LE();

	uint size = _width * _height;

	// Load All level2 gratings and animCycle

	_numGratings = in->readSint32LE();
	for (int i = 0; i < _numGratings; i++) {
		_gratings[i].x = in->readUint16LE();
		_gratings[i].y = in->readUint16LE();
		_gratings[i].tile = in->readUint16LE();
	}

	_animCycle = in->readSint32LE();

	// find out how many SLOW, MEDIUM & FAST bg tile anims there are and load them all out
	_listBGAnimSlow.resize(in->readUint32LE());
	for (int i = 0; (uint)i < _listBGAnimSlow.size(); i++) {
		_listBGAnimSlow[i] = in->readUint32LE();
	}

	_listBGAnimMedium.resize(in->readUint32LE());
	for (int i = 0; (uint)i < _listBGAnimMedium.size(); i++) {
		_listBGAnimMedium[i] = in->readUint32LE();
	}

	_listBGAnimFast.resize(in->readUint32LE());
	for (int i = 0; (uint)i < _listBGAnimFast.size(); i++) {
		_listBGAnimFast[i] = in->readUint32LE();
	}

	// find out how many SLOW, MEDIUM & FAST fg tile anims there are and load them all out

	_listFGAnimSlow.resize(in->readUint32LE());
	for (int i = 0; (uint)i < _listFGAnimSlow.size(); i++) {
		_listFGAnimSlow[i] = in->readUint32LE();
	}

	_listFGAnimMedium.resize(in->readUint32LE());
	for (int i = 0; (uint)i < _listFGAnimMedium.size(); i++) {
		_listFGAnimMedium[i] = in->readUint32LE();
	}

	_listFGAnimFast.resize(in->readUint32LE());
	for (uint i = 0; (uint)i < _listFGAnimFast.size(); i++) {
		_listFGAnimFast[i] = in->readUint32LE();
	}

	// load map data

	_background = new int16[size];
	for (uint i = 0; i < size; i++)
		_background[i] = in->readSint32LE();

	_foreground = new int16[size];
	for (uint i = 0; i < size; i++)
		_foreground[i] = in->readSint32LE();

	_mapExplosions = (byte *)malloc(size);
	for (uint i = 0; i < size; i++)
		_mapExplosions[i] = in->readByte();

	_mapExpBarrels = (byte *)malloc(size);
	for (uint i = 0; i < size; i++)
		_mapExpBarrels[i] = in->readByte();

	_mapLaserBeams = (byte *)malloc(size);
	for (uint i = 0; i < size; i++)
		_mapLaserBeams[i] = in->readByte();

	// load all the map's tiles (cache)
	loadTiles();
	_mapLoaded = true;
}

int Map::loadTiles() {

	int temp;
	int skyIndex = 0;

	// Load all tiles
	for (uint j = 0; j < _height; j++) {
		for (uint i = 0; i < _width; i++) {
			int tile = _background[j * _width + i];
			if ((temp = g_hdb->_gfx->isSky(tile)) && !skyIndex) {
				skyIndex = temp;
			}
			g_hdb->_gfx->getTile(tile);
			g_hdb->_gfx->getTile(_foreground[j * _width + i]);
		}
	}

	return skyIndex;
}

void Map::restartSystem() {
	_listBGAnimFast.clear();
	_listBGAnimMedium.clear();
	_listBGAnimSlow.clear();
	_listFGAnimFast.clear();
	_listFGAnimMedium.clear();
	_listFGAnimSlow.clear();

	delete[] _background;
	_background = nullptr;
	delete[] _foreground;
	_foreground = nullptr;
	delete[] _iconList;
	_iconList = nullptr;

	_width = _height = 0;
	_animCycle = 0;

	free(_mapExplosions);
	free(_mapExpBarrels);
	free(_mapLaserBeams);

	_mapExplosions = nullptr;
	_mapExpBarrels = nullptr;
	_mapLaserBeams = nullptr;

	// mark all in-memory tiles as being in memory, but able to be freed
	g_hdb->_gfx->markTileCacheFreeable();
	g_hdb->_gfx->markGfxCacheFreeable();

	_mapLoaded = false;
}

bool Map::loadMap(char *name) {
	Common::SeekableReadStream *mapStream = g_hdb->_fileMan->findFirstData(name, TYPE_BINARY);
	if (mapStream == nullptr) {
		warning("The %s MPC entry can't be found", name);
		return false;
	}

	load(mapStream);
	delete mapStream;

	return true;
}

bool Map::load(Common::SeekableReadStream *stream) {
	debug(5, "map stream size: %ld(%lx)", long(stream->size()), long(stream->size()));

	// Load MSM data header
	stream->read(_name, 32);
	_width = stream->readUint16LE();
	_height = stream->readUint16LE();
	_backgroundOffset = stream->readUint32LE();
	_foregroundOffset = stream->readUint32LE();
	_iconNum = stream->readUint16LE();
	_infoNum = stream->readUint16LE();            // not used in the original
	_iconListOffset = stream->readUint32LE();
	_infoListOffset = stream->readUint32LE();     // not used in the original

	debug(5, "map: w: %d(%x), h: %d(%x) bg: %x fg: %x icon#: %d(%x) icon: %x info#: %d(%x) info: %x",
			_width, _width, _height, _height, _backgroundOffset, _foregroundOffset, _iconNum, _iconNum,
			_iconListOffset, _infoNum, _infoNum, _infoListOffset);

	uint size = _width * _height;
	// Reading Background
	_background = new int16[size];
	stream->seek(_backgroundOffset);
	for (uint i = 0; i < size; i++) {
		_background[i] = stream->readUint16LE();
	}
	if (gDebugLevel >= 5) {
		debug(5, "Background:");
		Common::hexdump((const byte *)_background, 512);
	}

	// Reading Foreground
	_foreground = new int16[size];
	stream->seek(_foregroundOffset);
	for (uint i = 0; i < size; i++) {
		_foreground[i] = stream->readUint16LE();
	}

	if (gDebugLevel >= 5) {
		debug(5, "Foreground:");
		Common::hexdump((const byte *)_foreground, 512);
	}

	// Reading Icon List
	_iconList = new MSMIcon[_iconNum];
	for (uint i = 0; i < _iconNum; i++) {
		_iconList[i].icon = stream->readUint16LE();
		_iconList[i].x = stream->readUint16LE();
		_iconList[i].y = stream->readUint16LE();

		stream->read(_iconList[i].funcInit, 32);
		stream->read(_iconList[i].funcAction, 32);
		stream->read(_iconList[i].funcUse, 32);

		_iconList[i].dir = stream->readUint16LE();
		_iconList[i].level = stream->readUint16LE();
		_iconList[i].value1 = stream->readUint16LE();
		_iconList[i].value2 = stream->readUint16LE();
	}

	g_hdb->setInMapName(_name);

	_mapExplosions = (byte *)calloc(size, 1);
	_mapExpBarrels = (byte *)calloc(size, 1);
	_mapLaserBeams = (byte *)calloc(size, 1);

	int sky = loadTiles();
	g_hdb->_gfx->setSky(sky);
	_mapX = _mapY = 0;

	// Setup animating Tile lists
	for (uint i = 0; i < size; i++) {
		addBGTileAnimation(i % _width, i / _width);
		addFGTileAnimation(i % _width, i / _width);
	}

	struct {
		AIType type;
		AIDir dir;
	} aiInfo[] = {
		{ AI_GUY,			DIR_DOWN },
		{ AI_GUY,			DIR_UP },
		{ AI_GUY,			DIR_RIGHT },
		{ AI_GUY,			DIR_LEFT },

		{ ITEM_ENV_WHITE,	DIR_NONE },
		{ ITEM_ENV_BLUE,	DIR_NONE },
		{ ITEM_ENV_RED,		DIR_NONE },
		{ ITEM_ENV_GREEN,	DIR_NONE },

		{ AI_LASER,			DIR_RIGHT },
		{ AI_LASER,			DIR_DOWN },
		{ AI_LASER,			DIR_LEFT },
		{ AI_LASER,			DIR_UP },

		{ AI_DIVERTER,		DIR_DOWN },
		{ AI_DIVERTER,		DIR_UP },
		{ AI_DIVERTER,		DIR_RIGHT },
		{ AI_DIVERTER,		DIR_LEFT },

		{ AI_FOURFIRER,		DIR_RIGHT },
		{ AI_FOURFIRER,		DIR_DOWN},
		{ AI_FOURFIRER,		DIR_LEFT },
		{ AI_FOURFIRER,		DIR_UP },

		{ INFO_ARROW_TURN,	DIR_DOWN },
		{ INFO_ARROW_TURN,	DIR_UP },
		{ INFO_ARROW_TURN,	DIR_RIGHT },
		{ INFO_ARROW_TURN,	DIR_LEFT },

		{ INFO_ARROW_STOP,	DIR_DOWN },
		{ INFO_ARROW_STOP,	DIR_UP },
		{ INFO_ARROW_STOP,	DIR_RIGHT },
		{ INFO_ARROW_STOP,	DIR_LEFT },

		{ ITEM_CELL,		DIR_NONE },

		{ AI_CRATE,			DIR_NONE },
		{ AI_LIGHTBARREL,	DIR_NONE },
		{ AI_HEAVYBARREL,	DIR_NONE },
		{ AI_BOOMBARREL,	DIR_NONE },

		{ ITEM_TRANSCEIVER, DIR_NONE },
		{ ITEM_CLUB,		DIR_NONE },
		{ ITEM_ROBOSTUNNER, DIR_NONE },
		{ ITEM_SLUGSLINGER, DIR_NONE },

		{ AI_SCIENTIST,		DIR_DOWN },
		{ AI_SCIENTIST,		DIR_UP },
		{ AI_SCIENTIST,		DIR_RIGHT },
		{ AI_SCIENTIST,		DIR_LEFT },

		{ AI_WORKER,		DIR_DOWN },
		{ AI_WORKER,		DIR_UP },
		{ AI_WORKER,		DIR_RIGHT },
		{ AI_WORKER,		DIR_LEFT },

		{ AI_SHOCKBOT,		DIR_DOWN },
		{ AI_SHOCKBOT,		DIR_UP },
		{ AI_SHOCKBOT,		DIR_RIGHT },
		{ AI_SHOCKBOT,		DIR_LEFT },

		{ AI_RIGHTBOT,		DIR_DOWN },
		{ AI_RIGHTBOT,		DIR_UP },
		{ AI_RIGHTBOT,		DIR_RIGHT },
		{ AI_RIGHTBOT,		DIR_LEFT },

		{ AI_PUSHBOT,		DIR_DOWN },
		{ AI_PUSHBOT,		DIR_UP },
		{ AI_PUSHBOT,		DIR_RIGHT },
		{ AI_PUSHBOT,		DIR_LEFT },

		{ AI_LISTENBOT,		DIR_DOWN },
		{ AI_LISTENBOT,		DIR_UP },
		{ AI_LISTENBOT,		DIR_RIGHT },
		{ AI_LISTENBOT,		DIR_LEFT },

		{ ITEM_MONKEYSTONE, DIR_NONE },

		{ INFO_TELEPORTER1,	DIR_NONE },
		{ INFO_TELEPORTER2,	DIR_NONE },
		{ INFO_TELEPORTER3,	DIR_NONE },
		{ INFO_TELEPORTER4,	DIR_NONE },
		{ INFO_TELEPORTER5,	DIR_NONE },
		{ INFO_TELEPORTER6,	DIR_NONE },
		{ INFO_TELEPORTER7,	DIR_NONE },
		{ INFO_TELEPORTER8,	DIR_NONE },
		{ INFO_TELEPORTER9,	DIR_NONE },
		{ INFO_TELEPORTER10,	DIR_NONE },
		{ INFO_TELEPORTER11,	DIR_NONE },
		{ INFO_TELEPORTER12,	DIR_NONE },
		{ INFO_TELEPORTER13,	DIR_NONE },
		{ INFO_TELEPORTER14,	DIR_NONE },
		{ INFO_TELEPORTER15,	DIR_NONE },
		{ INFO_TELEPORTER16,	DIR_NONE },
		{ INFO_TELEPORTER17,	DIR_NONE },
		{ INFO_TELEPORTER18,	DIR_NONE },
		{ INFO_TELEPORTER19,	DIR_NONE },
		{ INFO_TELEPORTER20,	DIR_NONE },

		{ INFO_LEVELEXIT,	DIR_NONE },

		{ INFO_ACTION1,		DIR_NONE },
		{ INFO_ACTION2,		DIR_NONE },
		{ INFO_ACTION3,		DIR_NONE },
		{ INFO_ACTION4,		DIR_NONE },
		{ INFO_ACTION5,		DIR_NONE },
		{ INFO_ACTION6,		DIR_NONE },
		{ INFO_ACTION7,		DIR_NONE },
		{ INFO_ACTION8,		DIR_NONE },
		{ INFO_ACTION9,		DIR_NONE },
		{ INFO_ACTION10,	DIR_NONE },
		{ INFO_ACTION11,	DIR_NONE },
		{ INFO_ACTION12,	DIR_NONE },
		{ INFO_ACTION13,	DIR_NONE },
		{ INFO_ACTION14,	DIR_NONE },
		{ INFO_ACTION15,	DIR_NONE },
		{ INFO_ACTION16,	DIR_NONE },
		{ INFO_ACTION17,	DIR_NONE },
		{ INFO_ACTION18,	DIR_NONE },
		{ INFO_ACTION19,	DIR_NONE },
		{ INFO_ACTION20,	DIR_NONE },

		{ AI_SPACEDUDE,		DIR_DOWN },
		{ AI_SPACEDUDE,		DIR_UP },
		{ AI_SPACEDUDE,		DIR_RIGHT },
		{ AI_SPACEDUDE,		DIR_LEFT },

		{ AI_SERGEANT,		DIR_DOWN },
		{ AI_SERGEANT,		DIR_UP },
		{ AI_SERGEANT,		DIR_RIGHT },
		{ AI_SERGEANT,		DIR_LEFT },

		{ AI_MAINTBOT,		DIR_DOWN },
		{ AI_MAINTBOT,		DIR_UP },
		{ AI_MAINTBOT,		DIR_RIGHT },
		{ AI_MAINTBOT,		DIR_LEFT },

		{ INFO_ACTION_AUTO,	DIR_NONE },

		{ ITEM_GEM_WHITE,	DIR_NONE },
		{ ITEM_GEM_BLUE,	DIR_NONE },
		{ ITEM_GEM_RED,		DIR_NONE },
		{ ITEM_GEM_GREEN,	DIR_NONE },

		{ INFO_SET_MUSIC,	DIR_NONE },
		{ INFO_LUA,			DIR_NONE },
		{ INFO_HERE,		DIR_NONE },

		{ AI_VORTEXIAN,		DIR_DOWN },

		{ AI_CHICKEN,		DIR_DOWN },
		{ AI_CHICKEN,		DIR_UP },
		{ AI_CHICKEN,		DIR_RIGHT },
		{ AI_CHICKEN,		DIR_LEFT },

		{ ITEM_GOO_CUP,		DIR_NONE },
		{ ITEM_TEACUP,		DIR_NONE },
		{ ITEM_COOKIE,		DIR_NONE },
		{ ITEM_BURGER,		DIR_NONE },
		{ ITEM_PDA,			DIR_NONE },
		{ ITEM_BOOK,		DIR_NONE },
		{ ITEM_CLIPBOARD,	DIR_NONE },
		{ ITEM_NOTE,		DIR_NONE },
		{ ITEM_KEYCARD_WHITE,	DIR_NONE },
		{ ITEM_KEYCARD_BLUE,	DIR_NONE },
		{ ITEM_KEYCARD_RED,		DIR_NONE },
		{ ITEM_KEYCARD_GREEN,	DIR_NONE },
		{ ITEM_KEYCARD_PURPLE,	DIR_NONE },
		{ ITEM_KEYCARD_BLACK,	DIR_NONE },
		{ AI_MAGIC_EGG,		DIR_NONE },
		{ AI_ICE_BLOCK,		DIR_NONE },
		{ ITEM_CABKEY,		DIR_NONE },

		{ AI_DEADWORKER,	DIR_NONE },
		{ AI_OMNIBOT,		DIR_DOWN },
		{ AI_OMNIBOT,		DIR_UP },
		{ AI_OMNIBOT,		DIR_RIGHT },
		{ AI_OMNIBOT,		DIR_LEFT },
		{ AI_TURNBOT,		DIR_DOWN },
		{ AI_TURNBOT,		DIR_UP },
		{ AI_TURNBOT,		DIR_RIGHT },
		{ AI_TURNBOT,		DIR_LEFT },
		{ AI_DOLLY,			DIR_DOWN },
		{ AI_DOLLY,			DIR_UP },
		{ AI_DOLLY,			DIR_RIGHT },
		{ AI_DOLLY,			DIR_LEFT },

		{ INFO_TRIGGER,		DIR_NONE },

		{ ITEM_DOLLYTOOL1,	DIR_NONE },
		{ ITEM_DOLLYTOOL2,	DIR_NONE },
		{ ITEM_DOLLYTOOL3,	DIR_NONE },
		{ ITEM_DOLLYTOOL4,	DIR_NONE },

		{ AI_RAILRIDER_ON,	DIR_UP },
		{ AI_RAILRIDER_ON,	DIR_DOWN },
		{ AI_RAILRIDER_ON,	DIR_LEFT },
		{ AI_RAILRIDER_ON,	DIR_RIGHT },
		{ AI_RAILRIDER,		DIR_UP },
		{ AI_RAILRIDER,		DIR_DOWN },
		{ AI_RAILRIDER,		DIR_LEFT },
		{ AI_RAILRIDER,		DIR_RIGHT },

		{ ITEM_SODA,		DIR_NONE },
		{ INFO_ARROW_4WAY,	DIR_NONE },
		{ AI_DEADEYE,		DIR_DOWN },
		{ AI_DEADEYE,		DIR_UP },
		{ AI_DEADEYE,		DIR_RIGHT },
		{ AI_DEADEYE,		DIR_LEFT },
		{ AI_MEERKAT,		DIR_NONE },
		{ AI_FATFROG,		DIR_DOWN },
		{ AI_FATFROG,		DIR_RIGHT },
		{ AI_FATFROG,		DIR_LEFT },
		{ AI_GOODFAIRY,		DIR_DOWN },
		{ AI_GOODFAIRY,		DIR_UP },
		{ AI_GOODFAIRY,		DIR_RIGHT },
		{ AI_GOODFAIRY,		DIR_LEFT },
		{ AI_BADFAIRY,		DIR_DOWN },
		{ AI_BADFAIRY,		DIR_UP },
		{ AI_BADFAIRY,		DIR_RIGHT },
		{ AI_BADFAIRY,		DIR_LEFT },
		{ AI_ACCOUNTANT,	DIR_DOWN },
		{ AI_ACCOUNTANT,	DIR_UP },
		{ AI_ACCOUNTANT,	DIR_RIGHT },
		{ AI_ACCOUNTANT,	DIR_LEFT },
		{ AI_ICEPUFF,		DIR_NONE },
		{ AI_DRAGON,		DIR_NONE },
		{ AI_BUZZFLY,		DIR_DOWN },
		{ AI_BUZZFLY,		DIR_UP },
		{ AI_BUZZFLY,		DIR_RIGHT },
		{ AI_BUZZFLY,		DIR_LEFT },

		{ AI_FROGSTATUE,	DIR_NONE },
		{ ITEM_SLICER,		DIR_NONE },
		{ INFO_FAIRY_SRC,	DIR_NONE },
		{ INFO_FAIRY_SRC2,	DIR_NONE },
		{ INFO_FAIRY_SRC3,	DIR_NONE },
		{ INFO_FAIRY_SRC4,	DIR_NONE },
		{ INFO_FAIRY_SRC5,	DIR_NONE },
		{ INFO_FAIRY_DEST,	DIR_NONE },
		{ INFO_FAIRY_DEST2,	DIR_NONE },
		{ INFO_FAIRY_DEST3,	DIR_NONE },
		{ INFO_FAIRY_DEST4,	DIR_NONE },
		{ INFO_FAIRY_DEST5,	DIR_NONE },
		{ INFO_QMARK,		DIR_NONE },
		{ INFO_DEBUG,		DIR_NONE },
		{ AI_NONE,			DIR_NONE },

		{ AI_NONE,			DIR_NONE }
	};

	// Scan all icons and init all Entities
	g_hdb->setupProgressBar(_iconNum);
	for (int i = 0; i < _iconNum; i++) {
		if (_iconList[i].icon == 65535) {
			warning("Map::load(): Icon index at pos %d is too big: %d", i, _iconList[i].icon);
			continue;
		}

		debug(5, "%s, %d,%d,%s,%s,%s,%d,%d,%d,%d", AIType2Str(aiInfo[_iconList[i].icon].type), _iconList[i].x, _iconList[i].y, _iconList[i].funcInit,
				_iconList[i].funcAction, _iconList[i].funcUse, _iconList[i].dir, _iconList[i].level,
				_iconList[i].value1, _iconList[i].value2);

		g_hdb->makeProgress();

		// Don't spawn Action Mode Entities in Puzzle Mode
		if (!g_hdb->getActionMode()) {
			switch (aiInfo[_iconList[i].icon].type) {
			case AI_DEADEYE:
				if (_iconList[i].value1 == 1)	// For non-moving DeadEyes
					break;
				// fall through
			case AI_FOURFIRER:
			case AI_LISTENBOT:
			case ITEM_CLUB:
			case ITEM_ROBOSTUNNER:
			case ITEM_SLUGSLINGER:
				continue;
			default:
				break;
			}
		}

		// Handle special icons that aren't moving AI entities
		switch (aiInfo[_iconList[i].icon].type) {
		default:
			g_hdb->_ai->spawn(
				aiInfo[_iconList[i].icon].type,
				aiInfo[_iconList[i].icon].dir,
				_iconList[i].x,
				_iconList[i].y,
				_iconList[i].funcInit,
				_iconList[i].funcAction,
				_iconList[i].funcUse,
				(AIDir)_iconList[i].dir,
				_iconList[i].level,
				_iconList[i].value1,
				_iconList[i].value2,
				0
			);
			break;
		case INFO_ARROW_4WAY:
			g_hdb->_ai->addToPathList(
				_iconList[i].x,
				_iconList[i].y,
				2,
				aiInfo[_iconList[i].icon].dir
			);
			break;
		case INFO_ARROW_TURN:
			g_hdb->_ai->addToPathList(
				_iconList[i].x,
				_iconList[i].y,
				1,
				aiInfo[_iconList[i].icon].dir
			);
			break;
		case INFO_ARROW_STOP:
			g_hdb->_ai->addToPathList(
				_iconList[i].x,
				_iconList[i].y,
				0,
				aiInfo[_iconList[i].icon].dir
			);
			break;

		case INFO_ACTION1:
		case INFO_ACTION2:
		case INFO_ACTION3:
		case INFO_ACTION4:
		case INFO_ACTION5:
		case INFO_ACTION6:
		case INFO_ACTION7:
		case INFO_ACTION8:
		case INFO_ACTION9:
		case INFO_ACTION10:
		case INFO_ACTION11:
		case INFO_ACTION12:
		case INFO_ACTION13:
		case INFO_ACTION14:
		case INFO_ACTION15:
		case INFO_ACTION16:
		case INFO_ACTION17:
		case INFO_ACTION18:
		case INFO_ACTION19:
		case INFO_ACTION20:
			g_hdb->_ai->addToActionList(
				aiInfo[_iconList[i].icon].type - INFO_ACTION1,
				_iconList[i].x,
				_iconList[i].y,
				_iconList[i].funcInit,
				_iconList[i].funcUse
			);
			break;
		case INFO_ACTION_AUTO:
			g_hdb->_ai->addToAutoList(
				_iconList[i].x,
				_iconList[i].y,
				_iconList[i].funcInit,
				_iconList[i].funcUse
			);
			break;

		case INFO_TELEPORTER1:
		case INFO_TELEPORTER2:
		case INFO_TELEPORTER3:
		case INFO_TELEPORTER4:
		case INFO_TELEPORTER5:
		case INFO_TELEPORTER6:
		case INFO_TELEPORTER7:
		case INFO_TELEPORTER8:
		case INFO_TELEPORTER9:
		case INFO_TELEPORTER10:
		case INFO_TELEPORTER11:
		case INFO_TELEPORTER12:
		case INFO_TELEPORTER13:
		case INFO_TELEPORTER14:
		case INFO_TELEPORTER15:
		case INFO_TELEPORTER16:
		case INFO_TELEPORTER17:
		case INFO_TELEPORTER18:
		case INFO_TELEPORTER19:
		case INFO_TELEPORTER20:

			if (aiInfo[_iconList[i].icon].type == INFO_TELEPORTER8) {
				if (g_hdb->isPPC() && !scumm_stricmp(g_hdb->currentMapName(), "MAP29.MSM")) {
					if (_iconList[i].x == 45 && _iconList[i].y == 116) {
						warning("PATCHED Teleporter8 in MAP29");

						_iconList[i].level = 1;
					}
				}
			}

			g_hdb->_ai->addToTeleportList(
				aiInfo[_iconList[i].icon].type - INFO_TELEPORTER1,
				_iconList[i].x,
				_iconList[i].y,
				_iconList[i].dir,
				_iconList[i].level,
				_iconList[i].value1,
				_iconList[i].value2,
				_iconList[i].funcUse
			);
			break;

		case INFO_SET_MUSIC:
		case INFO_PROMOTE:
		case INFO_DEMOTE:
			break;

		case INFO_LUA:
			g_hdb->_ai->addToLuaList(
				_iconList[i].x,
				_iconList[i].y,
				_iconList[i].value1,
				_iconList[i].value2,
				_iconList[i].funcInit,
				_iconList[i].funcAction,
				_iconList[i].funcUse
			);
			break;
		case INFO_HERE:
			g_hdb->_ai->addToHereList(
				_iconList[i].funcInit,
				_iconList[i].x,
				_iconList[i].y
			);
			break;
		case INFO_TRIGGER:
			g_hdb->_ai->addToTriggerList(
				_iconList[i].funcInit,
				_iconList[i].funcUse,
				_iconList[i].x,
				_iconList[i].y,
				_iconList[i].value1,
				_iconList[i].value2,
				_iconList[i].funcAction
			);
			break;

		case INFO_FAIRY_SRC:
		case INFO_FAIRY_SRC2:
		case INFO_FAIRY_SRC3:
		case INFO_FAIRY_SRC4:
		case INFO_FAIRY_SRC5:
			g_hdb->_ai->addToFairystones(
				aiInfo[_iconList[i].icon].type - INFO_FAIRY_SRC,
				_iconList[i].x,
				_iconList[i].y,
				0
			);
			break;

		case INFO_FAIRY_DEST:
		case INFO_FAIRY_DEST2:
		case INFO_FAIRY_DEST3:
		case INFO_FAIRY_DEST4:
		case INFO_FAIRY_DEST5:
			g_hdb->_ai->addToFairystones(
				aiInfo[_iconList[i].icon].type - INFO_FAIRY_DEST,
				_iconList[i].x,
				_iconList[i].y,
				1
			);
			break;
		}
	}

	g_hdb->_ai->initAllEnts();
	g_hdb->stopProgress();

	_mapLoaded = true;

	return true;
}

void Map::draw() {
	if (!_mapLoaded)
		return;

	// Calculate Tile Offsets and Panning Offsets
	_mapTileX = _mapX / kTileWidth;
	_mapTileY = _mapY / kTileHeight;
	_mapTileXOff = -(_mapX % kTileWidth);
	_mapTileYOff = -(_mapY % kTileHeight);

	int matrixY = _mapTileY * _width;
	int screenY = _mapTileYOff;

	/*
		Note from Original Source:
		need to set the number of tiles to draw on the screen.  Most of the time
		we need to draw an extra tile because we're displaying a half-tile, but
		sometimes the offset is exactly at 0 and thus we don't need to draw a
		tile offscreen that we'll never see.  In fact, doing this fixes a bug
		that could occur because we would be accessing map data that's outside the map
		when we're at the very bottom of the map.
	*/

	int maxTileX = (_mapTileXOff >= -8) ? g_hdb->_map->_screenXTiles - 1 : g_hdb->_map->_screenXTiles;
	int maxTileY = (!_mapTileYOff) ? g_hdb->_map->_screenYTiles - 1 : g_hdb->_map->_screenYTiles;

	if (matrixY + (maxTileY - 1) * _width > _height * _width) {
		return;
	}

	// Sometimes we're 1 beyond the map, so avoid it
	if (_mapTileX + maxTileX - 1 >= _width)
		maxTileX--;

	_numForegrounds = _numGratings = 0;

	for (int j = 0; j < maxTileY; j++) {
		int screenX = _mapTileXOff;
		for (int i = 0; i < maxTileX; i++) {

			// Draw Background Tile
			int16 tileIndex = _background[matrixY + _mapTileX + i];
			if (tileIndex < 0) {
				tileIndex = 0;
			}

			// Draw if not a sky tile
			if (!g_hdb->_gfx->isSky(tileIndex)) {
				Tile *tile = g_hdb->_gfx->getTile(tileIndex);
				if (tile)
					tile->draw(screenX, screenY);
				else
					warning("Cannot find tile with index %d at %d,%d", tileIndex, _mapTileX + i, _mapTileY + j);
			}

			// Draw Foreground Tile
			tileIndex = _foreground[matrixY + _mapTileX + i];
			if (tileIndex >= 0) {
				Tile *fTile = g_hdb->_gfx->getTile(tileIndex);
				if (fTile && !(fTile->_flags & kFlagInvisible)) {

					if ((fTile->_flags & kFlagGrating) && (_numGratings < kMaxGratings)) {
						// Check for Gratings Flag
						_gratings[_numGratings].x = screenX;
						_gratings[_numGratings].y = screenY;
						_gratings[_numGratings].tile = tileIndex;
						if (_numGratings < kMaxGratings)
							_numGratings++;
					} else if ((fTile->_flags & kFlagForeground)) {
						// Check for Foregrounds Flag
						_foregrounds[_numForegrounds].x = screenX;
						_foregrounds[_numForegrounds].y = screenY;
						_foregrounds[_numForegrounds].tile = tileIndex;
						if (_numForegrounds < kMaxForegrounds)
							_numForegrounds++;
					} else {
						if (fTile->_flags & kFlagMasked) {
							fTile->drawMasked(screenX, screenY);
						} else {
							fTile->draw(screenX, screenY);
						}
					}
				}
			}

			screenX += kTileWidth;
		}
		matrixY += _width;
		screenY += kTileWidth;
	}

	if (g_hdb->isDemo() && g_hdb->isPPC())
		drawEnts();

	// Animate FAST Map Tiles
	if (!(_animCycle % kAnimFastFrames)) {
		for (Common::Array<uint32>::iterator it = _listBGAnimFast.begin(); it != _listBGAnimFast.end(); ++it) {
			_background[(*it)] = g_hdb->_gfx->animateTile(_background[(*it)]);
		}

		for (Common::Array<uint32>::iterator it = _listFGAnimFast.begin(); it != _listFGAnimFast.end(); ++it) {
			_foreground[(*it)] = g_hdb->_gfx->animateTile(_foreground[(*it)]);
		}
	}

	// Animate MEDIUM Map Tiles
	if (!(_animCycle % kAnimMediumFrames)) {
		for (Common::Array<uint32>::iterator it = _listBGAnimMedium.begin(); it != _listBGAnimMedium.end(); ++it) {
			_background[(*it)] = g_hdb->_gfx->animateTile(_background[(*it)]);
		}

		for (Common::Array<uint32>::iterator it = _listFGAnimMedium.begin(); it != _listFGAnimMedium.end(); ++it) {
			_foreground[(*it)] = g_hdb->_gfx->animateTile(_foreground[(*it)]);
		}
	}

	// Animate SLOW Map Tiles
	if (!(_animCycle % kAnimSlowFrames)) {
		for (Common::Array<uint32>::iterator it = _listBGAnimSlow.begin(); it != _listBGAnimSlow.end(); ++it) {
			_background[(*it)] = g_hdb->_gfx->animateTile(_background[(*it)]);
		}

		for (Common::Array<uint32>::iterator it = _listFGAnimSlow.begin(); it != _listFGAnimSlow.end(); ++it) {
			_foreground[(*it)] = g_hdb->_gfx->animateTile(_foreground[(*it)]);
		}
	}

	_animCycle++;
}

void Map::drawEnts() {
	g_hdb->_ai->drawEnts(_mapX, _mapY, g_hdb->_map->_screenXTiles * kTileWidth, g_hdb->_map->_screenYTiles * kTileHeight);
}

void Map::drawGratings() {
	for (int i = 0; i < _numGratings; i++) {
		g_hdb->_gfx->getTile(_gratings[i].tile)->drawMasked(_gratings[i].x, _gratings[i].y);
	}

	debug(8, "Gratings Count: %d", _numGratings);
}

void Map::drawForegrounds() {
	for (int i = 0; i < _numForegrounds; i++) {
		g_hdb->_gfx->getTile(_foregrounds[i].tile)->drawMasked(_foregrounds[i].x, _foregrounds[i].y);
	}

	debug(8, "Foregrounds Count: %d", _numForegrounds);
}

bool Map::onScreen(int x, int y) {
	if ((x >= _mapX / kTileWidth) && (x < (_mapX / kTileWidth) + g_hdb->_map->_screenXTiles) && (y >= _mapY / kTileHeight) && (y < (_mapY / kTileHeight) + g_hdb->_map->_screenYTiles))
		return true;
	return false;
}

uint32 Map::getMapBGTileFlags(int x, int y) {
	if (x < 0 || x >= _width || y < 0 || y >= _height)
		return 0;

	Tile *tile = g_hdb->_gfx->getTile(_background[y * _width + x]);
	if (tile)
		return tile->_flags;
	return 0;
}

uint32 Map::getMapFGTileFlags(int x, int y) {
	if (x < 0 || x >= _width || y < 0 || y >= _height)
		return 0;

	Tile *tile = g_hdb->_gfx->getTile(_foreground[y * _width + x]);
	if (tile)
		return tile->_flags;
	return 0;
}

int16 Map::getMapBGTileIndex(int x, int y) {
	if (x < 0 || x >= _width || y < 0 || y >= _height)
		return 0;

	return _background[y * _width + x];
}

int16 Map::getMapFGTileIndex(int x, int y) {
	if (x < 0 || x >= _width || y < 0 || y >= _height)
		return 0;

	return _foreground[y * _width + x];
}

void Map::setMapBGTileIndex(int x, int y, int index) {
	if (x < 0 || x >= _width || y < 0 || y >= _height)
		return;

	_background[y * _width + x] = index;
}

void Map::setMapFGTileIndex(int x, int y, int index) {
	if (x < 0 || x >= _width || y < 0 || y >= _height)
		return;

	_foreground[y * _width + x] = index;
}

void Map::addBGTileAnimation(int x, int y) {
	int i = y * _width + x;

	Tile *tile = g_hdb->_gfx->getTile(_background[i]);
	if (!tile)
		return;

	uint32 flags = tile->_flags;

	// BACKGROUND
	if ((flags & kFlagAnimFast) == kFlagAnimFast)
		// check 'fast' first since it's a combo of slow & medium
		_listBGAnimFast.push_back(i);
	else if ((flags & kFlagAnimSlow) == kFlagAnimSlow)
		_listBGAnimSlow.push_back(i);
	else if ((flags & kFlagAnimMedium) == kFlagAnimMedium)
		_listBGAnimMedium.push_back(i);
}

void Map::addFGTileAnimation(int x, int y) {

	int i = y * _width + x;

	Tile *tile = g_hdb->_gfx->getTile(_foreground[i]);
	if (!tile)
		return;
	uint32 flags = tile->_flags;

	// FOREGROUND
	if ((flags & kFlagAnimFast) == kFlagAnimFast)
		_listFGAnimFast.push_back(i);
	else if ((flags & kFlagAnimSlow) == kFlagAnimSlow)
		_listFGAnimSlow.push_back(i);
	else if ((flags & kFlagAnimMedium) == kFlagAnimMedium)
		_listFGAnimMedium.push_back(i);
}

void Map::removeBGTileAnimation(int x, int y) {
	uint idx = y * _width + x;

	for (uint i = 0; i < _listBGAnimFast.size(); i++) {
		if (_listBGAnimFast[i] == idx) {
			_listBGAnimFast.remove_at(i);
			return;
		}
	}

	for (uint i = 0; i < _listBGAnimSlow.size(); i++) {
		if (_listBGAnimSlow[i] == idx) {
			_listBGAnimSlow.remove_at(i);
			return;
		}
	}

	for (uint i = 0; i < _listBGAnimMedium.size(); i++) {
		if (_listBGAnimMedium[i] == idx) {
			_listBGAnimMedium.remove_at(i);
			return;
		}
	}
}

void Map::removeFGTileAnimation(int x, int y) {
	uint idx = y * _width + x;

	for (uint i = 0; i < _listFGAnimFast.size(); i++) {
		if (_listFGAnimFast[i] == idx) {
			_listFGAnimFast.remove_at(i);
			return;
		}
	}

	for (uint i = 0; i < _listFGAnimSlow.size(); i++) {
		if (_listFGAnimSlow[i] == idx) {
			_listFGAnimSlow.remove_at(i);
			return;
		}
	}

	for (uint i = 0; i < _listFGAnimMedium.size(); i++) {
		if (_listFGAnimMedium[i] == idx) {
			_listFGAnimMedium.remove_at(i);
			return;
		}
	}
}

void Map::getMapXY(int *x, int *y) {
	*x = _mapX;
	*y = _mapY;
}

void Map::setMapXY(int x, int y) {
	_mapX = CLIP(x, 0, _width * kTileWidth - g_hdb->_screenDrawWidth);
	_mapY = CLIP(y, 0, _height * kTileHeight - g_hdb->_screenDrawHeight);
}

// Sets _mapX and _mapY and tries to center the map around X, Y
void Map::centerMapXY(int x, int y) {
	int checkx = x / kTileWidth;
	int checky = y / kTileHeight;

	// Scan from centerX to right edge
	int maxx = (_width - (g_hdb->_map->_screenTileWidth / 2)) * kTileWidth;
	for (int i = checkx + 1; i <= checkx + (g_hdb->_map->_screenTileWidth / 2); i++) {
		if (!getMapBGTileIndex(i, checky)) {
			maxx = (i - (g_hdb->_map->_screenTileWidth / 2)) * kTileWidth;
			break;
		}
	}

	// Scan from centerX to left edge
	int minx = 0;
	for (int i = checkx - 1; i >= checkx - (g_hdb->_map->_screenTileWidth / 2); i--) {
		if (!getMapBGTileIndex(i, checky)) {
			// +1 because we don't want to see one whole tile
			minx = (1 + i + (g_hdb->_map->_screenTileWidth / 2)) * kTileWidth;
			break;
		}
	}

	// Scan from centerY to bottom edge
	int maxy = (_height - (g_hdb->_map->_screenTileHeight / 2)) * kTileHeight;
	for (int i = checky + 1; i <= checky + (g_hdb->_map->_screenTileHeight / 2); i++) {
		if (!getMapBGTileIndex(checkx, i)) {
			maxy = (i - (g_hdb->_map->_screenTileHeight / 2)) * kTileHeight;
			break;
		}
	}

	// Scan from centerY to top edge
	int miny = 0;
	for (int i = checky - 1; i >= checky - (g_hdb->_map->_screenTileHeight / 2); i--) {
		if (!getMapBGTileIndex(checkx, i)) {
			// +1 because we don't want to see one whole tile
			miny = (1 + i + (g_hdb->_map->_screenTileHeight / 2)) * kTileHeight;
			break;
		}
	}

	x = CLIP(x, minx, maxx);
	y = CLIP(y, miny, maxy);

	x -= (g_hdb->_screenDrawWidth / 2);
	y -= (g_hdb->_screenDrawHeight / 2);

	setMapXY(x, y);
}

bool Map::checkEntOnScreen(AIEntity *e) {
	return ((e->x > _mapX - 32) && (e->x < _mapX + g_hdb->_map->_screenXTiles * kTileWidth) && (e->y > _mapY - 32) && (e->y < _mapY + g_hdb->_map->_screenYTiles * kTileHeight));
}

bool Map::checkXYOnScreen(int x, int y) {
	return ((x > _mapX - 32) && (x < _mapX + g_hdb->_map->_screenXTiles * kTileWidth) && (y > _mapY - 32) && (y < _mapY + g_hdb->_map->_screenYTiles * kTileHeight));
}

bool Map::checkOneTileExistInRange(int tileIndex, int count) {
	for (int i = 0; i < _width * _height; i++) {
		if (_background[i] >= tileIndex && _background[i] < tileIndex + count)
			return true;
		if (_foreground[i] >= tileIndex && _foreground[i] < tileIndex + count)
			return true;
	}
	return true;
}

}
