/*******************************************************************************
 * Copyright (c) 2012, 2017 Ericsson, École Polytechnique de Montréal
 *
 * 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:
 *   Matthew Khouzam - Initial API and implementation
 *   Patrick Tasse - Updated for removal of context clone
 *   Geneviève Bastien - Added the createTimestamp function
 *******************************************************************************/

package org.eclipse.tracecompass.tmf.ctf.core.trace;

import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;

import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.ctf.core.CTFException;
import org.eclipse.tracecompass.ctf.core.event.CTFCallsite;
import org.eclipse.tracecompass.ctf.core.event.CTFClock;
import org.eclipse.tracecompass.ctf.core.event.IEventDeclaration;
import org.eclipse.tracecompass.ctf.core.event.types.StructDeclaration;
import org.eclipse.tracecompass.ctf.core.trace.CTFStreamInput;
import org.eclipse.tracecompass.ctf.core.trace.CTFTrace;
import org.eclipse.tracecompass.ctf.core.trace.CTFTraceReader;
import org.eclipse.tracecompass.ctf.core.trace.CTFTraceWriter;
import org.eclipse.tracecompass.ctf.core.trace.ICTFStream;
import org.eclipse.tracecompass.ctf.core.trace.Metadata;
import org.eclipse.tracecompass.internal.tmf.ctf.core.Activator;
import org.eclipse.tracecompass.internal.tmf.ctf.core.event.aspect.CtfEventContextAspect;
import org.eclipse.tracecompass.internal.tmf.ctf.core.event.aspect.CtfPacketContextAspect;
import org.eclipse.tracecompass.internal.tmf.ctf.core.event.aspect.CtfPacketHeaderAspect;
import org.eclipse.tracecompass.internal.tmf.ctf.core.event.aspect.CtfStreamContextAspect;
import org.eclipse.tracecompass.internal.tmf.ctf.core.trace.iterator.CtfIterator;
import org.eclipse.tracecompass.internal.tmf.ctf.core.trace.iterator.CtfIteratorManager;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
import org.eclipse.tracecompass.tmf.core.event.ITmfEventField;
import org.eclipse.tracecompass.tmf.core.event.TmfEventField;
import org.eclipse.tracecompass.tmf.core.event.aspect.ITmfEventAspect;
import org.eclipse.tracecompass.tmf.core.event.aspect.TmfBaseAspects;
import org.eclipse.tracecompass.tmf.core.exceptions.TmfTraceException;
import org.eclipse.tracecompass.tmf.core.project.model.ITmfPropertiesProvider;
import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
import org.eclipse.tracecompass.tmf.core.trace.ICyclesConverter;
import org.eclipse.tracecompass.tmf.core.trace.ITmfContext;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTraceKnownSize;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTraceWithPreDefinedEvents;
import org.eclipse.tracecompass.tmf.core.trace.TmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TraceValidationStatus;
import org.eclipse.tracecompass.tmf.core.trace.indexer.ITmfPersistentlyIndexable;
import org.eclipse.tracecompass.tmf.core.trace.indexer.ITmfTraceIndexer;
import org.eclipse.tracecompass.tmf.core.trace.indexer.TmfBTreeTraceIndexer;
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;
import org.eclipse.tracecompass.tmf.core.trace.trim.ITmfTrimmableTrace;
import org.eclipse.tracecompass.tmf.ctf.core.CtfConstants;
import org.eclipse.tracecompass.tmf.ctf.core.context.CtfLocation;
import org.eclipse.tracecompass.tmf.ctf.core.context.CtfLocationInfo;
import org.eclipse.tracecompass.tmf.ctf.core.context.CtfTmfContext;
import org.eclipse.tracecompass.tmf.ctf.core.event.CtfTmfEvent;
import org.eclipse.tracecompass.tmf.ctf.core.event.CtfTmfEventFactory;
import org.eclipse.tracecompass.tmf.ctf.core.event.CtfTmfEventType;
import org.eclipse.tracecompass.tmf.ctf.core.event.aspect.CtfChannelAspect;
import org.eclipse.tracecompass.tmf.ctf.core.event.aspect.CtfCpuAspect;
import org.eclipse.tracecompass.tmf.ctf.core.event.lookup.CtfTmfCallsite;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;

