/*
 * 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.jackrabbit.oak.plugins.document;

import java.io.Closeable;
import java.util.ArrayDeque;
import java.util.Iterator;
import java.util.Queue;

import org.apache.jackrabbit.oak.plugins.blob.ReferencedBlob;
import org.apache.jackrabbit.oak.plugins.document.util.Utils;

import org.apache.jackrabbit.guava.common.collect.AbstractIterator;

/**
 * An iterator over all referenced binaries.
 * <p>
 * Only top-level referenced are returned (indirection, if any, is not
 * resolved). The items are returned in no particular order. An item might be
 * returned multiple times.
 */
public class BlobReferenceIterator extends AbstractIterator<ReferencedBlob> implements Closeable {

    private final DocumentStore documentStore;
    private final BlobCollector blobCollector;
    private final Queue<ReferencedBlob> blobs = new ArrayDeque<>();

    private Iterator<NodeDocument> iterator;

    public BlobReferenceIterator(DocumentNodeStore nodeStore) {
        this.documentStore = nodeStore.getDocumentStore();
        this.blobCollector = new BlobCollector(nodeStore);
    }

    @Override
    protected ReferencedBlob computeNext() {
        if (blobs.isEmpty()) {
            loadBatch();
        }

        if (!blobs.isEmpty()) {
            return blobs.remove();
        } else {
            return endOfData();
        }
    }

    private void loadBatch() {
        if (this.iterator == null) {
            this.iterator = getIteratorOverDocsWithBinaries();
        }
        // Some node which have the '_bin' flag set might not have any binaries
        // in it so move forward if blobs is still empty and cursor has more
        // elements
        while (iterator.hasNext() && blobs.isEmpty()) {
            collectBinaries(iterator.next());
        }
    }

    private void collectBinaries(NodeDocument nodeDocument) {
        blobCollector.collect(nodeDocument, blobs);
    }

    /**
     * Override this document to use a document store specific iterator.
     */
    public Iterator<NodeDocument> getIteratorOverDocsWithBinaries() {
        int batchSize = 1000;
        return Utils.getSelectedDocuments(documentStore, NodeDocument.HAS_BINARY_FLAG, NodeDocument.HAS_BINARY_VAL, batchSize)
                .iterator();
    }

    @Override
    public void close() {
        Utils.closeIfCloseable(iterator);
    }
}
