/*******************************************************************************
 * Copyright (c) 2011, 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
 *     Bernd Hufmann - Updated for source and model lookup interfaces
 *******************************************************************************/

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

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

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.ctf.core.event.IEventDeclaration;
import org.eclipse.tracecompass.ctf.core.event.IEventDefinition;
import org.eclipse.tracecompass.ctf.core.event.types.ICompositeDefinition;
import org.eclipse.tracecompass.ctf.core.trace.ICTFStream;
import org.eclipse.tracecompass.tmf.core.event.ITmfCustomAttributes;
import org.eclipse.tracecompass.tmf.core.event.ITmfEventField;
import org.eclipse.tracecompass.tmf.core.event.ITmfEventType;
import org.eclipse.tracecompass.tmf.core.event.TmfEvent;
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.TmfCallsiteAspect;
import org.eclipse.tracecompass.tmf.core.event.lookup.ITmfCallsite;
import org.eclipse.tracecompass.tmf.core.event.lookup.ITmfModelLookup;
import org.eclipse.tracecompass.tmf.core.event.lookup.ITmfSourceLookup;
import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
import org.eclipse.tracecompass.tmf.core.trace.ITmfContext;
import org.eclipse.tracecompass.tmf.ctf.core.CtfConstants;
import org.eclipse.tracecompass.tmf.ctf.core.trace.CtfTmfTrace;

/**
 * A wrapper class around CTF's Event Definition/Declaration that maps all types
 * of Declaration to native Java types.
 *
 * @author Alexandre Montplaisir
 * @since 3.0
 */
