/**
 * Copyright 2010 - 2018 JetBrains s.r.o.
 *
 * 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 jetbrains.exodus.log;

import jetbrains.exodus.ByteIterator;
import jetbrains.exodus.bindings.LongBinding;
import org.jetbrains.annotations.NotNull;

public final class DataIterator extends ByteIterator {

    @NotNull
    private final Log log;
    private long pageAddress;
    private byte[] page;
    private int offset;
    private int length;

    public DataIterator(@NotNull final Log log) {
        this(log, -1L);
    }

    public DataIterator(@NotNull final Log log, final long startAddress) {
        this.log = log;
        pageAddress = -1L;
        if (startAddress >= 0) {
            checkPage(startAddress);
        }
    }

    @Override
    public boolean hasNext() {
        if (page == null) {
            return false;
        }
        if (offset >= length) {
            checkPage(getHighAddress());
            return hasNext();
        }
        return true;
    }

    @Override
    public byte next() {
        if (!hasNext()) {
            DataCorruptionException.raise(
                "DataIterator: no more bytes available", log, getHighAddress());
        }
        return page[offset++];
    }

    @Override
    public long skip(final long bytes) {
        long skipped = 0;
        while (page != null && skipped < bytes) {
            final long pageBytesToSkip = Math.min(bytes - skipped, length - offset);
            skipped += pageBytesToSkip;
            offset += pageBytesToSkip;
            if (offset < length) {
                break;
            }
            checkPage(getHighAddress());
        }
        return skipped;
    }

    @Override
    public long nextLong(final int length) {
        if (page == null || this.length - offset < length) {
            return LongBinding.entryToUnsignedLong(this, length);
        }
        final long result = LongBinding.entryToUnsignedLong(page, offset, length);
        offset += length;
        return result;
    }

    public void checkPage(final long highAddress) {
        final int cachePageSize = log.getCachePageSize();
        final int offset = ((int) highAddress) & (cachePageSize - 1);
        final long pageAddress = highAddress - offset;
        if (this.pageAddress != pageAddress) {
            try {
                page = log.cache.getPage(log, pageAddress);
                this.pageAddress = pageAddress;
            } catch (BlockNotFoundException e) {
                this.pageAddress = -1L;
                page = null;
                return;
            }
        }
        final int len = (int) Math.min(log.getHighAddress() - pageAddress, (long) cachePageSize);
        if (len <= offset) { // offset is >= 0 for sure
            this.pageAddress = -1L;
            page = null;
            return;
        }
        this.length = len;
        this.offset = offset;
    }

    byte[] getCurrentPage() {
        return page;
    }

    int getOffset() {
        return offset;
    }

    int getLength() {
        return length;
    }

    long getHighAddress() {
        return pageAddress + offset;
    }

    boolean availableInCurrentPage(final int bytes) {
        return length - offset >= bytes;
    }
}

@@ -847,24 +847,101 @@
 al Log log;%0A
+    private final int cachePageSize;%0A    private final long pageAddressMask;%0A
     private 
@@ -1208,24 +1208,129 @@
 .log = log;%0A
+        cachePageSize = log.getCachePageSize();%0A        pageAddressMask = ~((long) (cachePageSize - 1));%0A
         page
@@ -1398,16 +1398,20 @@
 heckPage
+Safe
 (startAd
@@ -1589,32 +1589,36 @@
        checkPage
+Safe
 (getHighAddress(
@@ -2314,16 +2314,20 @@
 heckPage
+Safe
 (getHigh
@@ -2751,21 +2751,17 @@
 al long 
-highA
+a
 ddress) 
@@ -2780,136 +2780,8 @@
 nal 
-int cachePageSize = log.getCachePageSize();%0A        final int offset = ((int) highAddress) & (cachePageSize - 1);%0A        final 
 long
@@ -2790,37 +2790,42 @@
 ageA
+ddress = a
 ddress 
-= high
+& page
 Address
- - offset
+Mask
 ;%0A  
@@ -2873,30 +2873,8 @@
 ) %7B%0A
-            try %7B%0A    
     
@@ -2929,36 +2929,32 @@
 s);%0A            
-    
 this.pageAddress
@@ -2981,70 +2981,76 @@
     
+%7D%0A
     
-%7D catch (BlockNotFoundException e) %7B%0A                this.
+    length = cachePageSize;%0A        offset = (int) (address - 
 page
@@ -3060,114 +3060,199 @@
 ress
- = -1L
+)
 ;%0A    
+%7D%0A%0A
     
-        page = null;%0A                return;%0A            %7D%0A        %7D%0A        final int
+private void checkPageSafe(final long address) %7B%0A        try %7B%0A            checkPage(address);%0A            final long pageAddress = address & pageAddressMask;%0A           
  len
+gth
  = (
@@ -3304,15 +3304,8 @@
 ess,
- (long)
  cac
@@ -3329,18 +3329,24 @@
     
+  
+  
 if (len
- %3C=
+gth %3E
  off
@@ -3355,182 +3355,153 @@
 t) %7B
- // offset is %3E= 0 for sure%0A            this.pageAddress = -1L;%0A            page = null;%0A            return;%0A        %7D%0A        this.length = len;%0A        this.offset = offset
+%0A                return;%0A            %7D%0A        %7D catch (BlockNotFoundException ignore) %7B%0A        %7D%0A        pageAddress = -1L;%0A        page = null
 ;%0A  

optimize DataIterator.checkPage(): don't catch BlockNotFoundException when it is not necessary