/*
 *  Copyright 2020 The WebRTC Project Authors. All rights reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "rtc_base/callback_list.h"

#include "rtc_base/checks.h"

namespace webrtc {
namespace callback_list_impl {

CallbackListReceivers::CallbackListReceivers() = default;

CallbackListReceivers::~CallbackListReceivers() {
  RTC_CHECK(!send_in_progress_);
}

void CallbackListReceivers::RemoveReceivers(const void* removal_tag) {
  RTC_DCHECK(removal_tag);

  // We divide the receivers_ vector into three regions: from right to left, the
  // "keep" region, the "todo" region, and the "remove" region. The "todo"
  // region initially covers the whole vector.
  size_t first_todo = 0;                    // First element of the "todo"
                                            // region.
  size_t first_remove = receivers_.size();  // First element of the "remove"
                                            // region.

  // Loop until the "todo" region is empty.
  while (first_todo != first_remove) {
    if (receivers_[first_todo].removal_tag != removal_tag) {
      // The first element of the "todo" region should be kept. Move the
      // "keep"/"todo" boundary.
      ++first_todo;
    } else if (receivers_[first_remove - 1].removal_tag == removal_tag) {
      // The last element of the "todo" region should be removed. Move the
      // "todo"/"remove" boundary.
      if (send_in_progress_) {
        // Tag this receiver for removal, which will be done when `ForEach`
        // has completed.
        receivers_[first_remove - 1].removal_tag = pending_removal_tag();
      }
      --first_remove;
    } else if (!send_in_progress_) {
      // The first element of the "todo" region should be removed, and the last
      // element of the "todo" region should be kept. Swap them, and then shrink
      // the "todo" region from both ends.
      RTC_DCHECK_NE(first_todo, first_remove - 1);
      using std::swap;
      swap(receivers_[first_todo], receivers_[first_remove - 1]);
      RTC_DCHECK_NE(receivers_[first_todo].removal_tag, removal_tag);
      ++first_todo;
      RTC_DCHECK_EQ(receivers_[first_remove - 1].removal_tag, removal_tag);
      --first_remove;
    }
  }

  if (!send_in_progress_) {
    // Discard the remove region.
    receivers_.resize(first_remove);
  }
}

void CallbackListReceivers::Foreach(
    rtc::FunctionView<void(UntypedFunction&)> fv) {
  RTC_CHECK(!send_in_progress_);
  bool removals_detected = false;
  send_in_progress_ = true;
  for (auto& r : receivers_) {
    RTC_DCHECK_NE(r.removal_tag, pending_removal_tag());
    fv(r.function);
    if (r.removal_tag == pending_removal_tag()) {
      removals_detected = true;
    }
  }
  send_in_progress_ = false;
  if (removals_detected) {
    RemoveReceivers(pending_removal_tag());
  }
}

template void CallbackListReceivers::AddReceiver(
    const void*,
    UntypedFunction::TrivialUntypedFunctionArgs<1>);
template void CallbackListReceivers::AddReceiver(
    const void*,
    UntypedFunction::TrivialUntypedFunctionArgs<2>);
template void CallbackListReceivers::AddReceiver(
    const void*,
    UntypedFunction::TrivialUntypedFunctionArgs<3>);
template void CallbackListReceivers::AddReceiver(
    const void*,
    UntypedFunction::TrivialUntypedFunctionArgs<4>);
template void CallbackListReceivers::AddReceiver(
    const void*,
    UntypedFunction::NontrivialUntypedFunctionArgs);
template void CallbackListReceivers::AddReceiver(
    const void*,
    UntypedFunction::FunctionPointerUntypedFunctionArgs);

template void CallbackListReceivers::AddReceiver(
    UntypedFunction::TrivialUntypedFunctionArgs<1>);
template void CallbackListReceivers::AddReceiver(
    UntypedFunction::TrivialUntypedFunctionArgs<2>);
template void CallbackListReceivers::AddReceiver(
    UntypedFunction::TrivialUntypedFunctionArgs<3>);
template void CallbackListReceivers::AddReceiver(
    UntypedFunction::TrivialUntypedFunctionArgs<4>);
template void CallbackListReceivers::AddReceiver(
    UntypedFunction::NontrivialUntypedFunctionArgs);
template void CallbackListReceivers::AddReceiver(
    UntypedFunction::FunctionPointerUntypedFunctionArgs);

}  // namespace callback_list_impl
}  // namespace webrtc
