//
//  shardok_server_main.cpp
//  eagle0
//
//  Created by Dan Crosby on 11/2/17.
//  Copyright © 2017 none. All rights reserved.
//

#include <memory>
#include <thread>

#include "src/main/cpp/net/eagle0/common/ProtobufWarningSuppression.hpp"

#pragma GCC diagnostic push
SUPPRESS_PROTOBUF_WARNINGS

#include <execinfo.h>
#include <grpcpp/channel.h>
#include <signal.h>
#include <unistd.h>

#pragma GCC diagnostic pop

#include "server/EagleInterfaceGrpcServer.hpp"
#include "server/ServerConfiguration.hpp"
#include "server/ShardokGamesManager.hpp"

using ::GameStatePersister;
using shardok::EagleInterfaceImpl;
using shardok::ShardokGamesManager;
using std::shared_ptr;
using std::string;
using std::unique_ptr;
using std::vector;

struct ServerThreadInfo {
    std::thread thread;
    std::shared_ptr<grpc::Service> service;
};

auto StartInThread(grpc::ServerBuilder *serverBuilder) -> ServerThreadInfo;

auto CreateServerBuilder(const string &serverAddress) -> grpc::ServerBuilder *;

auto CreateEagleInterfaceService(
        const std::shared_ptr<ServerConfiguration> &config,
        std::shared_ptr<ShardokGamesManager> shardokGamesManager) -> ServerThreadInfo;

void handler(const int sig) {
    void *array[10];
    size_t size;

    // get void*'s for all entries on the stack
    size = backtrace(array, 10);

    // print out all the frames to stderr
    fprintf(stderr, "Error: signal %d:\n", sig);
    backtrace_symbols_fd(array, size, STDERR_FILENO);
    exit(1);
}

auto main(const int argc, char **argv) -> int {
    (void)argc;
    (void)argv;

    signal(SIGSEGV, handler);  // install our handler

    FilesystemUtils::SetExecPath(argv[0]);

    auto extraSettings = std::vector<string>();
    extraSettings.reserve(argc - 1);
    for (int i = 1; i < argc; i++) { extraSettings.emplace_back(argv[i]); }

    const std::shared_ptr<ServerConfiguration> config = std::make_shared<ServerConfiguration>(
            FilesystemUtils::StaticEagle0FilesDirectory() + "/eagle0.conf");

    const auto shardokGamesManager = std::make_shared<ShardokGamesManager>(extraSettings);

    ServerThreadInfo eagleInterfaceInfo = CreateEagleInterfaceService(config, shardokGamesManager);

    eagleInterfaceInfo.thread.join();
}

auto CreateServerBuilder(const string &serverAddress) -> grpc::ServerBuilder * {
    auto *serverBuilder = new grpc::ServerBuilder;

    const auto credentials = grpc::InsecureServerCredentials();
    std::cout << "Creating insecure connection" << std::endl;

    serverBuilder->AddListeningPort(serverAddress, credentials);
    return serverBuilder;
}

auto CreateEagleInterfaceService(
        const std::shared_ptr<ServerConfiguration> &config,
        std::shared_ptr<ShardokGamesManager> shardokGamesManager) -> ServerThreadInfo {
    const string eagleInterfaceServerAddress =
            config->stringForKey(ServerConfiguration::kEagleInterfaceGrpcAddress);

    auto *eagleInterfaceServerBuilder = CreateServerBuilder(eagleInterfaceServerAddress);

    const auto eagleInterfaceService = std::make_shared<EagleInterfaceImpl>(shardokGamesManager);
    eagleInterfaceServerBuilder->RegisterService(eagleInterfaceService.get());

    ServerThreadInfo eagleInterfaceThreadInfo = StartInThread(eagleInterfaceServerBuilder);
    eagleInterfaceThreadInfo.service = eagleInterfaceService;
    std::cout << "Eagle interface server listening on " << eagleInterfaceServerAddress << std::endl;

    return eagleInterfaceThreadInfo;
}

auto StartInThread(grpc::ServerBuilder *serverBuilder) -> ServerThreadInfo {
    ServerThreadInfo info;
    std::mutex m{};
    std::condition_variable cv{};
    bool finished = false;

    std::unique_lock<std::mutex> lk(m);
    std::thread t([&serverBuilder, &finished, &m, &cv]() {
        const std::unique_ptr<grpc::Server> server(serverBuilder->BuildAndStart());

        {
            std::lock_guard<std::mutex> lk(m);
            finished = true;
            cv.notify_one();
        }

        server->Wait();
    });

    info.thread = std::move(t);
    cv.wait(lk, [&finished] { return finished; });
    lk.unlock();

    return info;
}
