/*******************************************************************************
 * Copyright (c) 2009, 2018 Red Hat, Inc.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Red Hat - initial API and implementation
 *******************************************************************************/

package org.eclipse.linuxtools.internal.callgraph;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.draw2d.LightweightSystem;
import org.eclipse.draw2d.parts.ScrollableThumbnail;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.linuxtools.internal.callgraph.core.PluginConstants;
import org.eclipse.linuxtools.internal.callgraph.core.SystemTapParser;
import org.eclipse.linuxtools.internal.callgraph.core.SystemTapUIErrorMessages;
import org.eclipse.linuxtools.internal.callgraph.core.SystemTapView;
import org.eclipse.linuxtools.internal.callgraph.graphlisteners.AutoScrollSelectionListener;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Spinner;
import org.eclipse.ui.plugin.AbstractUIPlugin;

/**
 *    The SystemTap View for displaying output of the 'stap' command, and acts
 *    as a container for any graph to be rendered. Any buttons/controls/actions
 *    necessary to the smooth running of SystemTap could be placed here.
 */
public class CallgraphView extends SystemTapView {

    private StapGraphParser parser;

    private Action viewTreeview;
    private Action viewRadialview;
    private Action viewAggregateview;
    private Action viewLevelview;
    private Action viewRefresh;
    private Action animationSlow;
    private Action animationFast;
    private Action modeCollapsedNodes;
    private Action markersNext;
    private Action markersPrevious;
    private Action limits;
    private Action gotoNext;
    private Action gotoPrevious;
    private Action gotoLast;
    private Action play;
    private Action saveDot;
    private Action saveColDot;
    private Action saveCurDot;
    private Action saveText;
    private ImageDescriptor playImage = getImageDescriptor("icons/perform.png"); //$NON-NLS-1$
    private ImageDescriptor pauseImage = getImageDescriptor("icons/pause.gif"); //$NON-NLS-1$

    private Composite graphComp;
    private Composite treeComp;

    private StapGraph g;
    private static final int TREE_SIZE = 200;

    /**
     * Initializes the view by creating composites (if necessary) and canvases
     * Calls loadData(), and calls finishLoad() if not in realTime mode (otherwise
     * it is up to the user-defined update methods to finish loading).
     *
     * @return status
     *
     */
    @Override
    public IStatus initializeView(Display targetDisplay, IProgressMonitor monitor) {

        if (targetDisplay == null && Display.getCurrent() == null) {
            Display.getDefault();
        }

        makeTreeComp();
        makeGraphComp();
        graphComp.setBackgroundMode(SWT.INHERIT_FORCE);

        //Create papa canvas
        Canvas papaCanvas = new Canvas(graphComp, SWT.BORDER);
        GridLayout papaLayout = new GridLayout(1, true);
        papaLayout.horizontalSpacing=0;
        papaLayout.verticalSpacing=0;
        papaLayout.marginHeight=0;
        papaLayout.marginWidth=0;
        papaCanvas.setLayout(papaLayout);
        GridData papaGD = new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false);
        papaGD.widthHint=160;
        papaCanvas.setLayoutData(papaGD);

        //Add first button
        Image image = getImageDescriptor("icons/up.gif").createImage(); //$NON-NLS-1$
        Button up = new Button(papaCanvas, SWT.PUSH);
        GridData buttonData = new GridData(SWT.CENTER, SWT.CENTER, true, false);
        buttonData.widthHint = 150;
        buttonData.heightHint = 20;
        up.setData(buttonData);
        up.setImage(image);
        up.setToolTipText(Messages.getString("CallgraphView.ThumbNailUp")); //$NON-NLS-1$

        //Add thumb canvas
        Canvas thumbCanvas = new Canvas(papaCanvas, SWT.NONE);

        //Add second button
        image = getImageDescriptor("icons/down.gif").createImage(); //$NON-NLS-1$
        Button down = new Button(papaCanvas, SWT.PUSH);
        buttonData = new GridData(SWT.CENTER, SWT.CENTER, true, false);
        buttonData.widthHint = 150;
        buttonData.heightHint = 0;
        down.setData(buttonData);
        down.setImage(image);
        down.setToolTipText(Messages.getString("CallgraphView.ThumbNailDown")); //$NON-NLS-1$

