/*
 * 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.imhotep.local;

import com.indeed.flamdex.api.DocIdStream;
import com.indeed.flamdex.api.FlamdexReader;
import com.indeed.flamdex.api.IntTermIterator;
import com.indeed.flamdex.api.StringTermIterator;
import com.indeed.util.core.Pair;
import com.indeed.util.core.io.Closeables2;
import com.indeed.util.core.reference.SharedReference;

import java.util.Iterator;
import java.util.Map;

class FlamdexSubsetFTGSIterator extends AbstractFlamdexFTGSIterator {

    protected StringTermIterator stringTermIterator;
    protected IntTermIterator intTermIterator;
    private final DocIdStream docIdStream;

    private final Iterator<Map.Entry<String, long[]>> intFieldToTermsIterator;
    private final Iterator<Map.Entry<String, String[]>> stringFieldToTermsIterator;

    private long[] currentIntFieldTerms;
    private String[] currentStringFieldTerms;
    private int currentFieldTermPtr = -1;

    public FlamdexSubsetFTGSIterator(ImhotepLocalSession imhotepLocalSession, SharedReference<FlamdexReader> flamdexReader, Map<String, long[]> intFieldToTerms, Map<String, String[]> stringFieldToTerms) {
        super(imhotepLocalSession, flamdexReader);
        this.intFieldToTermsIterator = intFieldToTerms.entrySet().iterator();
        this.stringFieldToTermsIterator = stringFieldToTerms.entrySet().iterator();
        docIdStream = flamdexReader.get().getDocIdStream();
    }

    @Override
    public final boolean nextField() {
        // todo: reset/cleanup term iterators etc that are in progress
        synchronized (session) {
            if (intFieldToTermsIterator.hasNext()) {
                final Map.Entry<String, long[]> entry = intFieldToTermsIterator.next();
                currentField = entry.getKey();
                currentIntFieldTerms = entry.getValue();
                currentFieldTermPtr = -1;
                currentFieldIsIntType = true;
                if (intTermIterator != null) Closeables2.closeQuietly(intTermIterator, ImhotepLocalSession.log);
                intTermIterator = flamdexReader.get().getIntTermIterator(currentField);
                if (session.fieldZeroDocBitsets != null) {
                    fieldZeroDocBitset = session.fieldZeroDocBitsets.get(Pair.of(currentField, currentFieldIsIntType));
                }
                termIndex = 0;
                return true;
            }
            if (stringFieldToTermsIterator.hasNext()) {
                final Map.Entry<String, String[]> entry = stringFieldToTermsIterator.next();
                currentField = entry.getKey();
                currentStringFieldTerms = entry.getValue();
                currentFieldTermPtr = -1;
                currentFieldIsIntType = false;
                if (stringTermIterator != null) Closeables2.closeQuietly(stringTermIterator, ImhotepLocalSession.log);
                stringTermIterator = flamdexReader.get().getStringTermIterator(currentField);
                if (session.fieldZeroDocBitsets != null) {
                    fieldZeroDocBitset = session.fieldZeroDocBitsets.get(Pair.of(currentField, currentFieldIsIntType));
                }
                termIndex = 0;
                return true;
            }
            currentField = null;
            close();
            if (ImhotepLocalSession.logTiming) {
                ImhotepLocalSession.log.info("intTermsTime: "+intTermsTime/1000000d+" ms, stringTermsTime: "+stringTermsTime/1000000d+" ms, docsTime: "+docsTime/1000000d+" ms, lookupsTime: "+lookupsTime/1000000d+" ms, timingErrorTime: "+timingErrorTime/1000000d+" ms");
            }
            return false;
        }
    }

    @Override
    public final void close() {
        synchronized (session) {
            if (docIdStream != null) {
                Closeables2.closeQuietly(docIdStream, ImhotepLocalSession.log);
            }
            if (intTermIterator != null) {
                Closeables2.closeQuietly(intTermIterator, ImhotepLocalSession.log);
                intTermIterator = null;
            }
            if (stringTermIterator != null) {
                Closeables2.closeQuietly(stringTermIterator, ImhotepLocalSession.log);
                stringTermIterator = null;
            }
            if (flamdexReader != null) {
                Closeables2.closeQuietly(flamdexReader, ImhotepLocalSession.log);
                flamdexReader = null;
            }
        }
    }

    @Override
    public final boolean nextTerm() {
        if (currentField == null) return false;
        resetGroupStats = true;
        if (currentFieldIsIntType) {
            if (ImhotepLocalSession.logTiming) intTermsTime -= System.nanoTime();
            try {
                while (true) {
                    if (currentFieldTermPtr + 1 >= currentIntFieldTerms.length) {
                        return false;
                    }
                    currentFieldTermPtr++;
                    intTermIterator.reset(currentIntFieldTerms[currentFieldTermPtr]);
                    if (intTermIterator.next() && intTermIterator.term() == currentIntFieldTerms[currentFieldTermPtr]) {
                        docIdStream.reset(intTermIterator);
                        return true;
                    }
                }
            } finally {
                if (ImhotepLocalSession.logTiming) intTermsTime += System.nanoTime();
            }
        } else {
            if (ImhotepLocalSession.logTiming) stringTermsTime -= System.nanoTime();
            try {
                while (true) {
                    if (currentFieldTermPtr + 1 >= currentStringFieldTerms.length) {
                        return false;
                    }
                    currentFieldTermPtr++;
                    stringTermIterator.reset(currentStringFieldTerms[currentFieldTermPtr]);
                    if (stringTermIterator.next() && stringTermIterator.term().equals(currentStringFieldTerms[currentFieldTermPtr])) {
                        docIdStream.reset(stringTermIterator);
                        return true;
                    }
                }
            } finally {
                if (ImhotepLocalSession.logTiming) stringTermsTime += System.nanoTime();
            }
        }
    }

    @Override
    public final long termDocFreq() {
        return currentFieldIsIntType ? intTermIterator.docFreq() : stringTermIterator.docFreq();
    }

    @Override
    public final long termIntVal() {
        return intTermIterator.term();
    }

    @Override
    public final String termStringVal() {
        return stringTermIterator.term();
    }

    @Override
    protected int fillDocIdBuffer() {
        return docIdStream.fillDocIdBuffer(session.docIdBuf);
    }
}