/*******************************************************************************
 * Copyright (c) 2012, 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
 *
 * Contributors:
 *   Alexandre Montplaisir - Initial API
 ******************************************************************************/

package org.eclipse.tracecompass.statesystem.core;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.statesystem.core.exceptions.StateValueTypeException;
import org.eclipse.tracecompass.statesystem.core.exceptions.TimeRangeException;
import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue;
import org.eclipse.tracecompass.statesystem.core.statevalue.TmfStateValue;

/**
 * This is the external interface to build or modify an existing state history.
 *
 * It extends ITmfStateSystem, so you can still use it for reading the history,
 * but it also provides write-access to it with the quark-creating and
 * state-change insertion methods.
 *
 * This should only be used by classes that need to build or modify the state
 * history. Views, etc. (who will only be reading from it) should use the
 * ITmfStateSystem interface instead.
 *
 * @author Alexandre Montplaisir
 * @noimplement Only the internal StateSystem class should implement this
 *              interface.
 * @since 3.0
 */
public interface ITmfStateSystemBuilder extends ITmfStateSystem {

    /**
     * Special state provider version number that will tell the backend to
     * ignore the version check and open an existing file even if the versions
     * don't match.
     */
    int IGNORE_PROVIDER_VERSION = -42;

    /**
     * @name Read/write quark-getting methods
     */

    /**
     * Basic quark-retrieving method. Pass an attribute in parameter as an array
     * of strings, the matching quark will be returned.
     * <p>
     * This version WILL create new attributes: if the attribute passed in
     * parameter is new in the system, it will be added and its new quark will
     * be returned.
     * </p>
     *
     * @param attribute
     *            Attribute given as its full path in the Attribute Tree
     * @return The quark of the attribute (which either existed or just got
     *         created)
     */
    int getQuarkAbsoluteAndAdd(String... attribute);

    /**
     * "Relative path" quark-getting method. Instead of specifying a full path,
     * if you know the path is relative to another attribute for which you
     * already have the quark, use this for better performance.
     * <p>
     * This is useful for cases where a lot of modifications or queries will
     * originate from the same branch of the attribute tree : the common part of
     * the path won't have to be re-hashed for every access.
     * </p>
     * <p>
     * This version WILL create new attributes: if the attribute passed in
     * parameter is new in the system, it will be added and its new quark will
     * be returned.
     * </p>
     *
     * @param startingNodeQuark
     *            The quark of the attribute from which 'subPath' originates.
     * @param subPath
     *            "Rest" of the path to get to the final attribute
     * @return The matching quark, either if it's new of just got created.
     * @throws IndexOutOfBoundsException
     *             If the starting node quark is out of range
     */
    int getQuarkRelativeAndAdd(int startingNodeQuark, String... subPath);

    /**
     * @name State-changing methods
     */

    /**
     * Modify a current "ongoing" state (instead of inserting a state change,
     * like modifyAttribute() and others).
     * <p>
     * This can be used to update the value of a previous state change, for
     * example when we get information at the end of the state and not at the
     * beginning. (return values of system calls, etc.)
     * </p>
     * <p>
     * Note that past states can only be modified while they are still in
     * memory, so only the "current state" can be updated. Once they get
     * committed to disk (by inserting a new state change) it becomes too late.
     * </p>
     *
     * @param newValue
     *            The new value that will overwrite the "current" one.
     * @param attributeQuark
     *            For which attribute in the system
     * @throws IndexOutOfBoundsException
     *             If the attribute quark is out of range
     */
    void updateOngoingState(@NonNull ITmfStateValue newValue, int attributeQuark);

    /**
     * Modify a current "ongoing" state (instead of inserting a state change,
     * like modifyAttribute() and others).
     * <p>
     * This can be used to update the value of a previous state change, for
     * example when we get information at the end of the state and not at the
     * beginning. (return values of system calls, etc.)
     * </p>
     * <p>
     * Note that past states can only be modified while they are still in
     * memory, so only the "current state" can be updated. Once they get
     * committed to disk (by inserting a new state change) it becomes too late.
     * </p>
     *
     * @param newValue
     *            The new value that will overwrite the "current" one.
     * @param attributeQuark
     *            For which attribute in the system
     * @throws IndexOutOfBoundsException
     *             If the attribute quark is out of range
     * @since 4.0
     */
    default void updateOngoingState(@Nullable Object newValue, int attributeQuark) {
        updateOngoingState(TmfStateValue.newValue(newValue), attributeQuark);
    }

    /**
     * Basic attribute modification method, we simply specify a new value, for a
     * given attribute, effective at the given timestamp.
     *
     * @param t
     *            Timestamp of the state change
     * @param value
     *            The State Value we want to assign to the attribute
     * @param attributeQuark
     *            Integer value of the quark corresponding to the attribute we
     *            want to modify
     * @throws TimeRangeException
     *             If the requested time is outside of the trace's range
     * @throws IndexOutOfBoundsException
     *             If the attribute quark is out of range
     * @throws StateValueTypeException
     *             If the inserted state value's type does not match what is
     *             already assigned to this attribute.
     * @since 3.1
     */
    void modifyAttribute(long t, Object value, int attributeQuark)
            throws StateValueTypeException;

