/*******************************************************************************
 * Copyright (c) 2016 EfficiOS Inc., 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
 ******************************************************************************/

package org.eclipse.tracecompass.internal.analysis.os.linux.ui.views.controlflow.filters;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringJoiner;
import java.util.regex.Pattern;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
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.Group;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;

import com.google.common.base.Objects;
import com.google.common.collect.Range;

/**
 * The Dynamic Filters configuration dialog.
 *
 * @author Jonathan Rajotte Julien
 */
public class DynamicFilterDialog extends TitleAreaDialog {

    /** Pattern for CPUS ranges e.g.: 1,1-200,2,3 */
    private static final Pattern CPU_RANGE = Pattern.compile("^((\\d+(\\-\\d+)?, ?)*(\\d+(\\-\\d+)?))+$"); //$NON-NLS-1$
    private static final @NonNull String INTERNAL_RANGE_SEPARATOR = "-"; //$NON-NLS-1$
    private static final @NonNull String RANGES_DELIMITER = ","; //$NON-NLS-1$

    /** The internal ActiveThreadsFilter result */
    private @NonNull ActiveThreadsFilter fInternalActiveThreadsFilter;
    private final @Nullable ITmfTrace fTrace;

    private Button fActiveThreadEnabledButton;
    private Button fAllActiveThreadsRadionButton;
    private Button fCpuRangesRadioButton;
    private Text fCpuRangesField;

    /**
     * Constructor to create a DynamicFilterDialog
     *
     * @param parentShell
     *            The parent shell
     * @param filter
     *            An
     *            {@link org.eclipse.tracecompass.internal.analysis.os.linux.ui.views.controlflow.filters.ActiveThreadsFilter
     *            ActiveThreadFilter} instance.
     * @param trace
     *            The relevant trace
     */
    public DynamicFilterDialog(Shell parentShell, @NonNull ActiveThreadsFilter filter, @Nullable ITmfTrace trace) {
        super(parentShell);
        fInternalActiveThreadsFilter = filter;
        fTrace = trace;
    }

    @Override
    public void create() {
        super.create();
        setTitle(Messages.DynamicFilterDialog_Title);
        this.getShell().setText(Messages.DynamicFilterDialog_Title);
    }

    private static boolean validateCpuRange(final String newString) {
        return CPU_RANGE.matcher(newString).matches();
    }

