/*******************************************************************************
 * Copyright (c) 2012, 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.oprofile.launch.configuration;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.cdt.debug.core.ICDTLaunchConfigurationConstants;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.ui.AbstractLaunchConfigurationTab;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.ListViewer;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.jface.window.IShellProvider;
import org.eclipse.linuxtools.internal.oprofile.core.Oprofile.OprofileProject;
import org.eclipse.linuxtools.internal.oprofile.core.OprofileCorePlugin;
import org.eclipse.linuxtools.internal.oprofile.core.daemon.OpEvent;
import org.eclipse.linuxtools.internal.oprofile.core.daemon.OpUnitMask;
import org.eclipse.linuxtools.internal.oprofile.core.daemon.OprofileDaemonEvent;
import org.eclipse.linuxtools.internal.oprofile.launch.OprofileLaunchMessages;
import org.eclipse.linuxtools.internal.oprofile.launch.OprofileLaunchPlugin;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem;
import org.eclipse.swt.widgets.Text;

public abstract class AbstractEventConfigTab extends AbstractLaunchConfigurationTab {
    protected Button defaultEventCheck;
    protected OprofileCounter[] counters = null;
    protected CounterSubTab[] counterSubTabs;
    private Composite top;

    /**
     * Essentially the constructor for this tab; creates the 'default event'
     * checkbox and an appropriate number of counter tabs.
     * @param parent the parent composite
     */
    @Override
    public void createControl(Composite parent) {
        Composite top = new Composite(parent, SWT.NONE);
        setControl(top);
        top.setLayout(new GridLayout());
        this.top = top;
    }
    /**
     * @since 1.1
     * @param top
     */
    private void createCounterTabs(Composite top){
        //tabs for each of the counters
        counters = getOprofileCounters(null);
        TabItem[] counterTabs = new TabItem[counters.length];

        // create only one counter for operf/opcontrol
        if (counters.length > 0) {
        	counterSubTabs = new CounterSubTab[1];
        } else {
        	counterSubTabs = new CounterSubTab[0];
        }

        TabFolder tabFolder = new TabFolder(top, SWT.NONE);
        tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));


        // As per Roland suggestion if we decide to list all the selected events
        // in a separate list viewer, then it makes sense to always show just one tab.
        // This approach would make operf/opcontrol event selection more similar.
        for (int i = 0; i < counters.length; i++) {
            Composite c = new Composite(tabFolder, SWT.NONE);
            CounterSubTab currentTab = new CounterSubTab(c, counters[i]);
            counterSubTabs[i] = currentTab;

            counterTabs[i] = new TabItem(tabFolder, SWT.NONE);
            counterTabs[i].setControl(c);
            counterTabs[i].setText(OprofileLaunchMessages.getString("tab.event.counterTab.counterText")); //$NON-NLS-1$
            // just one tab for operf/opcontrol
            break;
        }

        getTabFolderComposite();
    }

    /**
     * @since 1.1
     */
    private Composite getTabFolderComposite(){
        // check for length and first tab being null to prevent AIOBE
        if(counterSubTabs.length == 0 ||counterSubTabs[0] == null){
            return null;
        } else {
            Composite c = counterSubTabs[0].getTabTopContainer();
            while(c != null && !(c instanceof TabFolder)){
                c = c.getParent();
            }
            return c.getParent();
        }
    }

    @Override
    public void initializeFrom(ILaunchConfiguration config) {

        IProject previousProject = getOprofileProject();
        IProject project = getProject(config);
        setOprofileProject(project);

        updateOprofileInfo();

        String previousHost = null;
        if(previousProject != null){
            if(previousProject.getLocationURI() != null){
                previousHost = previousProject.getLocationURI().getHost();
            }
        }

        String host;
        if (project != null) {
            host = project.getLocationURI().getHost();
        } else {
            host = null;
        }

        // Create the counter tabs if host has changed or if they haven't been created yet
        // Check that initialization is not done for current project.
        // Any calculation based on project doesn't work as the very first time for local project they are both null.
        if(previousProject == null || previousHost != host || host == null || counters == null){
            Control[] children = top.getChildren();

            for (Control control : children) {
                control.dispose();
            }

            OprofileCounter [] ctrs = getOprofileCounters(null);
            if (getOprofileTimerMode() || (ctrs.length > 0 && ctrs[0].getValidEvents() == null)) {
                Label timerModeLabel = new Label(top, SWT.LEFT);
                timerModeLabel.setText(OprofileLaunchMessages.getString("tab.event.timermode.no.options")); //$NON-NLS-1$
            } else {
                createVerticalSpacer(top, 1);

                //default event checkbox
                defaultEventCheck = new Button(top, SWT.CHECK);
                defaultEventCheck.setText(OprofileLaunchMessages.getString("tab.event.defaultevent.button.text")); //$NON-NLS-1$
                defaultEventCheck.setLayoutData(new GridData());
				defaultEventCheck
						.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> handleEnabledToggle()));
                createVerticalSpacer(top, 1);
                createCounterTabs(top);
            }

        }

        if(!getOprofileTimerMode()){
			if (counters == null) {
				OprofileCorePlugin.showErrorDialog("countersNotFound", null); //$NON-NLS-1$
				return;
			}
            for (int i = 0; i < counters.length; i++) {
                counters[i].loadConfiguration(config);
            }

            for (CounterSubTab tab : counterSubTabs) {
                tab.initializeTab(config);
                tab.createEventsFilter();
            }
            try{
                boolean enabledState = config.getAttribute(OprofileLaunchPlugin.ATTR_USE_DEFAULT_EVENT, true);
                defaultEventCheck.setSelection(enabledState);
                setEnabledState(!enabledState);
            } catch (CoreException e) {
                e.printStackTrace();
            }
        }

    }

    @Override
    public boolean isValid(ILaunchConfiguration config) {
        IProject project = getProject(config);
        setOprofileProject(project);

        OprofileCounter [] ctrs = getOprofileCounters(null);
        if (ctrs.length > 0 && ctrs[0].getValidEvents() == null) {
            return false;
        }
        if (getOprofileTimerMode() || counterSubTabs == null) {
            return true;        //no options to check for validity
        } else {
            return validateEvents(config);
        }
    }

    /**
     * Validate events specified in the given configuration.
     * @param config
     * @return
     */
    private boolean validateEvents(ILaunchConfiguration config) {
        int numEnabledEvents = 0;
        boolean valid = true;

        try {
            if (config.getAttribute(OprofileLaunchPlugin.ATTR_USE_DEFAULT_EVENT, false)) {
                numEnabledEvents = 1;
            } else {
                //This seems like an odd way to validate, but since most of the validation
                // is done with the OprofileDaemonEvent that the counter wraps, this
                // is the easiest way.
                OprofileCounter[] counters = new OprofileCounter[getNumberOfOprofileCounters()];
                for (int i = 0; i < counters.length; i++) {
                    counters[i] = getOprofileCounter(i);
                    counters[i].loadConfiguration(config);

                    for (CounterSubTab counterSubTab : counterSubTabs){
                        int nr = counterSubTab.counter.getNumber();
                        if(counterSubTab.enabledCheck.getSelection() && config.getAttribute(OprofileLaunchPlugin.attrNumberOfEvents(nr), 0) == 0){
                            valid = false;
                        }
                        // if target list is empty valid is false
                        // target event list item count
                        int count = counterSubTab.selectedEventList.getList().getItemCount();
                        if(count == 0)
                        {
                            valid = false;
                        }
                    }

                    if (counters[i].getEnabled()) {
                        ++numEnabledEvents;

                        for (OpEvent event : counters[i].getEvents()) {
                            if (event == null) {
                                valid = false;
                                break;
                            }

                            // First check min count
                            int min = event.getMinCount();
                            if (counters[i].getCount() < min) {
								setErrorMessage(MessageFormat.format(
										OprofileLaunchMessages.getString("tab.event.counterSettings.count.too-small"), //$NON-NLS-1$
										min));
                                valid = false;
                                break;
                            }

                            // Next ask oprofile if it is valid
                            if (!checkEventSetupValidity(
                                    counters[i].getNumber(), event.getText(), event.getUnitMask().getMaskValue())) {
                                Object[] args = new Object[] { event.getText() };
                                setErrorMessage(MessageFormat.format(OprofileLaunchMessages.getString("tab.event.validation.msg"), args)); //$NON-NLS-1$
                                valid = false;
                                break;
                            }
                        }
                    }
                }
            }
        } catch (CoreException e) {
            e.printStackTrace();
        }

        return (numEnabledEvents > 0 && valid);
    }

    @Override
    public void performApply(ILaunchConfigurationWorkingCopy config) {
        if (getOprofileTimerMode() || counterSubTabs == null) {
            config.setAttribute(OprofileLaunchPlugin.ATTR_USE_DEFAULT_EVENT, true);
        } else {
            config.setAttribute(OprofileLaunchPlugin.ATTR_USE_DEFAULT_EVENT, defaultEventCheck.getSelection());
            for (CounterSubTab cst : counterSubTabs) {
                cst.performApply(config);
            }
        }
    }

    @Override
    public void setDefaults(ILaunchConfigurationWorkingCopy config) {
        boolean useDefault = true;
        IProject project = getProject(config);
        setOprofileProject(project);

        counters = getOprofileCounters(config);

        // When instantiated, the OprofileCounter will set defaults.
        for (int i = 0; i < counters.length; i++) {
            counters[i].saveConfiguration(config);
            if (counters[i].getEnabled()) {
                useDefault = false;
            }
        }

        config.setAttribute(OprofileLaunchPlugin.ATTR_USE_DEFAULT_EVENT, useDefault);
    }

    @Override
    public String getName() {
        return OprofileLaunchMessages.getString("tab.event.name"); //$NON-NLS-1$
    }

    @Override
    public Image getImage() {
        return OprofileLaunchPlugin.getImageDescriptor(OprofileLaunchPlugin.ICON_EVENT_TAB).createImage();
    }

    /**
     * Handles the toggling of the default event check box. Not meant to be called
     * directly.
     */
    private void handleEnabledToggle() {
        setEnabledState(!defaultEventCheck.getSelection());
        updateLaunchConfigurationDialog();
    }

    /**
     * Sets the state of the child counter tabs' widgets.
     * @param state true for enabled, false for disabled
     */
    private void setEnabledState(boolean state) {
        for (CounterSubTab cst : counterSubTabs) {
            cst.setEnabledState(state);
        }
    }

    /*
     * Extracted methods to be overridden by the test suite.
     */

    /**
     * Returns whether the event's unit mask is valid
     * @param counter counter number
     * @param name event name
     * @param maskValue unit mask value
     * @return true if valid config, false otherwise
     */
    protected abstract boolean checkEventSetupValidity(int counter, String name, int maskValue);

    /**
     *
     * @param config
     * @return
     * @since 1.1
     */
    private IProject getProject(ILaunchConfiguration config){
        String name = null;
        try {
            name = config.getAttribute(ICDTLaunchConfigurationConstants.ATTR_PROJECT_NAME, ""); //$NON-NLS-1$
        } catch (CoreException e) {
            return null;
        }
        if (name.isEmpty()) {
            return null;
        }

        return ResourcesPlugin.getWorkspace().getRoot().getProject(name);
    }

    /**
     * Returns counter with corresponding to counter number.
     * @param i the counter number
     */
    public abstract OprofileCounter getOprofileCounter(int i);

    /**
     * Returns counters in the given configuration.
     * @param config the launch configuration
     */
    protected abstract OprofileCounter[] getOprofileCounters(ILaunchConfiguration config);

    /**
     * Returns the number of hardware counters the cpu has
     * @return int number of counters
     */
    protected abstract int getNumberOfOprofileCounters();

    /**
     * Returns whether or not oprofile is operating in timer mode.
     * @return true if oprofile is in timer mode, false otherwise
     */
    protected abstract boolean getOprofileTimerMode();

    /**
     * Returns current project to profile by Oprofile.
     */
    protected abstract IProject getOprofileProject();

    /**
     * Set project to profile by Oprofile.
     * @param project the project to profile
     */
    protected abstract void setOprofileProject(IProject project);

    /**
     * Update generic Oprofile information.
     */
    protected abstract void updateOprofileInfo();

    /**
     * A sub-tab of the OprofileEventConfigTab launch configuration tab.
     * Essentially, it is a frontend to an OprofileCounter. This is an
     * inner class because it requires methods from the parent tab (such as
     * updateLaunchConfigurationDialog() when a widget changes state).
     */
    protected class CounterSubTab {

        private Button profileKernelCheck;
        private Button profileUserCheck;
        private Label countTextLabel;
        private Text countText;
        private Label eventDescLabel;
        private Text eventDescText;
        private UnitMaskViewer unitMaskViewer;
        private Text eventFilterText;
        private OprofileCounter counter;

        private ScrolledComposite scrolledTop;
        protected Composite tabTopContainer;
        protected Button enabledCheck;
        protected ListViewer eventList;

        protected Button add;
        protected Button addAll;
        protected Button remove;
        protected Button removeAll;
        protected Button customizeBtn;
        protected ListViewer selectedEventList;
        protected ScrolledComposite unitmaskScrollComposite;
        protected Composite unitMaskSubComposite ;
        private static final int ADD = 1;
        private static final int ADD_ALL = 2;
        private static final int REMOVE = 3;
        private static final int REMOVE_ALL = 4;
        private static final int CUSTOMIZE = 5;
        private List<OpEvent> sourceList = new ArrayList<>(0);
        private List<OpEvent> targetList = new ArrayList<>(0);


        public Composite getTabTopContainer() {
            return tabTopContainer;
        }

        public void setTabTopContainer(Composite tabTopContainer) {
            this.tabTopContainer = tabTopContainer;
        }

        /**
         * Constructor for a subtab. Creates the layout and widgets for its content.
         * @param parent composite the widgets will be created in
         * @param counter the associated OprofileCounter object
         */
        public CounterSubTab(Composite parent, OprofileCounter counter) {
            this.counter = counter;

            parent.setLayout(new GridLayout());

            //scrollable composite on top
            ScrolledComposite scrolledContainer = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.V_SCROLL);
            scrolledContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
            GridLayout layout = new GridLayout();
            layout.marginHeight = 0;
            layout.marginWidth = 0;
            scrolledContainer.setLayout(layout);
            scrolledContainer.setExpandHorizontal(true);
            scrolledContainer.setExpandVertical(true);

            //composite to contain the rest of the tab
            Composite tabTopContainer = new Composite(scrolledContainer, SWT.NONE);
            scrolledContainer.setContent(tabTopContainer);
            layout = new GridLayout();
            layout.marginHeight = 0;
            layout.marginWidth = 0;
            layout.numColumns = 2;
            tabTopContainer.setLayout(layout);
            tabTopContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

            //top cell
            Composite topCellComp = new Composite(tabTopContainer, SWT.NONE);
            layout = new GridLayout();
            layout.marginHeight = 0;
            layout.marginWidth = 0;
            layout.numColumns = 2;
            topCellComp.setLayout(layout);
            topCellComp.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false, 2, 1));

            createTopCell(topCellComp);

            createVerticalSpacer(tabTopContainer, 2);

            //left side composite group for eventList
            Composite eventListComp = new Composite(tabTopContainer, SWT.NONE);
            layout = new GridLayout();
            layout.marginHeight = 0;
            layout.marginWidth = 0;
            layout.numColumns = 3;
            eventListComp.setLayout(layout);
            eventListComp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
            //layoutdata is set later

            createLeftCell(eventListComp);


            scrolledTop = scrolledContainer;
            this.tabTopContainer = tabTopContainer;
            resizeScrollContainer();
        }

        /**
         * Creates the "Enabled" checkbox, and the event description text.
         * @param parent composite these widgets will be created in
         */
        private void createTopCell(Composite parent) {
            //checkbox
            enabledCheck = new Button(parent, SWT.CHECK);
            enabledCheck.setText(OprofileLaunchMessages.getString("tab.event.counterSettings.enabled.button.text")); //$NON-NLS-1$
            enabledCheck.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1));
			enabledCheck.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
				counter.setEnabled(enabledCheck.getSelection());
				internalSetEnabledState(counter.getEnabled());
				updateLaunchConfigurationDialog();
			}));
            enabledCheck.setEnabled(false);

            //label for textbox
            eventDescLabel = new Label(parent, SWT.NONE);
            eventDescLabel.setText(OprofileLaunchMessages.getString("tab.event.eventDescription.label.text")); //$NON-NLS-1$
            eventDescLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));

            //textbox
            eventDescText = new Text(parent, SWT.SINGLE | SWT.BORDER | SWT.READ_ONLY);
            eventDescText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        }

        /**
         * Creates the event list widget.
         * @param parent composite these widgets will be created in
         */
        private void createLeftCell(Composite parent) {
            // Text box used to filter the event list
            eventFilterText = new Text(parent, SWT.BORDER | SWT.SINGLE | SWT.ICON_CANCEL | SWT.SEARCH);
            eventFilterText.setMessage(OprofileLaunchMessages.getString("tab.event.eventfilter.message")); //$NON-NLS-1$
            GridData eventFilterLayout = new GridData();
            eventFilterLayout.horizontalAlignment = SWT.FILL;
            eventFilterLayout.grabExcessHorizontalSpace = true;
            eventFilterText.setLayoutData(eventFilterLayout);
            eventFilterText.addModifyListener(e -> eventList.refresh(false));

            // profile user binary and profile kernel
            createRightCell(parent);

            int options =  SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER;
            if (OprofileProject.getProfilingBinary().equals(OprofileProject.OPERF_BINARY)) {
                options |= SWT.MULTI;
            } else {
                options |= SWT.SINGLE;
            }
            eventList = new ListViewer(parent, options);
            GridData gdata = new GridData(SWT.FILL, SWT.FILL, true, true);
            gdata.verticalSpan = 5;
            eventList.getList().setLayoutData(gdata);

            eventList.setLabelProvider(new LabelProvider(){
                @Override
                public String getText(Object element) {
                    OpEvent e = (OpEvent) element;
                    return e.getText();
                }
                @Override
                public Image getImage(Object element) { return null; }
                @Override
                public boolean isLabelProperty(Object element, String property) { return false; }
            });

            eventList.setContentProvider(new IStructuredContentProvider() {
                @Override
                public Object[] getElements(Object inputElement) {
                    List<OpEvent> list = (List<OpEvent>)inputElement;
                    return list.toArray();
                }
                @Override
                public void dispose() { }
                @Override
                public void inputChanged(Viewer arg0, Object arg1, Object arg2) { }
            });

            // sorter
            ListviewerComparator comparator = new ListviewerComparator();
            eventList.setComparator(comparator);

            //adds the events to the list from the counter
            sourceList.addAll(Arrays.asList(counter.getValidEvents()));
            eventList.setInput(sourceList);

            eventList.addSelectionChangedListener(sce -> handleEventListSelectionChange());

            HandleButtonClick listener = new HandleButtonClick();
            add = new Button(parent, SWT.PUSH);
            add.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
            add.setText(OprofileLaunchMessages.getString("tab.event.addevent.button.text")); //$NON-NLS-1$
            add.setData(ADD);
            add.addListener(SWT.Selection, listener);

            selectedEventList = new ListViewer(parent, options);
            selectedEventList.getList().setLayoutData(gdata);


            selectedEventList.setLabelProvider(new ILabelProvider(){
                @Override
                public String getText(Object element) {
                    OpEvent e = (OpEvent) element;
                    return e.getText();
                }
                @Override
                public Image getImage(Object element) { return null; }
                @Override
                public void addListener(ILabelProviderListener listener) { }
                @Override
                public void dispose() { }
                @Override
                public boolean isLabelProperty(Object element, String property) { return false; }
                @Override
                public void removeListener(ILabelProviderListener listener) { }
            });

            selectedEventList.setContentProvider(new IStructuredContentProvider() {
                @Override
                public Object[] getElements(Object inputElement) {
                    List<OpEvent> list = (List<OpEvent>)inputElement;
                    return list.toArray();
                }
                @Override
                public void dispose() { }
                @Override
                public void inputChanged(Viewer arg0, Object arg1, Object arg2) { }
            });

            // sorter
             ListviewerComparator viewerComparator = new ListviewerComparator();
             selectedEventList.setComparator(viewerComparator);

            //adds the events to the list from the counter
            if(counter.getEvents().length != 0 && null != counter.getEvents()[0])
            {
            targetList.addAll(Arrays.asList(counter.getEvents()));
            }
            selectedEventList.setInput(targetList);

            selectedEventList.addSelectionChangedListener(sce -> {
			    handleListSelection(selectedEventList);
			    eventList.getList().deselectAll();
			    updateLaunchConfigurationDialog();
			});





            addAll = new Button(parent, SWT.PUSH);
            addAll.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
            addAll.setText(OprofileLaunchMessages.getString("tab.event.addallevent.button.text")); //$NON-NLS-1$
            addAll.setData(ADD_ALL);
            addAll.addListener(SWT.Selection, listener);


            remove = new Button(parent, SWT.PUSH);
            remove.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
            remove.setText(OprofileLaunchMessages.getString("tab.event.removeevent.button.text")); //$NON-NLS-1$
            remove.setData(REMOVE);
            remove.addListener(SWT.Selection, listener);


            removeAll = new Button(parent, SWT.PUSH);
            removeAll.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
            removeAll.setText(OprofileLaunchMessages.getString("tab.event.removeallevent.button.text")); //$NON-NLS-1$
            removeAll.setData(REMOVE_ALL);
            removeAll.addListener(SWT.Selection, listener);

            customizeBtn = new Button(parent, SWT.PUSH);
            customizeBtn.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
            customizeBtn.setText(OprofileLaunchMessages.getString("tab.event.customizeevent.button.text")); //$NON-NLS-1$
            customizeBtn.addListener(SWT.Selection, listener);
            customizeBtn.setData(CUSTOMIZE);


        }

        /**
         * Creates the 2 profile space checkboxes, event count and unit mask widget.
         * @param parent composite these widgets will be created in
         */
        private void createRightCell(Composite parent) {
            //profile kernel checkbox
            profileKernelCheck = new Button(parent, SWT.CHECK);
            profileKernelCheck.setText(OprofileLaunchMessages.getString("tab.event.counterSettings.profileKernel.check.text")); //$NON-NLS-1$
			profileKernelCheck
					.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> handleProfileKernelToggle()));

            //profile user checkbox -- should this ever be disabled?
            profileUserCheck = new Button(parent, SWT.CHECK);
            profileUserCheck.setText(OprofileLaunchMessages.getString("tab.event.counterSettings.profileUser.check.text")); //$NON-NLS-1$
			profileUserCheck
					.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> handleProfileUserToggle()));

        }

        /**
         * Creates a text filter for the events list widget
         */
        private void createEventsFilter(){
            // Event Filter
            ViewerFilter eventFilter = new ViewerFilter() {

                @Override
                public Object[] filter(Viewer viewer, Object parent, Object[] elements) {
                    Object[] filteredElements = super.filter(viewer,parent,elements);
                    handleEventListSelectionChange();
                    return filteredElements;
                }

                @Override
                public boolean select(Viewer viewer, Object parentElement, Object element) {
                    String[] filterTerms = eventFilterText.getText().trim().toLowerCase().split(" "); //$NON-NLS-1$
                    String eventName = ((OpEvent)element).getText().toLowerCase();
                    String eventDescription = ((OpEvent)element).getTextDescription().toLowerCase();

                    boolean contains = true;

                    for (String filterTerm : filterTerms) {
                        if(contains){
                            contains = eventName.contains(filterTerm) || eventDescription.contains(filterTerm);
                        }
                    }
                    return contains;
                }
            };
            if(eventList != null){
                eventList.addFilter(eventFilter);
            }
        }

        /**
         * Initializes the tab on first creation.
         * @param config default configuration for the counter and the associated widgets
         */
        public void initializeTab(ILaunchConfiguration config) {
            //make all controls inactive, since the 'default event' checkbox
            // is checked by default
            try {
                defaultEventCheck.setSelection(config.getAttribute(OprofileLaunchPlugin.ATTR_USE_DEFAULT_EVENT, true));
            } catch (CoreException e) {
                e.printStackTrace();
            }
            setEnabledState(false);

            if (config != null) {
                counter.loadConfiguration(config);
            }

            boolean enabled = counter.getEnabled();
            enabledCheck.setSelection(enabled);

            if (counter.getEvents().length == 0 || counter.getEvents()[0] == null) {
                // Default to first in list
                counter.setEvents(new OpEvent [] {counter.getValidEvents()[0]});
            }

            //load default states
            profileKernelCheck.setSelection(counter.getProfileKernel());
            profileUserCheck.setSelection(counter.getProfileUser());

            eventDescText.setText(counter.getEvents()[0].getTextDescription());


            // add opevent to target event list
            ArrayList<OpEvent> tmp = new ArrayList<>(Arrays.asList(counter.getEvents()));
            targetList.addAll(tmp);
            selectedEventList.add(tmp.toArray());
            selectedEventList.refresh();
            selectedEventList.setSelection(new StructuredSelection(tmp.toArray()));

            // remove selected opevent from source list

            sourceList.removeAll(tmp);
            eventList.remove(tmp.toArray());
            eventList.refresh();
        }

        /**
         * Applies the tab's current state to the launch configuration.
         * @param config launch config to apply to
         */
        public void performApply(ILaunchConfigurationWorkingCopy config) {
            counter.saveConfiguration(config);
        }

        /**
         * Enables/disables the widgets in this tab.
         * @param state true to enable to the counter's state, false to disable all
         */
        public void setEnabledState(boolean state) {
            enabledCheck.setEnabled(state);

            if (state) {
                internalSetEnabledState(counter.getEnabled());
            } else {
                internalSetEnabledState(false);
            }
        }

        /**
         * Method split from setEnabledState to avoid code duplication.
         * Not meant to be called directly.
         * @param state true to enable all widgets, false to disable all widgets
         */
        private void internalSetEnabledState(boolean state) {
            profileKernelCheck.setEnabled(state);
            profileUserCheck.setEnabled(state);
            eventDescText.setEnabled(state);
            eventList.getList().setEnabled(state);
            selectedEventList.getList().setEnabled(state);
            eventFilterText.setEnabled(state);
            add.setEnabled(state);
            addAll.setEnabled(state);
            remove.setEnabled(state);
            removeAll.setEnabled(state);
            customizeBtn.setEnabled(state);


        }

        /**
         * Handling method for the event list. Gets the selection from the listviewer
         * and updates the UnitMask and event description text box.
         */
        private void handleEventListSelectionChange() {
            handleListSelection(eventList);
            int[] indices = eventList.getList().getSelectionIndices();
            if (indices.length != 0) {
                customizeBtn.setEnabled(true);
                // unselected other list element
                // to keep customize button enable
                // for both list selection
                selectedEventList.getList().deselectAll();
            }
            updateLaunchConfigurationDialog();

        }

        /**
         * Generic method for handling source & target selection list
         * @param eventList - list to be handled
         * @since 3.0
         */
        private void handleListSelection(ListViewer eventList)
        {
            setErrorMessage(null);
            int [] indices = eventList.getList().getSelectionIndices();
            if (indices.length != 0) {
                ArrayList<OpEvent> tmp = new ArrayList<> ();
                for (int index : indices) {
                    OpEvent event = (OpEvent) eventList.getElementAt(index);
                    tmp.add(event);
                    eventDescText.setText(event.getTextDescription());

                }

                // Check the min count to update the error message (events
                // can have
                // different minimum reset counts)
                int min = Integer.MIN_VALUE;
                for (OpEvent ev : tmp) {
                    // We want the largest of the min values
                    if (ev.getMinCount() > min) {
                        min = ev.getMinCount();
                    }
                }
                if(counter.getEvents().length == 0 || counter.getEvents()[0] == null)
                {
                    counter.setEvents(new OpEvent [] {counter.getValidEvents()[0]});
                }
                if ((counter.getCount() < min)
                        && (!defaultEventCheck.getSelection())) {
                    setErrorMessage(getMinCountErrorMessage(min));
                }

                //counter.setEvents(tmp.toArray(new OpEvent[0]));
            } else {
                eventDescText.setText(""); //$NON-NLS-1$

            }



        }

        /**
         * Handles the toggling of the "profile user" button.
         */
        private void handleProfileUserToggle() {
            counter.setProfileUser(profileUserCheck.getSelection());
            updateLaunchConfigurationDialog();
        }

        /**
         * Handles the toggling of the "profile kernel" button.
         */
        private void handleProfileKernelToggle() {
            counter.setProfileKernel(profileKernelCheck.getSelection());
            updateLaunchConfigurationDialog();
        }

        /**
         * Handles text modify events in the count text widget.
         */
        private void handleCountTextModify() {
            String errorMessage = null;
            try {

                // This seems counter-intuitive, but we must save the count
                // so that isValid knows this launch config is invalid
                int count = Integer.parseInt(countText.getText());
                counter.setCount(count);

                // Check minimum count
                int min = Integer.MIN_VALUE;
                for (OpEvent event : counter.getEvents()) {
                    // We want the largest of the min values
                    if (event != null && event.getMinCount() > min) {
                        min = event.getMinCount();
                    }
                }
                if ((count < min) && (!defaultEventCheck.getSelection())) {
                    errorMessage = getMinCountErrorMessage(min);
                }
            } catch (NumberFormatException e) {
                errorMessage = OprofileLaunchMessages.getString("tab.event.counterSettings.count.invalid"); //$NON-NLS-1$
                counter.setCount(OprofileDaemonEvent.COUNT_INVALID);
            } finally {
                setErrorMessage(errorMessage);
                updateLaunchConfigurationDialog();
            }
        }

        /**
         * Returns a string with the minimum allowed count, suitable foruse with setErrorMessage().
         * @param min minimum count
         * @return a String containing the error message
         */
        private String getMinCountErrorMessage(int min) {
            String msg = OprofileLaunchMessages.getString("tab.event.counterSettings.count.too-small"); //$NON-NLS-1$
            return MessageFormat.format(msg, Integer.valueOf(min));
        }

        /**
         * Changes parameters for the top scrolled composite which makes the scroll bars
         * appear when content overflows the visible area. Called by the UnitMaskViewer
         * whenever a new set of unit mask buttons are created, since the number of them is
         * variable and there is no guarantee as to the default size of the launch configuration
         * dialog in general.
         */
        private void resizeScrollContainer() {
            scrolledTop.setMinSize(tabTopContainer.computeSize(SWT.DEFAULT, SWT.DEFAULT));
        }


        /**
         * This class displays event unit masks via check boxes and appropriate labels.
         */
        protected class UnitMaskViewer {
            private Label unitMaskLabel;
            private Composite top;
            private Composite maskListComp;
            private Button[] unitMaskButtons;

            /**
             * Constructor, creates the widget.
             * @param parent composite the widget will be created in
             */
            public UnitMaskViewer(Composite parent) {
                //"Unit Mask:" label
                unitMaskLabel = new Label(parent, SWT.NONE);
                unitMaskLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
                unitMaskLabel.setText(OprofileLaunchMessages.getString("unitmaskViewer.label.text")); //$NON-NLS-1$
                unitMaskLabel.setVisible(true);

                //composite to contain the button widgets
                Composite top = new Composite(parent, SWT.NONE);
                top.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
                GridLayout layout = new GridLayout();
                layout.marginHeight = 0;
                layout.marginWidth = 0;
                top.setLayout(layout);
                this.top = top;

                maskListComp = null;
                unitMaskButtons = null;
            }

            /**
             * Handles button toggles; updates the counter's unit mask to the appropriate value.
             * @param maskButton the button object
             * @param index the button's mask index (used in OpUnitMask for a proper mask value)
             */
            private void handleToggle(Button maskButton, int index) {
                OpUnitMask mask = counter.getUnitMask();
                if (mask != null) {
                    if (maskButton.getSelection()) {
                        mask.setMaskFromIndex(index);
                    } else {
                        mask.unSetMaskFromIndex(index);
                    }
                }

                //update the parent tab
                updateLaunchConfigurationDialog();
            }

            /**
             * Disposes of the old unit mask check list and creates a new one with
             *   the appropriate default value.
             * @param oe the event
             */
            public void displayEvent(OpEvent oe) {
                if (maskListComp != null) {
                    maskListComp.dispose();
                }

                if(oe == null){
                    return;
                }


                OpUnitMask mask = oe.getUnitMask();
                int totalMasks = mask.getNumMasks();

                Composite newMaskComp = new Composite(top, SWT.NONE);
                newMaskComp.setLayout(new GridLayout());
                newMaskComp.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, true));
                maskListComp = newMaskComp;

                //creates these buttons with the default masks
                mask.setDefaultMaskValue();

                ArrayList<Button> maskButtons = new ArrayList<>();

                for (int i = 0; i < totalMasks; i++) {
                    Button maskButton;

                    if (mask.getType() == OpUnitMask.INVALID) {
                        //big problem, most likely parsing went awry or opxml output mangled
                        OprofileCorePlugin.showErrorDialog("opxmlParse", null); //$NON-NLS-1$
                        return;
                    } else if (mask.getType() == OpUnitMask.MANDATORY) {
                        maskButton = new Button(newMaskComp, SWT.RADIO);
                        maskButton.setEnabled(false);
                        maskButton.setText(mask.getText(i));
                        maskButton.setSelection(true);
                    } else {
                        int buttonType;
                        final int maskButtonIndex = i;
                        boolean selected = mask.isMaskSetFromIndex(maskButtonIndex);

                        if (mask.getType() == OpUnitMask.EXCLUSIVE) {
                            buttonType = SWT.RADIO;
                        } else {    //mask type is OpUnitMask.BITMASK
                            buttonType = SWT.CHECK;
                        }

                        maskButton = new Button(newMaskComp, buttonType);
                        maskButton.setEnabled(true);
                        maskButton.setText(mask.getText(i));
                        maskButton.setSelection(selected);
						maskButton.addSelectionListener(SelectionListener
								.widgetSelectedAdapter(se -> handleToggle((Button) se.getSource(), maskButtonIndex)));

                        maskButtons.add(maskButton);
                    }
                }

                unitMaskButtons = new Button[maskButtons.size()];
                maskButtons.toArray(unitMaskButtons);


                resizeUnitMaskContainer();
            }

            /**
             * Enables and disables the viewer for UI input
             * @param enabled whether this viewer should be enabled
             */
            public void setEnabled(boolean enabled) {
                if (unitMaskButtons != null) {
                    for (Button b : unitMaskButtons) {
                        if (!b.isDisposed()) {
                            b.setEnabled(enabled);
                        }
                    }
                }
            }
        }

        /**
         * Dialog box for unit mask field modification
         * @since 3.0
         *
         */
        protected class UnitMaskDialog extends Dialog {
            private OpEvent event;
            public UnitMaskDialog(IShellProvider parentShell) {
                super(parentShell);
            }

            public UnitMaskDialog(Shell parentShell,OpEvent event) {
                super(parentShell);
                this.event = event;
            }

            @Override
            protected Control createDialogArea(Composite parent) {
                Composite child = (Composite)super.createDialogArea(parent);
                ScrolledComposite scrolledContainer = new ScrolledComposite(child, SWT.H_SCROLL|SWT.V_SCROLL);
                scrolledContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
                GridLayout layout = new GridLayout();
                layout.marginHeight = 0;
                layout.marginWidth = 0;
                scrolledContainer.setLayout(layout);
                scrolledContainer.setExpandHorizontal(true);
                scrolledContainer.setExpandVertical(true);
                Composite unitMaskSubComposite  = new Composite(scrolledContainer, SWT.None);
                unitMaskSubComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
                layout = new GridLayout();
                layout.numColumns=2;
                unitMaskSubComposite.setLayout(layout);
                createUnitMaskComponents(unitMaskSubComposite);
                scrolledContainer.setContent(unitMaskSubComposite);
                CounterSubTab.this.unitmaskScrollComposite = scrolledContainer;
                CounterSubTab.this.unitMaskSubComposite = unitMaskSubComposite;
                unitMaskViewer.displayEvent(event);
                return child;
            }

            @Override
            protected boolean isResizable() {
                return true;
            }

            @Override
            protected void configureShell(Shell newShell) {
                super.configureShell(newShell);
                newShell.setText(event.getText());
                newShell.setSize(400, 400);
            }
        }


        private void createUnitMaskComponents(Composite parent)
        {
            //event count label/text
            countTextLabel = new Label(parent, SWT.NONE);
            countTextLabel.setText(OprofileLaunchMessages.getString("tab.event.counterSettings.count.label.text")); //$NON-NLS-1$
            countText = new Text(parent, SWT.SINGLE | SWT.BORDER);
            countText.setText(Integer.toString(counter.getCount()));
            countText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
            countText.addModifyListener(me -> handleCountTextModify());

            //unit mask widget
            Composite unitMaskComp = new Composite(parent, SWT.NONE);
            GridLayout layout = new GridLayout();
            layout.marginHeight = 0;
            layout.marginWidth = 0;
            unitMaskComp.setLayout(layout);
            unitMaskComp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));

            unitMaskViewer = new UnitMaskViewer(unitMaskComp);
        }
        private void resizeUnitMaskContainer() {
            unitmaskScrollComposite.setMinSize(unitMaskSubComposite.computeSize(SWT.DEFAULT, SWT.DEFAULT));
        }

        /**
         * Events selection/removal button listener
         * @since 3.0
         *
         */
        protected class HandleButtonClick implements Listener {

            @Override
            public void handleEvent(Event event) {
                int btn_value = (Integer) event.widget.getData();
                switch (btn_value) {
                case ADD:
                    addButtonClicked();
                    updateLaunchConfigurationDialog();
                    break;
                case ADD_ALL:
                    addAllButtonClicked();
                    updateLaunchConfigurationDialog();
                    break;
                case REMOVE:
                    removeButtonClicked();
                    updateLaunchConfigurationDialog();
                    break;
                case REMOVE_ALL:
                    removeAllButtonClicked();
                    updateLaunchConfigurationDialog();
                    break;
                case CUSTOMIZE:
                    customizeButtonClicked();
                    break;
                default:
                    break;
                }

            }

            private void addButtonClicked() {
                int[] indices = eventList.getList().getSelectionIndices();
                if (indices.length != 0) {
                    ArrayList<OpEvent> tmp = new ArrayList<>();
                    for (int index : indices) {
                        OpEvent event = (OpEvent) eventList.getElementAt(index);
                        tmp.add(event);
                    }
                    // add to target list
                    targetList.addAll(tmp);
                    selectedEventList.add(tmp.toArray());

                    sourceList.removeAll(tmp);
                    eventList.remove(tmp.toArray());

                    int count = selectedEventList.getList().getItemCount();
                    tmp = new ArrayList<>();
                    for (int i = 0; i < count; i++) {
                        OpEvent event = (OpEvent) selectedEventList
                                .getElementAt(i);
                        tmp.add(event);

                    }
                    if (!tmp.isEmpty())
                        counter.setEvents(tmp.toArray(new OpEvent[0]));

                    eventList.refresh();
                    selectedEventList.refresh();


                }
            }

            private void addAllButtonClicked() {
                int count = eventList.getList().getItemCount();
                ArrayList<OpEvent> tmp = new ArrayList<>();
                for (int i = 0; i < count; i++) {
                    OpEvent event = (OpEvent) eventList.getElementAt(i);
                    tmp.add(event);

                }

                targetList.addAll(tmp);
                selectedEventList.add(tmp.toArray());
                eventList.remove(tmp.toArray());
                sourceList.removeAll(tmp);

                count = selectedEventList.getList().getItemCount();
                tmp = new ArrayList<>();
                for (int i = 0; i < count; i++) {
                    OpEvent event = (OpEvent) selectedEventList.getElementAt(i);
                    tmp.add(event);

                }
                if (!tmp.isEmpty())
                    counter.setEvents(tmp.toArray(new OpEvent[0]));

                eventList.refresh();
                selectedEventList.refresh();


            }

            private void removeButtonClicked() {
                int[] indices = selectedEventList.getList()
                        .getSelectionIndices();
                if (indices.length != 0) {
                    ArrayList<OpEvent> tmp = new ArrayList<>();
                    for (int index : indices) {
                        OpEvent event = (OpEvent) selectedEventList
                                .getElementAt(index);
                        tmp.add(event);
                    }
                    // add to target list
                    sourceList.addAll(tmp);
                    eventList.add(tmp.toArray());

                    targetList.removeAll(tmp);
                    selectedEventList.remove(tmp.toArray());

                    int count = selectedEventList.getList().getItemCount();
                    tmp = new ArrayList<>();
                    for (int i = 0; i < count; i++) {
                        OpEvent event = (OpEvent) selectedEventList
                                .getElementAt(i);
                        tmp.add(event);

                    }
                    if (!tmp.isEmpty())
                        counter.setEvents(tmp.toArray(new OpEvent[0]));
                    else
                        // add first valid element to counter due to NPE
                        counter.setEvents(new OpEvent[]{counter.getValidEvents()[0]});

                    eventList.refresh();
                    selectedEventList.refresh();

                }
            }

            private void removeAllButtonClicked() {

                int count = selectedEventList.getList().getItemCount();
                ArrayList<OpEvent> tmp = new ArrayList<>();
                for (int i = 0; i < count; i++) {
                    OpEvent event = (OpEvent) selectedEventList.getElementAt(i);
                    tmp.add(event);

                }

                if (!tmp.isEmpty()) {
                    sourceList.addAll(tmp);
                    eventList.add(tmp.toArray());

                    selectedEventList.remove(tmp.toArray());
                    targetList.removeAll(tmp);

                }
                counter.setEvents(new OpEvent[] { counter.getValidEvents()[0] });

                eventList.refresh();
                selectedEventList.refresh();

            }

            private void customizeButtonClicked() {
                UnitMaskDialog d = null;

                if(eventList.getList().getSelectionIndex() != -1)
                {
                    d = new UnitMaskDialog(Display.getCurrent()
                            .getActiveShell(),
                            (OpEvent) eventList.getElementAt(eventList.getList()
                                    .getSelectionIndex()));
                }
                else if(selectedEventList.getList().getSelectionIndex() != -1)
                {
                    d = new UnitMaskDialog(Display.getCurrent()
                            .getActiveShell(),
                            (OpEvent) selectedEventList.getElementAt(selectedEventList.getList()
                                    .getSelectionIndex()));
                }
                if(d != null)
                d.open();
            }

        }
    }

    /**
     *
     * Event sorting for selected as well as all available events
     * @since 3.0
     */
    protected class ListviewerComparator extends ViewerComparator
    {
        @Override
        public int compare(Viewer viewer, Object e1, Object e2) {
            OpEvent op1 = (OpEvent) e1;
            OpEvent op2 = (OpEvent) e2;
            String op1txt = op1.getText();
            String op2txt = op2.getText();
            if(op1txt !=null && op2txt !=null && op1txt.trim().length() !=0 && op2txt.trim().length() !=0)
                return getComparator().compare(op1txt, op2txt);
            return super.compare(viewer, e1, e2);
        }
    }
}
