//
// Copyright 2017 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.
//

// clang-format off
// This needs to be included before Backtrace.h to avoid a redefinition
// of DISALLOW_COPY_AND_ASSIGN
#include "log.h"
// clang-format on

#include <client/linux/handler/exception_handler.h>
#include <gflags/gflags.h>
#include <unwindstack/AndroidUnwinder.h>

#include <fstream>
#include <future>
#include <optional>

#include "model/setup/async_manager.h"
#include "net/posix/posix_async_socket_connector.h"
#include "net/posix/posix_async_socket_server.h"
#include "test_environment.h"

using ::android::net::PosixAsyncSocketConnector;
using ::android::net::PosixAsyncSocketServer;
using rootcanal::AsyncManager;
using rootcanal::TestEnvironment;
using namespace rootcanal;

DEFINE_string(controller_properties_file, "", "deprecated");
DEFINE_string(configuration, "", "controller configuration (see config.proto)");
DEFINE_string(configuration_file, "", "controller configuration file path (see config.proto)");
DEFINE_string(default_commands_file, "", "deprecated");
DEFINE_bool(enable_log_color, false, "enable log colors");
DEFINE_bool(enable_hci_sniffer, false, "enable hci sniffer");
DEFINE_bool(enable_baseband_sniffer, false, "enable baseband sniffer");
DEFINE_bool(enable_pcap_filter, false, "enable PCAP filter");
DEFINE_bool(disable_address_reuse, false, "prevent rootcanal from reusing device addresses");
DEFINE_uint32(test_port, 6401, "test tcp port");
DEFINE_uint32(hci_port, 6402, "hci server tcp port");
DEFINE_uint32(link_port, 6403, "link server tcp port");
DEFINE_uint32(link_ble_port, 6404, "le link server tcp port");

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

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;
    ERROR("Process crashed, signal: {}[{}], tid: {}", strsignal(signal_number), signal_number,
          ctx->tid);
  } else {
    ERROR("Process crashed, signal: unknown, tid: unknown");
  }
  unwindstack::AndroidLocalUnwinder unwinder;
  unwindstack::AndroidUnwinderData data;
  if (!unwinder.Unwind(tid, data)) {
    ERROR("Unwind failed");
    return false;
  }
  ERROR("Backtrace:");
  for (const auto& frame : data.frames) {
    ERROR("{}", unwinder.FormatFrame(frame));
  }
  return true;
}

int main(int argc, 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);

  gflags::ParseCommandLineFlags(&argc, &argv, true);
  rootcanal::log::SetLogColorEnable(FLAGS_enable_log_color);

  INFO("starting rootcanal");

  if (FLAGS_test_port > UINT16_MAX) {
    ERROR("test_port out of range: {}", FLAGS_test_port);
    return -1;
  }

  if (FLAGS_hci_port > UINT16_MAX) {
    ERROR("hci_port out of range: {}", FLAGS_hci_port);
    return -1;
  }

  if (FLAGS_link_port > UINT16_MAX) {
    ERROR("link_port out of range: {}", FLAGS_link_port);
    return -1;
  }

  if (FLAGS_link_ble_port > UINT16_MAX) {
    ERROR("link_ble_port out of range: {}", FLAGS_link_ble_port);
    return -1;
  }

  std::string configuration_str;
  if (!FLAGS_configuration.empty()) {
    configuration_str = FLAGS_configuration;
  } else if (!FLAGS_configuration_file.empty()) {
    std::ifstream file(FLAGS_configuration_file);
    std::stringstream buffer;
    buffer << file.rdbuf();
    configuration_str.assign(buffer.str());
  }

  TestEnvironment root_canal(
          [](AsyncManager* am, int port) {
            return std::make_shared<PosixAsyncSocketServer>(port, am);
          },
          [](AsyncManager* am) { return std::make_shared<PosixAsyncSocketConnector>(am); },
          static_cast<int>(FLAGS_test_port), static_cast<int>(FLAGS_hci_port),
          static_cast<int>(FLAGS_link_port), static_cast<int>(FLAGS_link_ble_port),
          configuration_str, FLAGS_enable_hci_sniffer, FLAGS_enable_baseband_sniffer,
          FLAGS_enable_pcap_filter, FLAGS_disable_address_reuse);

  std::promise<void> barrier;
  std::future<void> barrier_future = barrier.get_future();
  root_canal.initialize(std::move(barrier));
  barrier_future.wait();
  root_canal.close();
  return 0;
}
