/*
 * Copyright 2006-2019 DLR, Germany
 * 
 * SPDX-License-Identifier: EPL-1.0
 * 
 * http://www.rcenvironment.de/
 */

package de.rcenvironment.core.gui.workflow.view.timeline;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import org.apache.commons.logging.LogFactory;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Slider;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.ViewPart;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import de.rcenvironment.core.communication.common.CommunicationException;
import de.rcenvironment.core.communication.common.ResolvableNodeId;
import de.rcenvironment.core.component.api.DistributedComponentKnowledgeService;
import de.rcenvironment.core.component.management.api.DistributedComponentEntry;
import de.rcenvironment.core.component.model.api.ComponentInstallation;
import de.rcenvironment.core.datamanagement.MetaDataService;
import de.rcenvironment.core.datamanagement.commons.ComponentRunInterval;
import de.rcenvironment.core.datamanagement.commons.WorkflowRunTimline;
import de.rcenvironment.core.datamodel.api.TimelineIntervalType;
import de.rcenvironment.core.gui.resources.api.ImageManager;
import de.rcenvironment.core.gui.resources.api.StandardImages;
import de.rcenvironment.core.utils.common.JsonUtils;
import de.rcenvironment.core.utils.incubator.ServiceRegistry;
import de.rcenvironment.core.utils.incubator.ServiceRegistryAccess;

/**
 * The complete Timeline View.
 * 
 * @author Hendrik Abbenhaus
 * @author David Scholz
 * 
 */
public class TimelineView extends ViewPart implements AreaChangedListener, ResizeListener, ControlListener, SelectionListener {

    /** Identifier used to find the this view within the workbench. */
    public static final String ID = "de.rcenvironment.gui.TimeTable";

    private static final int SCROLLBAR_MAXIMUM = 10000;

    private static final Map<String, Image> COMPONENT_ICON_CACHE = new HashMap<>();

    private Composite rootComposite = null;

    private TimelineNavigationControl navigation = null;

    private SashForm sashMiddle = null;

    private TimelineComponentList list = null;

    private TimelineComponentRow[] rows = new TimelineComponentRow[0];

    private Slider scrollBar = null;

    private Action refreshAction = null;

    private Action zoomInAction = null;

    private Action zoomOutAction = null;

    private Action filterAction = null;

    private Date wfStartDate = null;

    private Date wfEndDate = null;

    private Label workflowNameLabel = null;

    private String[] allowedComponentNames = null;

    private final Color backgroundColorWhite = Display.getCurrent().getSystemColor(SWT.COLOR_WHITE);

    private final int defaultMaximumZoomValue = 10000;

    private final int defaultMinimumZoomValue = 45;

    private int currentZoomValue = defaultMaximumZoomValue;

    private MetaDataService metaDataService;

    private Long workflowDmId;

    private ResolvableNodeId workflowCtrlNode;

    private boolean workflowTerminated = false;

    private Composite parentComposite;

    private boolean actualContentSet = false;

    private Label refreshLabel;

    public TimelineView() {
        metaDataService = ServiceRegistry.createAccessFor(this).getService(MetaDataService.class);
    }

    @Override
    public void createPartControl(Composite parent) {
        parentComposite = parent;
        parentComposite.setBackground(backgroundColorWhite);

        refreshLabel = new Label(parentComposite, SWT.NONE);
        refreshLabel.setText("Fetching workflow timeline...");

        rootComposite = new Composite(new Shell(), SWT.NONE);
        rootComposite.setBackground(backgroundColorWhite);
        GridLayout mainLayout = new GridLayout(1, false);
        mainLayout.marginHeight = 0;
        mainLayout.marginBottom = 0;
        mainLayout.verticalSpacing = 0;
        rootComposite.setLayout(mainLayout);
        // For itself
        GridData gridData = new GridData();
        gridData.horizontalAlignment = GridData.FILL;
        gridData.horizontalSpan = 1;
        gridData.grabExcessHorizontalSpace = true;
        gridData.verticalAlignment = GridData.FILL;
        gridData.verticalSpan = 1;
        gridData.grabExcessVerticalSpace = true;

        rootComposite.setLayoutData(gridData);

        makeActions(rootComposite);
        initGUI(rootComposite);
        contributeToActionBars();

        rootComposite.setEnabled(false);
    }

