/*
 * Copyright 2014-2017 Real Logic Ltd.
 *
 * 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 io.aeron.logbuffer;

import org.junit.Before;
import org.junit.Test;
import org.agrona.concurrent.UnsafeBuffer;

import static java.nio.ByteBuffer.allocateDirect;
import static org.agrona.BitUtil.SIZE_OF_LONG;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static io.aeron.logbuffer.LogBufferDescriptor.*;
import static io.aeron.protocol.HeaderFlyweight.HEADER_LENGTH;

public class LogBufferUnblockerTest
{
    private static final int TERM_LENGTH = TERM_MIN_LENGTH;
    private static final int TERM_ID_1 = 1;
    private static final int PARTITION_INDEX = 0;
    private static final int TERM_TAIL_COUNTER_OFFSET = TERM_TAIL_COUNTERS_OFFSET + (PARTITION_INDEX * SIZE_OF_LONG);


    private final UnsafeBuffer logMetaDataBuffer = spy(new UnsafeBuffer(allocateDirect(LOG_META_DATA_LENGTH)));
    private final UnsafeBuffer[] termBuffers = new UnsafeBuffer[PARTITION_COUNT];

    private final int positionBitsToShift = Integer.numberOfTrailingZeros(TERM_LENGTH);

    @Before
    public void setUp()
    {
        initialTermId(logMetaDataBuffer, TERM_ID_1);

        for (int i = 0; i < PARTITION_COUNT; i++)
        {
            termBuffers[i] = spy(new UnsafeBuffer(allocateDirect(TERM_LENGTH)));
        }

        initialiseTailWithTermId(logMetaDataBuffer, PARTITION_INDEX, TERM_ID_1);
    }

    @Test
    public void shouldNotUnblockWhenPositionHasCompleteMessage()
    {
        final int blockedOffset = HEADER_LENGTH * 4;
        final long blockedPosition = computePosition(TERM_ID_1, blockedOffset, positionBitsToShift, TERM_ID_1);
        final int activeIndex = indexByPosition(blockedPosition, positionBitsToShift);

        when(termBuffers[activeIndex].getIntVolatile(blockedOffset)).thenReturn(HEADER_LENGTH);

        assertFalse(LogBufferUnblocker.unblock(termBuffers, logMetaDataBuffer, blockedPosition));

        final long rawTail = rawTailVolatile(logMetaDataBuffer);
        assertThat(computePosition(termId(rawTail), blockedOffset, positionBitsToShift, TERM_ID_1),
            is(blockedPosition));
    }

    @Test
    public void shouldUnblockWhenPositionHasNonCommittedMessageAndTailWithinTerm()
    {
        final int blockedOffset = HEADER_LENGTH * 4;
        final int messageLength = HEADER_LENGTH * 4;
        final long blockedPosition = computePosition(TERM_ID_1, blockedOffset, positionBitsToShift, TERM_ID_1);
        final int activeIndex = indexByPosition(blockedPosition, positionBitsToShift);

        when(termBuffers[activeIndex].getIntVolatile(blockedOffset)).thenReturn(-messageLength);

        assertTrue(LogBufferUnblocker.unblock(termBuffers, logMetaDataBuffer, blockedPosition));

        final long rawTail = rawTailVolatile(logMetaDataBuffer);
        assertThat(computePosition(termId(rawTail), blockedOffset + messageLength, positionBitsToShift, TERM_ID_1),
            is(blockedPosition + messageLength));
    }

    @Test
    public void shouldUnblockWhenPositionHasNonCommittedMessageAndTailAtEndOfTerm()
    {
        final int messageLength = HEADER_LENGTH * 4;
        final int blockedOffset = TERM_LENGTH - messageLength;
        final long blockedPosition = computePosition(TERM_ID_1, blockedOffset, positionBitsToShift, TERM_ID_1);
        final int activeIndex = indexByPosition(blockedPosition, positionBitsToShift);

        when(termBuffers[activeIndex].getIntVolatile(blockedOffset)).thenReturn(0);

        logMetaDataBuffer.putLong(TERM_TAIL_COUNTER_OFFSET, pack(TERM_ID_1, TERM_LENGTH));

        assertTrue(LogBufferUnblocker.unblock(termBuffers, logMetaDataBuffer, blockedPosition));

        verify(logMetaDataBuffer).putIntOrdered(LOG_ACTIVE_TERM_COUNT_OFFSET, activeIndex + 1);

        final long rawTail = rawTailVolatile(logMetaDataBuffer);
        final int termId = termId(rawTail);
        assertThat(computePosition(termId, 0, positionBitsToShift, TERM_ID_1),
            is(blockedPosition + messageLength));
    }

    @Test
    public void shouldUnblockWhenPositionHasNonCommittedMessageAndTailPastEndOfTerm()
    {
        final int messageLength = HEADER_LENGTH * 4;
        final int blockedOffset = TERM_LENGTH - messageLength;
        final long blockedPosition = computePosition(TERM_ID_1, blockedOffset, positionBitsToShift, TERM_ID_1);
        final int activeIndex = indexByPosition(blockedPosition, positionBitsToShift);

        when(termBuffers[activeIndex].getIntVolatile(blockedOffset)).thenReturn(0);

        logMetaDataBuffer.putLong(TERM_TAIL_COUNTER_OFFSET, pack(TERM_ID_1, TERM_LENGTH + HEADER_LENGTH));

        assertTrue(LogBufferUnblocker.unblock(termBuffers, logMetaDataBuffer, blockedPosition));

        verify(logMetaDataBuffer).putIntOrdered(LOG_ACTIVE_TERM_COUNT_OFFSET, activeIndex + 1);

        final long rawTail = rawTailVolatile(logMetaDataBuffer);
        assertThat(computePosition(termId(rawTail), 0, positionBitsToShift, TERM_ID_1),
            is(blockedPosition + messageLength));
    }

    private static long pack(final int termId, final int offset)
    {
        return (((long)termId) << 32) | offset;
    }
}

@@ -4244,105 +4244,8 @@
 );%0A%0A
-        verify(logMetaDataBuffer).putIntOrdered(LOG_ACTIVE_TERM_COUNT_OFFSET, activeIndex + 1);%0A%0A
     
@@ -4469,32 +4469,132 @@
 messageLength));
+%0A%0A        verify(logMetaDataBuffer).putIntOrdered(LOG_ACTIVE_TERM_COUNT_OFFSET, termId - TERM_ID_1);
 %0A    %7D%0A%0A    @Tes
@@ -5298,105 +5298,8 @@
 );%0A%0A
-        verify(logMetaDataBuffer).putIntOrdered(LOG_ACTIVE_TERM_COUNT_OFFSET, activeIndex + 1);%0A%0A
     
@@ -5351,32 +5351,76 @@
 etaDataBuffer);%0A
+        final int termId = termId(rawTail);%0A
         assertTh
@@ -5444,25 +5444,16 @@
 n(termId
-(rawTail)
 , 0, pos
@@ -5523,32 +5523,132 @@
 messageLength));
+%0A%0A        verify(logMetaDataBuffer).putIntOrdered(LOG_ACTIVE_TERM_COUNT_OFFSET, termId - TERM_ID_1);
 %0A    %7D%0A%0A    priv

update test for new field meaning.