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

package org.eclipse.tracecompass.internal.lttng2.kernel.core.analysis.graph.handlers;

import org.eclipse.tracecompass.analysis.os.linux.core.execution.graph.OsExecutionGraphProvider;
import org.eclipse.tracecompass.analysis.os.linux.core.execution.graph.OsSystemModel;
import org.eclipse.tracecompass.analysis.os.linux.core.execution.graph.OsWorker;
import org.eclipse.tracecompass.analysis.os.linux.core.kernel.LinuxValues;
import org.eclipse.tracecompass.analysis.os.linux.core.model.HostThread;
import org.eclipse.tracecompass.analysis.os.linux.core.model.ProcessStatus;
import org.eclipse.tracecompass.analysis.os.linux.core.trace.IKernelAnalysisEventLayout;
import org.eclipse.tracecompass.common.core.NonNullUtils;
import org.eclipse.tracecompass.internal.lttng2.kernel.core.analysis.graph.model.EventField;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
import org.eclipse.tracecompass.tmf.core.event.ITmfEventField;
import org.eclipse.tracecompass.tmf.core.event.aspect.TmfCpuAspect;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;

/**
 * Provides the current task running on a CPU according to scheduling events
 *
 * @author Francis Giraldeau
 * @author Geneviève Bastien
 */
public class TraceEventHandlerSched extends BaseHandler {

    private static final String TASK_UNKNOWN = "Unknown"; //$NON-NLS-1$

    /**
     * Constructor
     *
     * @param provider
     *            The parent graph provider
     * @param priority
     *            The priority of this handler. It will determine when it will be
     *            executed
     */
    public TraceEventHandlerSched(OsExecutionGraphProvider provider, int priority) {
        super(provider, priority);
    }

    @Override
    public void handleEvent(ITmfEvent ev) {
        String eventName = ev.getName();
        IKernelAnalysisEventLayout eventLayout = getProvider().getEventLayout(ev.getTrace());

        if (eventName.equals(eventLayout.eventSchedSwitch())) {
            handleSchedSwitch(ev);
        } else if (eventName.equals(eventLayout.eventSchedProcessFork())) {
            handleSchedProcessFork(ev);
        } else if (eventName.equals(eventLayout.eventSchedProcessExit())) {
            handleSchedProcessExit(ev);
        } else if (eventName.equals(eventLayout.eventSchedProcessExec())) {
            handleSchedProcessExec(ev);
        } else if (isWakeupEvent(ev)) {
            handleSchedWakeup(ev);
        }
    }

    private void handleSchedSwitch(ITmfEvent event) {
        Integer cpu = NonNullUtils.checkNotNull(TmfTraceUtils.resolveIntEventAspectOfClassForEvent(event.getTrace(), TmfCpuAspect.class, event));
        IKernelAnalysisEventLayout eventLayout = getProvider().getEventLayout(event.getTrace());
        OsSystemModel system = getProvider().getSystem();
        ITmfEventField content = event.getContent();

        Integer next = content.getFieldValue(Integer.class, eventLayout.fieldNextTid());
        Integer prev = content.getFieldValue(Integer.class, eventLayout.fieldPrevTid());
        if (next == null || prev == null) {
            return;
        }
        long ts = event.getTimestamp().getValue();
        Integer prev_state = content.getFieldValue(Integer.class, eventLayout.fieldPrevState());
        prev_state = prev_state == null ? 0 : prev_state & (LinuxValues.TASK_STATE_RUNNING | LinuxValues.TASK_INTERRUPTIBLE | LinuxValues.TASK_UNINTERRUPTIBLE);
        String host = event.getTrace().getHostId();

        system.cacheTidOnCpu(cpu, new HostThread(event.getTrace().getHostId(), next));

        HostThread nextHt = new HostThread(host, next);
        OsWorker nextTask = system.findWorker(nextHt);
        if (nextTask == null) {
            String name = EventField.getOrDefault(event, eventLayout.fieldNextComm(), NonNullUtils.checkNotNull(Messages.TraceEventHandlerSched_UnknownThreadName));
            nextTask = new OsWorker(nextHt, name, ts);
            system.addWorker(nextTask);
        }
        nextTask.setStatus(ProcessStatus.RUN);

        HostThread prevHt = new HostThread(host, prev);
        OsWorker prevTask = system.findWorker(prevHt);
        String name = EventField.getOrDefault(event, eventLayout.fieldPrevComm(), NonNullUtils.checkNotNull(Messages.TraceEventHandlerSched_UnknownThreadName));
        if (prevTask == null) {
            prevTask = new OsWorker(prevHt, name, ts);
            system.addWorker(prevTask);
        } else if (prev != 0) {
            /* update the process name if changed at runtime */
            prevTask.setName(name);
        }
        /* prev_state == 0 means runnable, thus waits for cpu */
        if (prev_state == 0) {
            prevTask.setStatus(ProcessStatus.WAIT_CPU);
        } else {
            prevTask.setStatus(ProcessStatus.WAIT_BLOCKED);
        }
    }

