/**********************************************************************
 * Copyright (c) 2012, 2016 Ericsson
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License 2.0 which
 * accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Bernd Hufmann - Initial API and implementation
 *   Bernd Hufmann - Updated for support of LTTng Tools 2.1
 *   Simon Delisle - Updated for support of LTTng Tools 2.2
 *   Marc-Andre Laperle - Support for creating a live session
 *   Markus Schorn - Bug 448058: Use org.eclipse.remote in favor of RSE
 **********************************************************************/
package org.eclipse.tracecompass.internal.lttng2.control.ui.views.service;

import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;
import static org.eclipse.tracecompass.common.core.NonNullUtils.nullToEmptyString;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.IBaseEventInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.IChannelInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.IDomainInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.IEventInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.IFieldInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.IProbeEventInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.ISessionInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.ISnapshotInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.ITraceLogLevel;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.IUstProviderInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.LogLevelType;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.TraceDomainType;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.TraceEventType;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.TraceLogLevel;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.BaseEventInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.BufferType;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.ChannelInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.DomainInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.EventInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.FieldInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.ProbeEventInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.SessionInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.SnapshotInfo;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.impl.UstProviderInfo;
import org.eclipse.tracecompass.internal.lttng2.control.ui.views.logging.ControlCommandLogger;
import org.eclipse.tracecompass.internal.lttng2.control.ui.views.messages.Messages;
import org.eclipse.tracecompass.internal.lttng2.control.ui.views.preferences.ControlPreferences;
import org.eclipse.tracecompass.tmf.remote.core.shell.ICommandInput;
import org.eclipse.tracecompass.tmf.remote.core.shell.ICommandResult;
import org.eclipse.tracecompass.tmf.remote.core.shell.ICommandShell;

/**
 * <p>
 * Service for sending LTTng trace control commands to remote host.
 * </p>
 *
 * @author Bernd Hufmann
 */
public class LTTngControlService implements ILttngControlService {

    // ------------------------------------------------------------------------
    // Attributes
    // ------------------------------------------------------------------------
    /**
     * The command shell implementation
     */
    private final @NonNull ICommandShell fCommandShell;

    /**
     * The version string.
     */
    private @NonNull LttngVersion fVersion = LttngVersion.NULL_VERSION;

    // ------------------------------------------------------------------------
    // Constructors
    // ------------------------------------------------------------------------

    /**
     * Constructor
     *
     * @param shell
     *            - the command shell implementation to use
     */
    public LTTngControlService(@NonNull ICommandShell shell) {
        fCommandShell = shell;
    }

    // ------------------------------------------------------------------------
    // Accessors
    // ------------------------------------------------------------------------

    @Override
    public String getVersionString() {
        return nullToEmptyString(fVersion.toString());
    }

    @Override
    public LttngVersion getVersion() {
        return fVersion;
    }

    /**
     * Sets the version of the LTTng 2.0 control service.
     *
     * @param version
     *            - a version to set
     */
    public void setVersion(@Nullable String version) {
        if (version != null) {
            fVersion = new LttngVersion(version);
        }
    }

    /**
     * Sets the version of the LTTng 2.x control service.
     *
     * @param version
     *            - a version to set
     */
    protected void setVersion(LttngVersion version) {
        if (version != null) {
            fVersion = version;
        }
    }

    @Override
    public boolean isVersionSupported(String version) {
        LttngVersion tmp = new LttngVersion(version);
        return (fVersion.compareTo(tmp) >= 0) ? true : false;
    }

    /**
     * Returns the command shell implementation.
     *
     * @return the command shell implementation
     */
    protected ICommandShell getCommandShell() {
        return fCommandShell;
    }

    // ------------------------------------------------------------------------
    // Operations
    // ------------------------------------------------------------------------

    @Override
    public List<String> getSessionNames(IProgressMonitor monitor) throws ExecutionException {
        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_LIST);

        ICommandResult result = executeCommand(command, monitor);

        // Output:
        // Available tracing sessions:
        // 1) mysession1 (/home/user/lttng-traces/mysession1-20120123-083928)
        // [inactive]
        // 2) mysession (/home/user/lttng-traces/mysession-20120123-083318)
        // [inactive]
        //
        // Use lttng list <session_name> for more details

