/*******************************************************************************
 * 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.analysis.os.linux.core.execution.graph;

import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.analysis.os.linux.core.model.HostThread;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;

/**
 * This class contains the model of a Linux system
 *
 * TODO: This model is custom made for the LTTng dependency analysis for ease of
 * development of the feature, but most of it and the classes it uses also apply
 * to any Linux OS trace, so the classes in this package should be moved to
 * analysis.os.linux
 *
 * @author Francis Giraldeau
 * @author Geneviève Bastien
 * @since 2.4
 */
public class OsSystemModel {

    private final Table<String, Integer, HostThread> fCurrentTids = HashBasedTable.create();
    private final Table<String, Integer, Deque<OsInterruptContext>> fIntCtxStacks = HashBasedTable.create();
    private final Map<HostThread, OsWorker> fWorkerMap = new HashMap<>();

    /**
     * Cache the TID currently on the CPU of a host, for easier access later on
     *
     * @param cpu
     *            The CPU ID
     * @param ht
     *            The {@link HostThread} object that is running on this CPU
     */
    public void cacheTidOnCpu(Integer cpu, HostThread ht) {
        fCurrentTids.put(ht.getHost(), cpu, ht);
    }

    /**
     * Get the {@link OsWorker} object that is currently running on the CPU
     * of a host
     *
     * @param host
     *            The identifier of the trace/machine of the CPU
     * @param cpu
     *            The CPU ID on which the worker is running
     * @return The {@link OsWorker} running on the CPU
     */
    public @Nullable OsWorker getWorkerOnCpu(String host, Integer cpu) {
        HostThread ht = fCurrentTids.get(host, cpu);
        if (ht == null) {
            return null;
        }
        return findWorker(ht);
    }

    /**
     * Return the worker associated with this host TID
     *
     * @param ht
     *            The host thread associated with a worker
     * @return The {@link OsWorker} associated with a host thread
     */
    public @Nullable OsWorker findWorker(HostThread ht) {
        return fWorkerMap.get(ht);
    }

    /**
     * Add a new worker to the system
     *
     * @param worker
     *            The worker to add
     */
    public void addWorker(OsWorker worker) {
        fWorkerMap.put(worker.getHostThread(), worker);
    }

    /**
     * Get the list of workers on this system
     *
     * @return The list of workers on the system
     */
    public Collection<OsWorker> getWorkers() {
        return fWorkerMap.values();
    }

    /**
     * Pushes an interrupt context on the stack for a CPU on a host
     *
     * @param hostId
     *            The host ID of the trace/machine the interrupt context belongs
     *            to
     * @param cpu
     *            The CPU this interrupt happened on
     * @param interruptCtx
     *            The interrupt context to push on the stack
     */
    public void pushContextStack(String hostId, Integer cpu, OsInterruptContext interruptCtx) {
        Deque<OsInterruptContext> stack = fIntCtxStacks.get(hostId, cpu);
        if (stack == null) {
            stack = new ArrayDeque<>();
            fIntCtxStacks.put(hostId, cpu, stack);
        }
        stack.addFirst(interruptCtx);
    }

    /**
     * Peeks the top of the interrupt context stack for a CPU on a host, to see
     * what is the latest context.
     *
     * @param hostId
     *            The host ID of the trace/machine the interrupt context belongs
     *            to
     * @param cpu
     *            The CPU this interrupt happened on
     * @return The latest interrupt context on the CPU of the host
     */
    public OsInterruptContext peekContextStack(String hostId, Integer cpu) {
        Deque<@NonNull OsInterruptContext> stack = fIntCtxStacks.get(hostId, cpu);
        if (stack == null) {
            return OsInterruptContext.DEFAULT_CONTEXT;
        }
        if (stack.isEmpty()) {
            return OsInterruptContext.DEFAULT_CONTEXT;
        }
        return Objects.requireNonNull(stack.peekFirst());
    }

    /**
     * Removes the top of the interrupt context stack for a CPU on a host and
     * returns the result
     *
     * @param hostId
     *            The host ID of the trace/machine the interrupt context belongs
     *            to
     * @param cpu
     *            The CPU this interrupt happened on
     * @return The latest interrupt context on the CPU of the host
     */
    public @Nullable OsInterruptContext popContextStack(String hostId, Integer cpu) {
        Deque<OsInterruptContext> stack = fIntCtxStacks.get(hostId, cpu);
        if (stack == null) {
            return null;
        }
        if (stack.isEmpty()) {
            return null;
        }
        return stack.removeFirst();
    }

}
