/*
 * Copyright (c) 2013-2016 Cinchapi 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.cinchapi.concourse.server.storage.temp;

import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;

import javax.annotation.Nullable;

import com.cinchapi.concourse.server.model.Value;
import com.cinchapi.concourse.server.storage.Action;
import com.cinchapi.concourse.server.storage.PermanentStore;
import com.cinchapi.concourse.server.storage.cache.BloomFilter;
import com.cinchapi.concourse.thrift.Type;
import com.cinchapi.concourse.util.Producer;
import com.google.common.collect.Lists;

/**
 * A {@link Queue} is a very simple form of {@link Limbo} that represents
 * data as a sequence of {@link Write} objects. New data is appended to the
 * sequence and the returned {@link Iterator} traverses the list.
 * 
 * @author Jeff Nelson
 */
public class Queue extends Limbo {

    /**
     * The threshold at which an internal bloom filter is dynamically created to
     * speed up verifies.
     */
    private static final int BLOOM_FILTER_CREATION_THRESHOLD = 10;

    /**
     * An empty array of writes that is used to specify type conversion in the
     * {@link ArrayList#toArray(Object[])} method.
     */
    private static final Write[] EMPTY_WRITES_ARRAY = new Write[0];

    /**
     * A global producer that provides BloomFilters to instances that need them.
     * To some extent, this producer will queue up bloom filters so that the
     * overhead of creating them is not incurred directly by the caller.
     */
    private static final Producer<BloomFilter> producer = new Producer<BloomFilter>(
            new Callable<BloomFilter>() {

                @Override
                public BloomFilter call() throws Exception {
                    // TODO: at some point this size should be determined based
                    // on some intelligent heuristic
                    BloomFilter filter = BloomFilter.create(500000);
                    filter.disableThreadSafety();
                    return filter;
                }

            });

    /**
     * Revisions are stored as a sequential list of {@link Write} objects, which
     * means most reads are <em>at least</em> an O(n) scan.
     */
    protected final List<Write> writes;

    /**
     * The bloom filter used to speed up verifies.
     */
    private BloomFilter filter = null;

    /**
     * A cache of the {@link #getOldestWriteTimestamp() timestamp} for the oldest
     * write in the Queue. This value is not expected to change often, so it
     * makes sense to cache it for situations that frequently look for it.
     */
    private long oldestWriteTimestampCache = 0;

    /**
     * Construct a Limbo with enough capacity for {@code initialSize}. If
     * necessary, the structure will grow to accommodate more data.
     * 
     * @param initialSize
     */
    public Queue(int initialSize) {
        writes = Lists.newArrayListWithCapacity(initialSize);
    }

    /**
     * Return an unmodifiable copy of the writes contained in the Queue.
     * 
     * @return the writes
     */
    public List<Write> getWrites() {
        return writes;
    }

    @Override
    public boolean insert(Write write, boolean sync) {
        writes.add(write); // #sync is meaningless since Queue is a memory store
        if(filter != null) {
            filter.putCached(write.getKey(), write.getValue(),
                    write.getRecord());
        }
        else if(writes.size() > BLOOM_FILTER_CREATION_THRESHOLD) {
            filter = producer.consume();
            for (int i = 0; i < writes.size(); ++i) {
                Write stored = writes.get(i);
                filter.put(stored.getKey(), stored.getValue(),
                        stored.getRecord());
            }
        }
        return true;
    }

    @Override
    public Iterator<Write> iterator() {
        return writes.iterator();
    }

    /**
     * Return the number of writes in this Queue.
     * 
     * @return the number of writes
     */
    public int size() {
        return writes.size();
    }

    @Override
    public void start() {
        // do nothing
    }

    @Override
    public void stop() {
        // do nothing
    }

    @Override
    public void transport(PermanentStore destination, boolean sync) {
        // For transactions, this method will only be called once, so we can
        // optimize it by not using the services of an Iterator (e.g. hasNext(),
        // remove(), etc) and, if the number of writes in the Queue is large
        // enough, grabbing elements from the backing array directly.
        oldestWriteTimestampCache = 0;
        int length = writes.size();
        Write[] elts = length > 10000 ? writes.toArray(EMPTY_WRITES_ARRAY)
                : null;
        for (int i = 0; i < length; ++i) {
            Write write = elts == null ? writes.get(i) : elts[i];
            destination.accept(write, sync);
        }
    }

    @Override
    public boolean verify(Write write, long timestamp, boolean exists) {
        if(filter == null
                || (filter != null && filter.mightContainCached(write.getKey(),
                        write.getValue(), write.getRecord()))) {
            return super.verify(write, timestamp, exists);
        }
        else {
            return exists;
        }
    }

    @Override
    @Nullable
    protected Action getLastWriteAction(Write write, long timestamp) {
        if(filter == null
                || (filter != null && filter.mightContain(write.getKey(),
                        write.getValue(), write.getRecord()))) {
            return super.getLastWriteAction(write, timestamp);
        }
        else {
            return null;
        }
    }

    @Override
    protected long getOldestWriteTimestamp() {
        // When there is no data in the buffer return the max possible timestamp
        // so that no query's timestamp is less than this timestamp
        if(writes.size() == 0) {
            return Long.MAX_VALUE;
        }
        else {
            if(oldestWriteTimestampCache == 0) {
                Write oldestWrite = writes.get(0);
                oldestWriteTimestampCache = oldestWrite.getVersion();
            }
            return oldestWriteTimestampCache;
        }

    }

    @Override
    protected Iterator<Write> getSearchIterator(String key) {
        return iterator();
    }

    @Override
    protected boolean isPossibleSearchMatch(String key, Write write, Value value) {
        return write.getKey().toString().equals(key)
                && value.getType() == Type.STRING;
    }

}

@@ -2997,16 +2997,23 @@
  for the
+%0A     *
  oldest%0A
@@ -6073,16 +6073,22 @@
 tContain
+Cached
 (write.g

use cached bloom filiter lookup in Queue#getLastWriteAction