//
//  ZoneOfControlCalculator.cpp
//  eagle0_engine
//
//  Created by Dan Crosby on 12/30/18.
//  Copyright © 2018 none. All rights reserved.
//

#include "ZoneOfControlCalculator.hpp"

#include "HexMapUtils.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/unit/Unit.hpp"

namespace shardok {

auto ExertsZoneOfControl(const SettingsGetter &settings, const Unit *unit, const PlayerId toPid)
        -> bool {
    int opponentKnowledge = 0;
    if (unit->hidden()) return false;

    if (toPid != UNCONTROLLED_PLAYER_ID) {
        opponentKnowledge = (int)(*unit->opponent_knowledge())[toPid];
    }

    if (unit->stun_rounds_remaining() > 0) return false;
    if (opponentKnowledge >= settings.Backing().minimum_knowledge_for_battalion_stats() &&
        unit->battalion().morale() < settings.Backing().minimum_morale_to_act())
        return false;

    if (unit->has_attached_hero()) {
        const auto attachedHero = unit->attached_hero();
        if (opponentKnowledge >= settings.Backing().minimum_knowledge_for_hero_stats() &&
            attachedHero.vigor() < settings.Backing().minimum_vigor_to_act())
            return false;

        if (opponentKnowledge >= settings.Backing().minimum_knowledge_for_battalion_stats() &&
            attachedHero.control_info().controlled_unit_id() != -1)
            return false;

        if (opponentKnowledge >= settings.Backing().minimum_knowledge_for_profession()) {
            const auto castState = attachedHero.profession_info().meteor_cast_state();
            if (castState == net::eagle0::shardok::storage::fb::MultiroundMagicState_TARGET ||
                castState == net::eagle0::shardok::storage::fb::MultiroundMagicState_CAST)
                return false;
        }
    }

    return true;
}

auto ExertsZoneOfControl(const SettingsGetter &settings, const UnitView &unit) -> bool {
    if (unit.has_stun_rounds_remaining() && unit.stun_rounds_remaining().value() > 0) return false;
    if (unit.hidden()) return false;
    if (unit.battalion().has_morale() &&
        unit.battalion().morale().value() < settings.Backing().minimum_morale_to_act())
        return false;

    if (unit.has_attached_hero()) {
        const auto &attachedHero = unit.attached_hero();
        if (attachedHero.has_stats() &&
            attachedHero.stats().vigor() < settings.Backing().minimum_vigor_to_act())
            return false;
        if (attachedHero.has_control_info()) return false;

        if (attachedHero.has_profession_info()) {
            const auto castState = attachedHero.profession_info().meteor_cast_state();
            if (castState == net::eagle0::shardok::api::
                                     HeroView_ProfessionSpecificInfo_MultiroundMagicState_TARGET ||
                castState == net::eagle0::shardok::api::
                                     HeroView_ProfessionSpecificInfo_MultiroundMagicState_CAST)
                return false;
        }
    }

    return true;
}

auto AttackOrientationForAttack(
        const SettingsGetter &settings,
        const PlayerId attackingPlayer,
        const Units *units,
        const HexMap *map,
        const Coords &origin,
        const Coords &destination) -> AttackOrientation {
    const HexMapDirection direction = DirectionsTo(map, origin, destination).main;

    const auto &targetOccupant = Occupant(units, destination);

    const Unit *rearOccupant = MaybeOccupantInDirection(map, units, destination, direction);
    if (rearOccupant) {
        if (rearOccupant->player_id() == attackingPlayer &&
            ExertsZoneOfControl(settings, rearOccupant, targetOccupant->player_id())) {
            return AttackOrientation::rear;
        }
    }

    const Unit *flankOccupant = MaybeOccupantInDirection(map, units, destination, direction - 1);
    if (flankOccupant) {
        if (flankOccupant->player_id() == attackingPlayer &&
            ExertsZoneOfControl(settings, flankOccupant, targetOccupant->player_id())) {
            return AttackOrientation::flank;
        }
    }

    flankOccupant = MaybeOccupantInDirection(map, units, destination, direction + 1);
    if (flankOccupant) {
        if (flankOccupant->player_id() == attackingPlayer &&
            ExertsZoneOfControl(settings, flankOccupant, targetOccupant->player_id())) {
            return AttackOrientation::flank;
        }
    }

    return AttackOrientation::front;
}

[[nodiscard]] auto TilesInEnemyZoc(
        const SettingsGetter &settings,
        const PlayerId playerId,
        const Units *units,
        const HexMap *hexMap,
        const vector<PlayerId> &allyPids) -> CoordsSet {
    CoordsSet cs(hexMap);  // NOLINT(readability-identifier-length)
    for (const Unit *unit : *units) {
        if (unit->status() != net::eagle0::shardok::storage::fb::UnitStatus_NORMAL_UNIT) continue;
        if (unit->player_id() == playerId) continue;
        if (common::Contains(allyPids, unit->player_id())) continue;
        if (!ExertsZoneOfControl(settings, unit, playerId)) continue;

        cs += HexMapUtils::GetAdjacentCoords(hexMap, unit->location());
    }
    return cs;
}

[[nodiscard]] auto IsInEnemyZoc(
        const SettingsGetter &settings,
        const PlayerId playerId,
        const Units *units,
        const HexMap *hexMap,
        const vector<PlayerId> &allyPids,
        const Coords &coords) -> bool {
    CoordsSet adjCoords = HexMapUtils::GetAdjacentCoords(hexMap, coords);
    return std::any_of(std::begin(adjCoords), std::end(adjCoords), [&](const Coords &adjCoords) {
        const Unit *possibleZocEnemy = KnownEnemyOccupant(playerId, units, allyPids, adjCoords);
        return possibleZocEnemy && ExertsZoneOfControl(settings, possibleZocEnemy, playerId);
    });
}

}  // namespace shardok
