//
//  ShardokGamesManager.cpp
//  Server
//
//  Created by Dan Crosby on 10/29/17.
//  Copyright © 2017 none. All rights reserved.
//

#include "ShardokGamesManager.hpp"

#include <memory>
#include <shared_mutex>
#include <utility>

#include "ServerConfiguration.hpp"
#include "src/main/cpp/net/eagle0/common/ContainerUtils.hpp"
#include "src/main/cpp/net/eagle0/shardok/ai/ShardokAIClient.hpp"
#include "src/main/cpp/net/eagle0/shardok/controller/ShardokGameController.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/action_point_distances/FixedActionPointDistances.hpp"
#include "src/main/cpp/net/eagle0/shardok/library/settings_loader/SettingsLoader.hpp"
#include "src/main/cpp/net/eagle0/shardok/util/MapLoader.hpp"
#include "src/main/flatbuffer/net/eagle0/shardok/storage/unit.hpp"

namespace shardok {
using std::shared_ptr;

auto SetUpController(
        const GameSettingsSPtr &gameSettings,
        const GameId &gameId,
        const string &mapName,
        const string &customMapBytes,
        int month,
        const vector<Unit> &units,
        const vector<PlayerInfoProto> &playerSetupInfos,
        const string &serializedRequest) -> shared_ptr<ShardokGameController>;

ShardokGamesManager::ShardokGamesManager(const std::vector<std::string> &extraSettings) {
    gameSettings = SettingsLoader::LoadSettings();

    for (const string &str : extraSettings) {
        std::stringstream stream(str);
        string key, value;
        std::getline(stream, key, '=');
        std::getline(stream, value);
        std::cerr << key << "   <<    " << value << std::endl;
        std::cerr << "NOT SETTING" << std::endl;
        //        setter.SetRaw(key, value);
    }

    FixedActionPointDistances::SetCacheDirectory(
            FilesystemUtils::CacheFilesDirectory() + "apdCache/");
}

auto ShardokGamesManager::GetController(const GameId &gameId)
        -> std::shared_ptr<ShardokGameController> {
    std::shared_lock<std::shared_mutex> lk(runningControllersLock);
    const auto existing = runningControllers.find(gameId);
    if (existing == runningControllers.end()) return nullptr;

    return existing->second;
}

auto ShardokGamesManager::UnlockedCreateFromGameStateBytes(
        const byte_vector &gameStateBytes,
        int32_t startingHistoryCount,
        const string &mapName) -> GameId {
    std::shared_lock<std::shared_mutex> lk(runningControllersLock);

    auto gs = GameStateW::FromByteVector(gameStateBytes);

    const auto controller = std::make_shared<ShardokGameController>(
            std::make_unique<ShardokEngine>(gameSettings, gs, startingHistoryCount),
            mapName);

    runningControllers[gs->game_id()->str()] = controller;
    return gs->game_id()->str();
}

auto ShardokGamesManager::UnlockedCreateSpecifiedGame(
        const string &gameId,
        const vector<PlayerInfoWithUnits> &playerSetupInfos,
        const string &mapName,
        const string &customMapBytes,
        const int month,
        const string &serializedRequest) -> GameId {
    // Check for existing game with this ID and early-exit
    std::shared_lock<std::shared_mutex> lk(runningControllersLock);
    const auto existingGame = runningControllers.find(gameId);

    // If it exists, register new handlers and early-exit
    if (existingGame != runningControllers.end()) {
        if (serializedRequest != existingGame->second->SerializedRequest()) {
            printf("Got a new game request that does not match the existing one\n");
        }
        printf("Found a game with game_id %s\n", gameId.c_str());
        return gameId;
    }

    vector<PlayerInfoProto> playerInfos{};
    std::transform(
            std::begin(playerSetupInfos),
            std::end(playerSetupInfos),
            std::back_inserter(playerInfos),
            [](const PlayerInfoWithUnits &psi) { return psi.playerInfo; });

    vector<Unit> units{};
    for (const auto &info : playerSetupInfos) {
        units.insert(std::end(units), std::begin(info.units), std::end(info.units));
    }

    const shared_ptr<ShardokGameController> controller = SetUpController(
            gameSettings,
            gameId,
            mapName,
            customMapBytes,
            month,
            units,
            playerInfos,
            serializedRequest);

    return LockedCreateGame(controller, playerSetupInfos, serializedRequest);
}

auto ShardokGamesManager::LockedCreateGame(
        const shared_ptr<ShardokGameController> &controller,
        const vector<PlayerInfoWithUnits> &playerInfos,
        const string &serializedRequest) -> GameId {
    std::vector<std::shared_ptr<ShardokAIClient>> aiClients{};

    GameId newGameId = controller->GetGameId();

    runningControllers[newGameId] = controller;

    return newGameId;
}

auto SetUpController(
        const GameSettingsSPtr &gameSettings,
        const GameId &gameId,
        const string &mapName,
        const string &customMapBytes,
        const int month,
        const vector<Unit> &units,
        const vector<PlayerInfoProto> &playerInfos,
        const string &serializedRequest) -> shared_ptr<ShardokGameController> {
    auto unplacedUnits = std::vector<Unit>();

    UnitId nextUnitId = 0;
    for (auto up : units) {
        up.mutate_unit_id(nextUnitId++);
        up.mutate_status(net::eagle0::shardok::storage::fb::UnitStatus_RESERVE_UNIT);
        unplacedUnits.push_back(up);
    }

    unique_ptr<ShardokEngine> engine = std::make_unique<ShardokEngine>(
            gameSettings,
            std::move(unplacedUnits),
            customMapBytes.empty() ? LoadMap(mapName) : LoadMapFromBytes(customMapBytes),
            month,
            gameId,
            playerInfos);

    return std::make_shared<ShardokGameController>(std::move(engine), mapName, serializedRequest);
}

}  // namespace shardok