    private void handleSchedProcessFork(ITmfEvent event) {
        String host = event.getTrace().getHostId();
        IKernelAnalysisEventLayout eventLayout = getProvider().getEventLayout(event.getTrace());
        OsSystemModel system = getProvider().getSystem();
        ITmfEventField content = event.getContent();

        Integer childTid = content.getFieldValue(Integer.class, eventLayout.fieldChildTid());
        String name = content.getFieldValue(String.class, eventLayout.fieldChildComm());
        if (childTid == null) {
            return;
        }
        name = (name == null ? String.valueOf(childTid) : name);
        long ts = event.getTimestamp().getValue();

        HostThread childHt = new HostThread(host, childTid);

        OsWorker childTask = system.findWorker(childHt);
        if (childTask == null) {
            childTask = new OsWorker(childHt, name, ts);
            system.addWorker(childTask);
        }

        childTask.setStatus(ProcessStatus.WAIT_FORK);
    }

    private void handleSchedWakeup(ITmfEvent event) {
        String host = event.getTrace().getHostId();
        Integer cpu = NonNullUtils.checkNotNull(TmfTraceUtils.resolveIntEventAspectOfClassForEvent(event.getTrace(), TmfCpuAspect.class, event));
        IKernelAnalysisEventLayout eventLayout = getProvider().getEventLayout(event.getTrace());
        OsSystemModel system = getProvider().getSystem();

        Integer tid = event.getContent().getFieldValue(Integer.class, eventLayout.fieldTid());
        if (tid == null) {
            return;
        }
        HostThread targetHt = new HostThread(host, tid);

        OsWorker target = system.findWorker(targetHt);
        OsWorker current = system.getWorkerOnCpu(host, cpu);

        if (target == null) {
            String name = EventField.getOrDefault(event, eventLayout.fieldComm(), NonNullUtils.checkNotNull(Messages.TraceEventHandlerSched_UnknownThreadName));
            target = new OsWorker(targetHt, name, event.getTimestamp().getValue());
            system.addWorker(target);
            target.setStatus(ProcessStatus.WAIT_BLOCKED);
        }
        // spurious wakeup
        ProcessStatus status = target.getStatus();
        if ((current != null && target.getHostThread().equals(current.getHostThread())) ||
                status == ProcessStatus.WAIT_CPU) {
            return;
        }
        if (status == ProcessStatus.WAIT_BLOCKED ||
                status == ProcessStatus.WAIT_FORK ||
                status == ProcessStatus.UNKNOWN) {
            target.setStatus(ProcessStatus.WAIT_CPU);
            return;
        }
    }

    private void handleSchedProcessExit(ITmfEvent event) {
        String host = event.getTrace().getHostId();
        IKernelAnalysisEventLayout eventLayout = getProvider().getEventLayout(event.getTrace());
        OsSystemModel system = getProvider().getSystem();

        Integer tid = event.getContent().getFieldValue(Integer.class, eventLayout.fieldTid());
        if (tid == null) {
            return;
        }
        OsWorker task = system.findWorker(new HostThread(host, tid));
        if (task == null) {
            return;
        }
        task.setStatus(ProcessStatus.EXIT);
    }

    private void handleSchedProcessExec(ITmfEvent event) {
        String host = event.getTrace().getHostId();
        Integer cpu = NonNullUtils.checkNotNull(TmfTraceUtils.resolveIntEventAspectOfClassForEvent(event.getTrace(), TmfCpuAspect.class, event));
        IKernelAnalysisEventLayout eventLayout = getProvider().getEventLayout(event.getTrace());
        OsSystemModel system = getProvider().getSystem();

        String filename = event.getContent().getFieldValue(String.class, eventLayout.fieldFilename());
        OsWorker task = system.getWorkerOnCpu(host, cpu);
        if (task == null) {
            return;
        }
        task.setName(filename == null ? TASK_UNKNOWN : filename);
    }

}
