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

/*
 * This code is based on the CRAB engine
 *
 * Copyright (c) Arvind Raja Yadav
 *
 * Licensed under MIT
 *
 */

#include "crab/image/ImageManager.h"
#include "crab/text/TextManager.h"
#include "crab/TMX/TMXMap.h"

namespace Crab {

using namespace TMX;
using namespace pyrodactyl::image;

static bool propCompare(const MapLayer &l1, const MapLayer &l2) {
	return l1._pos.y + l1._pos.h < l2._pos.y + l2._pos.h;
}

TMXMap::TMXMap() {
	_tileRows = 0;
	_tileCols = 0;
	_pathRows = 0;
	_pathCols = 0;

	_w = 0;
	_h = 0;
	_spriteLayer = 0;
	_grid = nullptr;

	_movementCosts._noWalk = 0;
	_movementCosts._open = 0;
	_movementCosts._stairs = 0;
}

//------------------------------------------------------------------------
// Purpose: Load stuff via a .tmx file set to xml storage (no compression)
//------------------------------------------------------------------------

void TMXMap::load(const Common::Path &path, const Common::String &filename) {
	XMLDoc conf(path.appendComponent(filename));
	if (conf.ready()) {
		rapidxml::xml_node<char> *node = conf.doc()->first_node("map");
		if (nodeValid(node)) {
			loadNum(_tileRows, "width", node);
			loadNum(_tileCols, "height", node);
			loadNum(_tileSize.x, "tilewidth", node);
			loadNum(_tileSize.y, "tileheight", node);

			// Pathfinding info load
			_pathSize.x = 0;
			_pathSize.y = 0;

			loadNum(_pathSize.x, "pathwidth", node);
			loadNum(_pathSize.y, "pathheight", node);

			// Load costs...Not sure if this is right. (SZ)
			loadNum(_movementCosts._open, "opencost", node);
			loadNum(_movementCosts._noWalk, "nowalkcost", node);
			loadNum(_movementCosts._stairs, "stairscost", node);

			// if(path_size.x == 0)
			//	path_size.x = tile_size.x;
			// if(path_size.y == 0)
			//	path_size.y = tile_size.y;
			// Testing
			if (_pathSize.x == 0)
				_pathSize.x = 40;
			if (_pathSize.y == 0)
				_pathSize.y = 40;

			_w = _tileRows * _tileSize.x;
			_h = _tileCols * _tileSize.y;

			_pathRows = (int)ceil((float)_w / (float)_pathSize.x + .5f); // Adding .5 before casting in order to round up (SZ)
			_pathCols = (int)ceil((float)_h / (float)_pathSize.y + .5f);

			g_engine->_imageManager->_tileset.load(path, node);

			// Reset the layer at which sprites are drawn
			_spriteLayer = 0;
			uint layerCount = 0;

			// We need to cycle through all tile and object layers in order to
			// see the level at which the sprites will be drawn
			for (auto groupnode = node->first_node(); groupnode != nullptr; groupnode = groupnode->next_sibling()) {
				// Store the name for easy comparison
				Common::String name = groupnode->name();

				if (name == "layer" || name == "imagelayer") { // Is this a tile or an image layer
					MapLayer l;
					l.load(path, groupnode);
					l._pos.x *= _tileSize.x;
					l._pos.y *= _tileSize.y;
					l._pos.w *= _tileSize.x;
					l._pos.h *= _tileSize.y;

					if (l._type == LAYER_PROP)
						_prop.push_back(l);
					else
						_layer.push_back(l);

					layerCount++;
				} else if (name == "objectgroup") { // Is this an object layer
					Common::String groupName;
					loadStr(groupName, "name", groupnode);
					if (groupName == "exit") {
						for (auto n = groupnode->first_node("object"); n != nullptr; n = n->next_sibling("object")) {
							pyrodactyl::level::Exit le(n);
							_areaExit.push_back(le);
						}
					} else if (groupName == "walk") {
						auto n = groupnode->first_node("object");
						if (n != nullptr)
							_areaWalk.load(n, true, "x", "y", "width", "height");
					} else if (groupName == "no_walk") {
						for (auto n = groupnode->first_node("object"); n != nullptr; n = n->next_sibling("object")) {
							Shape s;
							s.load(n);
							_areaNowalk.push_back(s);
						}
					} else if (groupName == "trigger") {
						for (auto n = groupnode->first_node("object"); n != nullptr; n = n->next_sibling("object")) {
							Shape s;
							s.load(n);

							uint pos = _areaTrig.size();
							loadNum(pos, "name", n);

							if (_areaTrig.size() <= pos)
								_areaTrig.resize(pos + 1);

							_areaTrig[pos] = s;
						}
					} else if (groupName == "stairs") {
						for (auto n = groupnode->first_node("object"); n != nullptr; n = n->next_sibling("object")) {
							pyrodactyl::level::Stairs s;
							s.load(n);
							_areaStairs.push_back(s);
						}
					} else if (groupName == "music") {
						for (auto n = groupnode->first_node("object"); n != nullptr; n = n->next_sibling("object")) {
							pyrodactyl::level::MusicArea ma;
							ma.load(n);
							_areaMusic.push_back(ma);
						}
					} else if (groupName == "sprites")
						_spriteLayer = layerCount;
				}
			}

			// Sort the props in the level according to y axis
			Common::sort(_prop.begin(), _prop.end(), propCompare);
		}
	}
}

//------------------------------------------------------------------------
// Purpose: Use this if you want to load a poly line from tmx
//------------------------------------------------------------------------
// void TMXMap::LoadPath(rapidxml::xml_node<char> *node)
//{
//	int pos = 0;
//	for (auto n = node->first_node("object"); n != nullptr; n = n->next_sibling("object"), ++pos)
//	{
//		Vector2i start;
//		start.load(n);
//
//		rapidxml::xml_node<char> *linenode = n->first_node("polyline");
//		if (linenode != nullptr)
//		{
//			Common::String points, x, y;
//			loadStr(points, "points", linenode);
//
//			path.resize(pos + 1);
//			bool comma = false;
//			for (auto i = points.begin(); i != points.end(); ++i)
//			{
//				if (*i == ',') comma = true;
//				else if (*i == ' ')
//				{
//					path[pos].push_back(GetPoint(start, x, y));
//					comma = false;
//					x.clear();
//					y.clear();
//				}
//				else if (comma) y.push_back(*i);
//				else x.push_back(*i);
//			}
//			path[pos].push_back(GetPoint(start, x, y));
//		}
//	}
//}
//------------------------------------------------------------------------
// Purpose: Convert point from string to vector
//------------------------------------------------------------------------
// const Vector2i TMXMap::GetPoint(const Vector2i &ref, Common::String &x, Common::String &y)
//{
//	Vector2i v;
//	v.x = ref.x + StringToNumber<int>(x);
//	v.y = ref.y + StringToNumber<int>(y);
//	return v;
//}

//------------------------------------------------------------------------
// Purpose: Clear all data from the level
//------------------------------------------------------------------------
void TMXMap::reset() {
	g_engine->_imageManager->_tileset.reset();
	_layer.clear();

	_areaNowalk.clear();
	_areaExit.clear();
	_areaTrig.clear();
	_areaStairs.clear();
	_areaMusic.clear();

	_prop.clear();
	_spriteLayer = 0;
}

//------------------------------------------------------------------------
// Purpose: Draw functions
//------------------------------------------------------------------------
void TMXMap::drawDebug(const Rect &camera) {
	using namespace pyrodactyl::text;

	for (auto &i : _areaTrig)
		i.draw(-camera.x, -camera.y, 0, 0, 254, 254);

	for (auto &i : _areaExit)
		i._dim.draw(-camera.x, -camera.y, 0, 254, 254, 254);

	for (auto &i : _prop)
		i._pos.draw(-camera.x, -camera.y, 254, 0, 254, 254);

	for (auto &i : _areaNowalk)
		i.draw(-camera.x, -camera.y, 254, 0, 0, 254);

	for (auto &i : _areaMusic)
		i.draw(-camera.x, -camera.y, 254, 254, 0, 254);

	for (auto &i : _areaStairs) {
		i.draw(-camera.x, -camera.y, 0, 254, 0, 254);
	}

	// Draw the pathfinding grid (SZ)
	for (int x = 0; x < _grid->getDimensions().x; ++x) {
		for (int y = 0; y < _grid->getDimensions().y; ++y) {
			if (_grid->getNodeAtCoords(x, y)->getMovementCost() < 0.0f)
				_grid->getNodeAtCoords(x, y)->getRect().draw(-camera.x, -camera.y, 0, 0, 0, 254);
		}
	}

	for (auto &i : _layer)
		i._pos.draw(-camera.x, -camera.y, 254, 216, 0);

	_areaWalk.draw(-camera.x, -camera.y, 254, 254, 254, 254);
}

//------------------------------------------------------------------------
// Purpose: Collision functions
//------------------------------------------------------------------------
void TMXMap::collideWithNoWalk(const Rect boundingBox, Common::List<CollisionData> &colliders) {
	CollisionData res;
	for (auto &i : _areaNowalk) {
		res = i.collide(boundingBox);
		if (res._intersect)
			colliders.push_back(res);
	}
}

bool TMXMap::insideNoWalk(const Vector2i &pos) {
	for (auto &i : _areaNowalk)
		if (i.contains(pos))
			return true;

	return false;
}

bool TMXMap::insideWalk(const Rect &boundingBox) {
	if (_areaWalk.contains(boundingBox))
		return true;

	return false;
}

bool TMXMap::insideWalk(const Vector2i &pos) {
	if (_areaWalk.contains(pos))
		return true;

	return false;
}

bool TMXMap::collideWithTrigger(const Rect rect, int index) {
	if (_areaTrig.size() > (uint)index)
		return _areaTrig[index].collide(rect)._intersect;

	return false;
}

void TMXMap::collideWithTrigger(const Rect rect, Common::Array<int> &collisionTable) {
	int index = 0;
	collisionTable.clear();

	for (auto i = _areaTrig.begin(); i != _areaTrig.end(); ++i, ++index)
		if (i->collide(rect)._intersect)
			collisionTable.push_back(index);
}

bool TMXMap::collideWithExit(const Rect rect, LevelResult &res) {
	for (const auto &i : _areaExit)
		if (i._dim.collide(rect)._intersect) {
			res._val = i._name;
			res._x = i._entry.x;
			res._y = i._entry.y;
			return true;
		}

	return false;
}

bool TMXMap::collideWithStairs(const Rect rect, Vector2f &velMod) {
	for (const auto &i : _areaStairs) {
		if (i.collide(rect)._intersect) {
			velMod = i._modifier;
			return true;
		}
	}

	// We are not colliding with any stairs, reset the modifier
	velMod.x = 1.0f;
	velMod.y = 1.0f;
	return false;
}

bool TMXMap::collideWithMusic(const Rect rect, pyrodactyl::level::MusicInfo &music) {
	for (const auto &i : _areaMusic) {
		if (i.collide(rect)._intersect) {
			music._id = i._id;
			music._track = i._track;
			music._loops = i._loops;
			return true;
		}
	}

	return false;
}

} // End of namespace Crab