    private void initGUI(Composite parent) {
        GridData gridData = new GridData();
        gridData.horizontalAlignment = GridData.FILL;
        gridData.horizontalSpan = 1;
        gridData.grabExcessHorizontalSpace = true;
        gridData.verticalAlignment = GridData.BEGINNING;
        gridData.verticalSpan = 1;
        gridData.grabExcessVerticalSpace = false;

        GridLayout gridLayout = new GridLayout();
        gridLayout.numColumns = 2;

        Composite titleComposite = new Composite(parent, SWT.None);
        titleComposite.setLayoutData(gridData);
        titleComposite.setBackground(backgroundColorWhite);
        titleComposite.setLayout(gridLayout);

        Composite workflowName = new Composite(titleComposite, SWT.None);
        workflowName.setLayoutData(gridData);
        workflowName.setBackground(backgroundColorWhite);
        workflowName.setLayout(new RowLayout());
        workflowNameLabel = new Label(workflowName, SWT.None);
        workflowNameLabel.setBackground(backgroundColorWhite);

        Composite colorLegend = new Composite(titleComposite, SWT.RIGHT_TO_LEFT);
        colorLegend.setLayoutData(gridData);
        colorLegend.setBackground(backgroundColorWhite);
        colorLegend.setLayout(new RowLayout());

        for (TimelineActivityType current : TimelineActivityType.values()) {
            if (current.getColor() != null) {
                Label label = new Label(colorLegend, SWT.None);
                label.setBackground(backgroundColorWhite);
                label.setText("   " + current.getDisplayName() + " ");
                Label colorLabel = new Label(colorLegend, SWT.None);
                colorLabel.setText("      ");
                // FIXME: the newly created color object will never be disposed
                colorLabel.setBackground(new Color(label.getDisplay(), current.getColor()));
            }
        }

        gridData = new GridData();
        gridData.horizontalAlignment = GridData.FILL;
        gridData.horizontalSpan = 1;
        gridData.grabExcessHorizontalSpace = true;
        gridData.verticalAlignment = GridData.FILL;
        gridData.verticalSpan = 1;
        gridData.grabExcessVerticalSpace = true;

        SashForm master = new SashForm(parent, SWT.VERTICAL);
        master.setSashWidth(2);
        // master.setBackground(backgroundColorWhite);
        master.setLayoutData(gridData);

        initTopGUI(master);
        initBottomGUI(master);

        master.setWeights(new int[] { 5, 1 });

    }

    private void initTopGUI(Composite parent) {
        Composite master = new Composite(parent, SWT.NONE);
        master.setBackground(backgroundColorWhite);
        master.setLayout(new GridLayout(1, true));
        // Top:
        list = new TimelineComponentList(master);
        GridData gridData = new GridData();
        gridData.horizontalAlignment = GridData.FILL;
        gridData.grabExcessHorizontalSpace = true;
        gridData.horizontalSpan = 1;
        gridData.grabExcessVerticalSpace = true;
        gridData.verticalAlignment = GridData.FILL;
        list.setLayoutData(gridData);
        list.addResizeListener(this);
        list.setBackground(backgroundColorWhite);

        // bottom:
        sashMiddle = new SashForm(master, SWT.HORIZONTAL);
        gridData = new GridData();
        gridData.horizontalAlignment = GridData.FILL;
        gridData.grabExcessHorizontalSpace = true;
        gridData.horizontalSpan = 1;
        sashMiddle.setLayoutData(gridData);

        // left:
        Composite zoomComposite = new Composite(sashMiddle, SWT.NONE);
        zoomComposite.setBackground(backgroundColorWhite);
        GridLayout layout = new GridLayout(3, false);
        layout.marginHeight = 0;
        zoomComposite.setLayout(layout);

        // right:
        scrollBar = new Slider(sashMiddle, SWT.HORIZONTAL);

        scrollBar.setMaximum(SCROLLBAR_MAXIMUM); // maximum
        scrollBar.setPageIncrement(SCROLLBAR_MAXIMUM);
        scrollBar.setIncrement(10); // jump on press left or right
        scrollBar.setThumb(scrollBar.getMaximum());
        // SWT Slider does not always disable slider if value == maximum value
        // as at start when value == maximum
        scrollBar.setEnabled(false);
        scrollBar.setLayoutData(gridData);
        scrollBar.addControlListener(this);
        scrollBar.addSelectionListener(this);

        sashMiddle.setWeights(new int[] { 1, 7 });
    }