        //Initialize graph
        g = new StapGraph(graphComp, SWT.BORDER, treeComp, papaCanvas, this);
        g.setLayoutData(new GridData(masterComposite.getBounds().width,Display.getCurrent().getBounds().height - TREE_SIZE));

        up.addSelectionListener(new AutoScrollSelectionListener(
                AutoScrollSelectionListener.AUTO_SCROLL_UP, g));
        down.addSelectionListener(new AutoScrollSelectionListener(
                AutoScrollSelectionListener.AUTO_SCROLL_DOWN, g));

        //Initialize thumbnail
        GridData thumbGD = new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false);
        thumbGD.widthHint=160;
        thumbCanvas.setLayoutData(thumbGD);
        LightweightSystem lws = new LightweightSystem(thumbCanvas);
        ScrollableThumbnail thumb = new ScrollableThumbnail(g.getViewport());
        thumb.setSource(g.getContents());
        lws.setContents(thumb);

        loadData(monitor);
        return finishLoad(monitor);
    }

    /**
     * Load data.
     * @param mon -- Progress monitor.
     * @return
     */
    private IStatus loadData(IProgressMonitor mon) {
        IProgressMonitor monitor = mon;
        //Dummy node, set start time
        if (g.getNodeData(0) == null) {
            g.loadData(SWT.NONE, 0, StapGraph.CONSTANT_TOP_NODE_NAME,
                    1, 1, -1, false, ""); //$NON-NLS-1$
        }
        g.setStartTime(parser.startTime);
        g.setEndTime(parser.endingTimeInNS);


        /*
         * Load graph data
         */
        for (int id_parent : parser.serialMap.keySet()) {
            if (id_parent < 0) {
                continue;
            }
            boolean marked = false;
            String msg = ""; //$NON-NLS-1$
            if (g.getNodeData(id_parent) == null) {
                if (parser.markedMap.get(id_parent) != null) {
                    marked = true;
                    msg = parser.markedMap.remove(id_parent);
                }
                g.loadData(SWT.NONE, id_parent, parser.serialMap.get(id_parent), parser.timeMap.get(id_parent),
                        1, 0, marked, msg);
            }

            for (int key :parser.neighbourMaps.keySet()) {
                HashMap<Integer, ArrayList<Integer>> outNeighbours = parser.neighbourMaps.get(key);
                if (outNeighbours == null || outNeighbours.get(id_parent) == null) {
                    continue;
                }
                for (int id_child : outNeighbours.get(id_parent)) {
                    if (g.getNodeData(id_child) != null && id_child < 0) {
                        //Assume this is an additional call of the same node
                        //Should only happen in dot-files!!
                        g.addCalled(id_child);
                        continue;
                    } else if (g.getNodeData(id_child) != null) {
                        continue;
                    }
                    if (monitor.isCanceled()) {
                        return Status.CANCEL_STATUS;
                    }

                    marked = false;
                    msg = ""; //$NON-NLS-1$
                    if (parser.markedMap.get(id_child) != null) {
                        marked = true;
                        msg = parser.markedMap.remove(id_child);
                    }
                    if (id_child != -1) {
                        if (parser.timeMap.get(id_child) == null){
                            g.loadData(SWT.NONE, id_child, parser.serialMap
                                    .get(id_child), parser.timeMap.get(0),
                                    1, id_parent, marked,msg);
                        }else{
                            g.loadData(SWT.NONE, id_child, parser.serialMap
                                    .get(id_child), parser.timeMap.get(id_child),
                                    1, id_parent, marked,msg);
                        }
                    }
                }
            }

            if (parser.neighbourMaps.size() > 1) {
                g.setThreaded();
            }
        }

        monitor.worked(1);
        if (parser.markedMap.size() > 0) {
            //Still some markers left
            for (int key : parser.markedMap.keySet()) {
                g.insertMessage(key, parser.markedMap.get(key));
            }

            //Erase the remaining nodes, just in case
            parser.markedMap.clear();
        }


        if (g.aggregateTime == null) {
            g.aggregateTime = new HashMap<>();
        }
        if (g.aggregateCount == null) {
            g.aggregateCount = new HashMap<>();
        }

        g.aggregateCount.putAll(parser.countMap);
        g.aggregateTime.putAll(parser.aggregateTimeMap);
        //TODO: Do not set to 0.
        g.setLastFunctionCalled(0);


        //Finish off by collapsing nodes, initializing the tree and setting options
        g.recursivelyCollapseAllChildrenOfNode(g.getTopNode());
        monitor.worked(1);
        setGraphOptions(true);
        g.initializeTree();
        g.setProject(parser.project);


        return Status.OK_STATUS;
    }

    /**
     * Completes the loading process by calculating aggregate data.
     *
     * @param monitor
     * @return
     */
    private IStatus finishLoad(IProgressMonitor monitor) {

        if (g.aggregateCount == null) {
            g.aggregateCount = new HashMap<>();
        }

        g.aggregateCount.putAll(parser.countMap);

        if (g.aggregateTime == null) {
            g.aggregateTime = new HashMap<>();
        }
        g.aggregateTime.putAll(parser.aggregateTimeMap);

        //Set total time
        if (parser.totalTime != -1) {
            g.setTotalTime(parser.totalTime);
        }

        //-------------Finish initializations
        //Generate data for collapsed nodes
        if (monitor.isCanceled()) {
            return Status.CANCEL_STATUS;
        }
        g.initializeTree();


        if (monitor.isCanceled()) {
            return Status.CANCEL_STATUS;
        }
        g.setCallOrderList(parser.callOrderList);
        g.setProject(parser.project);


        this.initializePartControl();
        return Status.OK_STATUS;
    }


    /**
     * Enable or Disable the graph options
     * @param visible
     */
    private void setGraphOptions (boolean visible){
        play.setEnabled(visible);
        saveFile.setEnabled(visible);
        saveDot.setEnabled(visible);
        saveColDot.setEnabled(visible);
        saveCurDot.setEnabled(visible);
        saveText.setEnabled(visible);

        viewTreeview.setEnabled(visible);
        viewRadialview.setEnabled(visible);
        viewAggregateview.setEnabled(visible);
        viewLevelview.setEnabled(visible);
        viewRefresh.setEnabled(visible);
        limits.setEnabled(visible);

        markersNext.setEnabled(visible);
        markersPrevious.setEnabled(visible);

        animationSlow.setEnabled(visible);
        animationFast.setEnabled(visible);
        modeCollapsedNodes.setEnabled(visible);

        gotoNext.setEnabled(visible);
        gotoPrevious.setEnabled(visible);
        gotoLast.setEnabled(visible);
    }



    private void makeTreeComp() {
        if (treeComp != null && !treeComp.isDisposed()) {
            treeComp.dispose();
        }

        treeComp = new Composite(this.masterComposite, SWT.NONE);
        GridData treegd = new GridData(SWT.BEGINNING, SWT.FILL, false, true);
        treegd.widthHint = TREE_SIZE;
        treeComp.setLayout(new FillLayout());
        treeComp.setLayoutData(treegd);
    }

    private void makeGraphComp() {
        if (graphComp != null && !graphComp.isDisposed()) {
            graphComp.dispose();
        }
        graphComp = new Composite(this.masterComposite, SWT.NONE);
        GridData graphgd = new GridData(SWT.FILL, SWT.FILL, true, true);
        GridLayout gl = new GridLayout(2, false);
        gl.horizontalSpacing=0;
        gl.verticalSpacing=0;

        graphComp.setLayout(gl);
        graphComp.setLayoutData(graphgd);
    }


    /**
     * This must be executed before a Graph is displayed
     */
    private void initializePartControl(){
        setGraphOptions(true);
        if (graphComp == null) {
            return;
        }
        graphComp.setParent(masterComposite);

        if (treeComp != null) {
            treeComp.setParent(masterComposite);
        }

        graphComp.setSize(masterComposite.getSize().x ,masterComposite.getSize().y);
    }

    /**
     * The action performed by saveText.
     */
    private void saveTextAction() {
        //Prints an 80 char table
        Shell sh = new Shell();
        FileDialog dialog = new FileDialog(sh, SWT.SAVE);
        String filePath = dialog.open();

        if (filePath == null) {
            return;
        }
        File f = new File(filePath);
        f.delete();
        try (BufferedWriter out = new BufferedWriter(new FileWriter(f))) {
            f.createNewFile();
            StringBuilder builder = new StringBuilder();
            builder.append("                           Function                           | Called |  Time\n"); //$NON-NLS-1$

            for (StapData k : g.nodeDataMap.values()) {
                if ( (!k.isCollapsed ) && !k.isOnlyChildWithThisName()) {
                    continue;
                }
                if (k.isCollapsed) {
                    StringBuilder name = new StringBuilder(k.name);
                    name = fixString(name, 60);
                    builder.append(" " + name + " | "); //$NON-NLS-1$ //$NON-NLS-2$

                    StringBuilder called = new StringBuilder("" + k.timesCalled); //$NON-NLS-1$
                    called = fixString(called, 6);

                    StringBuilder time = new StringBuilder("" + //$NON-NLS-1$
                            StapNode.numberFormat.format((float) k.getTime()/g.getTotalTime() * 100)
                            + "%"); //$NON-NLS-1$
                    time = fixString(time, 6);

                    builder.append(called + " | " + time + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
                }

                if (builder.length() > 0) {
                    out.append(builder.toString());
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * This is a callback that will allow us to create the viewer and
     * initialize it.
     */
    @Override
    public void createPartControl(Composite parent) {
        if (masterComposite != null) {
            masterComposite.dispose();
        }
        masterComposite = parent;
        GridLayout layout = new GridLayout(2, false);
        layout.horizontalSpacing=0;
        GridData gd = new GridData(100, 100);

        parent.setLayout(layout);
        parent.setLayoutData(gd);

        // LOAD ALL ACTIONS
        createActions();

        //MENU FOR SYSTEMTAP BUTTONS
        IToolBarManager mgr = getViewSite().getActionBars().getToolBarManager();


        //MENU FOR SYSTEMTAP GRAPH OPTIONS
        IMenuManager menu = getViewSite().getActionBars().getMenuManager();

        // ADD OPTIONS TO THE GRAPH MENU
        addFileMenu();

        saveCurDot = new Action(Messages.getString("CallgraphView.SaveViewAsDot")) { //$NON-NLS-1$
            @Override
            public void run(){
                writeToDot(g.getCollapseMode(), g.nodeMap.keySet());
            }

        };
        saveDot = new Action(Messages.getString("CallgraphView.SaveAllUncollapsedAsDot")) { //$NON-NLS-1$
            @Override
            public void run(){
              writeToDot(false, g.nodeDataMap.keySet());
            }
        };

        saveColDot = new Action (Messages.getString("CallgraphView.SaveAllCollapsedAsDot")) { //$NON-NLS-1$
             @Override
            public void run(){
                    writeToDot(true, g.nodeDataMap.keySet());
                }

        };

        saveText = new Action (Messages.getString("CallgraphView.SaveCollapsedAsASCII")) { //$NON-NLS-1$
            @Override
            public void run() {
                saveTextAction();
            }
        };
        IMenuManager saveMenu = new MenuManager(Messages.getString("CallgraphView.SaveMenu")); //$NON-NLS-1$
        file.add(saveMenu);
        saveMenu.add(saveCurDot);
        saveMenu.add(saveColDot);
        saveMenu.add(saveText);
        saveMenu.add(saveDot);
        IMenuManager view = new MenuManager(Messages.getString("CallgraphView.ViewMenu")); //$NON-NLS-1$
        IMenuManager animation = new MenuManager(Messages.getString("CallgraphView.AnimationMenu")); //$NON-NLS-1$
        IMenuManager markers = new MenuManager(Messages.getString("CallgraphView.Markers")); //$NON-NLS-1$
        IMenuManager gotoMenu = new MenuManager(Messages.getString("CallgraphView.GoTo")); //$NON-NLS-1$
        menu.add(view);
        menu.add(gotoMenu);
        addHelpMenu();

        view.add(viewTreeview);
        view.add(viewRadialview);
        view.add(viewAggregateview);
        view.add(viewLevelview);
        view.add(getViewRefresh());
        view.add(modeCollapsedNodes);
        view.add(limits);
        view.add(animation);


        gotoMenu.add(play);
        gotoMenu.add(gotoPrevious);
        gotoMenu.add(gotoNext);
        gotoMenu.add(gotoLast);
        gotoMenu.add(markers);

        addKillButton();
        mgr.add(play);
        mgr.add(viewRadialview);
        mgr.add(viewTreeview);
        mgr.add(viewLevelview);
        mgr.add(viewAggregateview);
        mgr.add(modeCollapsedNodes);

        markers.add(markersNext);
        markers.add(markersPrevious);

        animation.add(animationSlow);
        animation.add(animationFast);

        setGraphOptions(false);
    }


    private static StringBuilder fixString(StringBuilder name, int length) {
        if (name.length() > length) {
            name = new StringBuilder(name.substring(0, length - 1));
        } else {
            int diff = length - name.length();
            boolean left = true;
            while (diff > 0) {
                if (left) {
                    name.insert(0, " "); //$NON-NLS-1$
                    left = false;
                } else {
                    name.append(" "); //$NON-NLS-1$
                    left = true;
                }
                diff--;
            }
        }
        return name;
    }


    private void createViewActions() {
        viewTreeview = new Action(Messages.getString("CallgraphView.TreeView")){ //$NON-NLS-1$
            @Override
            public void run() {
                g.draw(StapGraph.CONSTANT_DRAWMODE_TREE,
                        g.getAnimationMode(), g.getRootVisibleNodeNumber());
                g.scrollTo(g.getNode(g.getRootVisibleNodeNumber())
                        .getLocation().x - g.getBounds().width / 2, g
                        .getNode(g.getRootVisibleNodeNumber())
                        .getLocation().y);
                if (play != null) {
                    play.setEnabled(true);
                }
            }
        };
        ImageDescriptor treeImage = getImageDescriptor("icons/tree_view.gif"); //$NON-NLS-1$
        viewTreeview.setImageDescriptor(treeImage);


        //Set drawmode to radial view
        viewRadialview = new Action(Messages.getString("CallgraphView.RadialView")){ //$NON-NLS-1$
            @Override
            public void run(){
                g.draw(StapGraph.CONSTANT_DRAWMODE_RADIAL,
                        g.getAnimationMode(), g.getRootVisibleNodeNumber());
                if (play != null) {
                    play.setEnabled(true);
                }
            }
        };
        ImageDescriptor d = getImageDescriptor("/icons/radial_view.gif"); //$NON-NLS-1$
        viewRadialview.setImageDescriptor(d);

        //Set drawmode to aggregate view
        viewAggregateview = new Action(Messages.getString("CallgraphView.AggregateView")){ //$NON-NLS-1$
            @Override
            public void run(){
                g.draw(StapGraph.CONSTANT_DRAWMODE_AGGREGATE,
                        g.getAnimationMode(), g.getRootVisibleNodeNumber());
                if (play != null) {
                    play.setEnabled(false);
                }
            }
        };
        ImageDescriptor aggregateImage = getImageDescriptor("/icons/view_aggregateview.gif"); //$NON-NLS-1$
        viewAggregateview.setImageDescriptor(aggregateImage);


        //Set drawmode to level view
        viewLevelview = new Action(Messages.getString("CallgraphView.LevelView")){ //$NON-NLS-1$
            @Override
            public void run(){
                g.draw(StapGraph.CONSTANT_DRAWMODE_LEVEL,
                        g.getAnimationMode(), g.getRootVisibleNodeNumber());
                if (play != null) {
                    play.setEnabled(true);
                }
            }
        };
        ImageDescriptor levelImage = getImageDescriptor("/icons/showchild_mode.gif"); //$NON-NLS-1$
        viewLevelview.setImageDescriptor(levelImage);


        this.viewRefresh = new Action(Messages.getString("CallgraphView.Reset")){ //$NON-NLS-1$
            @Override
            public void run(){
                g.reset();
            }
        };
        ImageDescriptor refreshImage = getImageDescriptor("/icons/nav_refresh.gif"); //$NON-NLS-1$
        getViewRefresh().setImageDescriptor(refreshImage);

    }

    /**
     * Populates Animate menu.
     */
    private void createAnimateActions() {
        //Set animation mode to slow
        animationSlow = new Action(Messages.getString("CallgraphView.AnimationSlow"), IAction.AS_RADIO_BUTTON){ //$NON-NLS-1$
            @Override
            public void run(){
                g.setAnimationMode(StapGraph.CONSTANT_ANIMATION_SLOW);
                this.setChecked(true);
                animationSlow.setChecked(true);
                animationFast.setChecked(false);
            }
        };

        animationSlow.setChecked(true);

        //Set animation mode to fast
        animationFast = new Action(Messages.getString("CallgraphView.AnimationFast"), IAction.AS_RADIO_BUTTON){ //$NON-NLS-1$
            @Override
            public void run(){
                g.setAnimationMode(StapGraph.CONSTANT_ANIMATION_FASTEST);
                animationSlow.setChecked(false);
                animationFast.setChecked(true);
            }
        };

        //Toggle collapse mode
        modeCollapsedNodes = new Action(Messages.getString("CallgraphView.CollapsedMode"), IAction.AS_CHECK_BOX){ //$NON-NLS-1$
            @Override
            public void run(){

                if (g.isCollapseMode()) {
                    g.setCollapseMode(false);
                    g.draw(g.getRootVisibleNodeNumber());
                } else {
                    g.setCollapseMode(true);
                    g.draw(g.getRootVisibleNodeNumber());
                }
            }
        };

        ImageDescriptor newImage = getImageDescriptor("icons/mode_collapsednodes.gif"); //$NON-NLS-1$
        modeCollapsedNodes.setImageDescriptor(newImage);

        limits = new Action(Messages.getString("CallgraphView.SetLimits"), IAction.AS_PUSH_BUTTON) { //$NON-NLS-1$
            private Spinner limit;
            private Spinner buffer;
            private Shell sh;
            @Override
            public void run() {
                sh = new Shell();
                sh.setLayout(new GridLayout());
                sh.setSize(150, 200);
                Label limitLabel = new Label(sh, SWT.NONE);
                limitLabel.setLayoutData(new GridData(SWT.CENTER, SWT.DEFAULT, true, false));
                limitLabel.setText(Messages.getString("CallgraphView.MaxNodes")); //$NON-NLS-1$
                limit = new Spinner(sh, SWT.BORDER);
                limit.setMaximum(5000);
                limit.setSelection(g.getMaxNodes());
                limit.setLayoutData(new GridData(SWT.CENTER, SWT.DEFAULT, true, false));

                Label bufferLabel = new Label(sh, SWT.NONE);
                bufferLabel.setLayoutData(new GridData(SWT.CENTER, SWT.DEFAULT, true, false));
                bufferLabel.setText(Messages.getString("CallgraphView.MaxDepth")); //$NON-NLS-1$
                buffer = new Spinner(sh, SWT.BORDER);
                buffer.setMaximum(5000);
                buffer.setSelection(g.getLevelBuffer());
                buffer.setLayoutData(new GridData(SWT.CENTER, SWT.DEFAULT, true, false));

                Button setLimit = new Button(sh, SWT.PUSH);
                setLimit.setText(Messages.getString("CallgraphView.SetValues")); //$NON-NLS-1$
                setLimit.setLayoutData(new GridData(SWT.CENTER, SWT.DEFAULT, true, false));
				setLimit.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
					boolean redraw = false;
					if (limit.getSelection() >= 0 && buffer.getSelection() >= 0) {
						g.setMaxNodes(limit.getSelection());
						g.setLevelBuffer(buffer.getSelection());

						if (g.changeLevelLimits(g.getLevelOfNode(g.getRootVisibleNodeNumber()))) {
							SystemTapUIErrorMessages mess = new SystemTapUIErrorMessages(
									Messages.getString("CallgraphView.BufferTooHigh"), //$NON-NLS-1$
									Messages.getString("CallgraphView.BufferTooHigh"), //$NON-NLS-1$
									Messages.getString("CallgraphView.BufferMessage1") + //$NON-NLS-1$
							Messages.getString("CallgraphView.BufferMessage2") + //$NON-NLS-1$
							Messages.getString("CallgraphView.BufferMessage3") + //$NON-NLS-1$
							Messages.getString("CallgraphView.BufferMessage4") + g.getLevelBuffer() + //$NON-NLS-1$
							Messages.getString("CallgraphView.BufferMessage5") + PluginConstants.NEW_LINE //$NON-NLS-1$
											+ PluginConstants.NEW_LINE + Messages.getString("CallgraphView.BufferMessage6") + //$NON-NLS-1$
							Messages.getString("CallgraphView.BufferMessage7")); //$NON-NLS-1$
							mess.schedule();
						}

						redraw = true;
					}
					sh.dispose();

					if (redraw) {
						g.draw();
					}
				}));


                sh.open();            }
        };

    }

/**
 * Convenience method for creating all the various actions
 */
    private void createActions() {
        createViewActions();
        createAnimateActions();
        createMarkerActions();
        createMovementActions();

        modeCollapsedNodes.setChecked(true);

    }

    private void createMovementActions() {
        gotoNext = new Action(Messages.getString("CallgraphView.Next")) { //$NON-NLS-1$
            @Override
            public void run() {
                g.drawNextNode();
            }
        };

        gotoPrevious = new Action(Messages.getString("CallgraphView.Previous")) { //$NON-NLS-1$
            @Override
            public void run() {
                if (g.isCollapseMode()) {
                    g.setCollapseMode(false);
                }
                int toDraw = g.getPreviousCalledNode(g.getRootVisibleNodeNumber());
                if (toDraw != -1)
                    g.draw(toDraw);
            }
        };

        gotoLast = new Action(Messages.getString("CallgraphView.Last")) { //$NON-NLS-1$
            @Override
            public void run() {
                if (g.isCollapseMode())
                    g.setCollapseMode(false);
                g.draw(g.getLastFunctionCalled());
            }
        };

        play = new Action(Messages.getString("CallgraphView.Play")) { //$NON-NLS-1$
            @Override
            public void run() {
                if (g.getDrawMode() != StapGraph.CONSTANT_DRAWMODE_AGGREGATE) {
                    g.play();
                    togglePlayImage();
                }
            }
        };
        play.setImageDescriptor(playImage);
    }

    /**
     * Toggles the play/pause image
     * @param play
     */
    private void togglePlayImage() {
        if (play.getToolTipText() == Messages.getString("CallgraphView.Pause")) { //$NON-NLS-1$
            play.setImageDescriptor(playImage);
            play.setToolTipText(Messages.getString("CallgraphView.Play")); //$NON-NLS-1$
        }
        else {
            play.setImageDescriptor(pauseImage);
            play.setToolTipText(""); //$NON-NLS-1$
        }
    }

    private void createMarkerActions() {
        markersNext = new Action(Messages.getString("CallgraphView.nextMarker")) { //$NON-NLS-1$
            @Override
            public void run() {
                g.draw(g.getNextMarkedNode());
            }
        };

        markersPrevious = new Action(Messages.getString("CallgraphView.previousMarker")) { //$NON-NLS-1$
            @Override
            public void run() {
                g.draw(g.getPreviousMarkedNode());
            }
        };
    }

    @Override
    protected boolean createOpenAction() {
        //Opens from specified location
        openFile = new Action(Messages.getString("CallgraphView.Open")){ //$NON-NLS-1$
            @Override
            public void run(){
                FileDialog dialog = new FileDialog(new Shell(), SWT.DEFAULT);
                String filePath =  dialog.open();
                if (filePath != null){
                    StapGraphParser new_parser = new StapGraphParser();
                    new_parser.setSourcePath(filePath);
                        new_parser.setViewID(CallGraphConstants.VIEW_ID);
                    new_parser.schedule();
                }
            }
        };
        return true;
    }


    @Override
    protected boolean createOpenDefaultAction() {
        //Opens from the default location
        openDefault = new Action(Messages.getString("CallgraphView.OpenLastRun")){ //$NON-NLS-1$
            @Override
            public void run(){
                StapGraphParser new_parser = new StapGraphParser();
                new_parser.setViewID(CallGraphConstants.VIEW_ID);
                new_parser.schedule();
            }
        };

        return true;
    }

    @Override
    public boolean setParser(SystemTapParser newParser) {
        if (newParser instanceof StapGraphParser) {
            parser = (StapGraphParser) newParser;
            return true;
        }
        return false;

    }

    @Override
    public void setViewID() {
        viewID = "org.eclipse.linuxtools.callgraph.callgraphview";         //$NON-NLS-1$
    }

    public  Action getAnimationSlow() {
        return animationSlow;
    }

    public  Action getAnimationFast() {
        return animationFast;
    }

    public  Action getModeCollapsednodes() {
        return modeCollapsedNodes;
    }

    public  Action getViewRefresh() {
        return viewRefresh;
    }

    public  Action getGotoNext() {
        return gotoNext;
    }

    public  Action getGotoPrevious() {
        return gotoPrevious;
    }

    public  Action getGotoLast() {
        return gotoLast;
    }

    public  Action getViewTreeview() {
        return viewTreeview;
    }

    public  Action getViewRadialview() {
        return viewRadialview;
    }

    public  Action getViewAggregateview() {
        return viewAggregateview;
    }

    public  Action getViewLevelview() {
        return viewLevelview;
    }

    public Action getPlay() {
        return play;
    }

    public StapGraph getGraph() {
        return g;
    }

    @Override
    public void setFocus() {
        if(masterComposite != null){
            masterComposite.setFocus();
        }
    }


    @Override
    public void updateMethod() {
        IProgressMonitor m = new NullProgressMonitor();
        m.beginTask("Updating callgraph", 4); //$NON-NLS-1$

        loadData(m);
        m.worked(1);
        if (parser.totalTime > 0) {
            finishLoad(m);
        }
        m.worked(1);

        g.draw(StapGraph.CONSTANT_DRAWMODE_RADIAL, StapGraph.CONSTANT_ANIMATION_SLOW, g.getFirstUsefulNode());
    }

    @Override
    public SystemTapParser getParser() {
        return parser;
    }

    private void writeToDot(boolean mode, Set<Integer> keySet) {
        Shell sh = new Shell();
        FileDialog dialog = new FileDialog(sh, SWT.SAVE);

        String filePath = dialog.open();

        if (filePath != null) {
            File f = new File(filePath);
            f.delete();
            try {
                f.createNewFile();
            } catch (IOException e) {
                return;
            }

            try (BufferedWriter out = new BufferedWriter(new FileWriter(f))) {
                StringBuilder build = new StringBuilder(""); //$NON-NLS-1$

                out.write("digraph stapgraph {\n"); //$NON-NLS-1$
                for (int i : keySet) {
                    if (i == 0) {
                        continue;
                    }
                    StapData d = g.getNodeData(i);
                    if ( (d.isCollapsed != mode) && !d.isOnlyChildWithThisName()) {
                        continue;
                    }
                    build.append(i + " [label=\"" + d.name + " " ); //$NON-NLS-1$ //$NON-NLS-2$
                    build.append(StapNode.numberFormat.format((float) d.getTime()/g.getTotalTime() * 100) + "%\"]\n"); //$NON-NLS-1$
                    int j = d.parent;
                    if (mode) {
                        j = d.collapsedParent;
                    }

                    if (!keySet.contains(j) || j == 0) {
                        continue;
                    }

                    String called = mode ? " [label=\"" + g.getNodeData(i).timesCalled + "\"]\n" : "\n"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
                    build.append( j + "->" + i ); //$NON-NLS-1$
                    build.append( called );
                    out.write(build.toString());
                    build.setLength(0);
                }
                out.write("}"); //$NON-NLS-1$
                out.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static ImageDescriptor getImageDescriptor(String path) {
        return AbstractUIPlugin.imageDescriptorFromPlugin(CallGraphConstants.PLUGIN_ID, path);
    }


}