/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.lucene.util;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import org.apache.lucene.tests.util.LuceneTestCase;
import org.junit.Before;
import org.junit.Test;

/** Testcase for {@link RecyclingIntBlockAllocator} */
public class TestRecyclingIntBlockAllocator extends LuceneTestCase {

  /** */
  @Override
  @Before
  public void setUp() throws Exception {
    super.setUp();
  }

  private RecyclingIntBlockAllocator newAllocator() {
    return new RecyclingIntBlockAllocator(
        1 << (2 + random().nextInt(15)), random().nextInt(97), Counter.newCounter());
  }

  @Test
  public void testAllocate() {
    RecyclingIntBlockAllocator allocator = newAllocator();
    HashSet<int[]> set = new HashSet<>();
    int[] block = allocator.getIntBlock();
    set.add(block);
    assertNotNull(block);
    final int size = block.length;

    int num = atLeast(97);
    for (int i = 0; i < num; i++) {
      block = allocator.getIntBlock();
      assertNotNull(block);
      assertEquals(size, block.length);
      assertTrue("block is returned twice", set.add(block));
      assertEquals(4 * size * (i + 2), allocator.bytesUsed()); // zero based + 1
      assertEquals(0, allocator.numBufferedBlocks());
    }
  }

  @Test
  public void testAllocateAndRecycle() {
    RecyclingIntBlockAllocator allocator = newAllocator();
    HashSet<int[]> allocated = new HashSet<>();

    int[] block = allocator.getIntBlock();
    allocated.add(block);
    assertNotNull(block);
    final int size = block.length;

    int numIters = atLeast(97);
    for (int i = 0; i < numIters; i++) {
      int num = 1 + random().nextInt(39);
      for (int j = 0; j < num; j++) {
        block = allocator.getIntBlock();
        assertNotNull(block);
        assertEquals(size, block.length);
        assertTrue("block is returned twice", allocated.add(block));
        assertEquals(
            4 * size * (allocated.size() + allocator.numBufferedBlocks()), allocator.bytesUsed());
      }
      int[][] array = allocated.toArray(new int[0][]);
      int begin = random().nextInt(array.length);
      int end = begin + random().nextInt(array.length - begin);
      List<int[]> selected = new ArrayList<>();
      for (int j = begin; j < end; j++) {
        selected.add(array[j]);
      }
      allocator.recycleIntBlocks(array, begin, end);
      for (int j = begin; j < end; j++) {
        assertNull(array[j]);
        int[] b = selected.remove(0);
        assertTrue(allocated.remove(b));
      }
    }
  }

  @Test
  public void testAllocateAndFree() {
    RecyclingIntBlockAllocator allocator = newAllocator();
    HashSet<int[]> allocated = new HashSet<>();
    int freeButAllocated = 0;
    int[] block = allocator.getIntBlock();
    allocated.add(block);
    assertNotNull(block);
    final int size = block.length;

    int numIters = atLeast(97);
    for (int i = 0; i < numIters; i++) {
      int num = 1 + random().nextInt(39);
      for (int j = 0; j < num; j++) {
        block = allocator.getIntBlock();
        freeButAllocated = Math.max(0, freeButAllocated - 1);
        assertNotNull(block);
        assertEquals(size, block.length);
        assertTrue("block is returned twice", allocated.add(block));
        assertEquals(
            ""
                + (4 * size * (allocated.size() + allocator.numBufferedBlocks())
                    - allocator.bytesUsed()),
            4 * size * (allocated.size() + allocator.numBufferedBlocks()),
            allocator.bytesUsed());
      }

      int[][] array = allocated.toArray(new int[0][]);
      int begin = random().nextInt(array.length);
      int end = begin + random().nextInt(array.length - begin);
      for (int j = begin; j < end; j++) {
        int[] b = array[j];
        assertTrue(allocated.remove(b));
      }
      allocator.recycleIntBlocks(array, begin, end);
      for (int j = begin; j < end; j++) {
        assertNull(array[j]);
      }
      // randomly free blocks
      int numFreeBlocks = allocator.numBufferedBlocks();
      int freeBlocks = allocator.freeBlocks(random().nextInt(7 + allocator.maxBufferedBlocks()));
      assertEquals(allocator.numBufferedBlocks(), numFreeBlocks - freeBlocks);
    }
  }
}
