/**********************************************************************
 * Copyright (c) 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:
 *   Bruno Roy - Initial API and implementation
 **********************************************************************/
package org.eclipse.tracecompass.internal.lttng2.control.ui.views.dialogs;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CCombo;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
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.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.ITraceLogLevel;
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.TraceJulLogLevel;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.TraceLog4jLogLevel;
import org.eclipse.tracecompass.internal.lttng2.control.core.model.TracePythonLogLevel;
import org.eclipse.tracecompass.internal.lttng2.control.ui.views.messages.Messages;
import org.eclipse.tracecompass.internal.lttng2.control.ui.views.model.ITraceControlComponent;
import org.eclipse.tracecompass.internal.lttng2.control.ui.views.model.impl.BaseLoggerComponent;
import org.eclipse.tracecompass.internal.lttng2.control.ui.views.model.impl.TargetNodeComponent;
import org.eclipse.tracecompass.internal.lttng2.control.ui.views.model.impl.TraceControlContentProvider;
import org.eclipse.tracecompass.internal.lttng2.control.ui.views.model.impl.TraceControlLabelProvider;
import org.eclipse.tracecompass.internal.lttng2.control.ui.views.model.impl.TraceProviderGroup;
import org.eclipse.tracecompass.internal.lttng2.control.ui.views.model.impl.UstProviderComponent;
import org.eclipse.ui.dialogs.FilteredTree;
import org.eclipse.ui.dialogs.PatternFilter;

/**
 * A composite for collecting information about loggers to be enabled.
 *
 * @author Bruno Roy
 */
public class EnableLoggersComposite extends Composite implements IBaseEnableUstEvents {

    // ------------------------------------------------------------------------
    // Constants
    // ------------------------------------------------------------------------

    private enum GroupEnum { LOGGERS }

    // ------------------------------------------------------------------------
    // Attributes
    // ------------------------------------------------------------------------

    /**
     * The referenced trace provider group containing the loggers providers
     * component which contains a list of available tracepoints.
     */
    private final TraceProviderGroup fProviderGroup;
    /**
     * A button to enable/disable the loggers group
     */
    private Button fLoggersActivateButton;
    /**
     * A tree viewer for displaying and selection of available loggers.
     */
    private CheckboxTreeViewer fLoggersViewer;
    /**
     * A Text field for the specific logger name.
     */
    private Text fSpecificLoggerText;
    /**
     * The flag indicating that loggers are selected.
     */
    private boolean fIsLoggers;
    /**
     * The list of loggers to be enabled.
     */
    private List<String> fLoggers;
    /**
     * A button to enable/disable the log level group
     */
    private Button fLogLevelActivateButton;
    /**
     * A Combo box for selecting the log level.
     */
    private CCombo fLogLevelCombo;
    /**
     * A button for selecting the log level (range 0 to level).
     */
    private Button fLogLevelButton;
    /**
     * A button for selecting the specified log level only.
     */
    private Button fLogLevelOnlyButton;
    /**
     * The flag indicating that all log level are selected.
     */
    private boolean fIsLogLevel;
    /**
     * The flag indicating that all loggers (across providers) are selected.
     */
    private boolean fIsAllLoggers;
    /**
     * The type of the log level (loglevel or loglevel-only)
     */
    private LogLevelType fLogLevelType;
    /**
     * The selected log level.
     */
    private ITraceLogLevel fLogLevel;
    /**
     * The domain of the loggers.
     */
    private final TraceDomainType fDomain;

    // ------------------------------------------------------------------------
    // Constructors
    // ------------------------------------------------------------------------
    /**
     * Constructor
     *
     * @param parent
     *            a parent composite
     * @param style
     *            a composite style
     * @param providerGroup
     *            the trace provider group
     * @param domain
     *            the domain of the loggers
     */
    public EnableLoggersComposite(Composite parent, int style, TraceProviderGroup providerGroup, TraceDomainType domain) {
        super(parent, style);
        fProviderGroup = providerGroup;
        fDomain = domain;
    }

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

