/*******************************************************************************
 * Copyright (c) 2012, 2018 Ericsson, É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.analysis.os.linux.ui.views.resources;

import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.tracecompass.analysis.os.linux.core.kernel.StateValues;
import org.eclipse.tracecompass.internal.analysis.os.linux.core.registry.LinuxStyle;
import org.eclipse.tracecompass.internal.analysis.os.linux.core.resourcesstatus.ResourcesEntryModel;
import org.eclipse.tracecompass.internal.analysis.os.linux.core.resourcesstatus.ResourcesEntryModel.Type;
import org.eclipse.tracecompass.internal.analysis.os.linux.ui.Messages;
import org.eclipse.tracecompass.internal.provisional.tmf.core.model.timegraph.ITimeGraphEntryModelWeighted;
import org.eclipse.tracecompass.internal.tmf.core.model.filters.FetchParametersUtils;
import org.eclipse.tracecompass.tmf.core.model.OutputElementStyle;
import org.eclipse.tracecompass.tmf.core.model.StyleProperties;
import org.eclipse.tracecompass.tmf.core.model.filters.SelectionTimeQueryFilter;
import org.eclipse.tracecompass.tmf.core.model.timegraph.ITimeGraphDataProvider;
import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphEntryModel;
import org.eclipse.tracecompass.tmf.core.model.tree.ITmfTreeDataModel;
import org.eclipse.tracecompass.tmf.core.presentation.RGBAColor;
import org.eclipse.tracecompass.tmf.core.presentation.RotatingPaletteProvider;
import org.eclipse.tracecompass.tmf.core.response.TmfModelResponse;
import org.eclipse.tracecompass.tmf.ui.colors.ColorUtils;
import org.eclipse.tracecompass.tmf.ui.views.timegraph.BaseDataProviderTimeGraphView;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.StateItem;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.TimeGraphPresentationProvider;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeEvent;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.ITimeGraphEntry;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.MarkerEvent;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.NullTimeEvent;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.TimeEvent;
import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.model.TimeGraphEntry;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;

/**
 * Presentation provider for the Resource view, based on the generic TMF
 * presentation provider.
 *
 * @author Patrick Tasse
 */
public class ResourcesPresentationProvider extends TimeGraphPresentationProvider {

    private static final int SEPARATOR_HEIGHT = 4;
    private static final int NUM_COLORS = 25;
    private static final float BRIGHTNESS = 0.8f;
    private static final float SATURATION = 0.8f;
    private static final List<RGBAColor> PALETTE =  new RotatingPaletteProvider.Builder()
            .setNbColors(NUM_COLORS)
            .setBrightness(BRIGHTNESS)
            .setSaturation(SATURATION)
            .build().get();
    private static final int COLOR_DIFFERENCIATION_FACTOR = NUM_COLORS / 2 + 2;

    private static final Map<Integer, StateItem> STATE_MAP;

    private static final List<StateItem> STATE_LIST;
    private static final StateItem[] STATE_TABLE;

    private static StateItem createState(LinuxStyle style) {
        return new StateItem(style.toMap());
    }

    static {
        ImmutableMap.Builder<Integer, StateItem> builder = new ImmutableMap.Builder<>();
        builder.put(StateValues.CPU_STATUS_IDLE, new StateItem(LinuxStyle.IDLE.toMap()));
        builder.put(StateValues.CPU_STATUS_RUN_USERMODE, new StateItem(LinuxStyle.USERMODE.toMap()));
        builder.put(StateValues.CPU_STATUS_RUN_SYSCALL, new StateItem(LinuxStyle.SYSCALL.toMap()));
        builder.put(StateValues.CPU_STATUS_IRQ, createState(LinuxStyle.INTERRUPTED));
        builder.put(StateValues.CPU_STATUS_SOFTIRQ, createState(LinuxStyle.SOFT_IRQ));
        builder.put(StateValues.CPU_STATUS_SOFT_IRQ_RAISED, createState(LinuxStyle.SOFT_IRQ_RAISED));
        STATE_MAP = builder.build();
        STATE_LIST = ImmutableList.copyOf(STATE_MAP.values());
        STATE_TABLE = STATE_LIST.toArray(new StateItem[STATE_LIST.size()]);
    }

    /**
     * Default constructor
     */
    public ResourcesPresentationProvider() {
        super();
    }

    private static StateItem getEventState(TimeEvent event) {
        if (event instanceof NullTimeEvent) {
            return null;
        }
        ITimeGraphEntry entry = event.getEntry();
        if (entry instanceof TimeGraphEntry
                && ((TimeGraphEntry) entry).getEntryModel() instanceof ResourcesEntryModel) {
            int value = event.getValue();
            ResourcesEntryModel resourcesModel = (ResourcesEntryModel) ((TimeGraphEntry) entry).getEntryModel();
            Type type = resourcesModel.getType();
            switch (type) {
            case CPU:
                return STATE_MAP.get(value);
            case IRQ:
                return STATE_MAP.get(StateValues.CPU_STATUS_IRQ);
            case SOFT_IRQ:
                if (value == StateValues.CPU_STATUS_SOFT_IRQ_RAISED) {
                    return STATE_MAP.get(StateValues.CPU_STATUS_SOFT_IRQ_RAISED);
                }
                return STATE_MAP.get(StateValues.CPU_STATUS_SOFTIRQ);
            case GROUP:
                return null;
            case CURRENT_THREAD:
            case FREQUENCY:
                if (!event.hasValue()) {
                    return null;
                }
                return STATE_MAP.get(StateValues.CPU_STATUS_RUN_USERMODE);
            default:
                return null;
            }

        }
        return null;
    }

