/*

   Derby - Class org.apache.derbyTesting.functionTests.tests.store.PositionedStoreStreamTest

   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.derbyTesting.functionTests.tests.store;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import junit.framework.Test;
import org.apache.derby.iapi.error.StandardException;
import org.apache.derby.impl.jdbc.PositionedStoreStream;
import org.apache.derbyTesting.functionTests.util.streams.LoopingAlphabetStream;
import org.apache.derbyTesting.junit.BaseTestCase;
import org.apache.derbyTesting.junit.BaseTestSuite;

/**
 * Tests of the repositioning logic in {@link PositionedStoreStream}.
 */
public class PositionedStoreStreamTest
    extends BaseTestCase {

    public PositionedStoreStreamTest(String name) {
        super(name);
    }

    /**
     * Verifies that reading after EOF doesn't change the position.
     */
    public void testPositionAfterEOFRead()
            throws IOException, StandardException {
        InputStream in = new LoopingAlphabetStream(10);
        PositionedStoreStream pss = new PositionedStoreStream(in);
        assertEquals(0, pss.getPosition());
        for (int i=0; i < 10; i++) {
            pss.read();
            assertEquals(i +1, pss.getPosition());
        }
        assertEquals(10, pss.getPosition());
        assertEquals(-1, pss.read());
        assertEquals(10, pss.getPosition());
        assertEquals(-1, pss.read());
        assertEquals(0, pss.skip(199));
        assertEquals(10, pss.getPosition());
        pss.resetStream();
        assertEquals(0, pss.getPosition());
    }

    /**
     * Verifies that reading after EOF doesn't change the position.
     */
    public void testPositionAfterEOFReadArray()
            throws IOException, StandardException {
        InputStream in = new LoopingAlphabetStream(10);
        PositionedStoreStream pss = new PositionedStoreStream(in);
        assertEquals(0, pss.getPosition());
        byte[] two = new byte[2];
        for (int i=0; i < 10; i += 2) {
            assertEquals(2, pss.read(two, 0, 2));
            assertEquals(i +2, pss.getPosition());
        }
        assertEquals(10, pss.getPosition());
        assertEquals(-1, pss.read(two, 0, 2));
        assertEquals(10, pss.getPosition());
        assertEquals(-1, pss.read(two, 0, 2));
        assertEquals(0, pss.skip(21));
        assertEquals(-1, pss.read());
        assertEquals(10, pss.getPosition());
        pss.resetStream();
        assertEquals(0, pss.getPosition());
    }

    /**
     * Reads the whole stream repeatedly in one go, and resets it after each
     * read.
     */
    public void testReadEverythingInOneGo()
            throws IOException, StandardException {
        InputStream in = new LoopingAlphabetStream(127);
        PositionedStoreStream pss = new PositionedStoreStream(in);
        byte[] b = new byte[256];
        for (int i=0; i < 3; i++) {
            Arrays.fill(b, (byte)-1);
            assertEquals(127, pss.read(b, 0, 256));
            assertEquals(-1, pss.read(b, 127, 10));
            assertEquals(-1, b[127]);
            assertTrue( -1 != b[126]);
            assertEquals('a', b[0]);
            pss.reposition(0);
        }
    }

    public void testRepositionForwards()
            throws IOException, StandardException {
        final long length = 20L;
        InputStream in = new LoopingAlphabetStream(length);
        PositionedStoreStream pss = new PositionedStoreStream(in);
        assertEquals(0, pss.getPosition());
        //  Position forwards one by one.
        for (int i=0; i < length; i++) {
            InputStream inComp = new LoopingAlphabetStream(length);
            pss.reposition(i);
            inComp.skip(i);
            assertEquals(inComp.read(), pss.read());
        }
        // Position forwards two by two.
        for (int i=1; i < length; i += 2) {
            InputStream inComp = new LoopingAlphabetStream(length);
            pss.reposition(i);
            inComp.skip(i);
            assertEquals(inComp.read(), pss.read());
        }
    }
    
    public void testRepositionBackwards()
            throws IOException, StandardException {
        final long length = 20L;
        InputStream in = new LoopingAlphabetStream(length);
        PositionedStoreStream pss = new PositionedStoreStream(in);
        assertEquals(0, pss.getPosition());
        // Position backwards one by one.
        for (int i=(int)length; i >= 0; i--) {
            InputStream inComp = new LoopingAlphabetStream(length);
            pss.reposition(i);
            inComp.skip(i);
            assertEquals(inComp.read(), pss.read());
        }
        // Position backwards two by two.
        for (int i=(int)length -1; i >= 0; i -= 2) {
            InputStream inComp = new LoopingAlphabetStream(length);
            pss.reposition(i);
            inComp.skip(i);
            assertEquals(inComp.read(), pss.read());
        }
    }

    /**
     * Executes a simple read sequence against the lower case modern latin
     * alphabet, which involves some repositioning.
     */
    public void testSimpleReadSequence()
            throws IOException, StandardException {
        InputStream in = new LoopingAlphabetStream(26);
        PositionedStoreStream pss = new PositionedStoreStream(in);
        assertEquals('a', pss.read());
        pss.reposition(9);
        assertEquals('j', pss.read());
        pss.reposition(10);
        assertEquals('k', pss.read());
        pss.reposition(9);
        assertEquals('j', pss.read());
        pss.reposition(25);
        assertEquals('z', pss.read());
        assertEquals(-1, pss.read());
        pss.reposition(1);
        assertEquals('b', pss.read());
        pss.reposition(1);
        assertEquals('b', pss.read());
    }

    /**
     * A regression test for DERBY-3735.
     * <p>
     * If the bug is present, the repositioning will cause an EOF-exception to
     * be thrown in {@code skipFully}.
     */
    public void testDerby3735()
            throws IOException, StandardException {
        InputStream in = new LoopingAlphabetStream(2);
        PositionedStoreStream pss = new PositionedStoreStream(in);
        byte[] b = new byte[2];
        for (int i=0; i < 10; i++) {
            // After the first iteration, the position will be decreased and
            // eventually become negative.
            pss.read(b);
            println("Position at iteration " + i + ": " + pss.getPosition());
        }
        // This failed, because we tried skipping more bytes than there are in
        // the stream to get to position 0.
        pss.reposition(0);
    }

    /**
     * Tests that trying to move past the end of the stream leaves the stream
     * object in a consistent state, and can be repositioned again after the
     * failed reposition attempt.
     * <p>
     * Issue logged in Jira as DERBY-3781
     *
     * @throws IOException if reading the stream fails unexpectedly
     * @throws StandardException will never happen
     */
    public void testDerby3781()
            throws IOException, StandardException {
        final long size = 10;
        InputStream in = new LoopingAlphabetStream(size);
        PositionedStoreStream pss = new PositionedStoreStream(in);
        assertEquals("Invalid initial position", 0L, pss.getPosition());
        pss.reposition(size -1); // Goto end.
        assertEquals(size -1, pss.getPosition());
        assertEquals('j', pss.read());
        assertEquals(size, pss.getPosition());
        assertEquals(-1, pss.read());
        // This step is crucial, position must be different than zero when the
        // first exception below is thrown.
        pss.reposition(size / 2); // Goto middle.
        assertEquals(size / 2, pss.getPosition());
        try {
            pss.reposition(size *2); // Try to go past end.
            fail("Should have failed with EOFException");
        } catch (EOFException eofe) {
            // Ignore this exception
        }
        // Failed here before, because internal state was inconsistent.
        // Assumed: pos = 5, underlying stream at pos 5, skipped (size -1 - pos)
        // Actual: pos = 5, underlying stream at pos (size -1)
        pss.reposition(size -1); // Goto end.
        assertEquals(size -1, pss.getPosition());
        assertEquals('j', pss.read());
    }

    public static Test suite() {
        return new BaseTestSuite(
                PositionedStoreStreamTest.class, "PositionedStoreStreamTest");
    }
}
