// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "mojo/public/cpp/bindings/sync_event_watcher.h"

#include <algorithm>

#include "base/containers/stack_container.h"
#include "base/logging.h"

namespace mojo {

SyncEventWatcher::SyncEventWatcher(base::WaitableEvent* event,
                                   const base::Closure& callback)
    : event_(event),
      callback_(callback),
      registry_(SyncHandleRegistry::current()),
      destroyed_(new base::RefCountedData<bool>(false)) {}

SyncEventWatcher::~SyncEventWatcher() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (registered_)
    registry_->UnregisterEvent(event_, callback_);
  destroyed_->data = true;
}

void SyncEventWatcher::AllowWokenUpBySyncWatchOnSameThread() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  IncrementRegisterCount();
}

bool SyncEventWatcher::SyncWatch(const bool** stop_flags,
                                 size_t num_stop_flags) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  IncrementRegisterCount();
  if (!registered_) {
    DecrementRegisterCount();
    return false;
  }

  // This object may be destroyed during the Wait() call. So we have to preserve
  // the boolean that Wait uses.
  auto destroyed = destroyed_;

  constexpr size_t kFlagStackCapacity = 4;
  base::StackVector<const bool*, kFlagStackCapacity> should_stop_array;
  should_stop_array.container().push_back(&destroyed->data);
  std::copy(stop_flags, stop_flags + num_stop_flags,
            std::back_inserter(should_stop_array.container()));
  bool result = registry_->Wait(should_stop_array.container().data(),
                                should_stop_array.container().size());

  // This object has been destroyed.
  if (destroyed->data)
    return false;

  DecrementRegisterCount();
  return result;
}

void SyncEventWatcher::IncrementRegisterCount() {
  register_request_count_++;
  if (!registered_) {
    registry_->RegisterEvent(event_, callback_);
    registered_ = true;
  }
}

void SyncEventWatcher::DecrementRegisterCount() {
  DCHECK_GT(register_request_count_, 0u);
  register_request_count_--;
  if (register_request_count_ == 0 && registered_) {
    registry_->UnregisterEvent(event_, callback_);
    registered_ = false;
  }
}

}  // namespace mojo