//
//  ShardokAIClient.hpp
//  eagle0
//
//  Created by Dan Crosby on 10/18/17.
//  Copyright © 2017 none. All rights reserved.
//

#ifndef EAGLE0_SHARDOK_AI_SHARDOK_AI_CLIENT_HPP
#define EAGLE0_SHARDOK_AI_SHARDOK_AI_CLIENT_HPP

#include <chrono>
#include <optional>
#include <vector>

#include "src/main/cpp/net/eagle0/common/RandomGenerator.hpp"
#include "src/main/cpp/net/eagle0/common/mcts/abstract/MCTSTypes.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/AIConfig.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/AITimeBudget.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/AIWaterCrossingCommandChooser.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/IterativeDeepeningAI.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/score/AIScoreCalculator.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/ShardokCommand.hpp"
#include "src/main/protobuf/net/eagle0/shardok/api/game_state_view.pb.h"

namespace shardok {

using VictoryCondition = net::eagle0::shardok::storage::fb::VictoryCondition;

/// Results from AI command selection, including performance metrics.
struct CommandChoiceResults {
    size_t chosenIndex;            ///< Index of the chosen command in the available commands list
    size_t availableCommandCount;  ///< Total number of commands that were available to choose from
    int depthAchieved;             ///< Maximum search depth reached for the best command
    size_t commandCountEvaluated;  ///< Number of commands evaluated at the highest achieved depth
    EvaluationCompletionReason completionReason;  ///< Why evaluation stopped at this depth
};

//
//  A ShardokGameClient representing an AI player.
//
class ShardokAIClient {
private:
    const PlayerId playerId;
    const bool isDefender;
    const bool isAllAiBattle;  // Whether this battle has only AI players (for faster time budgets)
    const AIAlgorithmType aiAlgorithmType;
    const ScoringCalculatorType scoringCalculatorType;

    APDCache apdCache = std::make_shared<ActionPointDistancesCache>();
    ALCache alCache;

    const AIWaterCrossingCommandChooser waterCrossingCommandChooser;

    // MCTS configuration (only used when aiAlgorithmType == MCTS)
    mcts::MCTSConfig mctsConfig;

    [[nodiscard]] auto StandardChooseCommandIndex(
            const GameSettingsSPtr& settings,
            const GameStateW& guessedState,
            const CommandListSPtr& realAvailableCommands,
            std::optional<std::chrono::milliseconds> timeLimit) const -> CommandChoiceResults;
    [[nodiscard]] auto LateRoundAttackerChooseCommandIndex(
            const GameSettingsSPtr& settings,
            const GameStateW& guessedState,
            const CommandListSPtr& realAvailableCommands,
            std::optional<std::chrono::milliseconds> timeLimit) const -> CommandChoiceResults;
    [[nodiscard]] auto FinalRoundAttackerChooseCommandIndex(
            const GameSettingsSPtr& settings,
            const GameStateW& guessedState,
            const CommandListSPtr& realAvailableCommands,
            std::optional<std::chrono::milliseconds> timeLimit) const -> CommandChoiceResults;

public:
    explicit ShardokAIClient(
            PlayerId playerId,
            bool isDefender,
            bool isAllAiBattle,
            const HexMap* hexMap,
            const SettingsGetter& settings,
            AIAlgorithmType aiAlgorithmType,
            ScoringCalculatorType scoringCalculatorType,
            const mcts::MCTSConfig& mctsConfig);
    ~ShardokAIClient() = default;

    [[nodiscard]] auto GetPlayerId() const -> PlayerId { return playerId; }

    [[nodiscard]] auto ChooseCommandIndex(const ShardokEngine& engine) const
            -> CommandChoiceResults;

    // Overload that works on copies of state - allows caller to release lock during AI thinking
    [[nodiscard]] auto ChooseCommandIndex(
            const GameSettingsSPtr& settings,
            const net::eagle0::shardok::api::GameStateView& gsv,
            const CommandListSPtr& realAvailableCommands,
            std::optional<std::chrono::milliseconds> timeLimit) const -> CommandChoiceResults;

    // MCTS configuration methods (only relevant when using MCTS algorithm)
    [[nodiscard]] auto GetMCTSConfig() const -> const mcts::MCTSConfig& { return mctsConfig; }
    void SetMCTSConfig(const mcts::MCTSConfig& config) { mctsConfig = config; }
};
}  // namespace shardok

#endif  // EAGLE0_SHARDOK_AI_SHARDOK_AI_CLIENT_HPP
