/*
 * 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;

import com.google.common.base.Throwables;
import com.google.common.collect.Maps;
import com.indeed.flamdex.api.FlamdexOutOfMemoryException;
import com.indeed.flamdex.api.FlamdexReader;
import com.indeed.flamdex.api.GenericIntTermDocIterator;
import com.indeed.flamdex.api.GenericStringTermDocIterator;
import com.indeed.flamdex.api.IntTermDocIterator;
import com.indeed.flamdex.api.IntValueLookup;
import com.indeed.flamdex.api.StringTermDocIterator;
import com.indeed.flamdex.api.StringValueLookup;
import com.indeed.flamdex.fieldcache.FieldCacher;
import com.indeed.flamdex.fieldcache.UnsortedIntTermDocIterator;
import com.indeed.flamdex.fieldcache.UnsortedIntTermDocIteratorImpl;

import java.io.IOException;
import java.util.Map;

/**
 * @author jsgroth
 *
 * FlamdexReader base class that provides implementations of some of the {@link FlamdexReader} methods
 *
 * at the moment of this comment's writing, {@link FlamdexReader#getMetric} and {@link FlamdexReader#memoryRequired} are implemented here
 */
public abstract class AbstractFlamdexReader implements FlamdexReader {
    protected final String directory;
    protected final int numDocs;
    private final boolean useMMapMetrics;

    private final Map<String, FieldCacher> intFieldCachers;

    protected AbstractFlamdexReader(String directory, int numDocs) {
        this(directory, numDocs, System.getProperty("flamdex.mmap.fieldcache") != null);
    }

    protected AbstractFlamdexReader(String directory, int numDocs, boolean useMMapMetrics) {
        this.directory = directory;
        this.numDocs = numDocs;
        this.useMMapMetrics = useMMapMetrics;

        this.intFieldCachers = Maps.newHashMap();
    }

    // this implementation will be correct for any FlamdexReader, but
    // subclasses may want to override for efficiency reasons
    protected UnsortedIntTermDocIterator createUnsortedIntTermDocIterator(String field) {
        return UnsortedIntTermDocIteratorImpl.create(this, field);
    }

    @Override
    public final IntValueLookup getMetric(String metric) throws FlamdexOutOfMemoryException {
        final FieldCacher fieldCacher = getMetricCacher(metric);
        final UnsortedIntTermDocIterator iterator = createUnsortedIntTermDocIterator(metric);
        try {
            return cacheField(iterator, metric, fieldCacher);
        } finally {
            iterator.close();
        }
    }

    public StringValueLookup getStringLookup(final String field) throws FlamdexOutOfMemoryException {
        try {
            return FieldCacher.newStringValueLookup(field, this, directory);
        } catch (IOException e) {
            throw Throwables.propagate(e);
        }
    }

    private IntValueLookup cacheField(UnsortedIntTermDocIterator iterator, String metric, FieldCacher fieldCacher) {
        if (useMMapMetrics) {
            try {
                return fieldCacher.newMMapFieldCache(iterator, numDocs, metric, directory);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return fieldCacher.newFieldCache(iterator, numDocs);
    }

    @Override
    public final long memoryRequired(String metric) {
        if (useMMapMetrics) return 0;

        final FieldCacher fieldCacher = getMetricCacher(metric);
        return fieldCacher.memoryRequired(numDocs);
    }

    private FieldCacher getMetricCacher(String metric) {
        synchronized (intFieldCachers) {
            if (!intFieldCachers.containsKey(metric)) {
                final FieldCacher cacher = FieldCacher.getCacherForField(metric, this);
                intFieldCachers.put(metric, cacher);
            }
            return intFieldCachers.get(metric);
        }
    }

    @Override
    public IntTermDocIterator getIntTermDocIterator(final String field) {
        return new GenericIntTermDocIterator(getIntTermIterator(field), getDocIdStream());
    }

    @Override
    public StringTermDocIterator getStringTermDocIterator(final String field) {
        return new GenericStringTermDocIterator(getStringTermIterator(field), getDocIdStream());
    }
    
    @Override
    public int getNumDocs() {
        return numDocs;
    }

    @Override
    public String getDirectory() {
        return directory;
    }

}