    private void initBottomGUI(Composite parent) {
        this.navigation = new TimelineNavigationControl(parent);
        navigation.setBackground(backgroundColorWhite);
        GridData gridData = new GridData();
        gridData.horizontalAlignment = GridData.FILL;
        gridData.horizontalSpan = 3;
        gridData.grabExcessHorizontalSpace = true;
        this.navigation.setLayoutData(gridData);
        this.navigation.addAreaChangeListener(this);
    }

    /**
     * Sets dm id and controller node of workflow related to this view instance.
     * 
     * @param wfDmId dm id of workflow run
     * @param wfCtrlNode a generic or specific id referring to the workflow controller instance
     */
    public void initialize(Long wfDmId, ResolvableNodeId wfCtrlNode) {
        workflowDmId = wfDmId;
        workflowCtrlNode = wfCtrlNode;
        updateContent();
    }

    private void updateContent() {
        refreshAction.setEnabled(false);
        Job job = new Job("Workflow Timeline") {

            @Override
            protected IStatus run(IProgressMonitor monitor) {
                try {
                    monitor.beginTask("Fetching workflow timeline", 2);
                    monitor.worked(1);
                    WorkflowRunTimline timeline = metaDataService.getWorkflowTimeline(workflowDmId, workflowCtrlNode);
                    final String timelineAsString = getWorkflowRunTimelineAsJsonString(timeline);
                    monitor.worked(1);
                    Display.getDefault().asyncExec(new Runnable() {

                        @Override
                        public void run() {
                            if (!parentComposite.isDisposed()) {
                                boolean isZoomed = currentZoomValue != defaultMaximumZoomValue;
                                // Cache viewport by visible start and end time
                                Date visibleEndTime = null;
                                Date visibleStartTime = null;
                                if (isZoomed && navigation != null) {
                                    visibleEndTime = navigation.getVisibleEndTime();
                                    visibleStartTime = navigation.getVisibleStartTime();
                                }
                                if (updateContent(timelineAsString) && !actualContentSet) { // replace placeholder content with actual
                                                                                            // content
                                    refreshLabel.setParent(new Shell());
                                    refreshLabel.dispose();
                                    rootComposite.setParent(parentComposite);
                                    rootComposite.getShell().layout(true, true);
                                    enableZoomActions();
                                    filterAction.setEnabled(true);
                                    actualContentSet = true;
                                }
                                rootComposite.setEnabled(true);

                                // update the scrollbars (needed if new components were contributing to the timeline)
                                list.setMinSize(list.computeSize(SWT.DEFAULT, SWT.DEFAULT, true));
                                list.layout();
                                rootComposite.pack();
                                rootComposite.setBounds(rootComposite.getParent().getBounds());
                                // Restore viewport if timeline is zoomed in
                                if (isZoomed && visibleStartTime != null && visibleEndTime != null) {
                                    setVisibleArea(visibleStartTime, visibleEndTime);
                                }
                                refreshAction.setEnabled(!workflowTerminated);
                            }
                        }
                    });
                } catch (IOException | CommunicationException e) {
                    LogFactory.getLog(TimelineView.class).error("Failed to load timeline of workflow", e);
                    Display.getDefault().asyncExec(new Runnable() {

                        @Override
                        public void run() {
                            PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().hideView(TimelineView.this);
                            MessageDialog.openError(getSite().getShell(), "Workflow Timeline",
                                "Failed to load the timeline. Did you get disconnected from a remote instance?\n\n"
                                    + "Please refresh the workflow data browser and try again.\n\nSee log for more details.");
                        }
                    });
                } finally {
                    monitor.done();
                }
                return Status.OK_STATUS;
            };
        };
        job.setUser(true);
        job.schedule();
    }

