// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 <signal.h>
#include <cstdlib>
#include <iostream>

#include <arrow/api.h>
#include <arrow/flight/api.h>
#include <gflags/gflags.h>
#include <grpc++/grpc++.h>

#include "examples/arrow/helloworld.grpc.pb.h"
#include "examples/arrow/helloworld.pb.h"

// Demonstrate registering a gRPC service alongside a Flight service
//
// The gRPC service can be accessed with a gRPC client, on the same
// port as the Flight service. Additionally, the CMake config for this
// example links against the gRPC reflection library, enabling tools
// like grpc_cli and grpcurl to list and call RPCs on the server
// without needing local copies of the Protobuf definitions.
// For example, with grpcurl (https://github.com/fullstorydev/grpcurl):
//
// grpcurl -d '{"name": "Rakka"}' -plaintext localhost:31337 HelloWorldService/SayHello
//
// Note that for applications that wish to follow the example here,
// care must be taken to ensure that Protobuf and gRPC are not
// multiply linked, else the resulting program may crash or silently
// corrupt data. In particular:
//
// * If dynamically linking Arrow Flight, then your application and
//   Arrow Flight must also dynamically link Protobuf and gRPC. (The
//   same goes for static linking.)
// * The Flight packages on some platforms may make this difficult,
//   because the Flight dynamic library will itself have statically
//   linked Protobuf and gRPC since the platform does not ship a
//   recent enough version of those dependencies.
// * The versions of Protobuf and gRPC must be the same between Flight
//   and your application.
//
// See "Using Arrow C++ in your own project" in the documentation.

DEFINE_int32(port, -1, "Server port to listen on");

namespace flight = ::arrow::flight;

// Flight service
class SimpleFlightServer : public flight::FlightServerBase {};

// gRPC service
class HelloWorldServiceImpl : public HelloWorldService::Service {
  grpc::Status SayHello(grpc::ServerContext* ctx, const HelloRequest* request,
                        HelloResponse* reply) override {
    const std::string& name = request->name();
    if (name.empty()) {
      return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Must provide a name!");
    }
    reply->set_reply("Hello, " + name);
    return grpc::Status::OK;
  }
};

arrow::Status RunFlightGrpc() {
  std::unique_ptr<flight::FlightServerBase> server;
  server.reset(new SimpleFlightServer());

  flight::Location bind_location;
  ARROW_RETURN_NOT_OK(
      flight::Location::ForGrpcTcp("0.0.0.0", FLAGS_port).Value(&bind_location));
  flight::FlightServerOptions options(bind_location);

  HelloWorldServiceImpl grpc_service;
  int extra_port = 0;

  options.builder_hook = [&](void* raw_builder) {
    auto* builder = reinterpret_cast<grpc::ServerBuilder*>(raw_builder);
    builder->AddListeningPort("0.0.0.0:0", grpc::InsecureServerCredentials(),
                              &extra_port);
    builder->RegisterService(&grpc_service);
  };
  ARROW_RETURN_NOT_OK(server->Init(options));
  std::cout << "Listening on ports " << FLAGS_port << " and " << extra_port << std::endl;
  ARROW_RETURN_NOT_OK(server->SetShutdownOnSignals({SIGTERM}));
  ARROW_RETURN_NOT_OK(server->Serve());

  return arrow::Status::OK();
}

int main(int argc, char** argv) {
  gflags::ParseCommandLineFlags(&argc, &argv, true);

  if (FLAGS_port < 0) {
    // For CI
    std::cout << "Must specify a port with -port" << std::endl;
    return EXIT_SUCCESS;
  }

  auto status = RunFlightGrpc();
  if (!status.ok()) {
    std::cerr << status.ToString() << std::endl;
    return EXIT_FAILURE;
  }

  return EXIT_SUCCESS;
}