    /**
     * "Push" helper method. This uses the given integer attribute as a stack:
     * The value of that attribute will represent the stack depth (always
     * {@literal >=} 1). Sub-attributes will be created, their base-name will be
     * the position in the stack (1, 2, etc.) and their value will be the state
     * value 'value' that was pushed to this position.
     *
     * @param t
     *            Timestamp of the state change
     * @param value
     *            State value to assign to this stack position.
     * @param attributeQuark
     *            The base attribute to use as a stack. If it does not exist if
     *            will be created (with depth = 1)
     * @throws TimeRangeException
     *             If the requested timestamp is invalid
     * @throws IndexOutOfBoundsException
     *             If the attribute quark is out of range
     * @throws StateValueTypeException
     *             If the attribute 'attributeQuark' already exists, but is not
     *             of integer type.
     * @since 3.1
     */
    void pushAttribute(long t, Object value, int attributeQuark)
            throws StateValueTypeException;

    /**
     * Antagonist of the pushAttribute(), pops the top-most attribute on the
     * stack-attribute. If this brings it back to depth = 0, the attribute is
     * kept with depth = 0. If the value is already 0, or if the attribute
     * doesn't exist, nothing is done.
     *
     * @param t
     *            Timestamp of the state change
     * @param attributeQuark
     *            Quark of the stack-attribute to pop
     * @return The state value that was popped, or 'null' if nothing was
     *         actually removed from the stack.
     * @throws IndexOutOfBoundsException
     *             If the attribute quark is out of range
     * @throws TimeRangeException
     *             If the timestamp is invalid
     * @throws StateValueTypeException
     *             If the target attribute already exists, but its state value
     *             type is invalid (not an integer)
     */
    ITmfStateValue popAttribute(long t, int attributeQuark)
            throws StateValueTypeException;

    /**
     * Antagonist of the pushAttribute(), pops the top-most attribute on the
     * stack-attribute. If this brings it back to depth = 0, the attribute is
     * kept with depth = 0. If the value is already 0, or if the attribute
     * doesn't exist, nothing is done.
     *
     * @param t
     *            Timestamp of the state change
     * @param attributeQuark
     *            Quark of the stack-attribute to pop
     * @return The state value that was popped, or 'null' if nothing was
     *         actually removed from the stack.
     * @throws IndexOutOfBoundsException
     *             If the attribute quark is out of range
     * @throws TimeRangeException
     *             If the timestamp is invalid
     * @throws StateValueTypeException
     *             If the target attribute already exists, but its state value
     *             type is invalid (not an integer)
     * @since 3.1
     */
    default Object popAttributeObject(long t, int attributeQuark)
            throws StateValueTypeException {
        return popAttribute(t, attributeQuark).unboxValue();
    }

    /**
     * Remove attribute method. Similar to the above modify- methods, with value
     * = 0 / null, except we will also "nullify" all the sub-contents of the
     * requested path (a bit like "rm -rf")
     *
     * @param t
     *            Timestamp of the state change
     * @param attributeQuark
     *            Attribute to remove
     * @throws TimeRangeException
     *             If the timestamp is invalid
     * @throws IndexOutOfBoundsException
     *             If the attribute quark is out of range
     */
    void removeAttribute(long t, int attributeQuark);

    /**
     * Method to close off the History Provider. This happens for example when
     * we are done reading an off-line trace. First we close the TransientState,
     * commit it to the Provider, mark it as inactive, then we write the
     * Attribute Tree somewhere so we can reopen it later.
     *
     * @param endTime
     *            The requested End Time of the history, since it could be
     *            bigger than the timestamp of the last event or state change we
     *            have seen. All "ongoing" states will be extended until this
     *            'endTime'.
     * @throws TimeRangeException
     *             If the passed endTime doesn't make sense (for example, if
     *             it's earlier than the latest time) and the backend doesn't
     *             know how to handle it.
     */
    void closeHistory(long endTime);

    /**
     * Delete any generated files or anything that might have been created by
     * the history backend (either temporary or save files). By calling this, we
     * return to the state as it was before ever building the history.
     * <p>
     * You might not want to call automatically if, for example, you want an
     * index file to persist on disk. This could be limited to actions
     * originating from the user.
     * </p>
     *
     * @since 2.1
     */
    void removeFiles();

    /**
     * Returns the current state values we have (in the Transient State) for all the
     * attributes.
     * <p>
     * This is useful even for a StateHistorySystem, as we are guaranteed it
     * will only do a memory access and not go look on disk (and we don't even
     * have to provide a timestamp!)
     * </p>
     *
     * @return the list of transient state values
     * @since 4.3
     */
    default List<@Nullable Object> queryOngoing() {
        List<@Nullable Object> retVal = new ArrayList<>();
        for (int attr = 0; attr < getNbAttributes(); attr++) {
            retVal.add(queryOngoing(attr));
        }
        return retVal;
    }
}
