// Copyright 2021 gRPC authors.
//
// 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 <grpc/support/port_platform.h>

#include "src/core/ext/filters/client_idle/idle_filter_state.h"

#include <assert.h>

namespace grpc_core {

IdleFilterState::IdleFilterState(bool start_timer)
    : state_(start_timer ? kTimerStarted : 0) {}

void IdleFilterState::IncreaseCallCount() {
  uintptr_t state = state_.load(std::memory_order_relaxed);
  uintptr_t new_state;
  do {
    // Increment the counter, and flag that there's been activity.
    new_state = state;
    new_state |= kCallsStartedSinceLastTimerCheck;
    new_state += kCallIncrement;
  } while (!state_.compare_exchange_weak(
      state, new_state, std::memory_order_acq_rel, std::memory_order_relaxed));
}

bool IdleFilterState::DecreaseCallCount() {
  uintptr_t state = state_.load(std::memory_order_relaxed);
  uintptr_t new_state;
  bool start_timer;
  do {
    start_timer = false;
    new_state = state;
    // Decrement call count (and assert there's at least one call outstanding!)
    assert(new_state >= kCallIncrement);
    new_state -= kCallIncrement;
    // If that decrement reaches a call count of zero and we have not started a
    // timer
    if ((new_state >> kCallsInProgressShift) == 0 &&
        (new_state & kTimerStarted) == 0) {
      // Flag that we will start a timer, and mark it started so nobody else
      // does.
      start_timer = true;
      new_state |= kTimerStarted;
      new_state &= ~kCallsInProgressShift;
    }
  } while (!state_.compare_exchange_weak(
      state, new_state, std::memory_order_acq_rel, std::memory_order_relaxed));
  return start_timer;
}

bool IdleFilterState::CheckTimer() {
  uintptr_t state = state_.load(std::memory_order_relaxed);
  uintptr_t new_state;
  bool start_timer;
  do {
    if ((state >> kCallsInProgressShift) != 0) {
      // Still calls in progress: nothing needs updating, just return
      // and keep the timer going!
      return true;
    }
    new_state = state;
    bool is_active = false;
    if (new_state & kCallsStartedSinceLastTimerCheck) {
      // If any calls started since the last time we checked, then consider the
      // channel still active and try again.
      is_active = true;
      new_state &= ~kCallsStartedSinceLastTimerCheck;
    }
    if (is_active) {
      // If we are still active, we should signal that the timer should start
      // again.
      start_timer = true;
    } else {
      // Otherwise, we should not start the timer again, and we should signal
      // that in the updated state.
      start_timer = false;
      new_state &= ~kTimerStarted;
    }
  } while (!state_.compare_exchange_weak(
      state, new_state, std::memory_order_acq_rel, std::memory_order_relaxed));
  return start_timer;
}

}  // namespace grpc_core
