/*******************************************************************************
 * Copyright (c) 2016 Polytechnique
 *
 * 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:
 *   Loïc Prieur-Drevon - Initial API and implementation
 ******************************************************************************/

package org.eclipse.tracecompass.segmentstore.core;

import java.io.IOException;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Set;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.datastore.core.interval.IHTIntervalReader;
import org.eclipse.tracecompass.internal.segmentstore.core.arraylist.ArrayListStore;
import org.eclipse.tracecompass.internal.segmentstore.core.arraylist.LazyArrayListStore;
import org.eclipse.tracecompass.internal.segmentstore.core.segmentHistoryTree.HistoryTreeSegmentStore;
import org.eclipse.tracecompass.internal.segmentstore.core.treemap.TreeMapStore;

/**
 * Factory to create Segment Stores.
 *
 * Since segment stores are meant to be accessed using the {@link ISegmentStore}
 * interface, you can use this factory to instantiate new ones.
 *
 * @author Loïc Prieur-Drevon
 * @param <E>
 *            The type of segment held in this store
 * @since 1.1
 */
public final class SegmentStoreFactory<E> {
    /**
     * Flags to determine the type of SegmentStore to use.
     */
    public enum SegmentStoreType {
        /**
         * Segment Store should be as fast as possible to build and read.
         * Performance of individual operations may be slower, but overall the
         * speed should be faster.
         */
        Fast,
        /**
         * Segment Store based should have predictable performance. This does
         * not mean it's faster, it should not give a surprise random slow down
         * though. If it slows down, it will be on the {@link ISegment} that
         * slows it down.
         */
        Stable,
        /**
         * Segment Store should contain no duplicate segments
         */
        Distinct,
        /**
         * Segment store that doesn't have to reside entirely in memory, ideal
         * for very large stores, the performance are not as high as the other
         * segment stores. These kind of stores should be created using the
         * {@link SegmentStoreFactory#createOnDiskSegmentStore(Path, IHTIntervalReader, int)}
         * factory method
         *
         * @since 2.0
         */
        OnDisk
    }

    private SegmentStoreFactory() {
        // Do nothing
    }

    /**
     * New SegmentStore factory method
     *
     * @param segmentTypes
     *            Flags used to determine the type of Segment Store that will be
     *            created
     *
     * @return a new {@link ISegmentStore}
     */
    public static <E extends ISegment> ISegmentStore<E> createSegmentStore(@Nullable SegmentStoreType... segmentTypes) {
        Set<@NonNull SegmentStoreType> segments = getListOfFlags(segmentTypes);
        if (segments.contains(SegmentStoreType.Distinct)) {
            return createTreeMapStore();
        }
        if (segments.contains(SegmentStoreType.Stable)) {
            return createArrayListStore();
        }
        // default option is the fastest
        return createLazyArrayListStore();

    }

    /**
     * New SegmentStore factory method to create store from an array of Objects
     *
     * @param segmentTypes
     *            Flags used to determine the type of Segment Store that will be
     *            created
     * @param array
     *            the {@link Object} array we want the returned segment store to
     *            contain, {@link Object} are only inserted if they extend
     *            {@link ISegment}
     * @return an {@link ISegmentStore} containing the {@link ISegment}s from
     *         array.
     */
    public static <E extends ISegment> ISegmentStore<E> createSegmentStore(Object[] array, SegmentStoreType... segmentTypes) {
        Set<@NonNull SegmentStoreType> segments = getListOfFlags(segmentTypes);
        if (segments.contains(SegmentStoreType.Distinct)) {
            ISegmentStore<E> store = createTreeMapStore();
            for (Object elem : array) {
                if (elem instanceof ISegment) {
                    store.add((E) elem); // warning from type, it should be fine
                }
            }
            return store;
        }
        if (segments.contains(SegmentStoreType.Stable)) {
            return new ArrayListStore<>(array);
        }
        // default option is the fastest
        return new LazyArrayListStore<>(array);
    }

    /**
     * SegmentStore factory method that creates a segment store on disk
     *
     * @param segmentFile
     *            The file where to store the segments
     * @param segmentReader
     *            The factory to read the segments from a safe byte buffer
     * @param version
     *            The version number of the segment reader/writer
     *
     * @return an {@link ISegmentStore}
     * @throws IOException
     *             Exceptions when creating the segment store
     * @since 2.1
     */
    public static <E extends ISegment> ISegmentStore<E> createOnDiskSegmentStore(Path segmentFile, IHTIntervalReader<E> segmentReader, int version) throws IOException {
        return new HistoryTreeSegmentStore<>(segmentFile, segmentReader, version);
    }

    private static Set<@NonNull SegmentStoreType> getListOfFlags(SegmentStoreType... segmentTypes) {
        Set<@NonNull SegmentStoreType> segments = new HashSet<>();
        for(@Nullable SegmentStoreType segmentType : segmentTypes ) {
            if(segmentType != null) {
                segments.add(segmentType);
            }
        }
        return segments;
    }

    /**
     * New {@link TreeMapStore} factory method
     *
     * @return the new Segment Store
     */
    private static <E extends ISegment> ISegmentStore<E> createTreeMapStore() {
        return new TreeMapStore<>();
    }

    /**
     * New {@link ArrayListStore} factory method
     *
     * @return the new Segment Store
     */
    private static <E extends ISegment> ISegmentStore<E> createArrayListStore() {
        return new ArrayListStore<>();
    }

    /**
     * New {@link LazyArrayListStore} factory method
     *
     * @return the new Segment Store
     */
    private static <E extends ISegment> ISegmentStore<E> createLazyArrayListStore() {
        return new LazyArrayListStore<>();
    }

}
