/*
 * Copyright 2015 Ben Manes. All Rights Reserved.
 *
 * 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.
 */
package com.github.benmanes.caffeine.cache.buffer;

import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;

import com.github.benmanes.caffeine.cache.ReadBuffer;

/**
 * A bounded buffer that attempts to record once. This design has the benefit of retaining a
 * strict sequence and backing off on contention or when full. It uses a PTL scheme where the empty
 * slot is the next write counter value.
 * <p>
 * The negatives of this algorithm is that it uses a boxed instance of the write index to
 * track if the slot is free. This allows the buffer to be non-blocking, whereas PTL is blocking.
 *
 * https://blogs.oracle.com/dave/entry/ptlqueue_a_scalable_bounded_capacity
 *
 * @author ben.manes@gmail.com (Ben Manes)
 */
final class TicketBuffer<E> extends ReadBuffer<E> {
  final AtomicLong writeCounter;
  final AtomicReference<Object>[] buffer;

  long readCounter;

  @SuppressWarnings({"unchecked", "rawtypes"})
  TicketBuffer() {
    writeCounter = new AtomicLong();
    buffer = new AtomicReference[BUFFER_SIZE];
    for (int i = 0; i < BUFFER_SIZE; i++) {
      buffer[i] = new AtomicReference<>(new Turn(i));
    }
  }

  @Override
  public int offer(E e) {
    final long writeCount = writeCounter.get();

    final int index = (int) (writeCount & BUFFER_MASK);
    AtomicReference<Object> slot = buffer[index];
    Object value = slot.get();
    if (!(value instanceof Turn)) {
      // Either full or lost due to contention - try to drain
      return FULL;
    } else if (((Turn) value).id != writeCount) {
      // Ensures CAS reference equality, race should rarely occur
      return FAILED;
    }

    // Try to record, but we don't care if we win or lose
    if (slot.compareAndSet(value, e)) {
      writeCounter.lazySet(writeCount + 1);
      return SUCCESS;
    }
    return FAILED;
  }

  @Override
  public void drainTo(Consumer<E> consumer) {
    for (int i = 0; i < BUFFER_SIZE; i++) {
      final int index = (int) (readCounter & BUFFER_MASK);
      final AtomicReference<Object> slot = buffer[index];
      if (slot.get() instanceof Turn) {
        break;
      }
      long next = readCounter + BUFFER_SIZE;
      slot.lazySet(new Turn(next));
      readCounter++;
    }
  }

  @Override
  public int reads() {
    return (int) readCounter;
  }

  @Override
  public int writes() {
    return writeCounter.intValue();
  }

  static final class Turn {
    final long id;

    Turn(long id) {
      this.id = id;
    }

    @Override
    public String toString() {
      return Long.toString(id);
    }
  }
}
