/*******************************************************************************
 * Copyright (c) 2016 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
 *******************************************************************************/

package org.eclipse.tracecompass.analysis.os.linux.core.tid;

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

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.analysis.os.linux.core.kernel.KernelAnalysisModule;
import org.eclipse.tracecompass.analysis.os.linux.core.trace.DefaultEventLayout;
import org.eclipse.tracecompass.analysis.os.linux.core.trace.IKernelAnalysisEventLayout;
import org.eclipse.tracecompass.analysis.os.linux.core.trace.IKernelTrace;
import org.eclipse.tracecompass.common.core.NonNullUtils;
import org.eclipse.tracecompass.internal.analysis.os.linux.core.Activator;
import org.eclipse.tracecompass.internal.analysis.os.linux.core.Messages;
import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException;
import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
import org.eclipse.tracecompass.tmf.core.analysis.requirements.TmfAbstractAnalysisRequirement;
import org.eclipse.tracecompass.tmf.core.statesystem.ITmfStateProvider;
import org.eclipse.tracecompass.tmf.core.statesystem.TmfStateSystemAnalysisModule;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;

/**
 * Active tid analysis module, this only does one thing: figure out the active
 * tid on any given cpu. This analysis should be approx 10x faster than the full
 * {@link KernelAnalysisModule}.
 *
 * Note: this module exists as a way to accelerate the TID aspect, but also to
 * start splitting up the kernel analysis into smaller sections.
 *
 * @author Matthew Khouzam
 * @since 2.0
 */
public class TidAnalysisModule extends TmfStateSystemAnalysisModule {

    /** The ID of this analysis module */
    public static final @NonNull String ID = "org.eclipse.tracecompass.analysis.os.linux.kernel.tid"; //$NON-NLS-1$

    private final Map<Integer, ITmfStateInterval> fCache = new HashMap<>();

    /** The requirements as an immutable set */
    private static final @NonNull Set<@NonNull TmfAbstractAnalysisRequirement> REQUIREMENTS = Collections.emptySet();

    @Override
    public @NonNull Iterable<@NonNull TmfAbstractAnalysisRequirement> getAnalysisRequirements() {
        return REQUIREMENTS;
    }

    @Override
    public String getHelpText() {
        String msg = Messages.TidAnalysisModule_Description;
        return msg != null ? msg : super.getHelpText();
    }

    @Override
    public @NonNull String getHelpText(@NonNull ITmfTrace trace) {
        return getHelpText();
    }

    @Override
    protected @NonNull ITmfStateProvider createStateProvider() {
        ITmfTrace trace = checkNotNull(getTrace());
        IKernelAnalysisEventLayout layout = (trace instanceof IKernelTrace) ? ((IKernelTrace) trace).getKernelEventLayout() : DefaultEventLayout.getInstance();
        return new ActiveTidStateProvider(trace, layout);
    }

    /**
     * Gets the current thread ID on a given CPU for a given time
     *
     * @param cpu
     *            the CPU
     * @param time
     *            the time in nanoseconds
     * @return the current TID at the time on the CPU or {@code null} if not
     *         known
     */
    public @Nullable Integer getThreadOnCpuAtTime(int cpu, long time) {
        ITmfStateSystem stateSystem = getStateSystem();
        if (stateSystem == null || time < stateSystem.getStartTime()) {
            return null;
        }

        Integer tid = null;
        // Query at time - 1, because at the boundary (sched_switches), the "thread on
        // CPU" should be the previous thread, not the next one (as LTTng contexts and
        // perf traces show).
        long queryTime = Math.max(time - 1, stateSystem.getStartTime());
        try {
            int cpuQuark = stateSystem.optQuarkAbsolute(Integer.toString(cpu));
            if (cpuQuark == ITmfStateSystem.INVALID_ATTRIBUTE) {
                return null;
            }
            ITmfStateInterval state = fCache.get(cpuQuark);
            if (state == null || !state.intersects(queryTime)) {
                state = stateSystem.querySingleState(queryTime, cpuQuark);
                fCache.put(cpuQuark, state);
            }
            Object value = state.getValue();
            if (value instanceof Integer) {
                tid = (Integer) value;
            }
        } catch (StateSystemDisposedException | TimeRangeException e) {
            Activator.getDefault().logError(NonNullUtils.nullToEmptyString(e.getMessage()), e);
        }
        return tid;
    }

    /**
     * Gets the CPU a thread is running on for a given time <br>
     * Note: this is not designed to be fast, only convenient
     *
     * @param tid
     *            the tid
     * @param time
     *            the time in nanoseconds
     * @return the current CPU at the time for a TID or {@code null} if not
     *         available
     */
    public @Nullable Integer getCpuForTidAtTime(int tid, long time) {
        ITmfStateSystem stateSystem = getStateSystem();
        if (stateSystem == null) {
            return null;
        }

        try {
            for (ITmfStateInterval interval : stateSystem.queryFullState(time)) {
                if (tid == interval.getStateValue().unboxInt()) {
                    return Integer.parseInt(stateSystem.getAttributeName(interval.getAttribute()));
                }
            }
        } catch (StateSystemDisposedException e) {
            Activator.getDefault().logError(NonNullUtils.nullToEmptyString(e.getMessage()), e);
        }
        return null;
    }
}