/**
 * The CTf trace handler
 *
 * @version 1.0
 * @author Matthew khouzam
 */
public class CtfTmfTrace extends TmfTrace
        implements ITmfPropertiesProvider, ITmfPersistentlyIndexable,
        ITmfTraceWithPreDefinedEvents, ITmfTraceKnownSize, ICyclesConverter, ITmfTrimmableTrace {

    // -------------------------------------------
    // Constants
    // -------------------------------------------

    /**
     * Clock offset property
     *
     * @since 1.2
     */
    public static final String CLOCK_OFFSET = "clock_offset"; //$NON-NLS-1$

    /**
     * Default cache size for CTF traces
     */
    protected static final int DEFAULT_CACHE_SIZE = 50000;

    /**
     * Event aspects available for all CTF traces
     *
     * @since 1.0
     */
    protected static final @NonNull Collection<@NonNull ITmfEventAspect<?>> CTF_ASPECTS = ImmutableList.of(
            TmfBaseAspects.getTimestampAspect(),
            new CtfChannelAspect(),
            new CtfCpuAspect(),
            TmfBaseAspects.getEventTypeAspect(),
            TmfBaseAspects.getContentsAspect(),
            CtfPacketHeaderAspect.getInstance(),
            CtfPacketContextAspect.getInstance(),
            CtfStreamContextAspect.getInstance(),
            CtfEventContextAspect.getInstance());

    /**
     * The Ctf clock unique identifier field
     */
    private static final String CLOCK_HOST_PROPERTY = "uuid"; //$NON-NLS-1$
    private static final int CONFIDENCE = 10;
    private static final int MIN_CONFIDENCE = 1;

    /**
     * This is a reduction factor to avoid overflows.
     */
    private static final long REDUCTION_FACTOR = 4096;

    /**
     * Average CTF event size, used to estimate the trace size. (Inspired by
     * empirical observations with LTTng kernel traces, to avoid hanging at 100% for
     * too long)
     *
     * TODO: Find a more suitable approximation, perhaps per concrete trace type or
     * per trace directly with the metadata
     */
    private static final int CTF_AVG_EVENT_SIZE = 16;

    // -------------------------------------------
    // Fields
    // -------------------------------------------

    private final Map<@NonNull String, @NonNull CtfTmfEventType> fContainedEventTypes = Collections.synchronizedMap(new HashMap<>());

    private final CtfIteratorManager fIteratorManager = new CtfIteratorManager(this);

    private final @NonNull CtfTmfEventFactory fEventFactory;

    /** Reference to the CTF Trace */
    private CTFTrace fTrace;

    private UUID fUUID;

    // -------------------------------------------
    // Constructor
    // -------------------------------------------

    /**
     * Default constructor
     */
    public CtfTmfTrace() {
        this(CtfTmfEventFactory.instance());
    }

    /**
     * Constructor for sub-classes to specify their own event factory.
     *
     * @param eventFactory
     *            The event factory to use to generate trace events
     * @since 2.0
     */
    protected CtfTmfTrace(@NonNull CtfTmfEventFactory eventFactory) {
        super();
        fEventFactory = eventFactory;
    }

    // -------------------------------------------
    // TmfTrace Overrides
    // -------------------------------------------
    /**
     * Method initTrace.
     *
     * @param resource
     *            The resource associated with this trace
     * @param path
     *            The path to the trace file
     * @param eventType
     *            The type of events that will be read from this trace
     * @throws TmfTraceException
     *             If something went wrong while reading the trace
     */
    @Override
    public void initTrace(final IResource resource, final String path, final Class<? extends ITmfEvent> eventType)
            throws TmfTraceException {
        /*
         * Set the cache size. This has to be done before the call to super() because
         * the super needs to know the cache size.
         */
        setCacheSize();

        super.initTrace(resource, path, eventType);

        try {
            this.fTrace = new CTFTrace(path);
            CtfTmfContext ctx;
            /* Set the start and (current) end times for this trace */
            ctx = (CtfTmfContext) seekEvent(0L);
            CtfTmfEvent event = getNext(ctx);
            if ((ctx.getLocation().equals(CtfIterator.NULL_LOCATION)) || (ctx.getCurrentEvent() == null)) {
                /* Handle the case where the trace is empty */
                this.setStartTime(TmfTimestamp.BIG_BANG);
            } else {
                final ITmfTimestamp curTime = event.getTimestamp();
                this.setStartTime(curTime);
                this.setEndTime(curTime);
            }
            /*
             * Register every event type. When you call getType, it will register a trace to
             * that type in the TmfEventTypeManager
             */
            try (CtfIterator iter = fIteratorManager.getIterator(ctx)) {
                if (iter == null) {
                    throw new TmfTraceException("Failed to get CTF Iterator for path " + path); //$NON-NLS-1$
                }
                Set<@NonNull ITmfEventField> streamContextNames = new HashSet<>();
                for (IEventDeclaration ied : iter.getEventDeclarations()) {
                    CtfTmfEventType ctfTmfEventType = fContainedEventTypes.get(ied.getName());
                    if (ctfTmfEventType == null) {
                        List<ITmfEventField> content = new ArrayList<>();

                        /* Should only return null the first time */
                        final StructDeclaration fields = ied.getFields();
                        if (fields != null) {
                            for (String fieldName : fields.getFieldsList()) {
                                content.add(new TmfEventField(checkNotNull(fieldName), null, null));
                            }
                        }

                        /* Add stream contexts */
                        final StructDeclaration streamContexts = ied.getStream().getEventContextDecl();
                        if (streamContextNames.isEmpty()) {
                            if (streamContexts != null) {
                                for (String fieldName : streamContexts.getFieldsList()) {
                                    streamContextNames.add(new TmfEventField(checkNotNull(CtfConstants.CONTEXT_FIELD_PREFIX + fieldName), null, null));
                                }
                            }
                        }
                        content.addAll(streamContextNames);

                        if (!content.isEmpty()) {
                            ITmfEventField contentTree = new TmfEventField(
                                    ITmfEventField.ROOT_FIELD_ID,
                                    null,
                                    content.toArray(new ITmfEventField[content.size()]));

                            ctfTmfEventType = new CtfTmfEventType(checkNotNull(ied.getName()), contentTree);
                            fContainedEventTypes.put(ctfTmfEventType.getName(), ctfTmfEventType);
                        }
                    }
                }
            }
            ctx.dispose();
            fUUID = fTrace.getUUID();
        } catch (final CTFException e) {
            /*
             * If it failed at the init(), we can assume it's because the file was not found
             * or was not recognized as a CTF trace. Throw into the new type of exception
             * expected by the rest of TMF.
             */
            throw new TmfTraceException(e.getMessage(), e);
        }
    }

    @Override
    public synchronized void dispose() {
        fIteratorManager.dispose();
        fContainedEventTypes.clear();
        if (fTrace != null) {
            fTrace = null;
        }
        super.dispose();
    }

    /**
     * {@inheritDoc}
     * <p>
     * The default implementation of a CTF trace.
     *
     * Firstly a weak validation of the metadata is done to determine if the path is
     * actually for a CTF trace. After that a full validation is done.
     *
     * If the weak and full validation are successful the confidence is set to 10.
     *
     * If the weak validation was successful, but the full validation fails a
     * TraceValidationStatus with severity warning and confidence of 1 is returned.
     *
     * If both weak and full validation fails an error status is returned.
     */
    @Override
    public IStatus validate(final IProject project, final String path) {
        boolean isMetadataFile = false;
        String pluginId = Activator.PLUGIN_ID;
        try {
            isMetadataFile = Metadata.preValidate(path);
        } catch (final CTFException e) {
            return new Status(IStatus.ERROR, pluginId, Messages.CtfTmfTrace_ReadingError + ": " + e.toString(), e); //$NON-NLS-1$
        }

        if (isMetadataFile) {
            // Trace is pre-validated, continue will full validation
            try {
                final CTFTrace trace = new CTFTrace(path);
                if (!trace.majorIsSet()) {
                    if (isMetadataFile) {
                        return new TraceValidationStatus(MIN_CONFIDENCE, IStatus.WARNING, pluginId, Messages.CtfTmfTrace_MajorNotSet, null);
                    }
                    return new Status(IStatus.ERROR, pluginId, Messages.CtfTmfTrace_MajorNotSet);
                }

                // Validate using reader initialization
                try (CTFTraceReader ctfTraceReader = new CTFTraceReader(trace)) {
                    // do nothing
                }

                // Get contained event names
                Collection<String> eventNames = new HashSet<>();
                for (ICTFStream stream : trace.getStreams()) {
                    for (IEventDeclaration ed : trace.getEventDeclarations(stream.getId())) {
                        if (ed != null) {
                            eventNames.add(ed.getName());
                        }
                    }
                }

                // Trace is validated, return with confidence
                return new CtfTraceValidationStatus(CONFIDENCE, pluginId, trace.getEnvironment(), eventNames);

            } catch (final CTFException | BufferOverflowException e) {
                // return warning since it's a CTF trace but with errors in it
                return new TraceValidationStatus(MIN_CONFIDENCE, IStatus.WARNING, pluginId, Messages.CtfTmfTrace_ReadingError + ": " + e.toString(), e); //$NON-NLS-1$
            }
        }
        return new Status(IStatus.ERROR, pluginId, Messages.CtfTmfTrace_ReadingError);
    }

    @Override
    public Iterable<ITmfEventAspect<?>> getEventAspects() {
        return CTF_ASPECTS;
    }

    /**
     * Method getCurrentLocation. This is not applicable in CTF
     *
     * @return null, since the trace has no knowledge of the current location
     * @see org.eclipse.tracecompass.tmf.core.trace.ITmfTrace#getCurrentLocation()
     */
    @Override
    public ITmfLocation getCurrentLocation() {
        return null;
    }

    @Override
    public double getLocationRatio(ITmfLocation location) {
        final CtfLocation curLocation = (CtfLocation) location;
        final long startTime = getStartTime().getValue();
        final double diff = curLocation.getLocationInfo().getTimestamp() - startTime;
        final double total = getEndTime().getValue() - startTime;
        return Math.max(0.0, Math.min(1.0, diff / total));
    }

    /**
     * Method seekEvent.
     *
     * @param location
     *            ITmfLocation<?>
     * @return ITmfContext
     */
    @Override
    public synchronized ITmfContext seekEvent(final ITmfLocation location) {
        CtfLocation currentLocation = (CtfLocation) location;
        CtfTmfContext context = new CtfTmfContext(this);
        if (fTrace == null) {
            context.setLocation(null);
            context.setRank(ITmfContext.UNKNOWN_RANK);
            return context;
        }
        /*
         * The rank is set to 0 if the iterator seeks the beginning. If not, it will be
         * set to UNKNOWN_RANK, since CTF traces don't support seeking by rank for now.
         */
        if (currentLocation == null) {
            currentLocation = new CtfLocation(new CtfLocationInfo(0L, 0L));
            context.setRank(0);
        } else {
            context.setRank(ITmfContext.UNKNOWN_RANK);
        }
        /* This will seek and update the location after the seek */
        context.setLocation(currentLocation);
        return context;
    }

    @Override
    public synchronized ITmfContext seekEvent(double ratio) {
        CtfTmfContext context = new CtfTmfContext(this);
        if (fTrace == null) {
            context.setLocation(null);
            context.setRank(ITmfContext.UNKNOWN_RANK);
            return context;
        }
        final long end = getEndTime().getValue();
        final long start = getStartTime().getValue();
        final long diff = end - start;
        final long ratioTs = Math.round(diff * ratio) + start;
        context.seek(ratioTs);
        context.setRank(ITmfContext.UNKNOWN_RANK);
        return context;
    }

    /**
     * Method readNextEvent.
     *
     * @param context
     *            ITmfContext
     * @return CtfTmfEvent
     * @see org.eclipse.tracecompass.tmf.core.trace.ITmfTrace#getNext(ITmfContext)
     */
    @Override
    public synchronized CtfTmfEvent getNext(final ITmfContext context) {
        if (fTrace == null) {
            return null;
        }
        CtfTmfEvent event = null;
        if (context instanceof CtfTmfContext) {
            if (context.getLocation() == null || CtfLocation.INVALID_LOCATION.equals(context.getLocation().getLocationInfo())) {
                return null;
            }
            CtfTmfContext ctfContext = (CtfTmfContext) context;
            event = ctfContext.getCurrentEvent();

            if (event != null) {
                updateAttributes(context, event);
                ctfContext.advance();
                ctfContext.increaseRank();
            }
        }

        return event;
    }

    /**
     * Ctf traces have a clock with a unique uuid that will be used to identify the
     * host. Traces with the same clock uuid will be known to have been made on the
     * same machine.
     *
     * Note: uuid is an optional field, it may not be there for a clock.
     */
    @Override
    public String getHostId() {
        CTFTrace trace = fTrace;
        if (trace != null) {
            CTFClock clock = trace.getClock();
            if (clock != null) {
                String clockHost = (String) clock.getProperty(CLOCK_HOST_PROPERTY);
                if (clockHost != null) {
                    return clockHost;
                }
            }
        }
        return super.getHostId();
    }

    /**
     * Get the first callsite that matches the event name
     *
     * @param eventName
     *            The event name to look for
     * @return The best callsite candidate
     * @since 3.0
     */
    public @Nullable CtfTmfCallsite getCallsite(String eventName) {
        CTFTrace trace = fTrace;
        if (trace != null) {
            for (ICTFStream stream : fTrace.getStreams()) {
                for (IEventDeclaration eventDeclaration : stream.getEventDeclarations()) {
                    if (eventDeclaration != null && Objects.equals(eventDeclaration.getName(), eventName) &&
                            !eventDeclaration.getCallsites().isEmpty()) {
                        return new CtfTmfCallsite(eventDeclaration.getCallsites().get(0));
                    }
                }
            }
        }
        return null;
    }

    /**
     * Get the closest matching callsite for given event name and instruction
     * pointer
     *
     * @param eventName
     *            The event name
     * @param ip
     *            The instruction pointer
     * @return The closest matching callsite
     * @since 3.0
     */
    public @Nullable CtfTmfCallsite getCallsite(String eventName, long ip) {
        CtfTmfCallsite callsite = null;
        CTFTrace trace = fTrace;
        if (trace != null) {
            for (ICTFStream stream : trace.getStreams()) {
                for (IEventDeclaration eventDeclaration : stream.getEventDeclarations()) {
                    if (eventDeclaration != null && Objects.equals(eventDeclaration.getName(), eventName)) {
                        for (CTFCallsite cs : eventDeclaration.getCallsites()) {
                            if (cs.getIp() >= ip && (callsite == null || cs.getIp() < callsite.getIntructionPointer())) {
                                callsite = new CtfTmfCallsite(cs);
                            }
                        }
                    }
                }
            }
        }
        return callsite;
    }

    /**
     * Get the CTF environment variables defined in this CTF trace, in <name, value>
     * form. This comes from the trace's CTF metadata.
     *
     * @return The CTF environment
     */
    public Map<String, String> getEnvironment() {
        CTFTrace trace = fTrace;
        if (trace == null) {
            return Collections.emptyMap();
        }
        return trace.getEnvironment();
    }

    /**
     * @since 3.0
     */
    @Override
    public UUID getUUID() {
        UUID uuid = fUUID;
        return uuid == null ? super.getUUID() : uuid;
    }

    // -------------------------------------------
    // ITmfPropertiesProvider
    // -------------------------------------------

    /**
     * @since 2.0
     */
    @Override
    public Map<String, String> getProperties() {
        Map<String, String> properties = new HashMap<>();
        CTFTrace trace = fTrace;
        if (trace == null) {
            return properties;
        }
        properties.putAll(trace.getEnvironment());
        properties.put(CLOCK_OFFSET, Long.toUnsignedString(trace.getOffset()));
        properties.put(Messages.CtfTmfTrace_HostID, getHostId());
        return properties;
    }

    // -------------------------------------------
    // Clocks
    // -------------------------------------------

    /**
     * Gets the clock offset with respect to POSIX.1 Epoch in cycles
     *
     * @return the clock offset with respect to POSIX.1 Epoch in cycles
     */
    public long getOffset() {
        CTFTrace trace = fTrace;
        if (trace != null) {
            return trace.getOffset();
        }
        return 0;
    }

    /**
     * Convert a CTF timestamp in CPU cycles to its equivalent in nanoseconds for
     * this trace.
     *
     * @param cycles
     *            The timestamp in cycles, relative to the clock offset
     * @return The timestamp in nanoseconds, relative to POSIX.1 Epoch
     */
    public long timestampCyclesToNanos(long cycles) {
        CTFTrace trace = fTrace;
        if (trace != null) {
            return trace.timestampCyclesToNanos(cycles);
        }
        return 0;
    }

    /**
     * Convert a CTF timestamp in nanoseconds to its equivalent in CPU cycles for
     * this trace.
     *
     * @param nanos
     *            The timestamp in nanoseconds, relative to POSIX.1 Epoch
     * @return The timestamp in cycles, relative to the clock offset
     */
    public long timestampNanoToCycles(long nanos) {
        CTFTrace trace = fTrace;
        if (trace != null) {
            return trace.timestampNanoToCycles(nanos);
        }
        return 0;
    }

    /**
     * Gets the list of declared events
     */
    @Override
    public Set<@NonNull CtfTmfEventType> getContainedEventTypes() {
        return ImmutableSet.copyOf(fContainedEventTypes.values());
    }

    /**
     * Register an event type to this trace.
     *
     * Public visibility so that {@link CtfTmfEvent#getType} can call it.
     *
     * FIXME This could probably be made cleaner?
     *
     * @param eventType
     *            The event type to register
     */
    public void registerEventType(CtfTmfEventType eventType) {
        fContainedEventTypes.put(eventType.getName(), eventType);
    }

    // -------------------------------------------
    // Parser
    // -------------------------------------------

    @Override
    public CtfTmfEvent parseEvent(ITmfContext context) {
        CtfTmfEvent event = null;
        if (context instanceof CtfTmfContext) {
            final ITmfContext tmpContext = seekEvent(context.getLocation());
            event = getNext(tmpContext);
        }
        return event;
    }

    /**
     * Sets the cache size for a CtfTmfTrace.
     */
    protected void setCacheSize() {
        setCacheSize(DEFAULT_CACHE_SIZE);
    }

    // -------------------------------------------
    // CtfIterator factory methods
    // -------------------------------------------

    /**
     * Get the event factory for this trace to generate new events for it.
     *
     * @return The event factory
     * @since 2.0
     */
    public @NonNull CtfTmfEventFactory getEventFactory() {
        return fEventFactory;
    }

    /**
     * Get an iterator to the trace
     *
     * @return an iterator to the trace
     */
    public ITmfContext createIterator() {
        try {
            return new CtfIterator(fTrace, this);
        } catch (CTFException e) {
            Activator.getDefault().logError(e.getMessage(), e);
        }
        return null;
    }

    /**
     * Get an iterator to the trace, , which will initially point to the given
     * location/rank.
     *
     * @param ctfLocationData
     *            The initial timestamp the iterator will be pointing to
     * @param rank
     *            The initial rank
     * @return The new iterator
     */
    public ITmfContext createIterator(CtfLocationInfo ctfLocationData, long rank) {
        try {
            return new CtfIterator(fTrace, this, ctfLocationData, rank);
        } catch (CTFException e) {
            Activator.getDefault().logError(e.getMessage(), e);
        }
        return null;
    }

    /**
     * Create the 'CtfIterator' object from a CtfTmfContext.
     *
     * @param context
     *            The iterator will initially be pointing to this context
     * @return A new CtfIterator object
     * @since 1.0
     */
    public ITmfContext createIteratorFromContext(CtfTmfContext context) {
        return fIteratorManager.getIterator(context);
    }

    /**
     * Dispose an iterator that was create with {@link #createIteratorFromContext}
     *
     * @param context
     *            The last context that was pointed to by the iterator (this is the
     *            'key' to find the correct iterator to dispose).
     * @since 1.0
     */
    public void disposeContext(CtfTmfContext context) {
        fIteratorManager.removeIterator(context);
    }

    // ------------------------------------------------------------------------
    // Timestamp transformation functions
    // ------------------------------------------------------------------------

    /**
     * @since 1.0
     */
    @Override
    public @NonNull ITmfTimestamp createTimestamp(long ts) {
        return TmfTimestamp.fromNanos(getTimestampTransform().transform(ts));
    }

    private static int fCheckpointSize = -1;

    @Override
    public synchronized int getCheckpointSize() {
        if (fCheckpointSize == -1) {
            TmfCheckpoint c = new TmfCheckpoint(TmfTimestamp.fromNanos(0), new CtfLocation(0, 0), 0);
            ByteBuffer b = ByteBuffer.allocate(ITmfCheckpoint.MAX_SERIALIZE_SIZE);
            b.clear();
            c.serialize(b);
            fCheckpointSize = b.position();
        }

        return fCheckpointSize;
    }

    @Override
    protected ITmfTraceIndexer createIndexer(int interval) {
        return new TmfBTreeTraceIndexer(this, interval);
    }

    @Override
    public ITmfLocation restoreLocation(ByteBuffer bufferIn) {
        return new CtfLocation(bufferIn);
    }

    @Override
    public boolean isComplete() {
        if (getResource() == null) {
            return true;
        }

        String host = null;
        String port = null;
        String sessionName = null;
        try {
            host = getResource().getPersistentProperty(CtfConstants.LIVE_HOST);
            port = getResource().getPersistentProperty(CtfConstants.LIVE_PORT);
            sessionName = getResource().getPersistentProperty(CtfConstants.LIVE_SESSION_NAME);
        } catch (CoreException e) {
            Activator.getDefault().logError(e.getMessage(), e);
            // Something happened to the resource, assume we won't get any more
            // data from it
            return true;
        }
        return host == null || port == null || sessionName == null;
    }

    @Override
    public void setComplete(final boolean isComplete) {
        super.setComplete(isComplete);
        try {
            if (isComplete) {
                getResource().setPersistentProperty(CtfConstants.LIVE_HOST, null);
                getResource().setPersistentProperty(CtfConstants.LIVE_PORT, null);
                getResource().setPersistentProperty(CtfConstants.LIVE_SESSION_NAME, null);
            }
        } catch (CoreException e) {
            Activator.getDefault().logError(e.getMessage(), e);
        }
    }

    /**
     * @return the number of estimated chunks of events read. This reads the file
     *         size of the trace and divides it by a factor and the average event
     *         size, this is not accurate but can give a ball park figure of how
     *         much is done.
     * @since 2.1
     */
    @Override
    public int size() {
        long size = 0;
        CTFTrace trace = fTrace;
        if (trace != null) {
            Iterable<ICTFStream> streams = trace.getStreams();
            for (ICTFStream stream : streams) {
                for (CTFStreamInput si : stream.getStreamInputs()) {
                    size += si.getFile().length();
                }
            }
        }
        return (int) (size / REDUCTION_FACTOR / CTF_AVG_EVENT_SIZE);
    }

    /**
     * @return the number of events divided a reduction factor. Is monotonic.
     * @since 2.1
     */
    @Override
    public int progress() {
        return (int) (getNbEvents() / REDUCTION_FACTOR);
    }

    /**
     * @since 3.0
     */
    @Override
    public long cyclesToNanos(long cycles) {
        // CTFTrace adds the clock offset in cycles to the input
        CTFTrace trace = fTrace;
        if (trace != null) {
            return fTrace.timestampCyclesToNanos(cycles - trace.getOffset());
        }
        return 0;
    }

    /**
     * @since 3.0
     */
    @Override
    public long nanosToCycles(long nanos) {
        // CTFTrace subtracts the clock offset in cycles from the output
        CTFTrace trace = fTrace;
        if (trace != null) {
            return trace.timestampNanoToCycles(nanos) + fTrace.getOffset();
        }
        return 0;
    }

    /**
     * @since 3.0
     */
    @Override
    public ITmfTimestamp readStart() {
        return getStartTime();
    }

    /**
     * @since 3.0
     */
    @Override
    public ITmfTimestamp readEnd() {
        try (CTFTraceReader reader = new CTFTraceReader(fTrace)) {
            reader.goToLastEvent();
            long end = reader.getEndTime();
            return createTimestamp(end);
        } catch (CTFException e) {
            return null;
        }
    }

    @Override
    public Path trim(@NonNull TmfTimeRange range, @NonNull Path destinationPath, @NonNull IProgressMonitor monitor) throws CoreException {
        CTFTrace trace = fTrace;
        if (trace == null) {
            return null;
        }
        try {
            CTFTraceWriter ctfWriter = new CTFTraceWriter(trace);
            String tracePath = destinationPath.toAbsolutePath().toString();
            ctfWriter.copyPackets(range.getStartTime().toNanos(), range.getEndTime().toNanos(), tracePath);
            return destinationPath;
        } catch (CTFException e) {
            Activator.getDefault().logError("Error writing trace : " + destinationPath, e); //$NON-NLS-1$
        }
        return null;
    }

}
