/*
 * Copyright 2019 The Android Open Source Project
 *
 * 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 <bluetooth/log.h>
#include <sys/types.h>
#include <unistd.h>

#include <csignal>
#include <cstring>
#include <optional>
#include <string>
#include <thread>

#include "stack_manager.h"

// clang-format off
#include <client/linux/handler/exception_handler.h>
#include <unwindstack/AndroidUnwinder.h>
// clang-format on

#include "facade/grpc_root_server.h"
#include "hal/hci_hal_host.h"
#include "hal/snoop_logger.h"
#include "os/parameter_provider.h"
#include "os/system_properties.h"

using ::bluetooth::ModuleList;
using ::bluetooth::StackManager;
using ::bluetooth::hal::HciHalHostRootcanalConfig;
using ::bluetooth::os::Thread;
using namespace bluetooth;

extern "C" const char* __asan_default_options() { return "detect_container_overflow=0"; }

namespace {
::bluetooth::facade::GrpcRootServer grpc_root_server;

std::promise<void> interrupt_promise;
std::future<void> interrupt_future;
bool interrupted = false;
struct sigaction old_act = {};
void interrupt_handler(int signal_number) {
  if (!interrupted) {
    interrupted = true;
    log::info("Stopping gRPC root server due to signal: {}[{}]", strsignal(signal_number),
              signal_number);
    interrupt_promise.set_value();
  } else {
    log::warn("Already interrupted by signal: {}[{}]", strsignal(signal_number), signal_number);
  }
  if (old_act.sa_handler != nullptr && old_act.sa_handler != SIG_IGN &&
      old_act.sa_handler != SIG_DFL) {
    log::info("Calling saved signal handler");
    old_act.sa_handler(signal_number);
  }
}
struct sigaction new_act = {.sa_handler = interrupt_handler};

bool crash_callback(const void* crash_context, size_t crash_context_size, void* /* context */) {
  std::optional<pid_t> tid;
  if (crash_context_size >= sizeof(google_breakpad::ExceptionHandler::CrashContext)) {
    auto* ctx = static_cast<const google_breakpad::ExceptionHandler::CrashContext*>(crash_context);
    tid = ctx->tid;
    int signal_number = ctx->siginfo.si_signo;
    log::error("Process crashed, signal: {}[{}], tid: {}", strsignal(signal_number), signal_number,
               ctx->tid);
  } else {
    log::error("Process crashed, signal: unknown, tid: unknown");
  }
  unwindstack::AndroidLocalUnwinder unwinder;
  unwindstack::AndroidUnwinderData data;
  if (!unwinder.Unwind(tid, data)) {
    log::error("Unwind failed");
    return false;
  }
  log::error("Backtrace:");
  for (const auto& frame : data.frames) {
    log::error("{}", unwinder.FormatFrame(frame));
  }
  return true;
}

// Need to stop server on a thread that is not part of a signal handler due to an issue with gRPC
// See: https://github.com/grpc/grpc/issues/24884
void thread_check_shutdown() {
  log::info("shutdown thread waiting for interruption");
  interrupt_future.wait();
  log::info("interrupted, stopping server");
  grpc_root_server.StopServer();
}

}  // namespace

// The entry point for the binary with libbluetooth + facades
int main(int argc, const char** argv) {
  google_breakpad::MinidumpDescriptor descriptor(
          google_breakpad::MinidumpDescriptor::kMicrodumpOnConsole);
  google_breakpad::ExceptionHandler eh(descriptor, nullptr, nullptr, nullptr, true, -1);
  eh.set_crash_handler(crash_callback);

  int root_server_port = 8897;
  int grpc_port = 8899;

  const std::string arg_grpc_root_server_port = "--root-server-port=";
  const std::string arg_grpc_server_port = "--grpc-port=";
  const std::string arg_rootcanal_port = "--rootcanal-port=";
  const std::string arg_btsnoop_path = "--btsnoop=";
  const std::string arg_btsnooz_path = "--btsnooz=";
  const std::string arg_btconfig_path = "--btconfig=";
  for (int i = 1; i < argc; i++) {
    std::string arg = argv[i];
    if (arg.find(arg_grpc_root_server_port) == 0) {
      auto port_number = arg.substr(arg_grpc_root_server_port.size());
      root_server_port = std::stoi(port_number);
    }
    if (arg.find(arg_grpc_server_port) == 0) {
      auto port_number = arg.substr(arg_grpc_server_port.size());
      grpc_port = std::stoi(port_number);
    }
    if (arg.find(arg_rootcanal_port) == 0) {
      auto port_number = arg.substr(arg_rootcanal_port.size());
      HciHalHostRootcanalConfig::Get()->SetPort(std::stoi(port_number));
    }
    if (arg.find(arg_btsnoop_path) == 0) {
      auto btsnoop_path = arg.substr(arg_btsnoop_path.size());
      ::bluetooth::os::ParameterProvider::OverrideSnoopLogFilePath(btsnoop_path);
      log::assert_that(::bluetooth::os::SetSystemProperty(
                               ::bluetooth::hal::SnoopLogger::kBtSnoopLogModeProperty,
                               ::bluetooth::hal::SnoopLogger::kBtSnoopLogModeFull),
                       "assert failed: ::bluetooth::os::SetSystemProperty( "
                       "::bluetooth::hal::SnoopLogger::kBtSnoopLogModeProperty, "
                       "::bluetooth::hal::SnoopLogger::kBtSnoopLogModeFull)");
    }
    if (arg.find(arg_btsnooz_path) == 0) {
      auto btsnooz_path = arg.substr(arg_btsnooz_path.size());
      ::bluetooth::os::ParameterProvider::OverrideSnoozLogFilePath(btsnooz_path);
    }
    if (arg.find(arg_btconfig_path) == 0) {
      auto btconfig_path = arg.substr(arg_btconfig_path.size());
      ::bluetooth::os::ParameterProvider::OverrideConfigFilePath(btconfig_path);
    }
  }

  int ret = sigaction(SIGINT, &new_act, &old_act);
  if (ret < 0) {
    log::error("sigaction error: {}", strerror(errno));
  }

  log::info("Starting Server");
  grpc_root_server.StartServer("0.0.0.0", root_server_port, grpc_port);
  log::info("Server started");
  auto wait_thread = std::thread([] { grpc_root_server.RunGrpcLoop(); });
  interrupt_future = interrupt_promise.get_future();
  auto shutdown_thread = std::thread{thread_check_shutdown};
  wait_thread.join();
  log::info("Server terminated");
  shutdown_thread.join();
  log::info("Shutdown thread terminated");

  return 0;
}
