/*
 * 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.geode.internal.offheap;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;

import java.util.Arrays;

import org.assertj.core.api.JUnitSoftAssertions;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.ExpectedException;

import org.apache.geode.test.junit.categories.UnitTest;

@Category(UnitTest.class)
public class FragmentJUnitTest {

  private SlabImpl[] slabs;

  @Rule
  public ExpectedException expectedException = ExpectedException.none();

  @Rule
  public JUnitSoftAssertions softly = new JUnitSoftAssertions();

  @Before
  public void setUp() throws Exception {
    SlabImpl slab1 = new SlabImpl((int) OffHeapStorage.MIN_SLAB_SIZE);
    SlabImpl slab2 = new SlabImpl((int) OffHeapStorage.MIN_SLAB_SIZE);
    slabs = new SlabImpl[] {slab1, slab2};
  }

  @After
  public void tearDown() throws Exception {
    for (int i = 0; i < slabs.length; i++) {
      slabs[i].free();
    }
  }

  @Test
  public void fragmentConstructorThrowsExceptionForNon8ByteAlignedAddress() {
    expectedException.expect(IllegalStateException.class);
    expectedException.expectMessage("address was not 8 byte aligned");

    new Fragment(slabs[0].getMemoryAddress() + 2, 0);
    fail("Constructor failed to throw exception for non-8-byte alignment");
  }

  @Test
  public void zeroSizeFragmentHasNoFreeSpace() {
    Fragment fragment = new Fragment(slabs[0].getMemoryAddress(), 0);
    assertThat(fragment.freeSpace(), is(0));
  }

  @Test
  public void unallocatedFragmentHasFreeSpaceEqualToFragmentSize() {
    Fragment fragment = new Fragment(slabs[0].getMemoryAddress(), slabs[0].getSize());
    softly.assertThat(fragment.getSize()).isEqualTo((int) OffHeapStorage.MIN_SLAB_SIZE);
    softly.assertThat(fragment.freeSpace()).isEqualTo((int) OffHeapStorage.MIN_SLAB_SIZE);
  }

  @Test
  public void allocatingFromFragmentReducesFreeSpace() {
    Fragment fragment = new Fragment(slabs[0].getMemoryAddress(), slabs[0].getSize());
    softly.assertThat(fragment.allocate(fragment.getFreeIndex(), fragment.getFreeIndex() + 256))
        .isEqualTo(true);
    softly.assertThat(fragment.freeSpace()).isEqualTo(768);
    softly.assertThat(fragment.getFreeIndex()).isEqualTo(256);
  }

  @Test
  public void fragementAllocationIsUnsafeWithRespectToAllocationSize() {
    Fragment fragment = new Fragment(slabs[0].getMemoryAddress(), slabs[0].getSize());
    softly.assertThat(fragment.allocate(fragment.getFreeIndex(),
        fragment.getFreeIndex() + (int) OffHeapStorage.MIN_SLAB_SIZE + 8)).isEqualTo(true);
    softly.assertThat(fragment.freeSpace()).isEqualTo(-8);
  }

  @Test
  public void getBlockSizeReturnsFreeSpace() {
    Fragment fragment = new Fragment(slabs[0].getMemoryAddress(), slabs[0].getSize());
    softly.assertThat(fragment.allocate(fragment.getFreeIndex(), fragment.getFreeIndex() + 256))
        .isEqualTo(true);
    softly.assertThat(fragment.getBlockSize()).isEqualTo(fragment.freeSpace());
  }

  @Test
  public void getMemoryAdressIsAlwaysFragmentBaseAddress() {
    Fragment fragment = new Fragment(slabs[0].getMemoryAddress(), slabs[0].getSize());
    softly.assertThat(fragment.getAddress()).isEqualTo(slabs[0].getMemoryAddress());
    fragment.allocate(fragment.getFreeIndex(), fragment.getFreeIndex() + 256);
    softly.assertThat(fragment.getAddress()).isEqualTo(slabs[0].getMemoryAddress());
  }

  @Test
  public void getStateIsAlwaysStateUNUSED() {
    Fragment fragment = new Fragment(slabs[0].getMemoryAddress(), slabs[0].getSize());
    softly.assertThat(fragment.getState()).isEqualTo(MemoryBlock.State.UNUSED);
    fragment.allocate(fragment.getFreeIndex(), fragment.getFreeIndex() + 256);
    softly.assertThat(fragment.getState()).isEqualTo(MemoryBlock.State.UNUSED);
  }

  @Test
  public void getFreeListIdIsAlwaysMinus1() {
    Fragment fragment = new Fragment(slabs[0].getMemoryAddress(), slabs[0].getSize());
    softly.assertThat(fragment.getFreeListId()).isEqualTo(-1);
    fragment.allocate(fragment.getFreeIndex(), fragment.getFreeIndex() + 256);
    softly.assertThat(fragment.getFreeListId()).isEqualTo(-1);
  }

  @Test
  public void getRefCountIsAlwaysZero() {
    Fragment fragment = new Fragment(slabs[0].getMemoryAddress(), slabs[0].getSize());
    softly.assertThat(fragment.getRefCount()).isEqualTo(0);
    fragment.allocate(fragment.getFreeIndex(), fragment.getFreeIndex() + 256);
    softly.assertThat(fragment.getRefCount()).isEqualTo(0);
  }

  @Test
  public void getDataTypeIsAlwaysNA() {
    Fragment fragment = new Fragment(slabs[0].getMemoryAddress(), slabs[0].getSize());
    softly.assertThat(fragment.getDataType()).isEqualTo("N/A");
    fragment.allocate(fragment.getFreeIndex(), fragment.getFreeIndex() + 256);
    softly.assertThat(fragment.getDataType()).isEqualTo("N/A");
  }

  @Test
  public void isSerializedIsAlwaysFalse() {
    Fragment fragment = new Fragment(slabs[0].getMemoryAddress(), slabs[0].getSize());
    softly.assertThat(fragment.isSerialized()).isEqualTo(false);
    fragment.allocate(fragment.getFreeIndex(), fragment.getFreeIndex() + 256);
    softly.assertThat(fragment.isSerialized()).isEqualTo(false);
  }

  @Test
  public void isCompressedIsAlwaysFalse() {
    Fragment fragment = new Fragment(slabs[0].getMemoryAddress(), slabs[0].getSize());
    softly.assertThat(fragment.isCompressed()).isEqualTo(false);
    fragment.allocate(fragment.getFreeIndex(), fragment.getFreeIndex() + 256);
    softly.assertThat(fragment.isCompressed()).isEqualTo(false);
  }

  @Test
  public void getDataValueIsAlwaysNull() {
    Fragment fragment = new Fragment(slabs[0].getMemoryAddress(), slabs[0].getSize());
    softly.assertThat(fragment.getDataValue()).isNull();
    fragment.allocate(fragment.getFreeIndex(), fragment.getFreeIndex() + 256);
    softly.assertThat(fragment.getDataValue()).isNull();
  }

  @Test
  public void fragmentEqualsComparesMemoryBlockAddresses() {
    Fragment fragment0 = new Fragment(slabs[0].getMemoryAddress(), slabs[0].getSize());
    Fragment sameFragment = new Fragment(slabs[0].getMemoryAddress(), slabs[0].getSize());
    Fragment fragment1 = new Fragment(slabs[1].getMemoryAddress(), slabs[1].getSize());
    softly.assertThat(fragment0.equals(sameFragment)).isEqualTo(true);
    softly.assertThat(fragment0.equals(fragment1)).isEqualTo(false);
  }

  @Test
  public void fragmentEqualsIsFalseForNonFragmentObjects() {
    Fragment fragment0 = new Fragment(slabs[0].getMemoryAddress(), slabs[0].getSize());
    assertThat(fragment0.equals(slabs[0]), is(false));
  }

  @Test
  public void fragmentHashCodeIsHashCodeOfItsMemoryAddress() {
    Fragment fragment0 = new Fragment(slabs[0].getMemoryAddress(), slabs[0].getSize());
    Fragment fragment1 = new Fragment(slabs[1].getMemoryAddress(), slabs[1].getSize());
    Long fragmentAddress = fragment0.getAddress();
    softly.assertThat(fragment0.hashCode()).isEqualTo(fragmentAddress.hashCode())
        .isNotEqualTo(fragment1.hashCode());
  }

  @Test
  public void fragmentFillSetsAllBytesToTheSameConstantValue() {
    Fragment fragment = new Fragment(slabs[0].getMemoryAddress(), slabs[0].getSize());
    Long fragmentAddress = fragment.getAddress();
    byte[] bytes = new byte[(int) OffHeapStorage.MIN_SLAB_SIZE];
    byte[] expectedBytes = new byte[(int) OffHeapStorage.MIN_SLAB_SIZE];
    Arrays.fill(expectedBytes, OffHeapStoredObject.FILL_BYTE);;
    fragment.fill();
    AddressableMemoryManager.readBytes(fragmentAddress, bytes, 0,
        (int) OffHeapStorage.MIN_SLAB_SIZE);
    assertThat(bytes, is(equalTo(expectedBytes)));
  }

  @Test
  public void getNextBlockThrowsExceptionForFragment() {
    expectedException.expect(UnsupportedOperationException.class);

    Fragment fragment = new Fragment(slabs[0].getMemoryAddress(), slabs[0].getSize());
    fragment.getNextBlock();
    fail("getNextBlock failed to throw UnsupportedOperationException");
  }

  @Test
  public void getSlabIdThrowsExceptionForFragment() {
    expectedException.expect(UnsupportedOperationException.class);

    Fragment fragment = new Fragment(slabs[0].getMemoryAddress(), slabs[0].getSize());
    fragment.getSlabId();
    fail("getSlabId failed to throw UnsupportedOperationException");
  }

}
