/*
 * Copyright 2016 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;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;

import java.util.concurrent.atomic.AtomicInteger;

import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import com.github.benmanes.caffeine.testing.Awaits;
import com.github.benmanes.caffeine.testing.ConcurrentTestHarness;

/**
 * @author ben.manes@gmail.com (Ben Manes)
 */
public final class MpscGrowableArrayQueueTest {
  private static final int NUM_PRODUCERS = 10;
  private static final int PRODUCE = 100;

  private static final int POPULATED_SIZE = 10;
  private static final int FULL_SIZE = 32;

  /* ---------------- Size -------------- */

  @Test(dataProvider = "empty")
  public void size_whenEmpty(MpscGrowableArrayQueue<Integer> buffer) {
    assertThat(buffer.size(), is(0));
  }

  @Test(dataProvider = "populated")
  public void size_whenPopulated(MpscGrowableArrayQueue<Integer> buffer) {
    assertThat(buffer.size(), is(POPULATED_SIZE));
  }

  /* ---------------- Offer -------------- */

  @Test(dataProvider = "empty")
  public void offer_whenEmpty(MpscGrowableArrayQueue<Integer> buffer) {
    assertThat(buffer.offer(1), is(true));
    assertThat(buffer.size(), is(1));
  }

  @Test(dataProvider = "populated")
  public void offer_whenPopulated(MpscGrowableArrayQueue<Integer> buffer) {
    assertThat(buffer.offer(1), is(true));
    assertThat(buffer.size(), is(POPULATED_SIZE + 1));
  }

  @Test(dataProvider = "full")
  public void offer_whenFull(MpscGrowableArrayQueue<Integer> buffer) {
    assertThat(buffer.offer(1), is(false));
    assertThat(buffer.size(), is(FULL_SIZE));
  }

  /* ---------------- Poll -------------- */

  @Test(dataProvider = "empty")
  public void poll_whenEmpty(MpscGrowableArrayQueue<Integer> buffer) {
    assertThat(buffer.poll(), is(nullValue()));
  }

  @Test(dataProvider = "populated")
  public void poll_whenPopulated(MpscGrowableArrayQueue<Integer> buffer) {
    assertThat(buffer.poll(), is(not(nullValue())));
    assertThat(buffer.size(), is(POPULATED_SIZE - 1));
  }

  @Test(dataProvider = "full")
  public void poll_toEmpty(MpscGrowableArrayQueue<Integer> buffer) {
    while (buffer.poll() != null) {}
    assertThat(buffer.size(), is(0));
  }

  /* ---------------- Concurrency -------------- */

  @Test(dataProvider = "empty")
  public void oneProducer_oneConsumer(MpscGrowableArrayQueue<Integer> buffer) {
    AtomicInteger started = new AtomicInteger();
    AtomicInteger finished = new AtomicInteger();

    ConcurrentTestHarness.execute(() -> {
      started.incrementAndGet();
      Awaits.await().untilAtomic(started, is(2));
      for (int i = 0; i < PRODUCE; i++) {
        while (!buffer.offer(i)) {}
      }
      finished.incrementAndGet();
    });
    ConcurrentTestHarness.execute(() -> {
      started.incrementAndGet();
      Awaits.await().untilAtomic(started, is(2));
      for (int i = 0; i < PRODUCE; i++) {
        while (buffer.poll() == null) {}
      }
      finished.incrementAndGet();
    });

    Awaits.await().untilAtomic(finished, is(2));
    assertThat(buffer.size(), is(0));
  }

  @Test(dataProvider = "empty")
  public void manyProducers_noConsumer(MpscGrowableArrayQueue<Integer> buffer) {
    AtomicInteger count = new AtomicInteger();
    ConcurrentTestHarness.timeTasks(NUM_PRODUCERS, () -> {
      for (int i = 0; i < PRODUCE; i++) {
        if (buffer.offer(i)) {
          count.incrementAndGet();
        }
      }
    });
    assertThat(buffer.size(), is(count.get()));
  }

  @Test(dataProvider = "empty")
  public void manyProducers_oneConsumer(MpscGrowableArrayQueue<Integer> buffer) {
    AtomicInteger started = new AtomicInteger();
    AtomicInteger finished = new AtomicInteger();

    ConcurrentTestHarness.execute(() -> {
      started.incrementAndGet();
      Awaits.await().untilAtomic(started, is(NUM_PRODUCERS + 1));
      for (int i = 0; i < (NUM_PRODUCERS * PRODUCE); i++) {
        while (buffer.poll() == null) {}
      }
      finished.incrementAndGet();
    });

    ConcurrentTestHarness.timeTasks(NUM_PRODUCERS, () -> {
      started.incrementAndGet();
      Awaits.await().untilAtomic(started, is(NUM_PRODUCERS + 1));
      for (int i = 0; i < PRODUCE; i++) {
        while (!buffer.offer(i)) {}
      }
      finished.incrementAndGet();
    });

    Awaits.await().untilAtomic(finished, is(NUM_PRODUCERS + 1));
    assertThat(buffer.size(), is(0));
  }

  /* ---------------- Providers -------------- */

  @DataProvider(name = "empty")
  public Object[][] providesEmpty() {
    return new Object[][] {{ makePopulated(0) }};
  }

  @DataProvider(name = "populated")
  public Object[][] providesPopulated() {
    return new Object[][] {{ makePopulated(POPULATED_SIZE) }};
  }

  @DataProvider(name = "full")
  public Object[][] providesFull() {
    return new Object[][] {{ makePopulated(FULL_SIZE) }};
  }

  static MpscGrowableArrayQueue<Integer> makePopulated(int items) {
    MpscGrowableArrayQueue<Integer> buffer = new MpscGrowableArrayQueue<>(4, FULL_SIZE);
    for (int i = 0; i < items; i++) {
      buffer.offer(i);
    }
    return buffer;
  }
}