        ArrayList<String> retArray = new ArrayList<>();
        for (String line : result.getOutput()) {
            Matcher matcher = LTTngControlServiceConstants.SESSION_PATTERN.matcher(line);
            if (matcher.matches()) {
                retArray.add(matcher.group(2).trim());
            }
        }
        return retArray;
    }

    /**
     * Check if there is a pattern to be ignored into a sequence of string
     *
     * @param input
     *            an input list of Strings
     * @param pattern
     *            the pattern to search for
     * @return if the pattern exist in the array of string
     */
    protected boolean ignoredPattern(List<String> input, Pattern pattern) {
        for (String line : input) {
            Matcher matcher = pattern.matcher(line);
            if (matcher.matches()) {
                return true;
            }
        }
        return false;
    }

    @Override
    public ISessionInfo getSession(String sessionName, IProgressMonitor monitor) throws ExecutionException {
        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_LIST, sessionName);
        ICommandResult result = executeCommand(command, monitor);

        int index = 0;

        // Output:
        // Tracing session mysession2: [inactive]
        // Trace path: /home/eedbhu/lttng-traces/mysession2-20120123-110330
        ISessionInfo sessionInfo = new SessionInfo(sessionName);

        while (index < result.getOutput().size()) {
            // Tracing session mysession2: [inactive]
            // Trace path: /home/eedbhu/lttng-traces/mysession2-20120123-110330
            //
            // === Domain: Kernel ===
            //
            String line = result.getOutput().get(index);
            Matcher matcher = LTTngControlServiceConstants.TRACE_SESSION_PATTERN.matcher(line);
            if (matcher.matches()) {
                sessionInfo.setSessionState(matcher.group(2));
                index++;
                continue;
            }

            matcher = LTTngControlServiceConstants.TRACE_SNAPSHOT_SESSION_PATTERN.matcher(line);
            if (matcher.matches()) {
                sessionInfo.setSessionState(matcher.group(2));
                // real name will be set later
                ISnapshotInfo snapshotInfo = new SnapshotInfo(""); //$NON-NLS-1$
                sessionInfo.setSnapshotInfo(snapshotInfo);
                index++;
                continue;
            }

            if (!sessionInfo.isSnapshotSession()) {
                matcher = LTTngControlServiceConstants.TRACE_NETWORK_PATH_PATTERN.matcher(line);
                if (matcher.matches()) {
                    sessionInfo.setStreamedTrace(true);
                }

                matcher = LTTngControlServiceConstants.TRACE_SESSION_PATH_PATTERN.matcher(line);
                if (matcher.matches()) {
                    sessionInfo.setSessionPath(matcher.group(1).trim());
                    index++;
                    continue;
                }
            }

            matcher = LTTngControlServiceConstants.DOMAIN_KERNEL_PATTERN.matcher(line);
            if (matcher.matches()) {
                // Create Domain
                IDomainInfo domainInfo = new DomainInfo(Messages.TraceControl_KernelDomainDisplayName);

                // set kernel domain
                domainInfo.setDomain(TraceDomainType.KERNEL);

                // in domain kernel
                ArrayList<IChannelInfo> channels = new ArrayList<>();
                index = parseDomain(result.getOutput(), index, channels, domainInfo);

                if (!channels.isEmpty()) {
                    // add domain
                    sessionInfo.addDomain(domainInfo);

                    // set channels
                    domainInfo.setChannels(channels);
                }
                continue;
            }

            matcher = LTTngControlServiceConstants.DOMAIN_UST_GLOBAL_PATTERN.matcher(line);
            if (matcher.matches()) {
                IDomainInfo domainInfo = new DomainInfo(Messages.TraceControl_UstGlobalDomainDisplayName);

                // set kernel domain
                domainInfo.setDomain(TraceDomainType.UST);

                // in domain UST
                ArrayList<IChannelInfo> channels = new ArrayList<>();
                index = parseDomain(result.getOutput(), index, channels, domainInfo);

                if (!channels.isEmpty()) {
                    // add domain
                    sessionInfo.addDomain(domainInfo);

                    // set channels
                    domainInfo.setChannels(channels);
                }
                continue;
            }
            matcher = LTTngControlServiceConstants.LIST_LIVE_TIMER_INTERVAL_PATTERN.matcher(line);
            if (matcher.matches()) {
                long liveDelay = Long.parseLong(matcher.group(1));
                if ((liveDelay > 0) && (liveDelay <= LTTngControlServiceConstants.MAX_LIVE_TIMER_INTERVAL)) {
                    sessionInfo.setLive(true);
                    sessionInfo.setLiveUrl(SessionInfo.DEFAULT_LIVE_NETWORK_URL);
                    sessionInfo.setLivePort(SessionInfo.DEFAULT_LIVE_PORT);
                    sessionInfo.setLiveDelay(liveDelay);
                }
                index++;
                continue;
            }

            index++;
        }

        if (sessionInfo.isSnapshotSession()) {
            ISnapshotInfo snapshot = getSnapshotInfo(sessionName, monitor);
            sessionInfo.setSnapshotInfo(snapshot);
        }

        return sessionInfo;
    }

    @Override
    public ISnapshotInfo getSnapshotInfo(String sessionName, IProgressMonitor monitor) throws ExecutionException {
        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_SNAPSHOT, LTTngControlServiceConstants.COMMAND_LIST_SNAPSHOT_OUTPUT, LTTngControlServiceConstants.OPTION_SESSION, sessionName);
        ICommandResult result = executeCommand(command, monitor);

        int index = 0;

        // Output:
        // [1] snapshot-1: /home/user/lttng-traces/my-20130909-114431
        // or
        // [3] snapshot-3: net4://172.0.0.1/
        ISnapshotInfo snapshotInfo = new SnapshotInfo(""); //$NON-NLS-1$

        while (index < result.getOutput().size()) {
            String line = result.getOutput().get(index);
            Matcher matcher = LTTngControlServiceConstants.LIST_SNAPSHOT_OUTPUT_PATTERN.matcher(line);
            if (matcher.matches()) {
                snapshotInfo.setId(Integer.valueOf(matcher.group(1)));
                snapshotInfo.setName(matcher.group(2));
                snapshotInfo.setSnapshotPath(matcher.group(3));

                Matcher matcher2 = LTTngControlServiceConstants.SNAPSHOT_NETWORK_PATH_PATTERN.matcher(snapshotInfo.getSnapshotPath());
                if (matcher2.matches()) {
                    snapshotInfo.setStreamedSnapshot(true);
                }

                index++;
                break;
            }
            index++;
        }

        return snapshotInfo;
    }

    @Override
    public List<IBaseEventInfo> getKernelProvider(IProgressMonitor monitor) throws ExecutionException {
        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_LIST, LTTngControlServiceConstants.OPTION_KERNEL);
        ICommandResult result = executeCommand(command, monitor, false);

        List<IBaseEventInfo> events = new ArrayList<>();

        // Ignore the following 2 cases:
        // Spawning a session daemon
        // Error: Unable to list kernel events
        // or:
        // Error: Unable to list kernel events
        //
        if (ignoredPattern(result.getErrorOutput(), LTTngControlServiceConstants.LIST_KERNEL_NO_KERNEL_PROVIDER_PATTERN)) {
            return events;
        }

        if (isError(result)) {
            throw new ExecutionException(Messages.TraceControl_CommandError + " " + command.toString() + "\n" + result.toString()); //$NON-NLS-1$ //$NON-NLS-2$
        }

        // Kernel events:
        // -------------
        // sched_kthread_stop (type: tracepoint)
        getProviderEventInfo(result.getOutput(), 0, events);
        return events;
    }

    @Override
    public List<IUstProviderInfo> getUstProvider() throws ExecutionException {
        return getUstProvider(new NullProgressMonitor());
    }

    @Override
    public List<IUstProviderInfo> getUstProvider(IProgressMonitor monitor) throws ExecutionException {
        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_LIST, LTTngControlServiceConstants.OPTION_UST);

        if (isVersionSupported("2.1.0")) { //$NON-NLS-1$
            command.add(LTTngControlServiceConstants.OPTION_FIELDS);
        }

        ICommandResult result = executeCommand(command, monitor, false);
        List<IUstProviderInfo> allProviders = new ArrayList<>();

        // Workaround for versions 2.0.x which causes a segmentation fault for
        // this command
        // if LTTng Tools is compiled without UST support.
        if (!isVersionSupported("2.1.0") && (result.getResult() != 0)) { //$NON-NLS-1$
            return allProviders;
        }

        // Ignore the following 2 cases:
        // Spawning a session daemon
        // Error: Unable to list UST events: Listing UST events failed
        // or:
        // Error: Unable to list UST events: Listing UST events failed
        //
        if (ignoredPattern(result.getErrorOutput(), LTTngControlServiceConstants.LIST_UST_NO_UST_PROVIDER_PATTERN)) {
            return allProviders;
        }

        if (isError(result)) {
            throw new ExecutionException(Messages.TraceControl_CommandError + " " + command.toString() + "\n" + result.toString()); //$NON-NLS-1$ //$NON-NLS-2$
        }

        // Note that field print-outs exists for version >= 2.1.0
        //
        // UST events:
        // -------------
        //
        // PID: 3635 - Name:
        // /home/user/git/lttng-ust/tests/hello.cxx/.libs/lt-hello
        // ust_tests_hello:tptest_sighandler (loglevel: TRACE_EMERG0) (type:
        // tracepoint)
        // ust_tests_hello:tptest (loglevel: TRACE_EMERG0) (type: tracepoint)
        // field: doublefield (float)
        // field: floatfield (float)
        // field: stringfield (string)
        //
        // PID: 6459 - Name:
        // /home/user/git/lttng-ust/tests/hello.cxx/.libs/lt-hello
        // ust_tests_hello:tptest_sighandler (loglevel: TRACE_EMERG0) (type:
        // tracepoint)
        // ust_tests_hello:tptest (loglevel: TRACE_EMERG0) (type: tracepoint)
        // field: doublefield (float)
        // field: floatfield (float)
        // field: stringfield (string)

        IUstProviderInfo provider = null;

        int index = 0;
        while (index < result.getOutput().size()) {
            String line = result.getOutput().get(index);
            Matcher matcher = LTTngControlServiceConstants.UST_PROVIDER_PATTERN.matcher(line);
            if (matcher.matches()) {
                provider = new UstProviderInfo(matcher.group(2).trim());
                provider.setPid(Integer.valueOf(matcher.group(1).trim()));
                List<IBaseEventInfo> events = new ArrayList<>();
                index = getProviderEventInfo(result.getOutput(), ++index, events);
                provider.setEvents(events);
                allProviders.add(provider);
            } else {
                index++;
            }
        }
        return allProviders;
    }

    @Override
    public ISessionInfo createSession(ISessionInfo sessionInfo, IProgressMonitor monitor) throws ExecutionException {
        if (sessionInfo.isStreamedTrace()) {
            return createStreamedSession(sessionInfo, monitor);
        }

        ICommandInput command = prepareSessionCreationCommand(sessionInfo);

        ICommandResult result = executeCommand(command, monitor);

        // Session myssession2 created.
        // Traces will be written in
        // /home/user/lttng-traces/myssession2-20120209-095418
        List<String> output = result.getOutput();

        // Get and session name and path
        String name = null;
        String path = null;

        for (String line : output) {
            Matcher nameMatcher = LTTngControlServiceConstants.CREATE_SESSION_NAME_PATTERN.matcher(line);
            Matcher pathMatcher = LTTngControlServiceConstants.CREATE_SESSION_PATH_PATTERN.matcher(line);
            if (nameMatcher.matches()) {
                name = String.valueOf(nameMatcher.group(1).trim());
            } else if (pathMatcher.matches()) {
                path = String.valueOf(pathMatcher.group(1).trim());
            }
        }

        // Verify session name
        if ((name == null) || (!"".equals(sessionInfo.getName()) && !name.equals(sessionInfo.getName()))) { //$NON-NLS-1$
            // Unexpected name returned
            throw new ExecutionException(Messages.TraceControl_CommandError + " " + command.toString() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
                    Messages.TraceControl_UnexpectedNameError + ": " + name); //$NON-NLS-1$
        }

        sessionInfo.setName(name);
        // Verify session path
        if (!sessionInfo.isSnapshotSession() &&
                ((path == null) || ((sessionInfo.getSessionPath() != null) && (!path.contains(sessionInfo.getSessionPath()))))) {
            // Unexpected path
            throw new ExecutionException(Messages.TraceControl_CommandError + " " + command.toString() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
                    Messages.TraceControl_UnexpectedPathError + ": " + name); //$NON-NLS-1$
        }

        if (sessionInfo.isSnapshotSession()) {
            // Make it a snapshot session - content of snapshot info need to
            // set afterwards using getSession() or getSnapshotInfo()
            sessionInfo.setSnapshotInfo(new SnapshotInfo("")); //$NON-NLS-1$
        } else {
            sessionInfo.setSessionPath(path);
        }

        return sessionInfo;

    }

    /**
     * Basic generation of command for session creation
     *
     * @param sessionInfo
     *            the session to create
     * @return the basic command for command creation
     */
    protected @NonNull ICommandInput prepareSessionCreationCommand(ISessionInfo sessionInfo) {
        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_CREATE_SESSION);
        if (!sessionInfo.getName().isEmpty()) {
            command.add(sessionInfo.getName());
        }

        String newPath = sessionInfo.getSessionPath();
        if (newPath != null && !"".equals(newPath)) { //$NON-NLS-1$
            command.add(LTTngControlServiceConstants.OPTION_OUTPUT_PATH);
            command.add(newPath);
        }

        if (sessionInfo.isSnapshotSession()) {
            command.add(LTTngControlServiceConstants.OPTION_SNAPSHOT);
        }
        return command;
    }

    private @NonNull ISessionInfo createStreamedSession(ISessionInfo sessionInfo, IProgressMonitor monitor) throws ExecutionException {

        ICommandInput command = prepareStreamedSessionCreationCommand(sessionInfo);

        ICommandResult result = executeCommand(command, monitor);

        // Verify output
        List<String> output = result.getOutput();

        // Get and session name and path
        String name = null;
        String path = null;

        for (String line : output) {
            Matcher nameMatcher = LTTngControlServiceConstants.CREATE_SESSION_NAME_PATTERN.matcher(line);
            Matcher pathMatcher = LTTngControlServiceConstants.CREATE_SESSION_PATH_PATTERN.matcher(line);

            if (nameMatcher.matches()) {
                name = String.valueOf(nameMatcher.group(1).trim());
            } else if (pathMatcher.matches() && (sessionInfo.getNetworkUrl() != null)) {
                path = String.valueOf(pathMatcher.group(1).trim());
            }
        }

        // Verify session name
        if ((name == null) || (!"".equals(sessionInfo.getName()) && !name.equals(sessionInfo.getName()))) { //$NON-NLS-1$
            // Unexpected name returned
            throw new ExecutionException(Messages.TraceControl_CommandError + " " + command.toString() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
                    Messages.TraceControl_UnexpectedNameError + ": " + name); //$NON-NLS-1$
        }

        sessionInfo.setName(name);

        sessionInfo.setStreamedTrace(true);

        // Verify session path
        if (sessionInfo.getNetworkUrl() != null) {
            if (!sessionInfo.isSnapshotSession() && (path == null)) {
                // Unexpected path
                throw new ExecutionException(Messages.TraceControl_CommandError + " " + command.toString() + "\n" + //$NON-NLS-1$ //$NON-NLS-2$
                        Messages.TraceControl_UnexpectedPathError + ": " + name); //$NON-NLS-1$
            }

            if (sessionInfo.isSnapshotSession()) {
                sessionInfo.setStreamedTrace(false);
            } else {
                sessionInfo.setSessionPath(path);
                // Check file protocol
                Matcher matcher = LTTngControlServiceConstants.TRACE_FILE_PROTOCOL_PATTERN.matcher(path);
                if (matcher.matches()) {
                    sessionInfo.setStreamedTrace(false);
                }
            }
        }

        // When using controlUrl and dataUrl the full session path is not known
        // yet and will be set later on when listing the session

        return sessionInfo;
    }

    /**
     * Basic generation of command for streamed session creation
     *
     * @param sessionInfo
     *            the session to create
     * @return the basic command for command creation
     */
    protected @NonNull ICommandInput prepareStreamedSessionCreationCommand(ISessionInfo sessionInfo) {
        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_CREATE_SESSION);
        if (!sessionInfo.getName().isEmpty()) {
            command.add(sessionInfo.getName());
        }

        if (sessionInfo.isSnapshotSession()) {
            command.add(LTTngControlServiceConstants.OPTION_SNAPSHOT);
        } else if (sessionInfo.isLive()) {
            command.add(LTTngControlServiceConstants.OPTION_LIVE);
            if (sessionInfo.getLiveDelay() != LTTngControlServiceConstants.UNUSED_VALUE) {
                command.add(String.valueOf(sessionInfo.getLiveDelay()));
            }
        }

        if (sessionInfo.getNetworkUrl() != null) {
            command.add(LTTngControlServiceConstants.OPTION_NETWORK_URL);
            command.add(sessionInfo.getNetworkUrl());
        } else {
            command.add(LTTngControlServiceConstants.OPTION_CONTROL_URL);
            command.add(sessionInfo.getControlUrl());

            command.add(LTTngControlServiceConstants.OPTION_DATA_URL);
            command.add(sessionInfo.getDataUrl());
        }
        return command;
    }

    @Override
    public void destroySession(String sessionName, IProgressMonitor monitor) throws ExecutionException {

        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_DESTROY_SESSION, sessionName);

        ICommandResult result = executeCommand(command, monitor, false);
        boolean isError = isError(result);
        if (isError && !ignoredPattern(result.getErrorOutput(), LTTngControlServiceConstants.SESSION_NOT_FOUND_ERROR_PATTERN)) {
            throw new ExecutionException(Messages.TraceControl_CommandError + " " + command.toString() + "\n" + result.toString()); //$NON-NLS-1$ //$NON-NLS-2$
        }

        // Session <sessionName> destroyed
    }

    @Override
    public void startSession(String sessionName, IProgressMonitor monitor) throws ExecutionException {

        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_START_SESSION, sessionName);

        executeCommand(command, monitor);

        // Session <sessionName> started
    }

    @Override
    public void stopSession(String sessionName, IProgressMonitor monitor) throws ExecutionException {
        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_STOP_SESSION, sessionName);

        executeCommand(command, monitor);

        // Session <sessionName> stopped

    }

    @Override
    public void enableChannels(String sessionName, List<String> channelNames, TraceDomainType domain, IChannelInfo info, IProgressMonitor monitor) throws ExecutionException {

        // no channels to enable
        if (channelNames.isEmpty()) {
            return;
        }

        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_ENABLE_CHANNEL);

        command.add(toCsv(channelNames));

        command.add(getDomainOption(domain));

        command.add(LTTngControlServiceConstants.OPTION_SESSION);
        command.add(sessionName);

        if (info != null) {
            // --discard Discard event when buffers are full (default)

            // --overwrite Flight recorder mode
            if (info.isOverwriteMode()) {
                command.add(LTTngControlServiceConstants.OPTION_OVERWRITE);
            }
            // --subbuf-size SIZE Subbuffer size in bytes
            // (default: 4096, kernel default: 262144)
            if (info.getSubBufferSize() != LTTngControlServiceConstants.UNUSED_VALUE) {
                command.add(LTTngControlServiceConstants.OPTION_SUB_BUFFER_SIZE);
                command.add(String.valueOf(info.getSubBufferSize()));
            }

            // --num-subbuf NUM Number of subbufers
            if (info.getNumberOfSubBuffers() != LTTngControlServiceConstants.UNUSED_VALUE) {
                command.add(LTTngControlServiceConstants.OPTION_NUM_SUB_BUFFERS);
                command.add(String.valueOf(info.getNumberOfSubBuffers()));
            }

            // --switch-timer USEC Switch timer interval in usec
            if (info.getSwitchTimer() != LTTngControlServiceConstants.UNUSED_VALUE) {
                command.add(LTTngControlServiceConstants.OPTION_SWITCH_TIMER);
                command.add(String.valueOf(info.getSwitchTimer()));
            }

            // --read-timer USEC Read timer interval in usec
            if (info.getReadTimer() != LTTngControlServiceConstants.UNUSED_VALUE) {
                command.add(LTTngControlServiceConstants.OPTION_READ_TIMER);
                command.add(String.valueOf(info.getReadTimer()));
            }

            if (isVersionSupported("2.2.0")) { //$NON-NLS-1$
                // --buffers-uid Every application sharing the same UID use the
                // same buffers --buffers-pid Buffers are allocated per PID
                if (domain.equals(TraceDomainType.UST)) {
                    if (info.getBufferType() == BufferType.BUFFER_PER_PID) {
                        command.add(LTTngControlServiceConstants.OPTION_PER_PID_BUFFERS);

                    } else if (info.getBufferType() == BufferType.BUFFER_PER_UID) {
                        command.add(LTTngControlServiceConstants.OPTION_PER_UID_BUFFERS);
                    }
                }

                // -C SIZE Maximum size of trace files in bytes
                if (info.getMaxSizeTraceFiles() != LTTngControlServiceConstants.UNUSED_VALUE) {
                    command.add(LTTngControlServiceConstants.OPTION_MAX_SIZE_TRACE_FILES);
                    command.add(String.valueOf(info.getMaxSizeTraceFiles()));
                }

                // -W NUM Maximum number of trace files
                if (info.getMaxNumberTraceFiles() != LTTngControlServiceConstants.UNUSED_VALUE) {
                    command.add(LTTngControlServiceConstants.OPTION_MAX_TRACE_FILES);
                    command.add(String.valueOf(info.getMaxNumberTraceFiles()));
                }
            }
        }

        executeCommand(command, monitor);

    }

    @Override
    public void disableChannels(String sessionName, List<String> channelNames, TraceDomainType domain, IProgressMonitor monitor) throws ExecutionException {

        // no channels to enable
        if (channelNames.isEmpty()) {
            return;
        }

        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_DISABLE_CHANNEL);

        command.add(toCsv(channelNames));

        command.add(getDomainOption(domain));

        command.add(LTTngControlServiceConstants.OPTION_SESSION);
        command.add(sessionName);

        executeCommand(command, monitor);
    }

    @Override
    public void enableEvents(String sessionName, String channelName, List<String> eventNames, TraceDomainType domain, String filterExpression, List<String> excludedEvents, IProgressMonitor monitor) throws ExecutionException {

        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_ENABLE_EVENT);
        boolean isAllEvents = ALL_EVENTS.equals(eventNames);

        if (isAllEvents || (eventNames == null) || (eventNames.isEmpty())) {
            command.add(LTTngControlServiceConstants.OPTION_ALL);
        } else {
            command.add(toCsv(eventNames));
        }

        command.add(getDomainOption(domain));

        command.add(LTTngControlServiceConstants.OPTION_SESSION);
        command.add(sessionName);

        if (channelName != null) {
            command.add(LTTngControlServiceConstants.OPTION_CHANNEL);
            command.add(channelName);
        }

        if (!isAllEvents) {
            command.add(LTTngControlServiceConstants.OPTION_TRACEPOINT);
        }

        if (filterExpression != null) {
            command.add(LTTngControlServiceConstants.OPTION_FILTER);
            command.add(filterExpression);
        }

        if (excludedEvents != null && !excludedEvents.isEmpty()) {
            command.add(LTTngControlServiceConstants.OPTION_EXCLUDE);
            command.add(toCsv(excludedEvents));
        }

        executeCommand(command, monitor);
    }

    @Override
    public void enableSyscalls(String sessionName, String channelName, List<String> syscallNames, IProgressMonitor monitor) throws ExecutionException {

        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_ENABLE_EVENT);

        boolean isAllSyscalls = ALL_EVENTS.equals(syscallNames);

        if (isAllSyscalls || (syscallNames == null) || (syscallNames.isEmpty())) {
            command.add(LTTngControlServiceConstants.OPTION_ALL);
        } else {
            command.add(toCsv(syscallNames));
        }

        command.add(LTTngControlServiceConstants.OPTION_KERNEL);

        command.add(LTTngControlServiceConstants.OPTION_SESSION);
        command.add(sessionName);

        if (channelName != null) {
            command.add(LTTngControlServiceConstants.OPTION_CHANNEL);
            command.add(channelName);
        }

        command.add(LTTngControlServiceConstants.OPTION_SYSCALL);

        executeCommand(command, monitor);
    }

    @Override
    public void enableProbe(String sessionName, String channelName, String eventName, boolean isFunction, String probe, IProgressMonitor monitor) throws ExecutionException {
        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_ENABLE_EVENT);

        command.add(eventName);
        command.add(LTTngControlServiceConstants.OPTION_KERNEL);

        command.add(LTTngControlServiceConstants.OPTION_SESSION);
        command.add(sessionName);

        if (channelName != null) {
            command.add(LTTngControlServiceConstants.OPTION_CHANNEL);
            command.add(channelName);
        }
        if (isFunction) {
            command.add(LTTngControlServiceConstants.OPTION_FUNCTION_PROBE);
        } else {
            command.add(LTTngControlServiceConstants.OPTION_PROBE);
        }

        command.add(probe);

        executeCommand(command, monitor);
    }

    @Override
    public void enableLogLevel(String sessionName, String channelName, List<String> eventNames, LogLevelType logLevelType, ITraceLogLevel level, String filterExpression, TraceDomainType domain, IProgressMonitor monitor) throws ExecutionException {
        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_ENABLE_EVENT);

        // Checking if we should enable all events (with option '-a')
        boolean isAllEvents = ALL_EVENTS.equals(eventNames);

        if (isAllEvents || (eventNames == null) || (eventNames.isEmpty())) {
            command.add(LTTngControlServiceConstants.OPTION_ALL);
        } else {
            command.add(toCsv(eventNames));
        }

        command.add(getDomainOption(domain));

        command.add(LTTngControlServiceConstants.OPTION_SESSION);
        command.add(sessionName);

        if (channelName != null) {
            command.add(LTTngControlServiceConstants.OPTION_CHANNEL);
            command.add(channelName);
        }

        if (logLevelType == LogLevelType.LOGLEVEL) {
            command.add(LTTngControlServiceConstants.OPTION_LOGLEVEL);
        } else if (logLevelType == LogLevelType.LOGLEVEL_ONLY) {
            command.add(LTTngControlServiceConstants.OPTION_LOGLEVEL_ONLY);
        } else {
            return;
        }
        command.add(level.getInName());
        executeCommand(command, monitor);
    }

    @Override
    public void disableEvent(String sessionName, String channelName, List<String> eventNames, TraceDomainType domain, IProgressMonitor monitor) throws ExecutionException {
        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_DISABLE_EVENT);

        if (eventNames == null) {
            command.add(LTTngControlServiceConstants.OPTION_ALL);
        } else {
            // no events to disable
            if (eventNames.isEmpty()) {
                return;
            }

            StringBuffer eventNameParameter = new StringBuffer();
            for (Iterator<String> iterator = eventNames.iterator(); iterator.hasNext();) {
                String event = iterator.next();
                eventNameParameter.append(event);
                if (iterator.hasNext()) {
                    eventNameParameter.append(',');
                }
            }
            command.add(eventNameParameter.toString());
        }

        command.add(getDomainOption(domain));

        command.add(LTTngControlServiceConstants.OPTION_SESSION);
        command.add(sessionName);

        if (channelName != null) {
            command.add(LTTngControlServiceConstants.OPTION_CHANNEL);
            command.add(channelName);
        }

        executeCommand(command, monitor);
    }

    @Override
    public List<String> getContextList(IProgressMonitor monitor) throws ExecutionException {

        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_ADD_CONTEXT, LTTngControlServiceConstants.OPTION_HELP);

        ICommandResult result = executeCommand(command, monitor);

        List<String> output = result.getOutput();

        List<String> contexts = new ArrayList<>(0);

        int index = 0;
        boolean inList = false;
        while (index < output.size()) {
            String line = output.get(index);

            Matcher startMatcher = LTTngControlServiceConstants.ADD_CONTEXT_HELP_CONTEXTS_INTRO.matcher(line);
            Matcher endMatcher = LTTngControlServiceConstants.ADD_CONTEXT_HELP_CONTEXTS_END_LINE.matcher(line);

            if (startMatcher.matches()) {
                inList = true;
            } else if (endMatcher.matches()) {
                break;
            } else if (inList) {
                String[] tmp = line.split(","); //$NON-NLS-1$
                for (int i = 0; i < tmp.length; i++) {
                    contexts.add(tmp[i].trim());
                }
            }
            index++;
        }
        return contexts;
    }

    @Override
    public void addContexts(String sessionName, String channelName, String eventName, TraceDomainType domain, List<String> contextNames, IProgressMonitor monitor) throws ExecutionException {
        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_ADD_CONTEXT);

        command.add(LTTngControlServiceConstants.OPTION_SESSION);
        command.add(sessionName);

        if (channelName != null) {
            command.add(LTTngControlServiceConstants.OPTION_CHANNEL);
            command.add(channelName);
        }

        if (eventName != null) {
            command.add(LTTngControlServiceConstants.OPTION_EVENT);
            command.add(eventName);
        }

        command.add(getDomainOption(domain));

        for (Iterator<String> iterator = contextNames.iterator(); iterator.hasNext();) {
            String context = iterator.next();
            command.add(LTTngControlServiceConstants.OPTION_CONTEXT_TYPE);
            command.add(context);
        }

        executeCommand(command, monitor);

    }

    @Override
    public void recordSnapshot(String sessionName, IProgressMonitor monitor)
            throws ExecutionException {
        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_SNAPSHOT, LTTngControlServiceConstants.COMMAND_RECORD_SNAPSHOT);

        String newSessionName = sessionName;
        command.add(LTTngControlServiceConstants.OPTION_SESSION);
        command.add(newSessionName);

        executeCommand(command, monitor);
    }

    @Override
    public void loadSession(String inputPath, boolean isForce, IProgressMonitor monitor)
            throws ExecutionException {
        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_LOAD_SESSION);

        if (inputPath != null) {
            command.add(LTTngControlServiceConstants.OPTION_INPUT_PATH);
            command.add(inputPath);
        }

        if (isForce) {
            command.add(LTTngControlServiceConstants.OPTION_FORCE);
        }
        executeCommand(command, monitor);
    }

    @Override
    public void saveSession(String session, String outputPath, boolean isForce, IProgressMonitor monitor) throws ExecutionException {
        ICommandInput command = createCommand(LTTngControlServiceConstants.COMMAND_SAVE_SESSION);

        if (outputPath != null) {
            command.add(LTTngControlServiceConstants.OPTION_OUTPUT_PATH);
            command.add(outputPath);
        }

        if (isForce) {
            command.add(LTTngControlServiceConstants.OPTION_FORCE);
        }

        if (session != null) {
            command.add(session);
        }
        executeCommand(command, monitor);
    }

    @Override
    public void runCommands(IProgressMonitor monitor, List<String> commandLines) throws ExecutionException {
        for (String commandLine : commandLines) {
            if (monitor.isCanceled()) {
                return;
            }

            if (commandLine.isEmpty() || commandLine.startsWith("#")) { //$NON-NLS-1$
                continue;
            }
            String[] args = commandLine.split("\\s+"); //$NON-NLS-1$
            ICommandInput command = fCommandShell.createCommand();
            command.addAll(Arrays.asList(args));
            ICommandResult result = executeCommand(command, monitor);

            if (isError(result)) {
                throw new ExecutionException(Messages.TraceControl_CommandError + " " + command.toString() + "\n" + result.toString()); //$NON-NLS-1$ //$NON-NLS-2$
            }
        }
    }

    // ------------------------------------------------------------------------
    // Helper methods
    // ------------------------------------------------------------------------

    private static @NonNull String getDomainOption(TraceDomainType domain) {
        switch (domain) {
        case KERNEL:
            return LTTngControlServiceConstants.OPTION_KERNEL;
        case UST:
            return LTTngControlServiceConstants.OPTION_UST;
        case JUL:
            return LTTngControlServiceConstants.OPTION_JUL;
        case LOG4J:
            return LTTngControlServiceConstants.OPTION_LOG4J;
        case PYTHON:
            return LTTngControlServiceConstants.OPTION_PYTHON;
        case UNKNOWN:
        default:
            return TraceDomainType.UNKNOWN.name();
        }
    }

    /**
     * Checks if command result is an error result.
     *
     * @param result
     *            - the command result to check
     * @return true if error else false
     */
    protected boolean isError(ICommandResult result) {
        // Check return code and length of returned strings

        if ((result.getResult()) != 0) {
            return true;
        }

        // Look for error pattern
        for (String line : result.getErrorOutput()) {
            Matcher matcher = LTTngControlServiceConstants.ERROR_PATTERN.matcher(line);
            if (matcher.matches()) {
                return true;
            }
        }

        return false;
    }

    /**
     * Creates a comma separated string from list of names
     *
     * @param names
     *          List of name to convert
     * @return comma separated string
     */
    protected String toCsv(List<String> names) {
        StringBuilder csvString = new StringBuilder();
        for (Iterator<String> iterator = names.iterator(); iterator.hasNext();) {
            String name = iterator.next();
            csvString.append(name);
            if (iterator.hasNext()) {
                csvString.append(',');
            }
        }
        return csvString.toString();
    }

    /**
     * Parses the domain information.
     *
     * @param output
     *            a command output list
     * @param currentIndex
     *            current index in command output list
     * @param channels
     *            list for returning channel information
     * @param domainInfo
     *            The domain information
     * @return the new current index in command output list
     */
    protected int parseDomain(List<String> output, int currentIndex, List<IChannelInfo> channels, IDomainInfo domainInfo) {
        int index = currentIndex;

        // if kernel set the buffer type to shared
        if (domainInfo.getDomain().equals(TraceDomainType.KERNEL)) {
            domainInfo.setBufferType(BufferType.BUFFER_SHARED);
        }

        // Channels:
        // -------------
        // - channnel1: [enabled]
        //
        // Attributes:
        // overwrite mode: 0
        // subbufers size: 262144
        // number of subbufers: 4
        // switch timer interval: 0
        // read timer interval: 200
        // output: splice()

        while (index < output.size()) {
            String line = output.get(index);

            if (isVersionSupported("2.2.0")) { //$NON-NLS-1$
                Matcher bufferTypeMatcher = LTTngControlServiceConstants.BUFFER_TYPE_PATTERN.matcher(line);
                if (bufferTypeMatcher.matches()) {
                    String bufferTypeString = getAttributeValue(line);
                    if (BufferType.BUFFER_PER_PID.getInName().equals(bufferTypeString)) {
                        domainInfo.setBufferType(BufferType.BUFFER_PER_PID);
                    } else if (BufferType.BUFFER_PER_UID.getInName().equals(bufferTypeString)) {
                        domainInfo.setBufferType(BufferType.BUFFER_PER_UID);
                    } else {
                        domainInfo.setBufferType(BufferType.BUFFER_TYPE_UNKNOWN);
                    }
                }
            } else {
                domainInfo.setBufferType(BufferType.BUFFER_TYPE_UNKNOWN);
            }
            Matcher outerMatcher = LTTngControlServiceConstants.CHANNELS_SECTION_PATTERN.matcher(line);
            Matcher noKernelChannelMatcher = LTTngControlServiceConstants.DOMAIN_NO_KERNEL_CHANNEL_PATTERN.matcher(line);
            Matcher noUstChannelMatcher = LTTngControlServiceConstants.DOMAIN_NO_UST_CHANNEL_PATTERN.matcher(line);
            if (outerMatcher.matches()) {
                IChannelInfo channelInfo = null;
                while (index < output.size()) {
                    String subLine = output.get(index);

                    Matcher innerMatcher = LTTngControlServiceConstants.CHANNEL_PATTERN.matcher(subLine);
                    if (innerMatcher.matches()) {
                        channelInfo = new ChannelInfo(""); //$NON-NLS-1$
                        // get channel name
                        channelInfo.setName(innerMatcher.group(1));

                        // get channel enablement
                        channelInfo.setState(innerMatcher.group(2));

                        // set BufferType
                        channelInfo.setBufferType(domainInfo.getBufferType());

                        // add channel
                        channels.add(channelInfo);

                    } else if (LTTngControlServiceConstants.OVERWRITE_MODE_ATTRIBUTE.matcher(subLine).matches()) {
                        String value = getAttributeValue(subLine);
                        if (channelInfo != null) {
                            channelInfo.setOverwriteMode(!LTTngControlServiceConstants.OVERWRITE_MODE_ATTRIBUTE_FALSE.equals(value));
                        }
                    } else if (LTTngControlServiceConstants.SUBBUFFER_SIZE_ATTRIBUTE.matcher(subLine).matches()) {
                        if (channelInfo != null) {
                            channelInfo.setSubBufferSize(Long.valueOf(getAttributeValue(subLine)));
                        }

                    } else if (LTTngControlServiceConstants.NUM_SUBBUFFERS_ATTRIBUTE.matcher(subLine).matches()) {
                        if (channelInfo != null) {
                            channelInfo.setNumberOfSubBuffers(Integer.valueOf(getAttributeValue(subLine)));
                        }

                    } else if (LTTngControlServiceConstants.SWITCH_TIMER_ATTRIBUTE.matcher(subLine).matches()) {
                        if (channelInfo != null) {
                            channelInfo.setSwitchTimer(Long.valueOf(getAttributeValue(subLine)));
                        }

                    } else if (LTTngControlServiceConstants.READ_TIMER_ATTRIBUTE.matcher(subLine).matches()) {
                        if (channelInfo != null) {
                            channelInfo.setReadTimer(Long.valueOf(getAttributeValue(subLine)));
                        }

                    } else if (LTTngControlServiceConstants.OUTPUT_ATTRIBUTE.matcher(subLine).matches()) {
                        if (channelInfo != null) {
                            channelInfo.setOutputType(getAttributeValue(subLine));
                        }

                    } else if (LTTngControlServiceConstants.TRACE_FILE_COUNT_ATTRIBUTE.matcher(subLine).matches()) {
                        if (channelInfo != null) {
                            channelInfo.setMaxNumberTraceFiles(Integer.valueOf(getAttributeValue(subLine)));
                        }

                    } else if (LTTngControlServiceConstants.TRACE_FILE_SIZE_ATTRIBUTE.matcher(subLine).matches()) {
                        if (channelInfo != null) {
                            channelInfo.setMaxSizeTraceFiles(Long.valueOf(getAttributeValue(subLine)));
                        }
                    } else if (LTTngControlServiceConstants.EVENT_SECTION_PATTERN.matcher(subLine).matches()) {
                        List<IEventInfo> events = new ArrayList<>();
                        index = parseEvents(output, index, events);
                        if (channelInfo != null) {
                            channelInfo.setEvents(events);
                        }
                        // we want to stay at the current index to be able to
                        // exit the domain
                        continue;
                    } else if (LTTngControlServiceConstants.DOMAIN_KERNEL_PATTERN.matcher(subLine).matches()) {
                        return index;

                    } else if (LTTngControlServiceConstants.DOMAIN_UST_GLOBAL_PATTERN.matcher(subLine).matches()) {
                        return index;
                    }
                    index++;
                }
            } else if (noKernelChannelMatcher.matches() || noUstChannelMatcher.matches()) {
                // domain indicates that no channels were found -> return
                index++;
                return index;
            }
            index++;
        }
        return index;
    }

    /**
     * Parses the event information within a domain.
     *
     * @param output
     *            a command output list
     * @param currentIndex
     *            current index in command output list
     * @param events
     *            list for returning event information
     * @return the new current index in command output list
     */
    protected int parseEvents(List<String> output, int currentIndex, List<IEventInfo> events) {
        int index = currentIndex;

        while (index < output.size()) {
            String line = output.get(index);
            if (LTTngControlServiceConstants.CHANNEL_PATTERN.matcher(line).matches()) {
                // end of channel
                return index;
            } else if (LTTngControlServiceConstants.DOMAIN_KERNEL_PATTERN.matcher(line).matches()) {
                // end of domain
                return index;
            } else if (LTTngControlServiceConstants.DOMAIN_UST_GLOBAL_PATTERN.matcher(line).matches()) {
                // end of domain
                return index;
            }

            Matcher matcher = LTTngControlServiceConstants.EVENT_PATTERN.matcher(line);
            Matcher matcher2 = LTTngControlServiceConstants.WILDCARD_EVENT_PATTERN.matcher(line);

            if (matcher.matches()) {
                IEventInfo eventInfo = new EventInfo(matcher.group(1).trim());
                eventInfo.setLogLevelType(matcher.group(2).trim());
                eventInfo.setLogLevel(matcher.group(3).trim());
                eventInfo.setEventType(matcher.group(4).trim());
                eventInfo.setState(matcher.group(5));
                if (("[" + LTTngControlServiceConstants.HAS_EXCLUSIONS + "]").equals(matcher.group(6))) { //$NON-NLS-1$ //$NON-NLS-2$
                    eventInfo.setExcludedEvents(LTTngControlServiceConstants.HAS_EXCLUSIONS);
                }
                if (("[" + LTTngControlServiceConstants.WITH_FILTER + "]").equals(matcher.group(7))) { //$NON-NLS-1$ //$NON-NLS-2$
                    eventInfo.setFilterExpression(LTTngControlServiceConstants.WITH_FILTER);
                }
                events.add(eventInfo);
                index++;
            } else if (matcher2.matches()) {
                IEventInfo eventInfo = new EventInfo(matcher2.group(1).trim());
                eventInfo.setLogLevel(TraceLogLevel.LEVEL_UNKNOWN);
                eventInfo.setEventType(matcher2.group(2).trim());
                eventInfo.setState(matcher2.group(3));
                if (("[" + LTTngControlServiceConstants.HAS_EXCLUSIONS + "]").equals(matcher2.group(4))) { //$NON-NLS-1$ //$NON-NLS-2$
                    eventInfo.setExcludedEvents(LTTngControlServiceConstants.HAS_EXCLUSIONS);
                }
                if (("[" + LTTngControlServiceConstants.WITH_FILTER + "]").equals(matcher2.group(5))) { //$NON-NLS-1$ //$NON-NLS-2$
                    eventInfo.setFilterExpression(LTTngControlServiceConstants.WITH_FILTER);
                }

                if ((eventInfo.getEventType() == TraceEventType.PROBE) ||
                        (eventInfo.getEventType() == TraceEventType.FUNCTION)) {
                    IProbeEventInfo probeEvent = new ProbeEventInfo(eventInfo.getName());
                    probeEvent.setLogLevel(eventInfo.getLogLevel());
                    probeEvent.setEventType(eventInfo.getEventType());
                    probeEvent.setState(eventInfo.getState());

                    // Overwrite eventinfo
                    eventInfo = probeEvent;

                    // myevent2 (type: probe) [enabled]
                    // addr: 0xc0101340
                    // myevent0 (type: function) [enabled]
                    // offset: 0x0
                    // symbol: init_post
                    index++;
                    while (index < output.size()) {
                        String probeLine = output.get(index);
                        // parse probe
                        Matcher addrMatcher = LTTngControlServiceConstants.PROBE_ADDRESS_PATTERN.matcher(probeLine);
                        Matcher offsetMatcher = LTTngControlServiceConstants.PROBE_OFFSET_PATTERN.matcher(probeLine);
                        Matcher symbolMatcher = LTTngControlServiceConstants.PROBE_SYMBOL_PATTERN.matcher(probeLine);
                        if (addrMatcher.matches()) {
                            String addr = addrMatcher.group(2).trim();
                            probeEvent.setAddress(addr);
                        } else if (offsetMatcher.matches()) {
                            String offset = offsetMatcher.group(2).trim();
                            probeEvent.setOffset(offset);
                        } else if (symbolMatcher.matches()) {
                            String symbol = symbolMatcher.group(2).trim();
                            probeEvent.setSymbol(symbol);
                        } else if ((LTTngControlServiceConstants.EVENT_PATTERN.matcher(probeLine).matches()) || (LTTngControlServiceConstants.WILDCARD_EVENT_PATTERN.matcher(probeLine).matches())) {
                            break;
                        } else if (LTTngControlServiceConstants.CHANNEL_PATTERN.matcher(probeLine).matches()) {
                            break;
                        } else if (LTTngControlServiceConstants.DOMAIN_KERNEL_PATTERN.matcher(probeLine).matches()) {
                            // end of domain
                            break;
                        } else if (LTTngControlServiceConstants.DOMAIN_UST_GLOBAL_PATTERN.matcher(probeLine).matches()) {
                            // end of domain
                            break;
                        }
                        index++;
                    }
                    events.add(eventInfo);
                } else {
                    events.add(eventInfo);
                    index++;
                    continue;
                }
            } else {
                index++;
            }
        }

        return index;
    }

    /**
     * Parses a line with attributes: <attribute Name>: <attribute value>
     *
     * @param line
     *            - attribute line to parse
     * @return the attribute value as string
     */
    protected String getAttributeValue(String line) {
        String[] temp = line.split("\\: "); //$NON-NLS-1$
        return temp[1];
    }

    /**
     * Parses the event information within a provider.
     *
     * @param output
     *            a command output list
     * @param currentIndex
     *            current index in command output list
     * @param events
     *            list for returning event information
     * @return the new current index in command output list
     */
    protected int getProviderEventInfo(List<String> output, int currentIndex, List<IBaseEventInfo> events) {
        int index = currentIndex;
        IBaseEventInfo eventInfo = null;
        while (index < output.size()) {
            String line = output.get(index);
            Matcher matcher = LTTngControlServiceConstants.PROVIDER_EVENT_PATTERN.matcher(line);
            if (matcher.matches()) {
                // sched_kthread_stop (loglevel: TRACE_EMERG0) (type:
                // tracepoint)
                eventInfo = new BaseEventInfo(matcher.group(1).trim());
                eventInfo.setLogLevel(matcher.group(2).trim());
                eventInfo.setEventType(matcher.group(3).trim());
                events.add(eventInfo);
                index++;
            } else if (LTTngControlServiceConstants.EVENT_FIELD_PATTERN.matcher(line).matches()) {
                if (eventInfo != null) {
                    List<IFieldInfo> fields = new ArrayList<>();
                    index = getFieldInfo(output, index, fields);
                    eventInfo.setFields(fields);
                } else {
                    index++;
                }
            } else if (LTTngControlServiceConstants.UST_PROVIDER_PATTERN.matcher(line).matches()) {
                return index;
            } else {
                index++;
            }
        }
        return index;
    }

    /**
     * Parse a field's information.
     *
     * @param output
     *            A command output list
     * @param currentIndex
     *            The current index in the command output list
     * @param fields
     *            List for returning the field information
     * @return The new current index in the command output list
     */
    protected int getFieldInfo(List<String> output, int currentIndex, List<IFieldInfo> fields) {
        int index = currentIndex;
        IFieldInfo fieldInfo = null;
        while (index < output.size()) {
            String line = output.get(index);
            Matcher matcher = LTTngControlServiceConstants.EVENT_FIELD_PATTERN.matcher(line);
            if (matcher.matches()) {
                // field: content (string)
                fieldInfo = new FieldInfo(matcher.group(2).trim());
                fieldInfo.setFieldType(matcher.group(3).trim());
                fields.add(fieldInfo);
            } else if (LTTngControlServiceConstants.PROVIDER_EVENT_PATTERN.matcher(line).matches()) {
                return index;
            } else if (LTTngControlServiceConstants.UST_PROVIDER_PATTERN.matcher(line).matches()) {
                return index;
            }
            index++;
        }
        return index;
    }

    /**
     * Creates a command input instance
     *
     * @param segments
     *            array of string that makes up a command line
     * @return {@link ICommandInput} instance
     */
    protected @NonNull ICommandInput createCommand(String... segments) {
        ICommandInput command = fCommandShell.createCommand();
        command.add(LTTngControlServiceConstants.CONTROL_COMMAND);
        List<@NonNull String> groupOption = getTracingGroupOption();
        if (!groupOption.isEmpty()) {
            command.addAll(groupOption);
        }
        String verboseOption = getVerboseOption();
        if (!verboseOption.isEmpty()) {
            command.add(verboseOption);
        }
        for (String string : segments) {
            command.add(checkNotNull(string));
        }
        return command;
    }

    /**
     * @return the tracing group option if configured in the preferences
     */
    protected @NonNull List<@NonNull String> getTracingGroupOption() {
        List<@NonNull String> groupOption = new ArrayList<>();
        if (!ControlPreferences.getInstance().isDefaultTracingGroup() && !ControlPreferences.getInstance().getTracingGroup().equals("")) { //$NON-NLS-1$
            groupOption.add(LTTngControlServiceConstants.OPTION_TRACING_GROUP);
            groupOption.add(ControlPreferences.getInstance().getTracingGroup());
        }
        return groupOption;
    }

    /**
     * @return the verbose option as configured in the preferences
     */
    protected String getVerboseOption() {
        if (ControlPreferences.getInstance().isLoggingEnabled()) {
            String level = ControlPreferences.getInstance().getVerboseLevel();
            if (ControlPreferences.TRACE_CONTROL_VERBOSE_LEVEL_VERBOSE.equals(level)) {
                return LTTngControlServiceConstants.OPTION_VERBOSE;
            }
            if (ControlPreferences.TRACE_CONTROL_VERBOSE_LEVEL_V_VERBOSE.equals(level)) {
                return LTTngControlServiceConstants.OPTION_VERY_VERBOSE;
            }
            if (ControlPreferences.TRACE_CONTROL_VERBOSE_LEVEL_V_V_VERBOSE.equals(level)) {
                return LTTngControlServiceConstants.OPTION_VERY_VERY_VERBOSE;
            }
        }
        return ""; //$NON-NLS-1$
    }

    /**
     * Method that logs the command and command result if logging is enabled as
     * well as forwards the command execution to the shell.
     *
     * @param command
     *            - the command to execute
     * @param monitor
     *            - a progress monitor
     * @return the command result
     * @throws ExecutionException
     *             If the command fails
     */
    protected ICommandResult executeCommand(@NonNull ICommandInput command,
            @Nullable IProgressMonitor monitor) throws ExecutionException {
        return executeCommand(command, monitor, true);
    }

    /**
     * Method that logs the command and command result if logging is enabled as
     * well as forwards the command execution to the shell.
     *
     * @param command
     *            - the command to execute
     * @param monitor
     *            - a progress monitor
     * @param checkForError
     *            - true to verify command result, else false
     * @return the command result
     * @throws ExecutionException
     *             in case of error result
     */
    protected ICommandResult executeCommand(@NonNull ICommandInput command,
            @Nullable IProgressMonitor monitor, boolean checkForError)
            throws ExecutionException {
        if (ControlPreferences.getInstance().isLoggingEnabled()) {
            ControlCommandLogger.log(command.toString());
        }

        ICommandResult result = fCommandShell.executeCommand(command, monitor);

        if (ControlPreferences.getInstance().isLoggingEnabled()) {
            ControlCommandLogger.log(result.toString());
        }

        if (checkForError && isError(result)) {
            throw new ExecutionException(Messages.TraceControl_CommandError
                    + " " + command.toString() + "\n" + result.toString()); //$NON-NLS-1$ //$NON-NLS-2$
        }

        return result;
    }
}