@NonNullByDefault
public class CtfTmfEvent extends TmfEvent
        implements ITmfSourceLookup, ITmfModelLookup, ITmfCustomAttributes {

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

    private static final String EMPTY_CTF_EVENT_NAME = "Empty CTF event"; //$NON-NLS-1$

    // ------------------------------------------------------------------------
    // Support attributes
    // Not part of this event's "definition", but used to populate lazy-loaded
    // fields.
    // ------------------------------------------------------------------------

    private final @Nullable IEventDeclaration fEventDeclaration;
    private final IEventDefinition fEvent;

    // ------------------------------------------------------------------------
    // Attributes
    // ------------------------------------------------------------------------

    /* Fields that are introduced by and part of this event's definition. */
    private final int fSourceCpu;
    private final String fChannel;

    /**
     * Field to override {@link TmfEvent#getName()}, to bypass the type-getting
     */
    private final String fEventName;

    /** Lazy-loaded field containing the event's payload */
    private transient @Nullable ITmfEventField fContent;

    /** Lazy-loaded field for the type, overriding TmfEvent's field */
    private transient @Nullable CtfTmfEventType fEventType;

    private final @Nullable ICTFStream fStream;
    private final Map<String, Object> fPacketAttributes;

    // ------------------------------------------------------------------------
    // Constructors
    // ------------------------------------------------------------------------

    /**
     * Constructor, used by {@link CtfTmfEventFactory#createEvent}.
     *
     * Only subclasses should call this. It is imperative that the subclass also
     * has a constructor with the EXACT same parameter signature, because the
     * factory will look for a constructor with the same arguments.
     *
     * @param trace
     *            The trace to which this event belongs
     * @param rank
     *            The rank of the event
     * @param timestamp
     *            The timestamp
     * @param channel
     *            The CTF channel of this event
     * @param cpu
     *            The event's CPU
     * @param declaration
     *            The event declaration
     * @param eventDefinition
     *            The event definition
     * @since 2.0
     */
    protected CtfTmfEvent(CtfTmfTrace trace,
            long rank,
            ITmfTimestamp timestamp,
            String channel,
            int cpu,
            IEventDeclaration declaration,
            IEventDefinition eventDefinition) {
        super(trace,
                rank,
                timestamp,
                /*
                 * Event type. We don't use TmfEvent's field here, we
                 * re-implement getType().
                 */
                null,
                /*
                 * Content handled with a lazy-loaded field re-implemented in
                 * getContent().
                 */
                null);

        fEventDeclaration = declaration;
        fSourceCpu = cpu;
        fEventName = checkNotNull(declaration.getName());
        fEvent = eventDefinition;
        fChannel = channel;
        fStream = fEvent.getDeclaration().getStream();
        fPacketAttributes = eventDefinition.getPacketAttributes();
    }

    /**
     * Inner constructor to create "null" events. Don't use this directly in
     * normal usage, use {@link CtfTmfEventFactory#getNullEvent(CtfTmfTrace)} to
     * get an instance of an empty event.
     *
     * There is no need to give higher visibility to this method than package
     * visible.
     *
     * @param trace
     *            The trace associated with this event
     */
    CtfTmfEvent(CtfTmfTrace trace) {
        super(trace,
                ITmfContext.UNKNOWN_RANK,
                TmfTimestamp.fromNanos(-1),
                null,
                new TmfEventField("", null, new CtfTmfEventField[0])); //$NON-NLS-1$
        fSourceCpu = -1;
        fEventName = EMPTY_CTF_EVENT_NAME;
        fEventDeclaration = null;
        fEvent = NullEventDefinition.INSTANCE;
        fChannel = ""; //$NON-NLS-1$
        fStream = null;
        fPacketAttributes = Collections.emptyMap();
    }

    /**
     * Default constructor. Do not use directly, but it needs to be present
     * because it's used in extension points, and the framework will use this
     * constructor to get the class type.
     *
     * @deprecated Should not be called by normal code
     */
    @Deprecated
    public CtfTmfEvent() {
        super();
        fSourceCpu = -1;
        fEventName = EMPTY_CTF_EVENT_NAME;
        fEventDeclaration = null;
        fEvent = NullEventDefinition.INSTANCE;
        fChannel = ""; //$NON-NLS-1$
        fStream = null;
        fPacketAttributes = Collections.emptyMap();
    }

    // ------------------------------------------------------------------------
    // Getters/Setters/Predicates
    // ------------------------------------------------------------------------

    /**
     * Gets the cpu core the event was recorded on.
     *
     * @return The cpu id for a given source. In lttng it's from CPUINFO
     */
    public int getCPU() {
        return fSourceCpu;
    }

    /**
     * Return the CTF trace's channel from which this event originates.
     *
     * @return The event's channel
     * @since 2.0
     */
    public String getChannel() {
        return fChannel;
    }

    /**
     * Get the stream Id
     *
     * @return the stream ID or -1 if the stream is null
     * @since 2.0
     */
    public long getStreamId() {
        ICTFStream stream = fStream;
        if (stream == null) {
            return -1;
        }
        return stream.getId();
    }

    /**
     * Get the event unscaled time. This is often the number of cycles run. In
     * some cases it is the same as the actual timestamp.
     *
     * @return the event unscaled time in long format.
     * @since 3.0
     */
    public long getUnscaledTime() {
        return fEvent.getTimestamp();
    }

    // ------------------------------------------------------------------------
    // TmfEvent
    // ------------------------------------------------------------------------

    @Override
    public CtfTmfTrace getTrace() {
        /*
         * Should be of the right type, since we take a CtfTmfTrace at the
         * constructor
         */
        return (CtfTmfTrace) super.getTrace();
    }

    @Override
    public synchronized ITmfEventType getType() {
        CtfTmfEventType type = fEventType;
        if (type == null) {
            type = new CtfTmfEventType(fEventName, getContent());

            /*
             * Register the event type in the owning trace, but only if there is
             * one
             */
            getTrace().registerEventType(type);
            fEventType = type;
        }
        return type;
    }

    @Override
    public String getName() {
        return fEventName;
    }

    @Override
    public synchronized ITmfEventField getContent() {
        ITmfEventField content = fContent;
        if (content == null) {
            content = new TmfEventField(
                    ITmfEventField.ROOT_FIELD_ID, null, parseFields(fEvent));
            fContent = content;
        }
        return content;
    }

    /**
     * Extract the field information from the structDefinition haze-inducing
     * mess, and put them into something ITmfEventField can cope with.
     */
    private static CtfTmfEventField[] parseFields(IEventDefinition eventDef) {
        List<CtfTmfEventField> fields = new ArrayList<>();

        ICompositeDefinition structFields = eventDef.getFields();
        if (structFields != null) {
            if (structFields.getFieldNames() != null) {
                for (String fn : structFields.getFieldNames()) {
                    fields.add(CtfTmfEventField.parseField(structFields.getDefinition(fn), fn));
                }
            }
        }
        /* Add context information as CtfTmfEventField */
        ICompositeDefinition structContext = eventDef.getContext();
        if (structContext != null) {
            for (String contextName : structContext.getFieldNames()) {
                /* Prefix field name */
                String curContextName = CtfConstants.CONTEXT_FIELD_PREFIX + contextName;
                fields.add(CtfTmfEventField.parseField(structContext.getDefinition(contextName), curContextName));
            }
        }

        return fields.toArray(new @NonNull CtfTmfEventField[fields.size()]);
    }

    // ------------------------------------------------------------------------
    // ITmfCustomAttributes
    // ------------------------------------------------------------------------

    @Override
    public Set<String> listCustomAttributes() {
        IEventDeclaration declaration = fEventDeclaration;
        if (declaration == null) {
            return new HashSet<>();
        }
        return declaration.getCustomAttributes();
    }

    @Override
    public @Nullable String getCustomAttribute(@Nullable String name) {
        IEventDeclaration declaration = fEventDeclaration;
        if (declaration == null) {
            return null;
        }
        return declaration.getCustomAttribute(name);
    }

    // ------------------------------------------------------------------------
    // ITmfSourceLookup
    // ------------------------------------------------------------------------

    /**
     * Get the call site for this event.
     *
     * @return the call site information, or null if there is none
     * @since 3.0
     */
    @Override
    public @Nullable ITmfCallsite getCallsite() {
        ITmfCallsite callsite = null;

        ITmfEventField ipField = getContent().getField(CtfConstants.CONTEXT_FIELD_PREFIX + CtfConstants.IP_KEY);
        if (ipField != null && ipField.getValue() instanceof Long) {
            long ip = (Long) ipField.getValue();
            callsite = getTrace().getCallsite(fEventName, ip);
        }

        if (callsite == null) {
            callsite = getTrace().getCallsite(fEventName);
        }
        return callsite;
    }

    @Override
    public @Nullable List<@NonNull ITmfCallsite> getCallsites() {
        for (ITmfEventAspect<?> aspect : getTrace().getEventAspects()) {
            if (aspect instanceof TmfCallsiteAspect) {
                List<ITmfCallsite> callsite = ((TmfCallsiteAspect) aspect).resolve(this);
                if (callsite != null) {
                    return callsite;
                }
            }
        }
        return ITmfSourceLookup.super.getCallsites();
    }

    // ------------------------------------------------------------------------
    // ITmfModelLookup
    // ------------------------------------------------------------------------

    @Override
    public @Nullable String getModelUri() {
        return getCustomAttribute(CtfConstants.MODEL_URI_KEY);
    }

    /**
     * Gets the packet attributes. The result is an instance of one of the
     * following classes: <code>Entry<String, Long></code>, <code>Long</code>,
     * <code>String</code> or <code>Double</code>. The map contains pairs of key
     * and values where the key and value can never be null.
     *
     * @return gets the packet attributes
     * @since 2.0
     */
    public Map<String, Object> getPacketAttributes() {
        return fPacketAttributes;
    }

    /**
     * Get the packet context
     *
     * @return the packet context
     * @since 4.2
     */
    public @Nullable ICompositeDefinition getPacketContext() {
        return fEvent.getPacketContext();
    }

    /**
     * Get the stream context
     *
     * @return the stream context
     * @since 4.2
     */
    public @Nullable ICompositeDefinition getStreamContext() {
        return fEvent.getStreamContext();
    }

    /**
     * Get the event context
     *
     * @return the event context
     * @since 4.2
     */
    public @Nullable ICompositeDefinition getEventContext() {
        return fEvent.getEventContext();
    }
    // ------------------------------------------------------------------------
    // Object
    // ------------------------------------------------------------------------

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = super.hashCode();
        result = prime * result + getCPU();
        result = prime * result + getChannel().hashCode();
        return result;
    }

    @Override
    public boolean equals(@Nullable Object obj) {
        if (!super.equals(obj)) {
            return false;
        }
        /* super.equals() checks that the classes are the same */
        CtfTmfEvent other = checkNotNull((CtfTmfEvent) obj);
        if (getCPU() != other.getCPU()) {
            return false;
        }
        return (getChannel().equals(other.getChannel()));
    }

}