    private String getWorkflowRunTimelineAsJsonString(WorkflowRunTimline timeline) throws IOException {
        final String timeNodeName = "Time";
        final String typeNodeName = "Type";
        ObjectMapper mapper = JsonUtils.getDefaultObjectMapper();
        ObjectNode rootNode = mapper.createObjectNode();
        rootNode.put("WorkflowName", timeline.getWorkflowRunName());
        rootNode.put("WorkflowStartTime", String.valueOf(timeline.getWorkflowRunInterval().getStartTime()));
        if (timeline.getWorkflowRunInterval().getEndTime() != null) {
            rootNode.put("WorkflowEndTime", String.valueOf(timeline.getWorkflowRunInterval().getEndTime()));
            workflowTerminated = true;
        } else {
            rootNode.put("WorkflowEndTime", String.valueOf(System.currentTimeMillis()));
        }
        ArrayNode componentsArray = rootNode.putArray("Components");
        Map<String, ObjectNode> componentNodes = new HashMap<>();
        Map<String, SortedMap<Long, ObjectNode>> componentEventNodes = new HashMap<>();
        for (ComponentRunInterval cri : timeline.getComponentRunIntervalsSortedByTime()) {
            if (!componentNodes.containsKey(cri.getComponentInstanceName())) {
                ObjectNode componentNode = mapper.createObjectNode();
                componentNode.put("Name", cri.getComponentInstanceName());
                componentNode.put("Id", cri.getComponentID());
                componentNode.putArray("Events");
                componentNodes.put(cri.getComponentInstanceName(), componentNode);
            }
            if (!componentEventNodes.containsKey(cri.getComponentInstanceName())) {
                componentEventNodes.put(cri.getComponentInstanceName(), new TreeMap<Long, ObjectNode>());
            }
            SortedMap<Long, ObjectNode> sortedEventNodes = componentEventNodes.get(cri.getComponentInstanceName());
            if (cri.getType() == TimelineIntervalType.COMPONENT_RUN) {
                ObjectNode eventNode = mapper.createObjectNode();
                eventNode.put(timeNodeName, String.valueOf(cri.getStartTime()));
                eventNode.put(typeNodeName, String.valueOf(cri.getType().name()));
                sortedEventNodes.put(cri.getStartTime(), eventNode);
                if (cri.getEndTime() != null) {
                    ObjectNode waitEventNode = mapper.createObjectNode();
                    waitEventNode.put(timeNodeName, String.valueOf(cri.getEndTime()));
                    waitEventNode.put(typeNodeName, "COMPONENT_WAIT");
                    sortedEventNodes.put(cri.getEndTime(), waitEventNode);
                }
            } else {
                ObjectNode eventNode = mapper.createObjectNode();
                eventNode.put(timeNodeName, String.valueOf(cri.getStartTime()));
                eventNode.put(typeNodeName, String.valueOf(cri.getType().name()));
                sortedEventNodes.put(cri.getStartTime(), eventNode);
                // second check: sanity check if tool run end and component run end are equal
                if (cri.getEndTime() != null && !sortedEventNodes.containsKey(cri.getEndTime())) {
                    ObjectNode runEventNode = mapper.createObjectNode();
                    runEventNode.put(timeNodeName, String.valueOf(cri.getEndTime()));
                    runEventNode.put(typeNodeName, TimelineIntervalType.COMPONENT_RUN.name());
                    sortedEventNodes.put(cri.getEndTime(), runEventNode);
                }
            }
        }

        for (String compInstanceName : componentNodes.keySet()) {
            ArrayNode eventsNode = (ArrayNode) componentNodes.get(compInstanceName).get("Events");
            for (ObjectNode eventNode : componentEventNodes.get(compInstanceName).values()) {
                eventsNode.add(eventNode);
            }
            componentsArray.add(componentNodes.get(compInstanceName));
        }
        ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();
        return writer.writeValueAsString(rootNode);
    }

