/*
 * TurnInfo.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 "TurnInfo.h"

#include "../IGameCallback.h"
#include "../IGameSettings.h"
#include "../TerrainHandler.h"
#include "../VCMI_Lib.h"
#include "../bonuses/BonusList.h"
#include "../json/JsonNode.h"
#include "../mapObjects/CGHeroInstance.h"
#include "../mapObjects/MiscObjects.h"

VCMI_LIB_NAMESPACE_BEGIN

TConstBonusListPtr TurnInfoBonusList::getBonusList(const CGHeroInstance * target, const CSelector & bonusSelector)
{
	std::lock_guard guard(bonusListMutex);

	if (target->getTreeVersion() == bonusListVersion)
		return bonusList;

	bonusList = target->getBonuses(bonusSelector);
	bonusListVersion = target->getTreeVersion();

	return bonusList;
}

int TurnInfo::hasWaterWalking() const
{
	return waterWalkingTest;
}

int TurnInfo::hasFlyingMovement() const
{
	return flyingMovementTest;
}

int TurnInfo::hasNoTerrainPenalty(const TerrainId &terrain) const
{
	return noterrainPenalty[terrain.num];
}

int TurnInfo::hasFreeShipBoarding() const
{
	return freeShipBoardingTest;
}

int TurnInfo::getFlyingMovementValue() const
{
	return flyingMovementValue;
}

int TurnInfo::getWaterWalkingValue() const
{
	return waterWalkingValue;
}

int TurnInfo::getRoughTerrainDiscountValue() const
{
	return roughTerrainDiscountValue;
}

int TurnInfo::getMovePointsLimitLand() const
{
	return movePointsLimitLand;
}

int TurnInfo::getMovePointsLimitWater() const
{
	return movePointsLimitWater;
}

TurnInfo::TurnInfo(TurnInfoCache * sharedCache, const CGHeroInstance * target, int Turn)
	: target(target)
	, noterrainPenalty(VLC->terrainTypeHandler->size())
{
	CSelector daySelector = Selector::days(Turn);

	int lowestSpeed;
	if (target->getTreeVersion() == sharedCache->heroLowestSpeedVersion)
	{
		lowestSpeed = sharedCache->heroLowestSpeedValue;
	}
	else
	{
		lowestSpeed = target->getLowestCreatureSpeed();
		sharedCache->heroLowestSpeedValue = lowestSpeed;
		sharedCache->heroLowestSpeedVersion = target->getTreeVersion();
	}

	{
		static const CSelector selector = Selector::type()(BonusType::WATER_WALKING);
		const auto & bonuses = sharedCache->waterWalking.getBonusList(target, selector);
		waterWalkingTest = bonuses->getFirst(daySelector) != nullptr;
		waterWalkingValue = bonuses->valOfBonuses(daySelector);
	}

	{
		static const CSelector selector = Selector::type()(BonusType::FLYING_MOVEMENT);
		const auto & bonuses = sharedCache->flyingMovement.getBonusList(target, selector);
		flyingMovementTest = bonuses->getFirst(daySelector) != nullptr;
		flyingMovementValue = bonuses->valOfBonuses(daySelector);
	}

	{
		static const CSelector selector = Selector::type()(BonusType::FREE_SHIP_BOARDING);
		const auto & bonuses = sharedCache->freeShipBoarding.getBonusList(target, selector);
		freeShipBoardingTest = bonuses->getFirst(daySelector) != nullptr;
	}

	{
		static const CSelector selector = Selector::type()(BonusType::ROUGH_TERRAIN_DISCOUNT);
		const auto & bonuses = sharedCache->roughTerrainDiscount.getBonusList(target, selector);
		roughTerrainDiscountValue = bonuses->getFirst(daySelector) != nullptr;
	}

	{
		static const CSelector selector = Selector::typeSubtype(BonusType::MOVEMENT, BonusCustomSubtype::heroMovementSea);
		const auto & vectorSea = target->cb->getSettings().getValue(EGameSettings::HEROES_MOVEMENT_POINTS_SEA).Vector();
		const auto & bonuses = sharedCache->movementPointsLimitWater.getBonusList(target, selector);
		int baseMovementPointsSea;
		if (lowestSpeed < vectorSea.size())
			baseMovementPointsSea = vectorSea[lowestSpeed].Integer();
		else
			baseMovementPointsSea = vectorSea.back().Integer();

		movePointsLimitWater = bonuses->valOfBonuses(daySelector, baseMovementPointsSea);
	}

	{
		static const CSelector selector = Selector::typeSubtype(BonusType::MOVEMENT, BonusCustomSubtype::heroMovementLand);
		const auto & vectorLand = target->cb->getSettings().getValue(EGameSettings::HEROES_MOVEMENT_POINTS_LAND).Vector();
		const auto & bonuses = sharedCache->movementPointsLimitLand.getBonusList(target, selector);
		int baseMovementPointsLand;
		if (lowestSpeed < vectorLand.size())
			baseMovementPointsLand = vectorLand[lowestSpeed].Integer();
		else
			baseMovementPointsLand = vectorLand.back().Integer();

		movePointsLimitLand = bonuses->valOfBonuses(daySelector, baseMovementPointsLand);
	}

	{
		static const CSelector selector = Selector::type()(BonusType::NO_TERRAIN_PENALTY);
		const auto & bonuses = sharedCache->noTerrainPenalty.getBonusList(target, selector);
		for (const auto & bonus : *bonuses)
		{
			TerrainId affectedTerrain = bonus->subtype.as<TerrainId>();
			noterrainPenalty.at(affectedTerrain.num) = true;
		}

		const auto nativeTerrain = target->getNativeTerrain();
		if (nativeTerrain.hasValue())
			noterrainPenalty.at(nativeTerrain.num) = true;

		if (nativeTerrain == ETerrainId::ANY_TERRAIN)
			boost::range::fill(noterrainPenalty, true);
	}
}

bool TurnInfo::isLayerAvailable(const EPathfindingLayer & layer) const
{
	switch(layer.toEnum())
	{
	case EPathfindingLayer::AIR:
		if(target && target->boat && target->boat->layer == EPathfindingLayer::AIR)
			break;

		if(!hasFlyingMovement())
			return false;

		break;

	case EPathfindingLayer::WATER:
		if(target && target->boat && target->boat->layer == EPathfindingLayer::WATER)
			break;

		if(!hasWaterWalking())
			return false;

		break;
	}

	return true;
}

int TurnInfo::getMaxMovePoints(const EPathfindingLayer & layer) const
{
	return layer == EPathfindingLayer::SAIL ? getMovePointsLimitWater() : getMovePointsLimitLand();
}

VCMI_LIB_NAMESPACE_END