/*
 * Copyright Matt Palmer 2011, All rights reserved.
 *
 */

package net.domesdaybook.reader;

import java.io.IOException;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 *
 * @author matt
 */
public abstract class AbstractReader implements Reader, Iterable<Window> {

    protected final static int DEFAULT_WINDOW_SIZE = 4096;
    
    protected final int windowSize;
    protected final WindowCache cache;
    private Window lastWindow = null;

    
    public AbstractReader(final WindowCache cache) {
        this(DEFAULT_WINDOW_SIZE, cache);
    }
    

    public AbstractReader(final int windowSize, final WindowCache cache) {
        this.windowSize = windowSize;
        this.cache = cache;
    }
    
    
    /**
     * Reads a byte in the file at the given position.
     *
     * @param position The position in the reader to read a byte from.
     * @return The byte at the given position.
     * @throws ReaderException if an error occurs reading the byte.
     * @throws IndexOutOfBoundsException If there are no bytes at the position given. 
     */
    @Override
    public final byte readByte(final long position) throws IOException {
        final Window window = getWindow(position);
        final int offset = (int) position % windowSize;
        if (window == null || offset >= window.getLimit()) {
            throw new IndexOutOfBoundsException("No bytes can be read from this position:" + position);
        }
        return window.getByte(offset);
    }
    
    
    /**
     * 
     * @return A Window backed by a byte array onto the data for a given position.
     *         If a window can't be provided for the given position, null is returned.
     * @throws IOException if an IO error occurred trying to create a new window.
     */
    @Override
    public final Window getWindow(final long position) throws IOException {
        final long windowStart =  position - (position % windowSize);
        if (lastWindow != null && lastWindow.getWindowPosition() == windowStart) {
            return lastWindow;
        }
        Window window = cache.getWindow(windowStart);
        if (window != null) {
            lastWindow = window;
        } else if (position >= 0) {
            window = createWindow(windowStart);
            if (window != null) {
                lastWindow = window;
                cache.addWindow(window);
            }
        } 
        return window;
    }
    
    
    abstract Window createWindow(final long windowStart) throws IOException;
    

    @Override
    public final Iterator<Window> iterator() {
        return new WindowIterator();
    }

    
    @Override
    public void close() {
        cache.clear();        
    }
    
    
    @Override
    public void finalize() throws Throwable {
        try {
            close();
        } finally {
            super.finalize();
        }
    }

    
    @Override
    public void clearCache() {
        cache.clear();
    }
    
    
    @Override
    public final int getWindowSize() {
        return windowSize;
    }
    
    
    private class WindowIterator implements Iterator<Window> {

        private int position = 0;
        
        
        @Override
        public boolean hasNext(){
            try {
                return getWindow(position) != null;
            } catch (IOException ex) {
                return false;
            }
        }

        
        @Override
        public Window next() {
            try {
                final Window window = getWindow(position);
                if (window != null) {
                    position += windowSize;
                    return window;
                }
            } catch (final IOException dropDownToNoSuchElementException) {
            }
            throw new NoSuchElementException();            
        }

        
        @Override
        public void remove() {
            throw new UnsupportedOperationException("Cannot remove a window from a reader.");
        }
    }
    
}

@@ -1870,32 +1870,65 @@
 s IOException %7B%0A
+        if (position %3E= 0) %7B%0A    
         final lo
@@ -1985,24 +1985,28 @@
 e);%0A        
+ 
+   
 if (lastWind
@@ -2076,24 +2076,28 @@
             
+    
 return lastW
@@ -2103,34 +2103,42 @@
 Window;%0A        
+  
-%7D%0A
+  %7D%0A    
         Window w
@@ -2175,32 +2175,36 @@
 Start);%0A        
+  
+  
 if (window != nu
@@ -2213,32 +2213,36 @@
 ) %7B%0A            
+    
 lastWindow = win
@@ -2258,36 +2258,25 @@
     
+   
+ 
 %7D else 
-if (position %3E= 0) %7B%0A
+%7B%0A    
     
@@ -2323,32 +2323,36 @@
 t);%0A            
+    
 if (window != nu
@@ -2365,32 +2365,36 @@
                 
+ 
+   
 lastWindow = win
@@ -2390,32 +2390,36 @@
 indow = window;%0A
+    
                 
@@ -2447,32 +2447,36 @@
 w);%0A            
+    
 %7D%0A        %7D %0A   
@@ -2469,16 +2469,20 @@
         
+ 
+   
 %7D %0A     
@@ -2476,32 +2476,36 @@
      %7D %0A        
+  
+  
 return window;%0A 
@@ -2499,24 +2499,55 @@
 urn window;%0A
+        %7D%0A        return null;%0A
     %7D%0A    %0A 

Fix for positions less than zero returning a window instead of null.