    /**
     * Updates the Content of the View.
     * 
     * @param resource the resource
     */
    @SuppressWarnings("unchecked")
    private boolean updateContent(String resource) {
        // delete all!
        this.list.clear();

        // read json file

        try {
            ObjectMapper mapper = JsonUtils.getDefaultObjectMapper();

            Map<String, Object> jsonStructureRoot = mapper.readValue(resource,
                new HashMap<String, Object>().getClass());
            String jsonWorkflowName = (String) jsonStructureRoot.get(TimelineViewConstants.JSON_WORKFLOWNAME);
            String jsonWorkflowStart = (String) jsonStructureRoot
                .get(TimelineViewConstants.JSON_WORKFLOWSTARTTIME);
            String jsonWorkflowEnd = (String) jsonStructureRoot
                .get(TimelineViewConstants.JSON_WORKFLOWENDTIME);
            List<Object> jsonComponentList = (ArrayList<Object>) jsonStructureRoot
                .get(TimelineViewConstants.JSON_COMPONENTS);
            if (jsonWorkflowName == null || jsonWorkflowName.equals("")
                || jsonWorkflowStart == null || jsonWorkflowStart.equals("")
                || jsonWorkflowEnd == null || jsonWorkflowEnd.equals("")
                || jsonComponentList == null || jsonComponentList.isEmpty()) {
                String message =
                    "Unable to open the timeline view for the selected workflow run."
                        + " An unexpected problem occured while reading the timeline data.";
                LogFactory.getLog(TimelineView.class).error(message);
                refreshLabel.setText(message);
                return false;
            }
            workflowNameLabel.setText(jsonWorkflowName);
            workflowNameLabel.pack();
            this.wfStartDate = new Date();
            this.wfStartDate.setTime(Long.valueOf(jsonWorkflowStart)
                .longValue());
            this.wfEndDate = new Date();
            this.wfEndDate.setTime(Long.valueOf(jsonWorkflowEnd).longValue());
            setVisibleArea(wfStartDate, wfEndDate);
            // set start end endtime to list composite legend
            // list.setWorkflowStartEndTime(wfStartDate, wfEndDate);
            this.navigation.setWorflowStartEndTime(wfStartDate, wfEndDate);
            List<TimelineComponentRow> currentRows = new ArrayList<TimelineComponentRow>();

            for (Object jsonCurrentComponentObject : jsonComponentList) {
                Map<String, Object> jsonCurrentComponent = (Map<String, Object>) jsonCurrentComponentObject;
                String currentComponentName = (String) jsonCurrentComponent
                    .get(TimelineViewConstants.JSON_COMPONENT_NAME);
                String currentComponentID = (String) jsonCurrentComponent
                    .get(TimelineViewConstants.JSON_COMPONENT_ID);
                TimelineComponentRow currentRow = new TimelineComponentRow(
                    currentComponentName, currentComponentID, wfStartDate,
                    wfEndDate);

                List<Object> jsonActivityList = (ArrayList<Object>) jsonCurrentComponent
                    .get(TimelineViewConstants.JSON_COMPONENT_EVENTS);

                Collections.sort(jsonActivityList, new Comparator<Object>() {

                    /*
                     * (non-Javadoc)
                     * 
                     * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
                     */
                    @Override
                    public int compare(Object o1, Object o2) {
                        Map<String, String> compare1 = (HashMap<String, String>) o1;
                        Map<String, String> compare2 = (HashMap<String, String>) o2;
                        return compare1
                            .get(TimelineViewConstants.JSON_ACTIVITYTIME)
                            .compareTo(
                                compare2.get(TimelineViewConstants.JSON_ACTIVITYTIME));
                    }
                });
                List<TimelineActivityPart> currentActivities = new ArrayList<TimelineActivityPart>();
                boolean extToolRunPrev = false;
                int run = 0;
                for (Object jsonCurrentActivity : jsonActivityList) {
                    Map<String, String> currentActivityMap = (Map<String, String>) jsonCurrentActivity;

                    Date currentActivityTime = new Date();
                    long activityTime = Long.valueOf(currentActivityMap
                        .get(TimelineViewConstants.JSON_ACTIVITYTIME));
                    currentActivityTime.setTime(activityTime);

                    String currentEventType = currentActivityMap
                        .get(TimelineViewConstants.JSON_ACTIVITYTYPE);
                    TimelineActivityType eventType = TimelineActivityType
                        .valueOfjsonName(currentEventType);
                    String comment = null;
                    if (currentActivityMap
                        .containsKey(TimelineViewConstants.JSON_COMPONENT_EVENT_INFOTEXT)) {
                        comment = currentActivityMap
                            .get(TimelineViewConstants.JSON_COMPONENT_EVENT_INFOTEXT);
                    }
                    TimelineActivityPart currentActivity = null;
                    if (eventType == TimelineActivityType.COMPONENT_RUN) {
                        if (extToolRunPrev) {
                            extToolRunPrev = false;
                        } else {
                            run++;
                        }
                        currentActivity = new TimelineActivityPart(currentComponentName,
                            eventType, currentActivityTime, String.valueOf(run), comment);
                    } else {
                        currentActivity = new TimelineActivityPart(currentComponentName,
                            eventType, currentActivityTime, String.valueOf(run), comment);
                        if (eventType == TimelineActivityType.EXTERNAL_TOOL_RUN_IN_COMPONENT_RUN) {
                            extToolRunPrev = true;
                        }
                    }

                    if (currentActivities.size() == 0
                        && !currentActivityTime.equals(wfStartDate)) {
                        TimelineActivityPart currentFirstActivity = new TimelineActivityPart(currentComponentName,
                            TimelineActivityType.WAITING, wfStartDate,
                            null);
                        currentFirstActivity.setEndtime(currentActivityTime);
                        if (currentActivities.size() > 0) {
                            currentActivities.get(currentActivities.size() - 1)
                                .setEndtime(currentActivityTime);
                        }
                        currentActivities.add(currentFirstActivity);
                    }
                    if (currentActivities.size() > 0) {
                        currentActivities.get(currentActivities.size() - 1)
                            .setEndtime(currentActivityTime);
                    }
                    currentActivities.add(currentActivity);
                }
                currentRow.setActivities(currentActivities
                    .toArray(new TimelineActivityPart[currentActivities
                        .size()]));
                currentRow.setWorkflowStartTime(wfStartDate);
                currentRow.setWorkflowEndTime(wfEndDate);
                currentRows.add(currentRow);
            }
            rows = currentRows.toArray(new TimelineComponentRow[currentRows
                .size()]);

        } catch (IOException e) {
            LogFactory.getLog(TimelineView.class).error(e);
            return false;
        }
        showComponentRows(this.rows);
        return true;
    }

