//
// Created by Dan Crosby on 3/23/20.
//

#include "ActionResultFilter.hpp"

#include "src/main/cpp/net/eagle0/shardok/library/unit/Unit.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/view_filters/GameStateFilter.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/view_filters/GameStateViewDiffer.hpp"
#include "src/main/protobuf/net/eagle0/shardok/common/action_type.pb.h"

namespace shardok {

using net::eagle0::shardok::api::ActionResultView;
using net::eagle0::shardok::storage::ActionResult;
using ActionResultType = net::eagle0::shardok::common::ActionType;

static auto IsHiddenFromOpponents(
        double minKnowledgeForProfession,
        ActionResultType type,
        double opponentKnowledgeOfActor,
        bool hiddenActor,
        bool wasHidden) -> bool;

static auto TargetIsHiddenFromOpponents(ActionResultType type) -> bool;

auto IsHiddenFromOpponents(
        const double minKnowledgeForProfession,
        const ActionResultType type,
        const double opponentKnowledgeOfActor,
        const bool hiddenActor,
        const bool wasHidden) -> bool {
    switch (type) {
        case ActionResultType::PLACE_HIDDEN_UNIT:
        case ActionResultType::SCOUTED: return true;

        case ActionResultType::METEOR_START:
        case ActionResultType::METEOR_TARGET:
        case ActionResultType::METEOR_CANCEL: {
            return opponentKnowledgeOfActor < minKnowledgeForProfession;
        }
        case ActionResultType::CONTROL_BROKEN: {
            return hiddenActor;
        }
        case ActionResultType::MOVE: {
            return hiddenActor && wasHidden;
        }
        default: return false;
    }
}

auto TargetIsHiddenFromOpponents(const ActionResultType type) -> bool {
    return type == net::eagle0::shardok::common::METEOR_TARGET;
}

[[nodiscard]] auto ActionResultFilteredForPlayer(
        const GameState *beforeState,
        const net::eagle0::shardok::api::GameStateView &beforeView,
        const net::eagle0::shardok::api::GameStateView &afterView,
        const SettingsGetter &settings,
        const ActionResult &result,
        const PlayerId askingPlayer) -> std::optional<ActionResultView> {
    double opponentKnowledgeOfActor = 0.0;
    bool hiddenActor = false;
    bool wasHidden = false;
    if (result.has_actor()) {
        const auto actorId = result.actor().value();
        for (const auto &unitData : result.changed_units_fb()) {
            const auto &unit = *(Unit *)unitData.data();
            if (unit.unit_id() == actorId) {
                opponentKnowledgeOfActor = OpponentKnowledge(unit, askingPlayer);
                hiddenActor = unit.hidden();
            }
        }
        wasHidden = beforeState && beforeState->units()->size() > actorId &&
                    beforeState->units()->Get(result.actor().value())->hidden();
    }
    if (IsHiddenFromOpponents(
                settings.Backing().minimum_knowledge_for_profession(),
                result.type(),
                opponentKnowledgeOfActor,
                hiddenActor,
                wasHidden) &&
        askingPlayer != result.player().value()) {
        return std::optional<ActionResultView>();
    }

    ActionResultView resultView{};
    resultView.set_type(result.type());
    if (result.has_player()) {
        resultView.mutable_player()->set_value(result.player().value());
    } else {
        resultView.clear_player();
    }

    if (result.has_actor()) {
        resultView.mutable_actor()->set_value(result.actor().value());
    } else {
        resultView.clear_actor();
    }

    if (result.has_target_unit()) {
        resultView.mutable_target_unit()->set_value(result.target_unit().value());
    } else {
        resultView.clear_target_unit();
    }

    if (result.has_target_coords() &&
        ((result.has_player() && result.player().value() == askingPlayer) ||
         !TargetIsHiddenFromOpponents(result.type()))) {
        *resultView.mutable_target_coords() = result.target_coords();
    } else {
        resultView.clear_target_coords();
    }

    const auto gsvDiff = MakeGameStateViewDiff(beforeView, afterView);

    if (gsvDiff.has_value()) { *resultView.mutable_game_state_view_diff() = gsvDiff.value(); }

    return resultView;
}

}  // namespace shardok
