/**********************************************************************
 * Copyright (c) 2017, 2018 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.internal.lttng2.ust.core.analysis.memory;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.analysis.os.linux.core.memory.MemoryUsageTreeModel;
import org.eclipse.tracecompass.internal.tmf.core.model.filters.FetchParametersUtils;
import org.eclipse.tracecompass.internal.tmf.core.model.xy.AbstractTreeCommonXDataProvider;
import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
import org.eclipse.tracecompass.statesystem.core.exceptions.StateSystemDisposedException;
import org.eclipse.tracecompass.statesystem.core.interval.ITmfStateInterval;
import org.eclipse.tracecompass.tmf.core.dataprovider.DataProviderParameterUtils;
import org.eclipse.tracecompass.tmf.core.model.YModel;
import org.eclipse.tracecompass.tmf.core.model.filters.SelectionTimeQueryFilter;
import org.eclipse.tracecompass.tmf.core.model.filters.TimeQueryFilter;
import org.eclipse.tracecompass.tmf.core.model.tree.TmfTreeModel;
import org.eclipse.tracecompass.tmf.core.model.xy.IYModel;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
import org.eclipse.tracecompass.tmf.core.trace.TmfTraceUtils;

import com.google.common.collect.ImmutableList;

/**
 * This data provider will return a XY model based on a query filter. The model
 * is used afterwards by any viewer to draw charts. Model returned is for Memory
 * Usage views
 *
 * @author Yonni Chen
 * @since 3.1
 */
@NonNullByDefault
public class UstMemoryUsageDataProvider extends AbstractTreeCommonXDataProvider<UstMemoryAnalysisModule, MemoryUsageTreeModel> {

    /**
     * Entry point ID.
     * @since 3.2
     */
    public static final String ID = "org.eclipse.tracecompass.lttng2.ust.core.analysis.memory.UstMemoryUsageDataProvider"; //$NON-NLS-1$

    /**
     * Create an instance of {@link UstMemoryUsageDataProvider}. Returns a null
     * instance if the analysis module is not found.
     *
     * @param trace
     *            A trace on which we are interested to fetch a model
     * @return A MemoryUsageDataProvider. If analysis module is not found, it
     *         returns null
     */
    public static @Nullable UstMemoryUsageDataProvider create(ITmfTrace trace) {
        UstMemoryAnalysisModule module = TmfTraceUtils.getAnalysisModuleOfClass(trace, UstMemoryAnalysisModule.class, UstMemoryAnalysisModule.ID);
        if (module != null) {
            module.schedule();
            return new UstMemoryUsageDataProvider(trace, module);
        }
        return null;
    }

    /**
     * Constructor
     */
    private UstMemoryUsageDataProvider(ITmfTrace trace, UstMemoryAnalysisModule module) {
        super(trace, module);
    }

    /**
     * @since 3.3
     */
    @Override
    protected @Nullable Collection<IYModel> getYSeriesModels(ITmfStateSystem ss, Map<String, Object> fetchParameters, @Nullable IProgressMonitor monitor) throws StateSystemDisposedException {
        SelectionTimeQueryFilter filter = FetchParametersUtils.createSelectionTimeQuery(fetchParameters);
        if (filter == null) {
            return null;
        }
        long[] xValues = filter.getTimesRequested();
        long currentEnd = ss.getCurrentEndTime();

        Map<Integer, IYModel> models = initYModels(ss, filter);

        for (ITmfStateInterval interval : ss.query2D(models.keySet(), getTimes(filter, ss.getStartTime(), currentEnd))) {
            if (monitor != null && monitor.isCanceled()) {
                return null;
            }

            IYModel model = models.get(interval.getAttribute());
            Object value = interval.getValue();
            if (model != null && value instanceof Number) {
                int from = Arrays.binarySearch(xValues, interval.getStartTime());
                from = (from >= 0) ? from : -1 - from;

                int to = Arrays.binarySearch(xValues, interval.getEndTime());
                to = (to >= 0) ? to + 1 : -1 - to;

                Arrays.fill(model.getData(), from, to, ((Number) value).doubleValue());
            }
        }

        return models.values();
    }

