/* 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/crab.h"
#include "crab/XMLDoc.h"
#include "crab/input/cursor.h"
#include "crab/ui/map.h"

namespace Crab {

using namespace pyrodactyl::ui;
using namespace pyrodactyl::image;
using namespace pyrodactyl::input;

//------------------------------------------------------------------------
// Purpose: Load stuff that can't be modified by the user
//------------------------------------------------------------------------
void Map::load(const Common::Path &filename, pyrodactyl::event::Info &info) {
	XMLDoc conf(filename);
	if (conf.ready()) {
		rapidxml::xml_node<char> *node = conf.doc()->first_node("map");
		if (nodeValid(node)) {
			if (nodeValid("img", node)) {
				rapidxml::xml_node<char> *imgnode = node->first_node("img");
				loadNum(_speed, "speed", imgnode);

				for (auto n = imgnode->first_node("map"); n != nullptr; n = n->next_sibling("map"))
					_map.push_back(n);
			}

			if (nodeValid("fg", node))
				_fg.load(node->first_node("fg"));

			if (nodeValid("dim", node)) {
				loadNum(_camera.w, "x", node->first_node("dim"));
				loadNum(_camera.h, "y", node->first_node("dim"));
			}

			if (nodeValid("pos", node))
				_pos.load(node->first_node("pos"));

			if (nodeValid("scroll", node))
				_scroll.load(node->first_node("scroll"));

			if (nodeValid("marker", node))
				_marker.load(node->first_node("marker"));

			if (nodeValid("title", node))
				_title.load(node->first_node("title"));

			if (nodeValid("locations", node))
				_travel.load(node->first_node("locations"));

			if (nodeValid("overlay", node))
				_buOverlay.load(node->first_node("overlay"));
		}
	}

	setImage(_cur, true);
	update(info);
	calcBounds();
}

//------------------------------------------------------------------------
// Purpose: Draw
//------------------------------------------------------------------------
void Map::draw(pyrodactyl::event::Info &info) {

	// The map graphic is clipped to fit inside the UI
	_imgBg.draw(_pos.x, _pos.y, &_camera);

	if (_overlay) {
		// The overlay needs to be clipped as well, so we must find the intersection of the camera and the clip itself
		for (auto &i : _map[_cur]._reveal) {
			Rect r = i;
			int X = _pos.x + i.x - _camera.x, Y = _pos.y + i.y - _camera.y;

			// Do not draw any area of the clip that is outside the camera bounds

			// If we're outside the left edges, we need to cull the left point
			if (X < _pos.x) {
				X += _camera.x - i.x;
				r.x += _camera.x - i.x;
				r.w -= _camera.x - i.x;

				if (r.w < 0)
					r.w = 0;
			}

			if (Y < _pos.y) {
				Y += _camera.y - i.y;
				r.y += _camera.y - i.y;
				r.h -= _camera.y - i.y;

				if (r.h < 0)
					r.h = 0;
			}

			// If we're outside the right edge, we need to cull the width and height
			if (X + r.w > _pos.x + _camera.w)
				r.w = ABS(_pos.x + _camera.w - X); // abs to fix crash incase _pos.x + _camera.w < X
			if (Y + r.h > _pos.y + _camera.h)
				r.h = ABS(_pos.y + _camera.h - Y); // abs to fix crash incase _pos.y + _camera.h < Y

			_imgOverlay.draw(X, Y, &r);
		}
	}

	_travel.draw(_camera.x - _pos.x, _camera.y - _pos.y);

	_fg.draw();
	_buOverlay.draw();

	_title._text = info.curLocName();
	_title.draw();

	_marker.draw(_pos, _playerPos, _camera);

	_scroll.draw();
}

//------------------------------------------------------------------------
// Purpose: Center the world map on a spot
//------------------------------------------------------------------------
void Map::center(const Vector2i &vec) {
	_camera.x = vec.x - _camera.w / 2;
	_camera.y = vec.y - _camera.h / 2;
	validate();
}

//------------------------------------------------------------------------
// Purpose: Keep the camera in bounds and decide marker visibility
//------------------------------------------------------------------------
void Map::validate() {
	// Make all scroll buttons visible first
	for (auto &i : _scroll._element)
		i._visible = true;

	// Keep camera in bounds
	if (_camera.x + _camera.w > _size.x)
		_camera.x = _size.x - _camera.w;
	if (_camera.y + _camera.h > _size.y)
		_camera.y = _size.y - _camera.h;
	if (_camera.x < 0)
		_camera.x = 0;
	if (_camera.y < 0)
		_camera.y = 0;

	// decide visibility of scroll buttons
	_scroll._element[DIRECTION_RIGHT]._visible = !(_camera.x == _size.x - _camera.w);
	_scroll._element[DIRECTION_DOWN]._visible = !(_camera.y == _size.y - _camera.h);
	_scroll._element[DIRECTION_LEFT]._visible = !(_camera.x == 0);
	_scroll._element[DIRECTION_UP]._visible = !(_camera.y == 0);
}

//------------------------------------------------------------------------
// Purpose: Move
//------------------------------------------------------------------------
void Map::move(const Common::Event &event) {
	// Reset the velocity to avoid weirdness
	_vel.x = 0;
	_vel.y = 0;

	// We don't use the result, but this keeps the button states up to date
	_scroll.handleEvents(event);

	switch (event.type) {
	case Common::EVENT_LBUTTONDOWN:
	case Common::EVENT_RBUTTONDOWN: {
		bool click = false;
		int count = 0;
		for (auto &i : _scroll._element) {
			if (i.contains(g_engine->_mouse->_button)) {
				if (count == DIRECTION_UP)
					_vel.y = -1 * _speed;
				else if (count == DIRECTION_DOWN)
					_vel.y = _speed;
				else if (count == DIRECTION_RIGHT)
					_vel.x = _speed;
				else if (count == DIRECTION_LEFT)
					_vel.x = -1 * _speed;

				click = true;
			}
			count++;
		}

		if (!click) {
			_pan = true;
			_vel.x = 0;
			_vel.y = 0;
		} else
			_pan = false;
	} break;

	case Common::EVENT_LBUTTONUP:
	case Common::EVENT_RBUTTONUP:
		_pan = false;
		break;

	case Common::EVENT_MOUSEMOVE:
		if (_pan) {
			_camera.x -= g_engine->_mouse->_rel.x;
			_camera.y -= g_engine->_mouse->_rel.y;
			validate();
		}
		break;
	default:
		// Move the map camera if player presses the direction keys
		if (g_engine->_inputManager->state(IU_UP))
			_vel.y = -1 * _speed;
		else if (g_engine->_inputManager->state(IU_DOWN))
			_vel.y = _speed;
		else if (g_engine->_inputManager->state(IU_RIGHT))
			_vel.x = _speed;
		else if (g_engine->_inputManager->state(IU_LEFT))
			_vel.x = -1 * _speed;
		// Stop moving when we release a key (but only in that direction)
		else if (!g_engine->_inputManager->state(IU_UP) && !g_engine->_inputManager->state(IU_DOWN))
			_vel.y = 0;
		else if (!g_engine->_inputManager->state(IU_LEFT) && !g_engine->_inputManager->state(IU_RIGHT))
			_vel.x = 0;
		break;
	}
}

//------------------------------------------------------------------------
// Purpose: Internal Events
//------------------------------------------------------------------------
void Map::internalEvents(pyrodactyl::event::Info &info) {
	// The map overlay and button state should be in sync
	_buOverlay._state = _overlay;

	_camera.x += _vel.x;
	_camera.y += _vel.y;
	validate();

	for (auto &i : _travel._element)
		i._visible = i.x >= _camera.x && i.y >= _camera.y;

	_marker.internalEvents(_pos, _playerPos, _camera, _bounds);
}

//------------------------------------------------------------------------
// Purpose: Handle Events
//------------------------------------------------------------------------
bool Map::handleEvents(pyrodactyl::event::Info &info, const Common::Event &event) {
	int choice = _travel.handleEvents(event, -1 * _camera.x, -1 * _camera.y);
	if (choice >= 0) {
		_curLoc = _travel._element[choice]._loc;
		_pan = false;
		return true;
	}

	_marker.handleEvents(_pos, _playerPos, _camera, event);

	move(event);
	if (_buOverlay.handleEvents(event) == BUAC_LCLICK)
		_overlay = _buOverlay._state;

	return false;
}

void Map::setImage(const uint &val, const bool &force) {
	if (force || (_cur != val && val < _map.size())) {
		_cur = val;

		_imgBg.deleteImage();
		_imgOverlay.deleteImage();

		_imgBg.load(_map[_cur]._pathBg);
		_imgOverlay.load(_map[_cur]._pathOverlay);

		_size.x = _imgBg.w();
		_size.y = _imgBg.h();

		_marker.clear();
		for (auto &i : _map[_cur]._dest)
			_marker.addButton(i._name, i._pos.x, i._pos.y);

		_marker.assignPaths();
	}
}

//------------------------------------------------------------------------
// Purpose: Select the marker corresponding to a quest title
//------------------------------------------------------------------------
void Map::selectDest(const Common::String &name) {
	_marker.selectDest(name);
}

//------------------------------------------------------------------------
// Purpose: Update the status of the fast travel buttons
//------------------------------------------------------------------------
void Map::update(pyrodactyl::event::Info &info) {
	for (auto &i : _travel._element) {
		i._unlock.evaluate(info);
		i._visible = i._unlock.result();
	}
}

//------------------------------------------------------------------------
// Purpose: Add a rectangle to the revealed world map data
//------------------------------------------------------------------------
void Map::revealAdd(const int &id, const Rect &area) {
	if ((uint)id < _map.size()) {
		for (auto &i : _map[id]._reveal)
			if (i == area)
				return;

		_map[id]._reveal.push_back(area);
	}
}

//------------------------------------------------------------------------
// Purpose: Add or remove a destination marker from the world map
//------------------------------------------------------------------------
void Map::destAdd(const Common::String &name, const int &x, const int &y) {
	if (_cur < _map.size()) {
		for (auto &i : _map[_cur]._dest) {
			if (i._name == name) {
				i._pos.x = x;
				i._pos.y = y;
				return;
			}
		}

		_map[_cur].destAdd(name, x, y);
		_marker.addButton(name, x, y);
		_marker.assignPaths();
	}
}

void Map::destDel(const Common::String &name) {
	if (_cur < _map.size()) {
		for (auto i = _map[_cur]._dest.begin(); i != _map[_cur]._dest.end(); ++i) {
			if (i->_name == name) {
				_map[_cur]._dest.erase(i);
				break;
			}
		}

		_marker.erase(name);
	}
}

//------------------------------------------------------------------------
// Purpose: Save and load object state
//------------------------------------------------------------------------
void Map::saveState(rapidxml::xml_document<> &doc, rapidxml::xml_node<char> *root) {
	rapidxml::xml_node<char> *child = doc.allocate_node(rapidxml::node_element, "map");

	child->append_attribute(doc.allocate_attribute("cur", g_engine->_stringPool->get(_cur)));
	saveBool(_overlay, "overlay", doc, child);

	for (auto &r : _map) {
		rapidxml::xml_node<char> *child_data = doc.allocate_node(rapidxml::node_element, "data");
		r.saveState(doc, child_data);
		child->append_node(child_data);
	}

	root->append_node(child);
}

void Map::loadState(rapidxml::xml_node<char> *node) {
	if (nodeValid("map", node)) {
		rapidxml::xml_node<char> *mapnode = node->first_node("map");
		loadBool(_overlay, "overlay", mapnode);

		int val = _cur;
		loadNum(val, "cur", mapnode);

		auto r = _map.begin();
		for (rapidxml::xml_node<char> *n = mapnode->first_node("data"); n != nullptr && r != _map.end(); n = n->next_sibling("data"), ++r)
			r->loadState(n);

		setImage(val, true);
	}
}

//------------------------------------------------------------------------
// Purpose: Reset the UI positions in response to change in resolution
//------------------------------------------------------------------------
void Map::setUI() {
	_pos.setUI();
	_fg.setUI();

	_travel.setUI();
	_marker.setUI();

	_buOverlay.setUI();
	_scroll.setUI();
	_title.setUI();

	calcBounds();
}

} // End of namespace Crab
