/*
* ObjectGraph.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 "ObjectGraph.h"
#include "ObjectGraphCalculator.h"
#include "AIPathfinderConfig.h"
#include "../../../lib/CRandomGenerator.h"
#include "../../../CCallback.h"
#include "../../../lib/mapping/CMap.h"
#include "../Engine/Nullkiller.h"
#include "../../../lib/logging/VisualLogger.h"
#include "Actions/QuestAction.h"
#include "../pforeach.h"
#include "Actions/BoatActions.h"

namespace NKAI
{

bool ObjectGraph::tryAddConnection(
	const int3 & from,
	const int3 & to,
	float cost,
	uint64_t danger)
{
	auto result =  nodes[from].connections[to].update(cost, danger);
	auto & connection = nodes[from].connections[to];

	if(result && isVirtualBoat(to) && !connection.specialAction)
	{
		connection.specialAction = std::make_shared<AIPathfinding::BuildBoatActionFactory>(virtualBoats[to]);
	}

	return result;
}

void ObjectGraph::removeConnection(const int3 & from, const int3 & to)
{
	nodes[from].connections.erase(to);
}

void ObjectGraph::updateGraph(const Nullkiller * ai)
{
	auto cb = ai->cb;

	ObjectGraphCalculator calculator(this, ai);

	calculator.setGraphObjects();
	calculator.calculateConnections();
	calculator.addMinimalDistanceJunctions();
	calculator.calculateConnections();

	if(NKAI_GRAPH_TRACE_LEVEL >= 1)
		dumpToLog("graph");
}

void ObjectGraph::addObject(const CGObjectInstance * obj)
{
	if(!hasNodeAt(obj->visitablePos()))
		nodes[obj->visitablePos()].init(obj);
}

void ObjectGraph::addVirtualBoat(const int3 & pos, const CGObjectInstance * shipyard)
{
	if(!isVirtualBoat(pos))
	{
		virtualBoats[pos] = shipyard->id;
	}
}

void ObjectGraph::registerJunction(const int3 & pos)
{
	if(!hasNodeAt(pos))
		nodes[pos].initJunction();

}

void ObjectGraph::removeObject(const CGObjectInstance * obj)
{
	nodes[obj->visitablePos()].objectExists = false;

	if(obj->ID == Obj::BOAT && !isVirtualBoat(obj->visitablePos()))
	{
		vstd::erase_if(nodes[obj->visitablePos()].connections, [&](const std::pair<int3, ObjectLink> & link) -> bool
			{
				auto tile = cb->getTile(link.first, false);

				return tile && tile->isWater();
			});
	}
}

void ObjectGraph::connectHeroes(const Nullkiller * ai)
{
	for(auto obj : ai->memory->visitableObjs)
	{
		if(obj && obj->ID == Obj::HERO)
		{
			addObject(obj);
		}
	}

	for(auto & node : nodes)
	{
		auto pos = node.first;
		auto paths = ai->pathfinder->getPathInfo(pos);

		for(AIPath & path : paths)
		{
			if(path.getFirstBlockedAction())
				continue;

			auto heroPos = path.targetHero->visitablePos();

			nodes[pos].connections[heroPos].update(
				std::max(0.0f, path.movementCost()),
				path.getPathDanger());

			nodes[heroPos].connections[pos].update(
				std::max(0.0f, path.movementCost()),
				path.getPathDanger());
		}
	}
}

void ObjectGraph::dumpToLog(std::string visualKey) const
{
	logVisual->updateWithLock(visualKey, [&](IVisualLogBuilder & logBuilder)
		{
			for(auto & tile : nodes)
			{
				for(auto & node : tile.second.connections)
				{
					if(NKAI_GRAPH_TRACE_LEVEL >= 2)
					{
						logAi->trace(
							"%s -> %s: %f !%d",
							node.first.toString(),
							tile.first.toString(),
							node.second.cost,
							node.second.danger);
					}

					logBuilder.addLine(tile.first, node.first);
				}
			}
		});
}

}