    private void createActiveThreadSection(Composite parent) {
        boolean usesCpuRanges = false;
        boolean filterActive = false;

        ActiveThreadsFilter filter = fInternalActiveThreadsFilter;

        filterActive = fInternalActiveThreadsFilter.isEnabled();
        usesCpuRanges = filter.isCpuRangesBased();

        GridData gd;
        GridLayout gl;
        Group activeThreadGroup = new Group(parent, SWT.SHADOW_NONE | SWT.BORDER);
        activeThreadGroup.setText(Messages.DynamicFilterDialog_ActiveThreadsFilterName);

        activeThreadGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        gl = new GridLayout(1, true);
        gl.marginLeft = gl.marginRight = 0;
        activeThreadGroup.setLayout(gl);

        fActiveThreadEnabledButton = new Button(activeThreadGroup, SWT.CHECK);
        fActiveThreadEnabledButton.setText(Messages.DynamicFilterDialog_ActiveThreadsFilterName);
        fActiveThreadEnabledButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

        /* Cpu selection */
        Group cpuSelectionGroup = new Group(activeThreadGroup, SWT.SHADOW_NONE);
        cpuSelectionGroup.setText(Messages.DynamicFilterDialog_OptionsGroupLabel);
        cpuSelectionGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        cpuSelectionGroup.setLayout(new GridLayout(2, false));

        fAllActiveThreadsRadionButton = new Button(cpuSelectionGroup, SWT.RADIO);
        fAllActiveThreadsRadionButton.setText(Messages.DynamicFilterDialog_RadioButtonAllActiveThreads);
        fAllActiveThreadsRadionButton.setToolTipText(Messages.DynamicFilterDialog_RadioButtonAllActiveThreadsToolTip);

        fAllActiveThreadsRadionButton.setSelection(!usesCpuRanges);
        gd = new GridData(SWT.FILL, SWT.FILL, true, true);
        gd.horizontalSpan = 2;
        fAllActiveThreadsRadionButton.setLayoutData(gd);

        fCpuRangesRadioButton = new Button(cpuSelectionGroup, SWT.RADIO);
        fCpuRangesRadioButton.setText(Messages.DynamicFilterDialog_CpuRangesLabel);
        fCpuRangesRadioButton.setToolTipText(Messages.DynamicFilterDialog_CpuRangesTooltip);

        fCpuRangesRadioButton.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, true));

        fCpuRangesField = new Text(cpuSelectionGroup, SWT.SINGLE | SWT.BORDER);
        fCpuRangesField.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, true, true));
        fCpuRangesField.setMessage(Messages.DynamicFilterDialog_CpuRangesExamples);

        fCpuRangesRadioButton.setToolTipText(Messages.DynamicFilterDialog_CpuRangesTooltip);

        /* Attach an automatic validation to the field */
        fCpuRangesField.addVerifyListener(e -> {
            /* Reconstruct the string */
            final String oldString = fCpuRangesField.getText();
            final String newString = oldString.substring(0, e.start) + e.text + oldString.substring(e.end);

            /* Validate the string */
            boolean valid = validateCpuRange(newString);

            Button okButton = getButton(IDialogConstants.OK_ID);
            if (okButton != null) {
                getButton(IDialogConstants.OK_ID).setEnabled(valid);
            }
            if (valid) {
                setErrorMessage(null);
            } else {
                setErrorMessage(Messages.DynamicFilterDialog_InvalidRangesErrorMsg);
            }
        });

        fAllActiveThreadsRadionButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                super.widgetSelected(e);
                boolean selected = ((Button) e.widget).getSelection();
                if (!selected) {
                    return;
                }
                setErrorMessage(null);
                getButton(IDialogConstants.OK_ID).setEnabled(true);
            }
        });

        fCpuRangesRadioButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                super.widgetSelected(e);
                boolean selected = ((Button) e.widget).getSelection();
                fCpuRangesField.setEnabled(selected);
                validateCpuRange(fCpuRangesField.getText());
            }
        });

        fActiveThreadEnabledButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                super.widgetSelected(e);
                boolean selected = ((Button) e.widget).getSelection();
                cpuSelectionGroup.setEnabled(selected);
                fAllActiveThreadsRadionButton.setEnabled(selected);
                fCpuRangesRadioButton.setEnabled(selected);
                fCpuRangesField.setEnabled(selected && fCpuRangesRadioButton.getSelection());
            }
        });

        /* Set the base state for the ui control */
        fActiveThreadEnabledButton.setSelection(filterActive);
        cpuSelectionGroup.setEnabled(filterActive);
        fCpuRangesRadioButton.setEnabled(filterActive);
        fAllActiveThreadsRadionButton.setEnabled(filterActive);

        fAllActiveThreadsRadionButton.setSelection(!usesCpuRanges);
        fCpuRangesRadioButton.setSelection(usesCpuRanges);

        fCpuRangesField.setEnabled(filterActive && usesCpuRanges);

        /* Populate the CPU ranges fields */
        if (!filter.getCpuRanges().isEmpty()) {
            StringJoiner joiner = new StringJoiner(RANGES_DELIMITER);
            for (Range<Long> range : filter.getCpuRanges()) {
                String rangeString = range.lowerEndpoint().toString();
                if (!Objects.equal(range.lowerEndpoint(), range.upperEndpoint())) {
                    rangeString = rangeString.concat(INTERNAL_RANGE_SEPARATOR + range.upperEndpoint());
                }
                joiner.add(rangeString);
            }
            fCpuRangesField.setText(joiner.toString());
        }
    }

    @Override
    protected Control createDialogArea(Composite parent) {
        Composite area = (Composite) super.createDialogArea(parent);
        Composite container = new Composite(area, SWT.NONE);
        container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        GridLayout layout = new GridLayout(1, true);
        container.setLayout(layout);
        createActiveThreadSection(container);
        return area;
    }

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

    private void saveInput() {
        if (!(fAllActiveThreadsRadionButton.getSelection() || fCpuRangesRadioButton.getSelection())
                || (fAllActiveThreadsRadionButton.getSelection() && fCpuRangesRadioButton.getSelection())) {
            throw new IllegalStateException(Messages.DynamicFilterDialog_InvalidRadioButtonState);
        }

        List<Range<Long>> ranges = null;
        ranges = parseCpuRangesText(fCpuRangesField.getText());

        fInternalActiveThreadsFilter = new ActiveThreadsFilter(ranges, fCpuRangesRadioButton.getSelection(), fTrace);
        fInternalActiveThreadsFilter.setEnabled(fActiveThreadEnabledButton.getSelection());
    }

    private static List<Range<Long>> parseCpuRangesText(final String string) {
        List<Range<Long>> results = new ArrayList<>();
        if (validateCpuRange(string)) {
            for (String range : string.split(RANGES_DELIMITER)) {
                if (range.contains(INTERNAL_RANGE_SEPARATOR)) {
                    /* Parse as a range */
                    String[] split = range.split(INTERNAL_RANGE_SEPARATOR);
                    if (split.length != 2) {
                        /* Invalid range */
                        continue;
                    }

                    long[] sorted = new long[split.length];
                    Arrays.setAll(sorted, i -> Long.parseLong(split[i]));
                    Arrays.sort(sorted);
                    results.add(Range.closed(sorted[0], sorted[1]));
                } else {
                    /* Parse as an individual number */
                    Long value = Long.parseLong(range);
                    results.add(Range.closed(value, value));
                }
            }
        }
        return results;
    }

    @Override
    protected void okPressed() {
        saveInput();
        super.okPressed();
    }

    @Override
    public boolean isHelpAvailable() {
        return false;
    }

    /**
     * Get the resulting ActiveThreadsFilter
     *
     * @return The configured
     *         {@link org.eclipse.tracecompass.internal.analysis.os.linux.ui.views.controlflow.filters.ActiveThreadsFilter
     *         ActiveThreadFilter} instance.
     */
    public @NonNull ActiveThreadsFilter getActiveThreadsResult() {
        return fInternalActiveThreadsFilter;
    }
}