    /**
     * Get map of UST_MEMORY_MEMORY_ATTRIBUTE to relevant model
     *
     * @param ss
     *            the queried {@link ITmfStateSystem}
     * @param filter
     *            the {@link TimeQueryFilter}
     * @return a map of the UST_MEMORY_MEMORY_ATTRIBUTE attributes to the
     *         initialized model
     */
    private Map<Integer, IYModel> initYModels(ITmfStateSystem ss, SelectionTimeQueryFilter filter) {
        Map<Integer, IYModel> selectedSeries = new HashMap<>();
        int length = filter.getTimesRequested().length;
        for (Entry<Long, Integer> entry : getSelectedEntries(filter).entrySet()) {
            int tidQuark = entry.getValue();
            int memoryAttribute = ss.optQuarkRelative(tidQuark, UstMemoryStrings.UST_MEMORY_MEMORY_ATTRIBUTE);
            if (memoryAttribute != ITmfStateSystem.INVALID_ATTRIBUTE) {
                String name = getTrace().getName() + ':' + ss.getAttributeName(tidQuark);
                selectedSeries.put(memoryAttribute, new YModel(entry.getKey(), name, new double[length]));
            }
        }
        return selectedSeries;
    }

    /**
     * @since 3.3
     */
    @Override
    protected TmfTreeModel<MemoryUsageTreeModel> getTree(ITmfStateSystem ss, Map<String, Object> parameters, @Nullable IProgressMonitor monitor)
            throws StateSystemDisposedException {

        TimeQueryFilter filter = FetchParametersUtils.createTimeQuery(parameters);
        if (filter == null) {
            return new TmfTreeModel<>(Collections.emptyList(), Collections.emptyList());
        }
        long start = filter.getStart();
        long end = filter.getEnd();

        // Let the list of active states be null if we aren't filtering
        List<ITmfStateInterval> active = null;
        Boolean isFiltered = DataProviderParameterUtils.extractIsFiltered(parameters);
        if (isFiltered != null && isFiltered) {
            if (start == end || start > ss.getCurrentEndTime() || end < ss.getStartTime()) {
                /*
                 * return an empty list if the filter is empty or does not intersect the state
                 * system
                 */
                return new TmfTreeModel<>(Collections.emptyList(), Collections.emptyList());
            }
            active = ss.queryFullState(Long.max(start, ss.getStartTime()));
        }

        List<ITmfStateInterval> nameFullState = ss.queryFullState(ss.getCurrentEndTime());
        List<Integer> tidQuarks = ss.getSubAttributes(-1, false);

        ImmutableList.Builder<MemoryUsageTreeModel> builder = ImmutableList.builder();
        long rootId = getId(ITmfStateSystem.ROOT_ATTRIBUTE);
        builder.add(new MemoryUsageTreeModel(rootId, -1L, -1, Collections.singletonList(getTrace().getName())));
        for (int quark : tidQuarks) {
            int memoryAttribute = ss.optQuarkRelative(quark, UstMemoryStrings.UST_MEMORY_MEMORY_ATTRIBUTE);
            int procNameQuark = ss.optQuarkRelative(quark, UstMemoryStrings.UST_MEMORY_PROCNAME_ATTRIBUTE);

            String name = ss.getAttributeName(quark);
            if (procNameQuark != ITmfStateSystem.INVALID_ATTRIBUTE && procNameQuark < nameFullState.size()) {
                name = String.valueOf(nameFullState.get(procNameQuark).getValue());
            }

            if (memoryAttribute != ITmfStateSystem.INVALID_ATTRIBUTE
                    && (active == null || (memoryAttribute < active.size() && active.get(memoryAttribute).getEndTime() < end))) {

                int tid = Integer.parseInt(ss.getAttributeName(quark));
                builder.add(new MemoryUsageTreeModel(getId(quark), rootId, tid, Collections.singletonList(name)));
            }
        }

        return new TmfTreeModel<>(Collections.emptyList(), builder.build());
    }

    /**
     * @since 3.2
     */
    @Override
    public String getId() {
        return ID;
    }

    /**
     * @since 3.3
     */
    @Override
    protected boolean isCacheable() {
        return false;
    }

    /**
     * @since 3.3
     */
    @Override
    protected String getTitle() {
        return Objects.requireNonNull(Messages.MemoryUsageDataProvider_Title);
    }
}
