/*
 * mapHandler.cpp, part of VCMI engine
 *
 * Authors: listed in file AUTHORS in main folder
 *
 * License: GNU General Public License v2.0 or later
 * Full text of license available in license.txt file, in main folder
 *
 */

#include "StdInc.h"
#include "IMapRendererObserver.h"
#include "mapHandler.h"

#include "../CCallback.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../gui/CGuiHandler.h"

#include "../../lib/texts/CGeneralTextHandler.h"
#include "../../lib/TerrainHandler.h"
#include "../../lib/mapObjectConstructors/CObjectClassesHandler.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/mapObjects/ObjectTemplate.h"
#include "../../lib/mapping/CMap.h"

bool CMapHandler::hasOngoingAnimations()
{
	for(auto * observer : observers)
		if(observer->hasOngoingAnimations())
			return true;

	return false;
}

void CMapHandler::waitForOngoingAnimations()
{
	for(auto * observer : observers)
	{
		if (observer->hasOngoingAnimations())
			observer->waitForOngoingAnimations();
	}
}

void CMapHandler::endNetwork()
{
	for(auto * observer : observers)
		observer->endNetwork();
}

std::string CMapHandler::getTerrainDescr(const int3 & pos, bool rightClick) const
{
	const TerrainTile & t = map->getTile(pos);

	if(t.hasFavorableWinds())
		return CGI->objtypeh->getObjectName(Obj::FAVORABLE_WINDS, 0);

	std::string result = t.terType->getNameTranslated();

	for(const auto & object : map->objects)
	{
		if(object && object->coveringAt(pos.x, pos.y) && object->pos.z == pos.z && object->isTile2Terrain())
		{
			result = object->getObjectName();
			break;
		}
	}

	if(LOCPLINT->cb->getTileDigStatus(pos, false) == EDiggingStatus::CAN_DIG)
	{
		return boost::str(
			boost::format(rightClick ? "%s\r\n%s" : "%s %s") // New line for the Message Box, space for the Status Bar
			% result % CGI->generaltexth->allTexts[330]
		); // 'digging ok'
	}

	return result;
}

bool CMapHandler::compareObjectBlitOrder(const CGObjectInstance * a, const CGObjectInstance * b)
{
	//FIXME: Optimize
	// this method is called A LOT on game start and some parts, e.g. for loops are too slow for that

	assert(a && b);
	if(!a)
		return true;
	if(!b)
		return false;

	// Background objects will always be placed below foreground objects
	if(a->appearance->printPriority != 0 || b->appearance->printPriority != 0)
	{
		if(a->appearance->printPriority != b->appearance->printPriority)
			return a->appearance->printPriority > b->appearance->printPriority;

		//Two background objects will be placed based on their placement order on map
		return a->id < b->id;
	}

	int aBlocksB = 0;
	int bBlocksA = 0;

	for(const auto & aOffset : a->getBlockedOffsets())
	{
		int3 testTarget = a->pos + aOffset + int3(0, 1, 0);
		if(b->blockingAt(testTarget.x, testTarget.y))
			bBlocksA += 1;
	}

	for(const auto & bOffset : b->getBlockedOffsets())
	{
		int3 testTarget = b->pos + bOffset + int3(0, 1, 0);
		if(a->blockingAt(testTarget.x, testTarget.y))
			aBlocksB += 1;
	}

	// Discovered by experimenting with H3 maps - object priority depends on how many tiles of object A are "blocked" by object B
	// For example if blockmap of two objects looks like this:
	//  ABB
	//  AAB
	// Here, in middle column object A has blocked tile that is immediately below tile blocked by object B
	// Meaning, object A blocks 1 tile of object B and object B blocks 0 tiles of object A
	// In this scenario in H3 object A will always appear above object B, irregardless of H3M order
	if(aBlocksB != bBlocksA)
		return aBlocksB < bBlocksA;

	// object that don't have clear priority via tile blocking will appear based on their row
	if(a->pos.y != b->pos.y)
		return a->pos.y < b->pos.y;

	// heroes should appear on top of objects on the same tile
	if(b->ID==Obj::HERO && a->ID!=Obj::HERO)
		return true;
	if(b->ID!=Obj::HERO && a->ID==Obj::HERO)
		return false;

	// or, if all other tests fail to determine priority - simply based on H3M order
	return a->id < b->id;
}

CMapHandler::CMapHandler(const CMap * map)
	: map(map)
{
}

const CMap * CMapHandler::getMap()
{
	return map;
}

bool CMapHandler::isInMap(const int3 & tile)
{
	return map->isInTheMap(tile);
}

void CMapHandler::onObjectFadeIn(const CGObjectInstance * obj, const PlayerColor & initiator)
{
	for(auto * observer : observers)
		observer->onObjectFadeIn(obj, initiator);
}

void CMapHandler::onObjectFadeOut(const CGObjectInstance * obj, const PlayerColor & initiator)
{
	for(auto * observer : observers)
		observer->onObjectFadeOut(obj, initiator);
}

void CMapHandler::onBeforeHeroEmbark(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
{
	for(auto * observer : observers)
		observer->onBeforeHeroEmbark(obj, from, dest);
}

void CMapHandler::onAfterHeroEmbark(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
{
	for(auto * observer : observers)
		observer->onAfterHeroEmbark(obj, from, dest);
}

void CMapHandler::onBeforeHeroDisembark(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
{
	for(auto * observer : observers)
		observer->onBeforeHeroDisembark(obj, from, dest);
}

void CMapHandler::onAfterHeroDisembark(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
{
	for(auto * observer : observers)
		observer->onAfterHeroDisembark(obj, from, dest);
}

void CMapHandler::onObjectInstantAdd(const CGObjectInstance * obj, const PlayerColor & initiator)
{
	for(auto * observer : observers)
		observer->onObjectInstantAdd(obj, initiator);
}

void CMapHandler::onObjectInstantRemove(const CGObjectInstance * obj, const PlayerColor & initiator)
{
	for(auto * observer : observers)
		observer->onObjectInstantRemove(obj, initiator);
}

void CMapHandler::onAfterHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
{
	assert(obj->pos == dest);
	for(auto * observer : observers)
		observer->onAfterHeroTeleported(obj, from, dest);
}

void CMapHandler::onBeforeHeroTeleported(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
{
	assert(obj->pos == from);
	for(auto * observer : observers)
		observer->onBeforeHeroTeleported(obj, from, dest);
}

void CMapHandler::onHeroMoved(const CGHeroInstance * obj, const int3 & from, const int3 & dest)
{
	assert(obj->pos == dest);
	for(auto * observer : observers)
		observer->onHeroMoved(obj, from, dest);
}

void CMapHandler::addMapObserver(IMapObjectObserver * object)
{
	observers.push_back(object);
}

void CMapHandler::removeMapObserver(IMapObjectObserver * object)
{
	vstd::erase(observers, object);
}

IMapObjectObserver::IMapObjectObserver()
{
	CGI->mh->addMapObserver(this);
}

IMapObjectObserver::~IMapObjectObserver()
{
	if (CGI && CGI->mh)
		CGI->mh->removeMapObserver(this);
}