/*
 * Heal.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 "Heal.h"
#include "Registry.h"
#include "../ISpellMechanics.h"

#include "../../NetPacks.h"
#include "../../battle/IBattleState.h"
#include "../../battle/CUnitState.h"
#include "../../battle/CBattleInfoCallback.h"
#include "../../battle/Unit.h"
#include "../../serializer/JsonSerializeFormat.h"

VCMI_LIB_NAMESPACE_BEGIN


static const std::string EFFECT_NAME = "core:heal";

namespace spells
{
namespace effects
{

VCMI_REGISTER_SPELL_EFFECT(Heal, EFFECT_NAME);

void Heal::apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const
{
	apply(m->getEffectValue(), server, m, target);
}

void Heal::apply(int64_t value, ServerCallback * server, const Mechanics * m, const EffectTarget & target) const
{
	BattleLogMessage logMessage;
	BattleUnitsChanged pack;
	prepareHealEffect(value, pack, logMessage, *server->getRNG(), m, target);
	if(!pack.changedStacks.empty())
		server->apply(&pack);
	if(!logMessage.lines.empty())
		server->apply(&logMessage);
}

bool Heal::isValidTarget(const Mechanics * m, const battle::Unit * unit) const
{
	const bool onlyAlive = healLevel == EHealLevel::HEAL;
	const bool validInGenaral = unit->isValidTarget(!onlyAlive);

	if(!validInGenaral)
		return false;

	auto injuries = unit->getTotalHealth() - unit->getAvailableHealth();

	if(injuries == 0)
		return false;

	if(minFullUnits > 0)
	{
		auto hpGained = std::min(m->getEffectValue(), injuries);
		if(hpGained < minFullUnits * unit->MaxHealth())
			return false;
	}

	if(unit->isDead())
	{
		//check if alive unit blocks resurrection
		for(const BattleHex & hex : battle::Unit::getHexes(unit->getPosition(), unit->doubleWide(), unit->unitSide()))
		{
			auto blocking = m->battle()->battleGetUnitsIf([hex, unit](const battle::Unit * other)
			{
				return other->isValidTarget(false) && other->coversPos(hex) && other != unit;
			});

			if(!blocking.empty())
				return false;
		}
	}
	return true;
}

void Heal::serializeJsonUnitEffect(JsonSerializeFormat & handler)
{
	static const std::vector<std::string> HEAL_LEVEL_MAP =
	{
		"heal",
		"resurrect",
		"overHeal"
	};

	static const std::vector<std::string> HEAL_POWER_MAP =
	{
		"oneBattle",
		"permanent"
	};

	handler.serializeEnum("healLevel", healLevel, EHealLevel::HEAL, HEAL_LEVEL_MAP);
	handler.serializeEnum("healPower", healPower, EHealPower::PERMANENT, HEAL_POWER_MAP);
	handler.serializeInt("minFullUnits", minFullUnits);
}

void Heal::prepareHealEffect(int64_t value, BattleUnitsChanged & pack, BattleLogMessage & logMessage, RNG & rng, const Mechanics * m, const EffectTarget & target) const
{
	for(const auto & oneTarget : target)
	{
		const battle::Unit * unit = oneTarget.unitValue;

		if(unit)
		{
			auto unitHPgained = m->applySpellBonus(value, unit);

			auto state = unit->acquire();
			const auto countBeforeHeal = state->getCount();
			state->heal(unitHPgained, healLevel, healPower);

			if(const auto resurrectedCount = std::max(0, state->getCount() - countBeforeHeal))
			{
				// %d %s rise from the dead!
				// in the table first comes plural string, then the singular one
				MetaString resurrectText;
				state->addText(resurrectText, MetaString::GENERAL_TXT, 116, resurrectedCount == 1);
				state->addNameReplacement(resurrectText);
				resurrectText.addReplacement(resurrectedCount);
				logMessage.lines.push_back(std::move(resurrectText));
			}
			else if (unitHPgained > 0 && m->caster->getCasterUnitId() >= 0) //Show text about healed HP if healed by unit
			{
				MetaString healText;
				auto casterUnit = dynamic_cast<const battle::CUnitState*>(m->caster)->acquire();
				healText.addTxt(MetaString::GENERAL_TXT, 414);
				casterUnit->addNameReplacement(healText, false);
				state->addNameReplacement(healText, false);
				healText.addReplacement((int)unitHPgained);
				logMessage.lines.push_back(std::move(healText));
			}

			if(unitHPgained > 0)
			{
				UnitChanges info(state->unitId(), UnitChanges::EOperation::RESET_STATE);
				info.healthDelta = unitHPgained;
				state->save(info.data);
				pack.changedStacks.push_back(info);
			}
		}
	}
}


}
}

VCMI_LIB_NAMESPACE_END