    @Override
    public ITraceLogLevel getLogLevel() {
        return fLogLevel;
    }

    @Override
    public LogLevelType getLogLevelType() {
        return fLogLevelType;
    }

    @Override
    public boolean isAllTracePoints() {
        return fIsAllLoggers;
    }

    @Override
    public List<String> getEventNames() {
        return new ArrayList<>(fLoggers);
    }

    @Override
    public boolean isLogLevel() {
        return fIsLogLevel;
    }

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

    /**
     * Create the contents of this event composite
     */
    public void createContent() {

        // Logger group
        createLoggersGroup();

        // Log Level Group
        createLogLevelGroup();

        // Set default enablements
        setEnablements(GroupEnum.LOGGERS);
    }

    /**
     * Validates the loggers composite input data.
     *
     * @return true if configured data is valid and can be retrieved.
     */
    public boolean isValid() {

        fIsLoggers = fLoggersActivateButton.getSelection();
        fIsLogLevel = fLogLevelActivateButton.getSelection();

        // Initialize loggers fields
        fLoggers = new ArrayList<>();
        if (fIsLoggers) {
            Set<String> set = new HashSet<>();
            Object[] checkedElements = fLoggersViewer.getCheckedElements();
            int checkedNbLoggers = 0;
            for (int i = 0; i < checkedElements.length; i++) {
                ITraceControlComponent component = (ITraceControlComponent) checkedElements[i];
                if (component instanceof BaseLoggerComponent) {
                    checkedNbLoggers++;
                    if (!set.contains(component.getName())) {
                        set.add(component.getName());
                        fLoggers.add(component.getName());
                    }
                }
            }

            // verify if all events are selected
            int nbAvailableLoggers = 0;
            List<ITraceControlComponent> comps = fProviderGroup.getChildren(UstProviderComponent.class);
            for (ITraceControlComponent comp : comps) {
                // We want the children of each UST provider
                ITraceControlComponent[] events = comp.getChildren();
                for (ITraceControlComponent event : events) {
                    if (event instanceof BaseLoggerComponent && fDomain.equals(((BaseLoggerComponent)event).getDomain())) {
                        nbAvailableLoggers++;
                    }
                }

            }
            // Either all available loggers are selected or no loggers are available but All checkbox is selected
            fIsAllLoggers = ((checkedNbLoggers > 0) && (nbAvailableLoggers == checkedNbLoggers)) ||
                            ((nbAvailableLoggers == 0) && fLoggersViewer.getCheckedElements().length == 1);

            String tmpSpecificLogger = fSpecificLoggerText.getText();
            if (!fIsAllLoggers && !tmpSpecificLogger.trim().isEmpty()) {
                // Format the text to a List<String>
                // Removing all non visible characters
                tmpSpecificLogger = tmpSpecificLogger.replaceAll("\\s", ""); //$NON-NLS-1$ //$NON-NLS-2$
                // Splitting the different events that are separated by commas
                List<String> specificLoggerList = Arrays.asList(tmpSpecificLogger.split(",")); //$NON-NLS-1$
                fLoggers.addAll(specificLoggerList);
                fLoggers = fLoggers.stream().distinct().collect(Collectors.toList());
            }
        }

        // initialize log level event name string
        fLogLevelType = LogLevelType.LOGLEVEL_NONE;
        if (fIsLogLevel) {
            if (fLogLevelButton.getSelection()) {
                fLogLevelType = LogLevelType.LOGLEVEL;
            } else if (fLogLevelOnlyButton.getSelection()) {
                fLogLevelType = LogLevelType.LOGLEVEL_ONLY;
            }

            ITraceLogLevel[] levels = getLogLevelNames();
            int id = fLogLevelCombo.getSelectionIndex();

            if (id < 0) {
                MessageDialog.openError(getShell(),
                        Messages.TraceControl_EnableLoggersDialogTitle,
                        Messages.TraceControl_InvalidLogLevel);

                return false;
            }

            if (fLoggers.isEmpty() && !fIsAllLoggers) {
                MessageDialog.openError(getShell(),
                        Messages.TraceControl_EnableLoggersDialogTitle,
                        Messages.TraceControl_InvalidLogger);

                return false;
            }
            if (levels != null) {
                fLogLevel = levels[id];
            }
        }

        // validation successful -> call super.okPressed()
        return true;
    }

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

