//
//  Copyright (C) 2015 Google, Inc.
//
//  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 <base/at_exit.h>
#include <base/bind.h>
#include <base/command_line.h>
#include <base/location.h>
#include <base/logging.h>
#include <base/message_loop/message_loop.h>
#include <base/run_loop.h>

#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>

#include <android/bluetooth/IBluetooth.h>

#include "heart_rate_server.h"

using android::sp;
using android::OK;
using android::bluetooth::IBluetooth;

using android::getService;

namespace {

std::string kServiceName = "bluetooth-service";

void QuitMessageLoop() {
  // I don't know why both of these calls are necessary but the message loop
  // doesn't stop unless I call both. Bug in base::MessageLoop?
  base::RunLoop().Quit();
  base::MessageLoop::current()->QuitNow();
}

// Handles the case where the Bluetooth process dies.
class BluetoothDeathRecipient : public android::IBinder::DeathRecipient {
 public:
  explicit BluetoothDeathRecipient(
      scoped_refptr<base::SingleThreadTaskRunner> main_task_runner)
      : main_task_runner_(main_task_runner) {}

  ~BluetoothDeathRecipient() override = default;

  // android::IBinder::DeathRecipient override:
  void binderDied(const android::wp<android::IBinder>& /* who */) override {
    LOG(ERROR) << "The Bluetooth daemon has died. Aborting.";

    // binderDied executes on a dedicated thread. We need to stop the main loop
    // on the main thread so we post a message to it here. The main loop only
    // runs on the main thread.
    main_task_runner_->PostTask(FROM_HERE, base::Bind(&QuitMessageLoop));

    android::IPCThreadState::self()->stopProcess();
  }

 private:
  scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
};

}  // namespace

int main(int argc, char* argv[]) {
  base::AtExitManager exit_manager;
  base::CommandLine::Init(argc, argv);
  logging::LoggingSettings log_settings;

  // Initialize global logging based on command-line parameters (this is a
  // libchrome pattern).
  if (!logging::InitLogging(log_settings)) {
    LOG(ERROR) << "Failed to set up logging";
    return EXIT_FAILURE;
  }

  // Set up a message loop so that we can schedule timed Heart Rate
  // notifications.
  base::MessageLoop main_loop;

  LOG(INFO) << "Starting GATT Heart Rate Service sample";

  sp<IBluetooth> bluetooth;
  status_t status = getService(String16(kServiceName.c_str()), &bluetooth);
  if (status != OK) {
    LOG(ERROR) << "Failed to get service binder: '" << kServiceName
               << "' status=" << status;
    return EXIT_FAILURE;
  }

  // Bluetooth needs to be enabled for our demo to work.
  bool enabled;
  bluetooth->IsEnabled(&enabled);
  if (!enabled) {
    LOG(ERROR) << "Bluetooth is not enabled.";
    return EXIT_FAILURE;
  }

  // Register for death notifications on the IBluetooth binder. This let's us
  // handle the case where the Bluetooth daemon process (bluetoothtbd) dies
  // outside of our control.
  sp<BluetoothDeathRecipient> dr(
      new BluetoothDeathRecipient(main_loop.task_runner()));
  if (android::IInterface::asBinder(bluetooth.get())->linkToDeath(dr) !=
      android::NO_ERROR) {
    LOG(ERROR) << "Failed to register DeathRecipient for IBluetooth";
    return EXIT_FAILURE;
  }

  // Initialize the Binder process thread pool. We have to set this up,
  // otherwise, incoming callbacks from the Bluetooth daemon would block the
  // main thread (in other words, we have to do this as we are a "Binder
  // server").
  android::ProcessState::self()->startThreadPool();

  // heart_rate::HeartRateServer notifies success or failure asynchronously
  // using a closure, so we set up a lambda for that here.
  auto callback = [&](bool success) {
    if (success) {
      LOG(INFO) << "Heart Rate service started successfully";
      return;
    }

    LOG(ERROR) << "Starting Heart Rate server failed asynchronously";
    main_loop.QuitWhenIdle();
  };

  bool advertise =
      base::CommandLine::ForCurrentProcess()->HasSwitch("advertise");

  // Create the Heart Rate server.
  std::unique_ptr<heart_rate::HeartRateServer> hr(
      new heart_rate::HeartRateServer(bluetooth, main_loop.task_runner(),
                                      advertise));
  if (!hr->Run(callback)) {
    LOG(ERROR) << "Failed to start Heart Rate server";
    return EXIT_FAILURE;
  }

  // Run the main loop on the main process thread. Binder callbacks will be
  // received in dedicated threads set up by the ProcessState::startThreadPool
  // call above but we use this main loop for sending out heart rate
  // notifications.
  main_loop.Run();

  LOG(INFO) << "Exiting";
  return EXIT_SUCCESS;
}
