/**
 * Copyright The Apache Software Foundation
 *
 * 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.hadoop.hbase.io.hfile.bucket;

import static org.junit.Assert.assertTrue;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hbase.SmallTests;
import org.apache.hadoop.hbase.io.hfile.BlockCacheKey;
import org.apache.hadoop.hbase.io.hfile.CacheTestUtils;
import org.apache.hadoop.hbase.io.hfile.Cacheable;
import org.apache.hadoop.hbase.io.hfile.bucket.BucketAllocator.BucketSizeInfo;
import org.apache.hadoop.hbase.io.hfile.bucket.BucketAllocator.IndexStatistics;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;

/**
 * Basic test of BucketCache.Puts and gets.
 * <p>
 * Tests will ensure that blocks' data correctness under several threads
 * concurrency
 */
@Category(SmallTests.class)
public class TestBucketCache {
  static final Log LOG = LogFactory.getLog(TestBucketCache.class);
  BucketCache cache;
  final int CACHE_SIZE = 1000000;
  final int NUM_BLOCKS = 100;
  final int BLOCK_SIZE = CACHE_SIZE / NUM_BLOCKS;
  final int NUM_THREADS = 1000;
  final int NUM_QUERIES = 10000;
  
  final long capacitySize = 32 * 1024 * 1024;
  final int writeThreads = BucketCache.DEFAULT_WRITER_THREADS;
  final int writerQLen = BucketCache.DEFAULT_WRITER_QUEUE_ITEMS;
  String ioEngineName = "heap";
  String persistencePath = null;

  private class MockedBucketCache extends BucketCache {

    public MockedBucketCache(String ioEngineName, long capacity,
        int writerThreads,
        int writerQLen, String persistencePath) throws FileNotFoundException,
        IOException {
      super(ioEngineName, capacity, 8192, writerThreads, writerQLen, persistencePath);
      super.wait_when_cache = true;
    }

    @Override
    public void cacheBlock(BlockCacheKey cacheKey, Cacheable buf,
        boolean inMemory) {
      if (super.getBlock(cacheKey, true, false) != null) {
        throw new RuntimeException("Cached an already cached block");
      }
      super.cacheBlock(cacheKey, buf, inMemory);
    }

    @Override
    public void cacheBlock(BlockCacheKey cacheKey, Cacheable buf) {
      if (super.getBlock(cacheKey, true, false) != null) {
        throw new RuntimeException("Cached an already cached block");
      }
      super.cacheBlock(cacheKey, buf);
    }
  }

  @Before
  public void setup() throws FileNotFoundException, IOException {
    cache = new MockedBucketCache(ioEngineName, capacitySize, writeThreads,
        writerQLen, persistencePath);
  }

  @After
  public void tearDown() {
    cache.shutdown();
  }

  @Test
  public void testBucketAllocator() throws BucketAllocatorException {
    BucketAllocator mAllocator = cache.getAllocator();
    /*
     * Test the allocator first
     */
    int[] blockSizes = new int[2];
    blockSizes[0] = 4 * 1024;
    blockSizes[1] = 8 * 1024;
    boolean full = false;
    int i = 0;
    ArrayList<Long> allocations = new ArrayList<Long>();
    // Fill the allocated extents
    while (!full) {
      try {
        allocations.add(new Long(mAllocator.allocateBlock(blockSizes[i
            % blockSizes.length])));
        ++i;
      } catch (CacheFullException cfe) {
        full = true;
      }
    }

    for (i = 0; i < blockSizes.length; i++) {
      BucketSizeInfo bucketSizeInfo = mAllocator
          .roundUpToBucketSizeInfo(blockSizes[0]);
      IndexStatistics indexStatistics = bucketSizeInfo.statistics();
      assertTrue(indexStatistics.freeCount() == 0);
    }

    for (long offset : allocations) {
      assertTrue(mAllocator.sizeOfAllocation(offset) == mAllocator
          .freeBlock(offset));
    }
    assertTrue(mAllocator.getUsedSize() == 0);
  }

  @Test
  public void testCacheSimple() throws Exception {
    CacheTestUtils.testCacheSimple(cache, BLOCK_SIZE, NUM_QUERIES);
  }

  @Test
  public void testCacheMultiThreadedSingleKey() throws Exception {
    CacheTestUtils.hammerSingleKey(cache, BLOCK_SIZE, NUM_THREADS, NUM_QUERIES);
  }

  @Test
  public void testHeapSizeChanges() throws Exception {
    cache.stopWriterThreads();
    CacheTestUtils.testHeapSizeChanges(cache, BLOCK_SIZE);
  }

}
