/*******************************************************************************
 * Copyright (c) 2013, 2017 Ericsson
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License 2.0 which
 * accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Marc-Andre Laperle - Initial API and implementation
 *******************************************************************************/

package org.eclipse.tracecompass.internal.tmf.core.trace.indexer;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.text.MessageFormat;

import org.eclipse.tracecompass.internal.tmf.core.Activator;
import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
import org.eclipse.tracecompass.tmf.core.trace.indexer.ITmfPersistentlyIndexable;
import org.eclipse.tracecompass.tmf.core.trace.indexer.checkpoint.ITmfCheckpoint;
import org.eclipse.tracecompass.tmf.core.trace.indexer.checkpoint.TmfCheckpoint;
import org.eclipse.tracecompass.tmf.core.trace.location.ITmfLocation;

/**
 * An array of checkpoints stored on disk. It is very efficient for searching
 * checkpoints by rank (O(1))
 *
 * @author Marc-Andre Laperle
 */
public class FlatArray extends AbstractFileCheckpointCollection {
    /**
     * Typical FlatArray file name
     */
    public static final String INDEX_FILE_NAME = "checkpoint_flatarray.idx"; //$NON-NLS-1$

    // Cached values
    private int fCheckpointSize = 0;
    private ByteBuffer fByteBuffer;

    /**
     * Constructs a FlatArray for a given trace from scratch or from an existing
     * file. When the FlatArray is created from scratch, it is populated by
     * subsequent calls to {@link #insert}.
     *
     * @param file
     *            the file to use as the persistent storage
     * @param trace
     *            the trace
     */
    public FlatArray(File file, ITmfPersistentlyIndexable trace) {
        super(file, trace);

        fCheckpointSize = getTrace().getCheckpointSize();
        fByteBuffer = ByteBuffer.allocate(fCheckpointSize);
        fByteBuffer.clear();
    }

    /**
     * Insert a checkpoint into the file-backed array
     *
     * @param checkpoint
     *            the checkpoint to insert
     */
    @Override
    public void insert(ITmfCheckpoint checkpoint) {
        markDirty();
        try {
            CheckpointCollectionFileHeader header = getHeader();
            ++header.fSize;
            getRandomAccessFile().seek(getRandomAccessFile().length());
            fByteBuffer.clear();
            checkpoint.serialize(fByteBuffer);
            getRandomAccessFile().write(fByteBuffer.array());
        } catch (IOException e) {
            Activator.logError(MessageFormat.format(Messages.FlatArray_IOErrorWriting, getFile()), e);
        }
    }

    /**
     * Get a checkpoint from a rank
     *
     * @param rank
     *            the rank to search
     * @return the checkpoint that has been found or null if not found
     */
    public ITmfCheckpoint get(long rank) {
        ITmfCheckpoint checkpoint = null;
        try {
            long pos = getHeader().getSize() + fCheckpointSize * rank;
            if (getRandomAccessFile() == null) {
                return null;
            }
            getRandomAccessFile().seek(pos);
            fByteBuffer.clear();
            getRandomAccessFile().read(fByteBuffer.array());
            ITmfLocation location = getTrace().restoreLocation(fByteBuffer);
            ITmfTimestamp timeStamp = TmfTimestamp.create(fByteBuffer);
            checkpoint = new TmfCheckpoint(timeStamp, location, fByteBuffer);
        } catch (IOException e) {
            Activator.logError(MessageFormat.format(Messages.FlatArray_IOErrorReading, getFile()), e);
        }
        return checkpoint;
    }

    /**
     * Search for a checkpoint and return the rank.
     *
     * @param checkpoint
     *            the checkpoint to search
     * @return the checkpoint rank of the searched checkpoint, if it is
     *         contained in the index; otherwise, (-(insertion point) - 1).
     */
    @Override
    public long binarySearch(ITmfCheckpoint checkpoint) {
        if (getHeader().fSize == 1) {
            return 0;
        }

        long lower = 0;
        long upper = getHeader().fSize - 1;
        long lastMiddle = -1;
        long middle = 0;
        while (lower <= upper && lastMiddle != middle) {
            lastMiddle = middle;
            middle = (lower + upper) / 2;
            ITmfCheckpoint found = get(middle);
            incCacheMisses();
            int compare = checkpoint.compareTo(found);
            if (compare == 0) {
                return middle;
            }

            if (compare < 0) {
                upper = middle;
            } else {
                lower = middle + 1;
            }
        }
        return -(lower) - 1;
    }
}
