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

import com.indeed.flamdex.api.IntValueLookup;
import com.indeed.imhotep.MemoryReserver;
import com.indeed.imhotep.api.ImhotepOutOfMemoryException;

import java.util.BitSet;

/**
 * @author dwahler
 */
public class CachedInterleavedMetrics {
    private final MemoryReserver memory;

    private final int numDocs, stride;
    private long[] interleavedData;

    private final BitSet closed;
    private boolean released;

    public CachedInterleavedMetrics(MemoryReserver memory, int numDocs, IntValueLookup... lookups) throws ImhotepOutOfMemoryException {
        this.memory = memory;
        this.numDocs = numDocs;
        this.stride = lookups.length;

        if (!memory.claimMemory(numDocs * 8L * stride)) {
            throw new ImhotepOutOfMemoryException();
        }
        interleavedData = new long[numDocs * stride];
        fillValues(lookups);

        closed = new BitSet(stride);
    }

    public IntValueLookup[] getLookups() {
        final IntValueLookup[] result = new IntValueLookup[stride];
        for (int i = 0; i < stride; i++) {
            result[i] = new InterleavedLookup(i);
        }
        return result;
    }

    private void fillValues(IntValueLookup... lookups) {
        final int BUFFER_SIZE = 8192;
        final int[] idBuffer = new int[BUFFER_SIZE];
        final long[] valBuffer = new long[BUFFER_SIZE];

        for (int start = 0; start < numDocs; start += BUFFER_SIZE) {
            final int end = Math.min(numDocs, start+BUFFER_SIZE);
            final int n = end-start;
            for (int i = 0; i < n; i++) {
                idBuffer[i] = start + i;
            }

            for (int offset = 0; offset < lookups.length; offset++) {
                lookups[offset].lookup(idBuffer, valBuffer, n);
                for (int i = 0; i < n; i++) {
                    interleavedData[(i+start)*stride+offset] = valBuffer[i];
                }
            }
        }
    }

    private void closeLookup(int offset) {
        closed.set(offset);
        if (closed.nextClearBit(0) == stride && !released) {
            // all lookups are closed; release memory from pool
            released = true;
            interleavedData = null;
            memory.releaseMemory(numDocs * 8L * stride);
        }
    }

    private class InterleavedLookup implements IntValueLookup {
        private final int offset;

        private InterleavedLookup(int offset) {
            this.offset = offset;
        }

        @Override
        public long getMin() {
            return Long.MIN_VALUE; // TODO compute this
        }

        @Override
        public long getMax() {
            return Long.MAX_VALUE; // TODO compute this;
        }

        @Override
        public void lookup(int[] docIds, long[] values, int n) {
            final long[] interleavedData = CachedInterleavedMetrics.this.interleavedData;
            final int stride = CachedInterleavedMetrics.this.stride;
            final int offset = this.offset;

            for (int i = 0; i < n; i++) {
                values[i] = interleavedData[docIds[i]*stride + offset];
            }
        }

        @Override
        public long memoryUsed() {
            return numDocs * 8L;
        }

        @Override
        public void close() {
            closeLookup(offset);
        }
    }
}
