/*
 * Copyright Matt Palmer 2011, All rights reserved.
 *
 * This code is licensed under a standard 3-clause BSD license:
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer.
 * 
 *  * Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 * 
 *  * The names of its contributors may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 */

package net.domesdaybook.reader;

import net.domesdaybook.reader.cache.CacheFailureException;
import net.domesdaybook.reader.cache.WindowCache;
import net.domesdaybook.reader.cache.AllWindowsCache;
import java.io.IOException;
import java.io.InputStream;

/**
 *
 * @author matt
 */
public class InputStreamReader extends AbstractReader {

    private final InputStream stream;
    private long streamPos = 0;
    private long length = UNKNOWN_LENGTH;
    
    
    /**
     * 
     * @param stream
     */
    public InputStreamReader(final InputStream stream) {
        this(stream, DEFAULT_WINDOW_SIZE);
    }
    
    
    /**
     * 
     * @param stream
     * @param cache
     */
    public InputStreamReader(final InputStream stream, final WindowCache cache) {
        this(stream, DEFAULT_WINDOW_SIZE, cache);
    }      
    
    
    /**
     * 
     * @param stream
     * @param windowSize
     */
    public InputStreamReader(final InputStream stream, final int windowSize) {
        this(stream, windowSize, new AllWindowsCache());
    }

    
    /**
     * 
     * @param stream
     * @param windowSize
     * @param cache
     */
    public InputStreamReader(final InputStream stream, final int windowSize, final WindowCache cache) { 
        super(windowSize, cache);
        this.stream = stream;
    }    
    
    
    @Override
    public final Window getWindow(final long position) 
            throws IOException, CacheFailureException {
        final Window window = super.getWindow(position);
        if (window == null && position < streamPos && position >= 0) {
            // No window was returned, but the position requested has already
            // been read. This means the cache algorithm selected to use with
            // this reader cannot return an earlier position, and being a stream,
            // we can't rewind to read it again.  There is nothing which can be
            // done at this point other than to throw an exception.
            // This runtime exception flags a programming error, in selecting an
            // innapropriate cache algorithm to use for the access needed.
            // A reader should always be able to return a window for a valid,
            // requested position, setting aside genuine IO Exceptions.
            final String message = "Cache failed to provide a window at position: %d when we have already read past this position, currently at: %d";
            throw new CacheFailureException(String.format(message, position, streamPos));
        }
        return window;
    }
 
    
    @Override
    Window createWindow(final long readPos) throws IOException {
        Window lastWindow = null;
        while (readPos > streamPos && length == UNKNOWN_LENGTH) {
            final byte[] bytes = new byte[windowSize];
            final int totalRead = ReadUtils.readBytes(stream, bytes);
            if (totalRead > 0) {
                lastWindow = new Window(bytes, streamPos, totalRead);  
                streamPos += totalRead;                                        
                cache.addWindow(lastWindow);
            }
            if (totalRead < windowSize) { // If we read less than the available array:
                length = streamPos; // then the length is whatever the streampos is now.
            }
        }
        return lastWindow;
    }    

    
    @Override
    public long length() throws IOException {
        while (length == UNKNOWN_LENGTH) {
            final byte[] bytes = new byte[windowSize];
            final int totalRead = ReadUtils.readBytes(stream, bytes);
            if (totalRead > 0) {
                final Window lastWindow = new Window(bytes, streamPos, totalRead); 
                streamPos += totalRead;                  
                cache.addWindow(lastWindow);
            }
            if (totalRead < windowSize) { // If we read less than the available array:
                length = streamPos;
            }
        }
        return length;
    }

    
    @Override
    public void clearCache() {
        cache.clear();
        // The cache is the only representation of the stream we can replay,
        // so clearing the cache would seem to be pretty final.
    }

    
    @Override
    public void close() throws IOException {
        try {
            stream.close();
        } finally {
            super.close();
        }
    }

    
}

@@ -1763,109 +1763,219 @@
 ort 
-net.domesdaybook.reader.cache.AllWindowsCache;%0Aimport java.io.IOException;%0Aimport java.io.InputStream
+java.io.IOException;%0Aimport java.io.InputStream;%0Aimport net.domesdaybook.reader.cache.MostRecentlyUsedCache;%0Aimport net.domesdaybook.reader.cache.TempFileCache;%0Aimport net.domesdaybook.reader.cache.TwoLevelCache
 ;%0A%0A/
@@ -2321,16 +2321,34 @@
 DOW_SIZE
+, DEFAULT_CAPACITY
 );%0A    %7D
@@ -2763,38 +2763,414 @@
 ze, 
-new AllWindowsCache());%0A    %7D%0A
+DEFAULT_CAPACITY);%0A    %7D%0A%0A    %0A    /**%0A     * %0A     * @param stream%0A     * @param windowSize%0A     * @param capacity %0A     */%0A    public InputStreamReader(final InputStream stream, final int windowSize, final int capacity) %7B%0A        this(stream, windowSize, %0A             TwoLevelCache.create(new MostRecentlyUsedCache(capacity),%0A                                  new TempFileCache(windowSize)));%0A    %7D%0A    
 %0A   
@@ -4094,314 +4094,8 @@
 on.%0A
-            // This runtime exception flags a programming error, in selecting an%0A            // innapropriate cache algorithm to use for the access needed.%0A            // A reader should always be able to return a window for a valid,%0A            // requested position, setting aside genuine IO Exceptions.%0A
     
@@ -6125,32 +6125,58 @@
 stream.close();%0A
+            clearCache();%0A
         %7D finall

Use a TwoLevelCache comprising a MostRecentlyUsedCache and a TempFileCache by default instead of the AllWindowCache. This ensures that streams are first read into memory, and only flushed to disk if the MostRecentlyUsedCache drops a Window. For small streams this should give good performance (staying in memory), while not exceeding available memory if the stream is large.