    private ITraceLogLevel[] getLogLevelNames() {
        switch (fDomain) {
        case JUL:
            return TraceJulLogLevel.values();
        case LOG4J:
            return TraceLog4jLogLevel.values();
        case PYTHON:
            return TracePythonLogLevel.values();
            //$CASES-OMITTED$
        default:
            return null;
        }
    }

    /**
     * Creates loggers group.
     */
    private void createLoggersGroup() {
        // Create the loggers group
        Group loggersMainGroup = new Group(this, SWT.SHADOW_NONE);
        loggersMainGroup.setText(Messages.TraceControl_EnableEventsLoggerGroupName);
        GridLayout layout = new GridLayout(2, false);
        loggersMainGroup.setLayout(layout);
        GridData data = new GridData(GridData.FILL_BOTH);
        loggersMainGroup.setLayoutData(data);

        // Create the Select button
        Composite buttonComposite = new Composite(loggersMainGroup, SWT.NONE);
        layout = new GridLayout(1, true);
        buttonComposite.setLayout(layout);
        data = new GridData(SWT.BEGINNING, SWT.CENTER, false, true);
        buttonComposite.setLayoutData(data);

        fLoggersActivateButton = new Button(buttonComposite, SWT.RADIO);
        fLoggersActivateButton.setText(Messages.TraceControl_EnableGroupSelectionName);
        data = new GridData(GridData.FILL_HORIZONTAL);
        fLoggersActivateButton.setLayoutData(data);
        fLoggersActivateButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                setEnablements(GroupEnum.LOGGERS);
            }
        });

        // Create the group for the tree
        Group loggersGroup = new Group(loggersMainGroup, SWT.SHADOW_NONE);
        layout = new GridLayout(1, true);
        loggersGroup.setLayout(layout);
        data = new GridData(GridData.FILL_BOTH);
        loggersGroup.setLayoutData(data);
        new FilteredTree(loggersGroup, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER, new PatternFilter(), true) {
            @Override
            protected TreeViewer doCreateTreeViewer(Composite aparent, int style) {
                fLoggersViewer = new CheckboxTreeViewer(aparent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
                fLoggersViewer.getTree().setToolTipText(Messages.TraceControl_EnableEventsLoggerTreeTooltip);
                fLoggersViewer.setContentProvider(new LoggersContentProvider());

                fLoggersViewer.setLabelProvider(new LoggersLabelProvider());
                fLoggersViewer.addCheckStateListener(new LoggersCheckStateListener());

                fLoggersViewer.setInput(fProviderGroup.getParent());
                fLoggersViewer.getTree().setLayoutData(new GridData(GridData.FILL_BOTH));
                return fLoggersViewer;
            }
        };

        Group specificLoggerGroup = new Group(loggersGroup, SWT.SHADOW_NONE);
        specificLoggerGroup.setText(Messages.TraceControl_EnableEventsSpecificLoggerGroupName);
        layout = new GridLayout(4, true);
        specificLoggerGroup.setLayout(layout);
        specificLoggerGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        Label specificLoggerLabel = new Label(specificLoggerGroup, SWT.LEFT);
        specificLoggerLabel.setText(Messages.TraceControl_EnableEventsNameLabel);
        data = new GridData(GridData.FILL_HORIZONTAL);
        data.horizontalSpan = 1;
        specificLoggerLabel.setLayoutData(data);

        fSpecificLoggerText = new Text(specificLoggerGroup, SWT.LEFT);
        fSpecificLoggerText.setToolTipText(Messages.TraceControl_EnableEventsSpecificLoggerTooltip);
        data = new GridData(GridData.FILL_HORIZONTAL);
        data.horizontalSpan = 3;
        fSpecificLoggerText.setLayoutData(data);
    }

    /**
     * Creates log level group.
     */
    private void createLogLevelGroup() {
        Group logLevelMainGroup = new Group(this, SWT.SHADOW_NONE);
        logLevelMainGroup.setText(Messages.TraceControl_EnableEventsLogLevelGroupName);
        GridLayout layout = new GridLayout(2, false);
        logLevelMainGroup.setLayout(layout);
        GridData data = new GridData(GridData.FILL_HORIZONTAL);
        logLevelMainGroup.setLayoutData(data);

        Composite buttonComposite = new Composite(logLevelMainGroup, SWT.NONE);
        layout = new GridLayout(1, false);
        buttonComposite.setLayout(layout);
        data = new GridData(SWT.BEGINNING, SWT.CENTER, false, true);
        buttonComposite.setLayoutData(data);

        fLogLevelActivateButton = new Button(buttonComposite, SWT.CHECK);
        fLogLevelActivateButton.setText(Messages.TraceControl_EnableGroupSelectionName);
        fLogLevelActivateButton.setSelection(false);
        data = new GridData(GridData.FILL_HORIZONTAL);
        fLogLevelActivateButton.setLayoutData(data);
        fLogLevelActivateButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                fLogLevelCombo.setEnabled(fLogLevelActivateButton.getSelection());
                fLogLevelButton.setEnabled(fLogLevelActivateButton.getSelection());
                fLogLevelOnlyButton.setEnabled(fLogLevelActivateButton.getSelection());
            }
        });

        Group logLevelGroup = new Group(logLevelMainGroup, SWT.SHADOW_NONE);
        layout = new GridLayout(2, true);
        logLevelGroup.setLayout(layout);
        logLevelGroup.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        fLogLevelButton = new Button(logLevelGroup, SWT.RADIO);
        fLogLevelButton.setText(Messages.TraceControl_EnableEventsLogLevelTypeName);
        fLogLevelButton.setToolTipText(Messages.TraceControl_EnableEventsLogLevelTypeTooltip);
        data = new GridData(GridData.FILL_BOTH);
        fLogLevelButton.setLayoutData(data);
        fLogLevelButton.setSelection(true);

        fLogLevelOnlyButton = new Button(logLevelGroup, SWT.RADIO);
        fLogLevelOnlyButton.setText(Messages.TraceControl_EnableEventsLogLevelOnlyTypeName);
        fLogLevelOnlyButton.setToolTipText(Messages.TraceControl_EnableEventsLogLevelOnlyTypeTooltip);
        data = new GridData(GridData.FILL_BOTH);
        fLogLevelButton.setLayoutData(data);

        fLogLevelCombo = new CCombo(logLevelGroup, SWT.READ_ONLY);
        ITraceLogLevel[] levels = getLogLevelNames();
        if (levels != null) {
            String[] levelNames = new String[levels.length - 1];
            int k = 0;
            for (int i = 0; i < levels.length; i++) {
                if (levels[i] != TraceJulLogLevel.LEVEL_UNKNOWN &&
                    levels[i] != TraceLog4jLogLevel.LEVEL_UNKNOWN &&
                    levels[i] != TracePythonLogLevel.LEVEL_UNKNOWN) {
                    levelNames[k++] = levels[i].getInName();
                }
            }
            fLogLevelCombo.setItems(levelNames);
        }

        fLogLevelCombo.setToolTipText(Messages.TraceControl_EnableEventsLogLevelTooltip);
        data = new GridData(GridData.FILL_HORIZONTAL);
        data.horizontalSpan = 4;
        fLogLevelCombo.setLayoutData(data);

        // By default the combo box and the buttons are not enabled
        fLogLevelCombo.setEnabled(false);
        fLogLevelButton.setEnabled(false);
        fLogLevelOnlyButton.setEnabled(false);

    }

    /**
     * Enable/selects widgets depending on the group specified.
     *
     * @param group
     *            group to enable.
     */
    private void setEnablements(GroupEnum group) {

        // Enable/disable trace point items
        fLoggersActivateButton.setSelection(group == GroupEnum.LOGGERS);
        fLoggersViewer.getTree().setEnabled(group == GroupEnum.LOGGERS);
    }

    /**
     * Content provider for the loggers tree.
     */
    public final class LoggersContentProvider extends TraceControlContentProvider {
        @Override
        public Object[] getChildren(Object parentElement) {
            if (parentElement instanceof TargetNodeComponent) {
                List<ITraceControlComponent> children = ((ITraceControlComponent) parentElement).getChildren(TraceProviderGroup.class);
                return children.toArray(new ITraceControlComponent[children.size()]);
            }
            if (parentElement instanceof TraceProviderGroup) {
                List<ITraceControlComponent> ustProviderChildren = ((ITraceControlComponent) parentElement).getChildren(UstProviderComponent.class)
                        .stream().filter(comp -> !((UstProviderComponent) comp).getLoggerComponents(fDomain).isEmpty())
                        .collect(Collectors.toList());
                return ustProviderChildren.toArray(new ITraceControlComponent[ustProviderChildren.size()]);
            }
            if (parentElement instanceof UstProviderComponent) {
                List<ITraceControlComponent> loggers = ((UstProviderComponent) parentElement).getLoggerComponents(fDomain);
                return loggers.toArray(new ITraceControlComponent[loggers.size()]);
            }
            return new Object[0];
        }
    }

    /**
     * Content label for the loggers tree.
     */
    public static final class LoggersLabelProvider extends TraceControlLabelProvider {
        @Override
        public Image getImage(Object element) {
            return null;
        }

        @Override
        public String getText(Object element) {
            if ((element != null) && (element instanceof TraceProviderGroup)) {
                return Messages.TraceControl_EnableEventsTreeAllLabel;
            }

            if ((element != null) && (element instanceof UstProviderComponent)) {
                return Messages.TraceControl_EnableEventsTreeAllLabel + " - " + ((UstProviderComponent)element).getName(); //$NON-NLS-1$
            }
            return super.getText(element);
        }
    }

    /**
     * Check state listener for the loggers tree.
     */
    public final class LoggersCheckStateListener implements ICheckStateListener {
        @Override
        public void checkStateChanged(CheckStateChangedEvent event) {
            if (event.getChecked()) {
                if (event.getElement() instanceof TraceProviderGroup) {
                    fLoggersViewer.setSubtreeChecked(event.getElement(), true);
                }
                if (event.getElement() instanceof UstProviderComponent) {
                    fLoggersViewer.setSubtreeChecked(event.getElement(), true);
                }
            } else {
                if (event.getElement() instanceof TraceProviderGroup) {
                    fLoggersViewer.setSubtreeChecked(event.getElement(), true);
                }
                if (event.getElement() instanceof UstProviderComponent) {
                    ITraceControlComponent component = (ITraceControlComponent) event.getElement();
                    fLoggersViewer.setSubtreeChecked(event.getElement(), false);
                    fLoggersViewer.setChecked(component.getParent(), false);
                } else {
                    ITraceControlComponent component = (ITraceControlComponent) event.getElement();
                    fLoggersViewer.setChecked(component.getParent(), false);
                    fLoggersViewer.setChecked(component.getParent().getParent(), false);
                }
            }
        }
    }
}
