//
// Created by Dan Crosby on 7/2/20.
//

#include "ReduceCommandFactory.hpp"

#include "src/main/cpp/net/eagle0/shardok/library/commands/ReduceCommand.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/util/HexMapUtils.hpp"

namespace shardok {

auto ReduceCommandFactory::AddAvailableReduceCommands(
        CommandList &existingCommands,
        const Unit *unit,
        const ActionPoints remainingActionPoints,
        const Coords &position,
        const HexMap *map,
        const vector<PlayerId> &allyPids,
        const Units *units) const -> void {
    const PlayerId playerId = unit->player_id();
    if (!unit->has_attached_hero()) return;

    const auto *currentTerrain = GetTerrain(map, position);
    // Can only reduce if in a castle or if fortified
    if (!unit->fortified() && !currentTerrain->modifier().castle().present()) return;

    const auto &hero = unit->attached_hero();
    if (hero.profession_info().profession() ==
                net::eagle0::shardok::storage::fb::Profession_ENGINEER &&
        !IsWater(currentTerrain->type())) {
        const ActionCost cost =
                settings.ActionCostFor(settings.Backing().reduce_action_point_cost());

        if (cost.IsPossible(remainingActionPoints)) {
            const int range = settings.Backing().reduce_range();

            const CoordsSet coordsToCheck =
                    HexMapUtils::TilesWithinDistanceUnblockedByMountains(map, position, range);

            for (const Coords &reduceCoords : coordsToCheck) {
                // Don't allow reducing own position
                if (reduceCoords == position) continue;

                const auto *const occupant = Occupant(units, reduceCoords);
                if (occupant && occupant->player_id() == playerId) continue;

                const auto *const enemyOccupant =
                        KnownEnemyOccupant(playerId, units, allyPids, reduceCoords);

                const auto *terrain = GetTerrain(map, reduceCoords);

                // Allow reducing if there's a bridge or castle with integrity > 0, OR an enemy
                // occupant. We already continued in the case of friendly occupants.
                if (terrain->modifier().bridge().present() ||
                    (terrain->modifier().castle().present() &&
                     terrain->modifier().castle().integrity() > 0.0) ||
                    enemyOccupant) {
                    existingCommands.push_back(std::make_shared<ReduceCommand>(
                            settings.ActionCostFor(settings.Backing().reduce_action_point_cost()),
                            unit->player_id(),
                            unit->unit_id(),
                            reduceCoords,
                            settings.Backing().reduce_forest_bonus(),
                            settings.Backing().average_reduce_debuf(),
                            settings.Backing().unit_damage_per_reduce_debuf(),
                            settings.Backing().reduce_agility_xp(),
                            settings.Backing().reduce_strength_xp(),
                            settings.Backing().minimum_knowledge_for_profession(),
                            settings));
                }
            }
        }
    }
}

void ReduceCommandFactory::AddAvailableCommands(CommandList &commands, const CommandParams &params)
        const {
    AddAvailableReduceCommands(
            commands,
            params.unit,
            params.remainingActionPoints,
            params.position,
            params.hexMap,
            params.allyPids,
            params.units);
}

}  // namespace shardok