    private void setVisibleArea(Date visibleStartTime, Date visibleEndTime) {
        this.list.setTimeArea(visibleStartTime, visibleEndTime);
        // if (!mouseDown) {
        this.navigation.setVisibleArea(visibleStartTime, visibleEndTime);
        // }
    }

    private void showComponentRows(TimelineComponentRow[] newrows) {
        this.navigation.setWorflowStartEndTime(wfStartDate, wfEndDate);
        this.navigation.setTimeTableComponentRows(filter(newrows,
            this.allowedComponentNames));
        this.list.setTimeTableComponentRows(filter(newrows,
            this.allowedComponentNames));
    }

    private TimelineComponentRow[] filter(TimelineComponentRow[] oldrows,
        String[] allowed) {
        if (allowed == null) {
            return oldrows;
        }
        List<TimelineComponentRow> showRows = new ArrayList<TimelineComponentRow>();
        for (TimelineComponentRow currentRow : oldrows) {
            if (filterContains(currentRow.getName(), allowed)) {
                showRows.add(currentRow);
            }
        }
        return showRows.toArray(new TimelineComponentRow[showRows.size()]);
    }

    private boolean filterContains(String name, String[] filter) {
        for (String filterKey : filter) {
            if (filterKey.equals(name)) {
                return true;
            }
        }
        return false;
    }

    private void makeActions(final Composite parent) {

        refreshAction = new Action("Refresh") {

            @Override
            public void run() {
                updateContent();
            }

        };
        refreshAction.setImageDescriptor(ImageManager.getInstance().getImageDescriptor(StandardImages.REFRESH_16));
        refreshAction.setEnabled(false);

        zoomInAction = new Action(Messages.zoomin) {

            @Override
            public void run() {
                int maxZoom = defaultMaximumZoomValue / 10;
                int minZoom = currentZoomValue - maxZoom;
                if (minZoom == 0) {
                    setZoom(defaultMinimumZoomValue);
                } else if (minZoom > 0) {
                    setZoom(minZoom);
                } else if (minZoom < 0) {
                    setZoom(defaultMinimumZoomValue);
                }
                enableZoomActions();
            }
        };
        zoomInAction.setImageDescriptor(ImageDescriptor
            .createFromURL(TimelineView.class
                .getResource("/resources/icons/zoom_in.gif")));
        zoomInAction.setEnabled(false);

        zoomOutAction = new Action(Messages.zoomout) {

            @Override
            public void run() {
                int maxZoom = defaultMaximumZoomValue / 10;
                int minZoom = defaultMaximumZoomValue - currentZoomValue;
                if (currentZoomValue + maxZoom <= defaultMaximumZoomValue) {
                    setZoom(currentZoomValue + maxZoom);
                } else if (minZoom > 0 && minZoom < maxZoom) {
                    setZoom(currentZoomValue + minZoom);
                }
                enableZoomActions();
            }
        };
        zoomOutAction.setImageDescriptor(ImageDescriptor
            .createFromURL(TimelineView.class
                .getResource("/resources/icons/zoom_out.gif")));
        zoomOutAction.setEnabled(false);

        filterAction = new Action("Filter") {

            @Override
            public void run() {
                if (allowedComponentNames == null) {
                    List<String> currentList = new ArrayList<String>();
                    for (TimelineComponentRow currentRow : rows) {
                        currentList.add(currentRow.getName());
                    }
                    allowedComponentNames = currentList
                        .toArray(new String[currentList.size()]);
                }
                TimelineFilterDialog dialog = new TimelineFilterDialog(
                    Display.getCurrent().getActiveShell(),
                    allowedComponentNames, rows);
                dialog.create();
                dialog.updateContent();
                if (dialog.open() == 0) { // 0 = ok
                    allowedComponentNames = dialog.getFilteredNames();
                    showComponentRows(rows);
                }

            }
        };
        filterAction.setImageDescriptor(ImageDescriptor
            .createFromURL(TimelineView.class
                .getResource("/resources/icons/filter.gif")));
        filterAction.setEnabled(false);
    }

