//
//  ShardokGameController.hpp
//  eagle0_engine
//
//  Created by Dan Crosby on 7/9/17.
//  Copyright © 2017 none. All rights reserved.
//

#ifndef ShardokGameController_hpp
#define ShardokGameController_hpp

#include <functional>
#include <mutex>
#include <utility>
#include <vector>

#include "GameUpdateReceiver.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/ShardokAIClient.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/ShardokEngine.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/UnitPlacementInfo.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/fb_helpers/GameStateHelpers.hpp"
#include "src/main/protobuf/net/eagle0/shardok/api/game_state_view.pb.h"

namespace shardok {
using net::eagle0::shardok::api::ActionResultView;
using net::eagle0::shardok::api::AvailableCommands;
using std::optional;
using std::shared_ptr;
using std::unique_ptr;
using std::weak_ptr;

class ShardokGameController {
private:
    // This lock should be held any time we call into engine or modify clients.
    std::mutex masterLock{};
    std::condition_variable aiCondition{};
    bool aiThreadKeepGoing = true;

    // Fires whenever there is a new game state update.
    mutable std::condition_variable updateCondition{};

    string serializedRequest;

    unique_ptr<ShardokEngine> engine;

    std::atomic_int incomingRegistrations = 0;

    const string mapName;
    const string logFilePath;

    vector<shared_ptr<ShardokAIClient>> aiClients;
    std::thread aiThread;

    static auto MakeLogFilePath() -> string {
        const time_t timer = time(nullptr);
        char buf[255];
        strftime(buf, 255, "/tmp/playthrough_%Y_%m_%d_%H%M%S.log", localtime(&timer));
        return string(buf);
    }

    void LockedNotifyClients() const;

    auto LockedCheckOneAICommand() -> bool;

    auto LockedAIClientForPid(PlayerId pid) const -> shared_ptr<ShardokAIClient>;

    static vector<shared_ptr<ShardokAIClient>> MakeAIClients(const unique_ptr<ShardokEngine> &e);

    void DoAIThread();

public:
    ShardokGameController(
            unique_ptr<ShardokEngine> e,
            string mapName,
            string serializedRequest = "")
        : serializedRequest(std::move(serializedRequest)),
          engine(std::move(e)),
          mapName(std::move(mapName)),
          logFilePath(MakeLogFilePath()),
          aiClients(MakeAIClients(engine)),
          aiThread(&ShardokGameController::DoAIThread, this) {
        std::unique_lock lk(masterLock);
        aiCondition.notify_one();
    }

    ~ShardokGameController();

    auto SerializedRequest() const -> string { return serializedRequest; }

    void PostCommand(
            PlayerId shardokPlayerId,
            int eagleFactionId,
            int64_t token,
            int64_t commandIndex,
            optional<int32_t> roll);

    void PostPlacementCommands(
            PlayerId shardokPlayerId,
            int eagleFactionId,
            int64_t token,
            const vector<UnitPlacementInfo> &infos);

    struct OnePlayerUpdates {
        int32_t eagleFactionId;
        vector<ActionResultView> resultViews;
        shared_ptr<AvailableCommands> availableCommands;

        OnePlayerUpdates(
                const int32_t fid,
                const vector<ActionResultView> &arvs,
                const shared_ptr<AvailableCommands> &acs)
            : eagleFactionId(fid),
              resultViews(arvs),
              availableCommands(acs) {}
    };

    struct AllUpdates {
        vector<ActionResult> mainResults;
        vector<OnePlayerUpdates> filteredResults;
        int32_t newUnfilteredCount;
    };
    auto GetUpdates(int64_t startingActionId) -> AllUpdates;
    auto GetCurrentGameStateBytes() -> byte_vector;

    auto GameIsOver() -> bool;
    auto GameOverStatus() -> net::eagle0::shardok::common::GameStatus;
    auto ResolvedPlayerInfos() -> vector<net::eagle0::shardok::common::PlayerInfo>;
    auto EndGameUnits() -> vector<net::eagle0::shardok::storage::ResolvedUnit>;

    [[nodiscard]] auto GetGameId() const -> GameId { return engine->GetGameId(); }

    [[nodiscard]] auto GetHexMap() const -> const HexMap * {
        return engine->GetCurrentGameState()->hex_map();
    }

    [[nodiscard]] auto GetLogFilePath() const -> string { return logFilePath; }
};
}  // namespace shardok

#endif /* ShardokGameController_hpp */