    @Override
    public int getItemHeight(ITimeGraphEntry entry) {
        if (!entry.hasTimeEvents() && entry.getParent() != null) {
            return SEPARATOR_HEIGHT;
        }
        return super.getItemHeight(entry);
    }

    @Override
    public int getStateTableIndex(ITimeEvent event) {
        StateItem state = getEventState((TimeEvent) event);
        if (state != null) {
            return STATE_LIST.indexOf(state);
        }
        if (event instanceof NullTimeEvent) {
            return INVISIBLE;
        }
        return TRANSPARENT;
    }

    @Override
    public StateItem[] getStateTable() {
        return STATE_TABLE;
    }

    @Override
    public String getEventName(ITimeEvent event) {
        StateItem state = getEventState((TimeEvent) event);
        if (state != null) {
            return state.getStateString();
        }
        if (event instanceof NullTimeEvent || isType(event.getEntry(), Type.CURRENT_THREAD)) {
            return null;
        }
        return Messages.ResourcesView_multipleStates;
    }

    @Override
    public Map<String, String> getEventHoverToolTipInfo(ITimeEvent event, long hoverTime) {
        ITimeGraphEntry entry = event.getEntry();

        if (event instanceof TimeEvent && ((TimeEvent) event).hasValue() && entry instanceof TimeGraphEntry) {
            ITmfTreeDataModel model = ((TimeGraphEntry) entry).getEntryModel();

            TimeEvent tcEvent = (TimeEvent) event;

            if (tcEvent.hasValue() && model instanceof ResourcesEntryModel) {
                ResourcesEntryModel resourcesModel = (ResourcesEntryModel) model;
                if (resourcesModel.getType().equals(Type.IRQ) || resourcesModel.getType().equals(Type.SOFT_IRQ) ||
                        resourcesModel.getType().equals(Type.CPU) || resourcesModel.getType().equals(Type.CURRENT_THREAD) ||
                        resourcesModel.getType().equals(Type.FREQUENCY)) {
                    ITimeGraphDataProvider<? extends TimeGraphEntryModel> provider = BaseDataProviderTimeGraphView.getProvider((TimeGraphEntry) entry);
                    if (provider != null) {
                        return getTooltip(provider, model.getId(), hoverTime);
                    }
                }
            }
        }
        return Collections.emptyMap();
    }

    private static Map<String, String> getTooltip(ITimeGraphDataProvider<? extends TimeGraphEntryModel> provider, long id, long hoverTime) {
        SelectionTimeQueryFilter filter = new SelectionTimeQueryFilter(Collections.singletonList(hoverTime), Collections.singleton(id));
        TmfModelResponse<Map<String, String>> response = provider.fetchTooltip(FetchParametersUtils.selectionTimeQueryToMap(filter), null);
        Map<String, String> tooltip = response.getModel();

        if (tooltip == null) {
            return Collections.emptyMap();
        }

        Map<String, String> retMap = new LinkedHashMap<>();
        retMap.putAll(tooltip);
        return retMap;
    }

    @Override
    public Map<String, Object> getSpecificEventStyle(ITimeEvent event) {
        Map<String, Object> map = new HashMap<>(super.getSpecificEventStyle(event));
        if (isType(event.getEntry(), Type.CURRENT_THREAD) && event instanceof TimeEvent) {
            if(event instanceof MarkerEvent) {
                TimeEvent timeEvent = (TimeEvent) event;
                OutputElementStyle style = timeEvent.getModel().getStyle();
                if (style != null) {
                    return style.getStyleValues();
                }
            } else {
                int threadEventValue = ((TimeEvent) event).getValue();
                RGBAColor color = PALETTE.get(Math.floorMod(threadEventValue + COLOR_DIFFERENCIATION_FACTOR, NUM_COLORS));
                map.put(StyleProperties.BACKGROUND_COLOR, ColorUtils.toHexColor(color.getRed(), color.getGreen(), color.getBlue()));
                map.put(StyleProperties.STYLE_NAME, String.valueOf(threadEventValue));
            }
        } else if (event.getEntry() instanceof TimeGraphEntry &&
                ((TimeGraphEntry) event.getEntry()).getEntryModel() instanceof ITimeGraphEntryModelWeighted) {
            ITimeGraphEntryModelWeighted model = (ITimeGraphEntryModelWeighted) ((TimeGraphEntry) event.getEntry()).getEntryModel();
            int eventValue = ((TimeEvent) event).getValue();

            map.put(StyleProperties.HEIGHT, (float) model.getWeight(eventValue));
        }
        return map;
    }

    private static boolean isType(ITimeGraphEntry entry, Type type) {
        if (entry instanceof TimeGraphEntry) {
            ITmfTreeDataModel model = ((TimeGraphEntry) entry).getEntryModel();
            if (model instanceof ResourcesEntryModel) {
                return (((ResourcesEntryModel) model).getType().equals(type));
            }
        }
        return false;
    }
}