    protected void enableZoomActions() {
        zoomOutAction.setEnabled(currentZoomValue != defaultMaximumZoomValue);
        zoomInAction.setEnabled(currentZoomValue != 0);
    }

    private void contributeToActionBars() {
        IActionBars bars = getViewSite().getActionBars();
        IToolBarManager manager = bars.getToolBarManager();

        manager.add(refreshAction);
        manager.add(new Separator());
        manager.add(zoomInAction);
        manager.add(zoomOutAction);
        manager.add(new Separator());
        manager.add(filterAction);
        manager.add(new Separator());
    }

    /**
     * Get an component Icon by its component type Id.
     * 
     * @param identifier the id
     * @param caller the caller
     * @return the image
     */
    public static Image getImageIconFromId(String identifier, Object caller) {
        ServiceRegistryAccess serviceRegistryAccess = ServiceRegistry
            .createAccessFor(caller);
        DistributedComponentKnowledgeService componentKnowledgeService = serviceRegistryAccess
            .getService(DistributedComponentKnowledgeService.class);
        Collection<DistributedComponentEntry> installations = componentKnowledgeService
            .getCurrentSnapshot().getAllInstallations();
        for (DistributedComponentEntry entry : installations) {
            ComponentInstallation installation = entry.getComponentInstallation();
            if (installation.getInstallationId().startsWith(identifier)) {
                if (!COMPONENT_ICON_CACHE.containsKey(installation.getInstallationId())) {
                    byte[] icon = installation.getComponentInterface().getIcon16();
                    if (icon != null) {
                        Image image = ImageDescriptor.createFromImage(new Image(Display.getCurrent(),
                            new ByteArrayInputStream(icon))).createImage();
                        COMPONENT_ICON_CACHE.put(installation.getInstallationId(), image);
                    } else {
                        COMPONENT_ICON_CACHE.put(installation.getInstallationId(),
                            ImageManager.getInstance().getSharedImage(StandardImages.RCE_LOGO_16));
                    }
                }
                return COMPONENT_ICON_CACHE.get(installation.getInstallationId());
            }
        }
        return null;
    }

    /**
     * Get a component group Name by an id.
     * 
     * @param identifier the id
     * @param caller the caller
     * @return the display name
     */
    public static String getComponentNameFromId(String identifier, Object caller) {
        if (identifier == null) {
            return null;
        }
        ServiceRegistryAccess serviceRegistryAccess = ServiceRegistry
            .createAccessFor(caller);
        DistributedComponentKnowledgeService componentKnowledgeService = serviceRegistryAccess
            .getService(DistributedComponentKnowledgeService.class);
        Collection<DistributedComponentEntry> installations = componentKnowledgeService
            .getCurrentSnapshot().getAllInstallations();
        for (DistributedComponentEntry entry : installations) {
            // FIXME this is unsafe: e.g. when looking for "comp1", "comp11" could be matched!
            // TODO 9.0.0 provide a DistributedComponentKnowledge lookup method instead?
            ComponentInstallation installation = entry.getComponentInstallation();
            if (installation.getInstallationId().startsWith(identifier)) {
                return installation.getComponentInterface().getDisplayName();
            }
        }
        return null;
    }

