/*******************************************************************************
 * Copyright (c) 2013, 2015 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:
 *     Alexandre Montplaisir - Initial API and implementation
 *******************************************************************************/

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

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.ctf.core.CTFStrings;
import org.eclipse.tracecompass.ctf.core.event.IEventDeclaration;
import org.eclipse.tracecompass.ctf.core.event.IEventDefinition;
import org.eclipse.tracecompass.ctf.core.event.types.IDefinition;
import org.eclipse.tracecompass.ctf.core.event.types.IntegerDefinition;
import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
import org.eclipse.tracecompass.tmf.core.trace.ITmfContext;
import org.eclipse.tracecompass.tmf.ctf.core.trace.CtfTmfTrace;

/**
 * Factory for CtfTmfEvent's.
 *
 * This code was moved out of CtfTmfEvent to provide better separation between
 * the parsing/instantiation of events, and the usual TMF API implementations.
 *
 * @author Alexandre Montplaisir
 */
@NonNullByDefault
public class CtfTmfEventFactory {

    private static final CtfTmfEventFactory INSTANCE = new CtfTmfEventFactory();

    /**
     * The file name to use when none is specified.
     *
     * FIXME Externalize?
     *
     * @since 2.0
     */
    protected static final String NO_STREAM = "No stream"; //$NON-NLS-1$

    /**
     * Protected constructor, only for use by sub-classes. Users should call
     * the {@link #instance()} method instead.
     *
     * @since 2.0
     */
    protected CtfTmfEventFactory() {}

    /**
     * Get the singleton factory instance
     *
     * @return The instance
     * @since 2.0
     */
    public static CtfTmfEventFactory instance() {
        return INSTANCE;
    }

    /**
     * Factory method to instantiate new CTF events.
     *
     * @param trace
     *            The trace to which the new event will belong
     * @param eventDef
     *            CTF EventDefinition object corresponding to this trace event
     * @param fileName
     *            The path to the trace file
     * @return The newly-built CtfTmfEvent
     * @since 2.0
     */
    public CtfTmfEvent createEvent(CtfTmfTrace trace, IEventDefinition eventDef, @Nullable String fileName) {

        /* Prepare what to pass to CtfTmfEvent's constructor */
        final IEventDeclaration eventDecl = eventDef.getDeclaration();
        final long ts = eventDef.getTimestamp();
        final ITmfTimestamp timestamp = trace.createTimestamp(trace.timestampCyclesToNanos(ts));

        int sourceCPU = eventDef.getCPU();

        String reference = (fileName == null ? NO_STREAM : fileName);

        /* Handle the special case of lost events */
        if (eventDecl.getName().equals(CTFStrings.LOST_EVENT_NAME)) {
            return createLostEvent(trace, eventDef, eventDecl, ts, timestamp, sourceCPU, reference);
        }

        /* Handle standard event types */
        return new CtfTmfEvent(trace,
                ITmfContext.UNKNOWN_RANK,
                timestamp,
                reference, // filename
                sourceCPU,
                eventDecl,
                eventDef);
    }

    /**
     * Create a new CTF lost event.
     *
     * @param trace
     *            The trace to which the new event will belong
     * @param eventDef
     *            The CTF event definition
     * @param eventDecl
     *            The CTF event declaration
     * @param ts
     *            The event's timestamp
     * @param timestamp
     *            The event's timestamp (FIXME again??)
     * @param sourceCPU
     *            The source CPU
     * @param fileName
     *            The file name
     * @return The new lost event
     * @since 2.0
     */
    protected static CtfTmfEvent createLostEvent(CtfTmfTrace trace,
            IEventDefinition eventDef,
            final IEventDeclaration eventDecl,
            final long ts,
            final ITmfTimestamp timestamp,
            int sourceCPU,
            String fileName) {

        IDefinition nbLostEventsDef = eventDef.getFields().getDefinition(CTFStrings.LOST_EVENTS_FIELD);
        IDefinition durationDef = eventDef.getFields().getDefinition(CTFStrings.LOST_EVENTS_DURATION);
        if (!(nbLostEventsDef instanceof IntegerDefinition) || !(durationDef instanceof IntegerDefinition)) {
            /*
             * One or both of these fields doesn't exist, or is not of the right
             * type. The event claims to be a "lost event", but is malformed.
             * Log it and return a null event instead.
             */
            return getNullEvent(trace);
        }
        long nbLostEvents = ((IntegerDefinition) nbLostEventsDef).getValue();
        long duration = ((IntegerDefinition) durationDef).getValue();
        ITmfTimestamp timestampEnd = trace.createTimestamp(
                trace.timestampCyclesToNanos(ts) + trace.timestampCyclesToNanos(duration - trace.getOffset()));

        CtfTmfLostEvent lostEvent = new CtfTmfLostEvent(trace,
                ITmfContext.UNKNOWN_RANK,
                fileName,
                sourceCPU,
                eventDecl,
                new TmfTimeRange(timestamp, timestampEnd),
                nbLostEvents,
                eventDef);
        return lostEvent;
    }

    /**
     * Get an instance of a null event.
     *
     * @param trace
     *            The trace to which the new null event will belong
     * @return An empty event
     * @since 2.0
     */
    public static CtfTmfEvent getNullEvent(CtfTmfTrace trace) {
        return new CtfTmfEvent(trace);
    }


}
