/*
 * Copyright 2018 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 "repeating_timer.h"

#include <base/functional/callback.h>
#include <bluetooth/log.h>
#include <com_android_bluetooth_flags.h>

#include <chrono>
#include <future>
#include <mutex>
#include <utility>

#include "message_loop_thread.h"

namespace bluetooth {

namespace common {

constexpr std::chrono::microseconds kMinimumPeriod = std::chrono::microseconds(1);

// This runs on user thread
RepeatingTimer::~RepeatingTimer() {
  std::lock_guard<std::recursive_mutex> api_lock(api_mutex_);
  bool is_running = false;
  if (com_android_bluetooth_flags_replace_message_loop_thread_with_gd_handler()) {
    is_running = message_loop_thread_ != nullptr && message_loop_thread_->IsRunning();
  } else {
    is_running =
            message_loop_thread_weak_ptr_ != nullptr && message_loop_thread_weak_ptr_->IsRunning();
  }

  if (is_running) {
    CancelAndWait();
  }
}

// This runs on user thread
bool RepeatingTimer::SchedulePeriodic(MessageLoopThread* thread, base::RepeatingClosure task,
                                      std::chrono::microseconds period) {
  if (period < kMinimumPeriod) {
    log::error("period must be at least {}", kMinimumPeriod.count());
    return false;
  }

  uint64_t time_now_us = clock_tick_us_();
  uint64_t time_next_task_us = time_now_us + period.count();
  std::lock_guard<std::recursive_mutex> api_lock(api_mutex_);
  if (thread == nullptr) {
    log::error("thread must be non-null");
    return false;
  }
  CancelAndWait();
  expected_time_next_task_us_ = time_next_task_us;
  task_ = std::move(task);
  task_wrapper_.Reset(base::Bind(&RepeatingTimer::RunTask, base::Unretained(this)));
  if (com_android_bluetooth_flags_replace_message_loop_thread_with_gd_handler()) {
    message_loop_thread_ = thread;
  } else {
    message_loop_thread_weak_ptr_ = thread->GetWeakPtr();
  }
  period_ = period;
  uint64_t time_until_next_us = time_next_task_us - clock_tick_us_();
  if (!thread->DoInThreadDelayed(task_wrapper_.callback(),
                                 std::chrono::microseconds(time_until_next_us))) {
    log::error("failed to post task to message loop for thread {}", thread->ToString());
    expected_time_next_task_us_ = 0;
    task_wrapper_.Cancel();
    message_loop_thread_ = nullptr;
    message_loop_thread_weak_ptr_ = nullptr;
    period_ = {};
    return false;
  }
  return true;
}

// This runs on user thread
void RepeatingTimer::Cancel() {
  std::promise<void> promise;
  CancelHelper(std::move(promise));
}

// This runs on user thread
void RepeatingTimer::CancelAndWait() {
  std::promise<void> promise;
  auto future = promise.get_future();
  CancelHelper(std::move(promise));
  future.wait();
}

// This runs on user thread
void RepeatingTimer::CancelHelper(std::promise<void> promise) {
  std::lock_guard<std::recursive_mutex> api_lock(api_mutex_);
  MessageLoopThread* scheduled_thread;

  if (com_android_bluetooth_flags_replace_message_loop_thread_with_gd_handler()) {
    scheduled_thread = message_loop_thread_;
    if (scheduled_thread == nullptr) {
      promise.set_value();
      return;
    }
    if (scheduled_thread->IsRunningOnSameThread()) {
      CancelClosure(std::move(promise));
      return;
    }
  } else {
    scheduled_thread = message_loop_thread_weak_ptr_.get();
    if (scheduled_thread == nullptr) {
      promise.set_value();
      return;
    }
    if (scheduled_thread->GetThreadId() == base::PlatformThread::CurrentId()) {
      CancelClosure(std::move(promise));
      return;
    }
  }

  scheduled_thread->DoInThread(base::BindOnce(&RepeatingTimer::CancelClosure,
                                              base::Unretained(this), std::move(promise)));
}

// This runs on message loop thread
void RepeatingTimer::CancelClosure(std::promise<void> promise) {
  message_loop_thread_weak_ptr_ = nullptr;
  message_loop_thread_ = nullptr;
  task_wrapper_.Cancel();
#if BASE_VER < 927031
  task_ = {};
#else
  task_ = base::NullCallback();
#endif
  period_ = std::chrono::microseconds(0);
  expected_time_next_task_us_ = 0;
  promise.set_value();
}

// This runs on user thread
bool RepeatingTimer::IsScheduled() const {
  std::lock_guard<std::recursive_mutex> api_lock(api_mutex_);
  if (com_android_bluetooth_flags_replace_message_loop_thread_with_gd_handler()) {
    return message_loop_thread_ != nullptr && message_loop_thread_->IsRunning();
  }

  return message_loop_thread_weak_ptr_ != nullptr && message_loop_thread_weak_ptr_->IsRunning();
}

// This runs on message loop thread
void RepeatingTimer::RunTask() {
  bool is_running = false;
  if (com_android_bluetooth_flags_replace_message_loop_thread_with_gd_handler()) {
    is_running = message_loop_thread_ != nullptr && message_loop_thread_->IsRunning();
  } else {
    is_running =
            message_loop_thread_weak_ptr_ != nullptr && message_loop_thread_weak_ptr_->IsRunning();
  }
  if (!is_running) {
    log::error("message_loop_thread_ is null or is not running");
    return;
  }

  if (com_android_bluetooth_flags_replace_message_loop_thread_with_gd_handler()) {
    log::assert_that(message_loop_thread_->IsRunningOnSameThread(),
                     "task must run on message loop thread");
  } else {
    log::assert_that(
            message_loop_thread_weak_ptr_->GetThreadId() == base::PlatformThread::CurrentId(),
            "task must run on message loop thread");
  }

  int64_t period_us = period_.count();
  expected_time_next_task_us_ += period_us;
  uint64_t time_now_us = clock_tick_us_();
  int64_t remaining_time_us = expected_time_next_task_us_ - time_now_us;
  if (remaining_time_us < 0) {
    // if remaining_time_us is negative, schedule the task to the nearest
    // multiple of period
    remaining_time_us = (remaining_time_us % period_us + period_us) % period_us;
  }
  if (com_android_bluetooth_flags_replace_message_loop_thread_with_gd_handler()) {
    message_loop_thread_->DoInThreadDelayed(task_wrapper_.callback(),
                                            std::chrono::microseconds(remaining_time_us));
  } else {
    message_loop_thread_weak_ptr_->DoInThreadDelayed(task_wrapper_.callback(),
                                                     std::chrono::microseconds(remaining_time_us));
  }
  uint64_t time_before_task_us = clock_tick_us_();
  task_.Run();
  uint64_t time_after_task_us = clock_tick_us_();
  auto task_time_us = static_cast<int64_t>(time_after_task_us - time_before_task_us);
  if (task_time_us > period_.count()) {
    log::error("Periodic task execution took {} microseconds, longer than interval {} microseconds",
               task_time_us, period_.count());
  }
}

}  // namespace common

}  // namespace bluetooth