    /**
     * Calculates the current zoom level.
     * 
     * @param zoom The current zoomfactor 10000 == 100%
     */
    public void setZoom(int zoom) {
        final int complete = 10000;
        Date vET = this.navigation.getVisibleEndTime();
        Date vST = this.navigation.getVisibleStartTime();
        long diffVSET = vET.getTime() - vST.getTime();
        double middleTimeStamp = vST.getTime() + (diffVSET / 2);

        long startTime = wfStartDate.getTime();
        long endTime = wfEndDate.getTime();

        double currentAddValue = (zoom * (wfEndDate.getTime() - wfStartDate.getTime())) / (2 * complete);
        Date newStartDate = new Date((long) (middleTimeStamp - currentAddValue));
        Date newEndDate = new Date((long) (middleTimeStamp + currentAddValue));

        long newStartTime = newStartDate.getTime();
        long newEndTime = newEndDate.getTime();

        // handle the newTime, if it reaches above or below the workflowTime
        if (newStartTime < startTime) {
            long overTime = startTime - newStartTime;
            newEndDate = new Date(newEndTime + overTime);
            newStartDate = wfStartDate;
        } else if (newEndTime > endTime) {
            long overTime = endTime - newEndTime;
            newStartDate = new Date(newStartTime + overTime);
            newEndDate = wfEndDate;
        }

        navigation.setVisibleArea(newStartDate, newEndDate);
        navigation.notifyAreaChangeListener();
    }

    /**
     * Converts a Pixel to a Date.
     * 
     * @param xPixel Contains the pixel which should be converted to Date
     * @param canvasSizeX Contains the size of the Canvas in the x direction or the possible maximum size of selection
     * @param currentWorkflowStartTime Contains the start time of the current workflow
     * @param currentWorkflowEndTime Contains the end time of the current worklfow
     * @return the converted Date
     */
    public static Date convertPixelToDate(int xPixel, int canvasSizeX,
        Date currentWorkflowStartTime, Date currentWorkflowEndTime) {
        long value = ((currentWorkflowEndTime.getTime() * xPixel) + (currentWorkflowStartTime
            .getTime() * (canvasSizeX - xPixel))) / canvasSizeX;
        Date date = new Date();
        date.setTime(value);
        return date;
    }

    /**
     * 
     * Converts a Date to a pixel.
     * 
     * @param date the date
     * @param canvasSizeX the canvas size
     * @param currentWorkflowStartTime the current workflowstarttime
     * @param currentWorkflowEndTime the current workflowendtime
     * @return the return
     */
    public static int convertDateToPixel(Date date, int canvasSizeX,
        Date currentWorkflowStartTime, Date currentWorkflowEndTime) {
        long value = date.getTime();
        int xPixel = (int) (((value - currentWorkflowStartTime.getTime()) * canvasSizeX) / (currentWorkflowEndTime
            .getTime() - currentWorkflowStartTime.getTime()));
        return xPixel;
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.eclipse.ui.part.WorkbenchPart#setFocus()
     */
    @Override
    public void setFocus() {

    }

    @Override
    public void resized() {
        sashMiddle.setWeights(this.list.getWeights());
    }

    @Override
    public void controlMoved(ControlEvent arg0) {

    }

    @Override
    public void controlResized(ControlEvent arg0) {
        this.list.setWeights(sashMiddle.getWeights());
    }

    @Override
    public void widgetDefaultSelected(SelectionEvent arg0) {

    }

    @Override
    public void widgetSelected(SelectionEvent arg0) {
        int begin = scrollBar.getMinimum() + scrollBar.getSelection();
        int end = scrollBar.getMinimum() + scrollBar.getSelection()
            + scrollBar.getThumb();
        setVisibleArea(
            convertPixelToDate(begin, scrollBar.getMaximum(), wfStartDate,
                wfEndDate),
            convertPixelToDate(end, scrollBar.getMaximum(), wfStartDate,
                wfEndDate));
    }

    @Override
    public void selectedAreaChanged(Date selectedStartTime, Date selectedEndTime) {
        final int percent = 100;
        this.list.setTimeArea(selectedStartTime, selectedEndTime);
        long selectedTime = selectedEndTime.getTime() - selectedStartTime.getTime();
        long wfTime = wfEndDate.getTime() - wfStartDate.getTime();
        float dividedTime = ((float) selectedTime / wfTime) * percent;
        currentZoomValue = Math.round(dividedTime) * percent;
        // SWT Slider does not always disable slider if value == maximum value
        this.scrollBar.setEnabled(currentZoomValue < scrollBar.getMaximum());

        if (currentZoomValue != 0) {
            this.scrollBar.setThumb(currentZoomValue);
        } else {
            this.scrollBar.setThumb(defaultMinimumZoomValue);
        }
        int beginSelection = convertDateToPixel(selectedStartTime,
            this.scrollBar.getMaximum(), wfStartDate, wfEndDate)
            - scrollBar.getMinimum();
        this.scrollBar.setSelection(beginSelection);
        enableZoomActions();
    }

}
