/*
 * Copyright (C) 2014 Indeed Inc.
 *
 * 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 com.indeed.flamdex.simple;

import com.indeed.util.core.reference.SharedReference;
import com.indeed.flamdex.api.DocIdStream;
import com.indeed.flamdex.api.TermIterator;
import com.indeed.util.mmap.DirectMemory;
import com.indeed.util.mmap.MMapBuffer;
import org.apache.log4j.Logger;

import java.io.IOException;

/**
 * @author jsgroth
 */
final class SimpleDocIdStream implements DocIdStream {
    private static final Logger log = Logger.getLogger(SimpleDocIdStream.class);

    public static final int BUFFER_SIZE = 8192;

    private final MapCache mapCache;

    private final byte[] buffer;
    private long bufferOffset;
    private int bufferLen;
    private int bufferPtr;

    private DirectMemory memory;
    private SharedReference<MMapBuffer> file;
    private int docsRemaining;
    private int lastDoc;

    private String currentFileOpen;

    SimpleDocIdStream(MapCache mapCache) {
        this(mapCache, new byte[BUFFER_SIZE]);
    }

    SimpleDocIdStream(MapCache mapCache, byte[] buffer) {
        this.mapCache = mapCache;
        this.buffer = buffer;
        bufferOffset = 0L;
        bufferLen = 0;
        bufferPtr = 0;
    }

    @Override
    public void reset(TermIterator term) {
        if (!(term instanceof SimpleTermIterator)) throw new IllegalArgumentException("invalid term iterator");

        try {
            internalReset((SimpleTermIterator)term);
        } catch (IOException e) {
            close();
            throw new RuntimeException(e);
        }
    }

    private void internalReset(SimpleTermIterator term) throws IOException {
        final String filename = term.getFilename();
        if (!filename.equals(currentFileOpen)) {

            if (file != null) file.close();
            file = mapCache.copyOrOpen(filename);

            memory = file.get().memory();
            currentFileOpen = filename;
            // to force a refill
            bufferOffset = 0L;
            bufferLen = 0;
            bufferPtr = 0;
        }

        final long offset = term.getOffset();
        if (offset >= bufferOffset && offset < bufferOffset + bufferLen) {
            bufferPtr = (int) (offset - bufferOffset);
        } else {
            refillBuffer(offset);
        }

        docsRemaining = term.docFreq();
        lastDoc = 0;
    }

    @Override
    public int fillDocIdBuffer(int[] docIdBuffer) {
        if (docsRemaining == 0) return 0;

        try {
            final int n = Math.min(docsRemaining, docIdBuffer.length);
            for (int i = 0; i < n; ++i) {
                final int docDelta = readVInt();
                lastDoc += docDelta;
                docIdBuffer[i] = lastDoc;
            }
            docsRemaining -= n;
            return n;
        } catch (IOException e) {
            close();
            throw new RuntimeException(e);
        }
    }

    @Override
    public void close() {
        try {
            if (file != null) {
                file.close();
                file = null;
            }
        } catch (IOException e) {
            log.error("error closing file", e);
        }
    }

    private int readVInt() throws IOException {
        int ret = 0;
        int shift = 0;
        do {
            if (bufferPtr == bufferLen) refillBuffer(bufferOffset + bufferLen);
            byte b = buffer[bufferPtr++];
            ret |= ((b & 0x7F) << shift);
            if (b >= 0) return ret;
            shift += 7;
        } while (true);
    }

    private void refillBuffer(long offset) throws IOException {
        bufferLen = (int)Math.min(buffer.length, memory.length() - offset);
        if (bufferLen > 0) {
            memory.getBytes(offset, buffer, 0, bufferLen);
        }
        bufferOffset = offset;
        bufferPtr = 0;
    }
}
