/*
 * Copyright 2020 Bloomberg Finance LP
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <bytestreamservicer.h>

#include <buildboxcommon_commandline.h>
#include <buildboxcommon_exception.h>
#include <buildboxcommon_logging.h>

#include <grpcpp/server.h>
#include <grpcpp/server_builder.h>
#include <grpcpp/server_context.h>

#include <csignal>
#include <span>
#include <string>
#include <vector>

const std::string DEFAULT_BIND_ADDRESS = "localhost:50070";

std::unique_ptr<grpc::Server> server = nullptr;

void handleSignal(int signum)
{
    BUILDBOX_LOG_INFO("Received signal " << signum
                                         << ". Stopping server and exiting.");

    if (server) {
        server->Shutdown();
    }
}

void startServerAndWait(const std::string &bindAddress)
{
    LogStreamBytestreamServicer::DataReceivedCallback callback =
        [](const std::string &, const std::string &data, const bool) {
            // We write to stdout the data as we receive it:
            std::cout << data << std::flush;
        };

    auto servicer = std::make_shared<LogStreamBytestreamServicer>(callback);

    BUILDBOX_LOG_INFO("Setting up server...");
    grpc::ServerBuilder builder;
    builder.AddListeningPort(bindAddress, grpc::InsecureServerCredentials());
    builder.RegisterService(servicer.get());

    server = builder.BuildAndStart();
    BUILDBOX_LOG_INFO("Server started, listening on '" << bindAddress << "'");

    server->Wait(); // Blocks until signaled to exit.
}

int main(int argc, char *argv[])
{
    auto args = std::span(argv, argc);
    buildboxcommon::logging::Logger::getLoggerInstance().initialize(args[0]);

    // Setup signal handlers for SIGINT and SIGTERM
    struct sigaction sa{};
    sa.sa_handler = handleSignal;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    if (sigaction(SIGINT, &sa, nullptr) == -1) {
        BUILDBOX_LOG_ERROR("Unable to register signal handler for SIGINT");
    }
    if (sigaction(SIGTERM, &sa, nullptr) == -1) {
        BUILDBOX_LOG_ERROR("Unable to register signal handler for SIGTERM");
    }

    using buildboxcommon::CommandLineTypes;
    const std::vector<CommandLineTypes::ArgumentSpec> d_spec = {
        {"bind", "Address or socket to bind to",
         CommandLineTypes::TypeInfo(
             CommandLineTypes::DataType::COMMANDLINE_DT_STRING),
         CommandLineTypes::ArgumentSpec::O_OPTIONAL,
         CommandLineTypes::ArgumentSpec::C_WITH_ARG,
         CommandLineTypes::DefaultValue(DEFAULT_BIND_ADDRESS)},
    };

    buildboxcommon::CommandLine commandLine(d_spec);
    if (!commandLine.parse(argc, argv)) {
        commandLine.usage();
        return 1;
    }

    if (commandLine.exists("help")) {
        std::cout << "Listens for LogStream API ByteStream.Write() requests "
                     "and prints the received data to stdout.\n";
        return 0;
    }
    if (commandLine.exists("version")) {
        return 0;
    }

    const auto bindAddress = commandLine.getString("bind");

    try {
        startServerAndWait(bindAddress);
    }
    catch (const std::exception &e) {
        BUILDBOX_LOG_ERROR("ERROR: Caught exception ["
                           << e.what() << "] while running " << args[0]);
        return -1;
    }
}
