/*
 * Copyright 2006-2023 DLR, Germany
 * 
 * SPDX-License-Identifier: EPL-1.0
 * 
 * https://rcenvironment.de/
 */

package de.rcenvironment.core.gui.workflow.editor.properties;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import org.eclipse.jface.viewers.ISelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetWidgetFactory;

import de.rcenvironment.core.component.api.LoopComponentConstants;
import de.rcenvironment.core.component.api.LoopComponentConstants.LoopBehaviorInCaseOfFailure;
import de.rcenvironment.core.component.model.api.ComponentDescription;
import de.rcenvironment.core.component.workflow.model.api.WorkflowNode;
import de.rcenvironment.core.gui.utils.incubator.NumericalTextConstraintListener;
import de.rcenvironment.core.gui.utils.incubator.WidgetGroupFactory;

/**
 * Config gui for behavior in case of loop failure.
 * 
 * @author Doreen Seider
 * @author Kathrin Schaffert
 */
public class FaultTolerantLoopSection extends ValidatingWorkflowNodePropertySection implements PropertyChangeListener {

    private static final String TEXT_DISCARD = "Discard the evaluation loop run and continue with next one";

    private static final String TEXT_RERUN_EVALUATION_LOOP = "Rerun the evaluation loop at the maximum of";

    private static final String TEXT_FINALLY_FAIL =
        "If an evaluation loop run was discarded, finally fail on loop termination (only applicable outside nested loops)";

    private static final String TEXT_ONLY_FAIL_LOOP =
        "Only fail the loop and forward failure to outer loop (only applicable if used in nested loop)";

    private boolean listenerRegistered = false;

    private Button failRadioButtonNAV;

    private Button discardAndContinueRadioButtonNAV;

    private Label labelDiscardAndContinue;

    private Button rerunAndDiscardRadioButtonNAV;

    private Label labelRerunAndDiscard;

    private Text rerunTimesAndFailTextNAV;

    private Button rerunAndFailRadioButtonNAV;

    private Text rerunTimesAndDiscardTextNAV;

    private Label rerunTimesAndDiscardLabelNAV;

    private Button failLoopIfAnyRunFailedCheckboxNAV;

    private Label labelFinallyFail;

    private Button onlyFailLoopCheckboxNAV;

    private Label labelOnlyFailLoop;

    private Button failRadioButtonCmpFlr;

    private Button discardAndContinueRadioButtonCmpFlr;

    private Label labelDiscardAndContinueCmpFlr;

    private boolean loopDriverSupportsDiscard;

    @Override
    protected void createCompositeContent(final Composite parent, final TabbedPropertySheetPage aTabbedPropertySheetPage) {

        parent.setLayout(new GridLayout(1, false));
        TabbedPropertySheetWidgetFactory factory = aTabbedPropertySheetPage.getWidgetFactory();

        final Section sectionPropertiesNAV = factory.createSection(parent, Section.TITLE_BAR | Section.EXPANDED);
        sectionPropertiesNAV.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        sectionPropertiesNAV.setText("Fault-tolerance in case of 'not-a-value' received");

        final Composite compositeNAV = factory.createFlatFormComposite(parent);
        compositeNAV.setLayout(new GridLayout(4, false));
        Label labelNAV = factory.createLabel(compositeNAV, "If a component in the loop sent 'not-a-value':");
        spanHorizontal(labelNAV, 4);

        BehaviorInCaseOfFailureSelectionListenerNAV listenerNAV = new BehaviorInCaseOfFailureSelectionListenerNAV();
        failRadioButtonNAV = new Button(compositeNAV, SWT.RADIO);
        createLabel(compositeNAV, "Fail", true);
        failRadioButtonNAV.setData(LoopComponentConstants.CONFIG_KEY_LOOP_FAULT_TOLERANCE_NAV, LoopBehaviorInCaseOfFailure.Fail);
        failRadioButtonNAV.addSelectionListener(listenerNAV);
        failRadioButtonNAV.setData(CONTROL_PROPERTY_KEY, LoopComponentConstants.CONFIG_KEY_LOOP_FAULT_TOLERANCE_NAV);

        discardAndContinueRadioButtonNAV = new Button(compositeNAV, SWT.RADIO);
        labelDiscardAndContinue = createLabel(compositeNAV, TEXT_DISCARD, true);
        discardAndContinueRadioButtonNAV.setData(LoopComponentConstants.CONFIG_KEY_LOOP_FAULT_TOLERANCE_NAV,
            LoopBehaviorInCaseOfFailure.Discard);
        discardAndContinueRadioButtonNAV.addSelectionListener(listenerNAV);

        rerunAndFailRadioButtonNAV = new Button(compositeNAV, SWT.RADIO);
        createLabel(compositeNAV, TEXT_RERUN_EVALUATION_LOOP, false);
        rerunAndFailRadioButtonNAV.setData(LoopComponentConstants.CONFIG_KEY_LOOP_FAULT_TOLERANCE_NAV,
            LoopBehaviorInCaseOfFailure.RerunAndFail);
        rerunAndFailRadioButtonNAV.addSelectionListener(listenerNAV);

        final int width = 40;
        rerunTimesAndFailTextNAV = new Text(compositeNAV, SWT.BORDER | (SWT.CENTER & WidgetGroupFactory.ALIGN_CENTER));
        GridData gridData = new GridData(SWT.CENTER & WidgetGroupFactory.ALIGN_CENTER);
        gridData.widthHint = width;
        rerunTimesAndFailTextNAV.setLayoutData(gridData);
        rerunTimesAndFailTextNAV.setData(CONTROL_PROPERTY_KEY, LoopComponentConstants.CONFIG_KEY_MAX_RERUN_BEFORE_FAIL_NAV);
        rerunTimesAndFailTextNAV
            .addVerifyListener(new NumericalTextConstraintListener(WidgetGroupFactory.GREATER_ZERO | WidgetGroupFactory.ONLY_INTEGER));
        factory.createLabel(compositeNAV, "time(s) and fail if maximum exceeded");

        rerunAndDiscardRadioButtonNAV = new Button(compositeNAV, SWT.RADIO);
        labelRerunAndDiscard = createLabel(compositeNAV, TEXT_RERUN_EVALUATION_LOOP, false);
        rerunAndDiscardRadioButtonNAV.setData(LoopComponentConstants.CONFIG_KEY_LOOP_FAULT_TOLERANCE_NAV,
            LoopBehaviorInCaseOfFailure.RerunAndDiscard);
        rerunAndDiscardRadioButtonNAV.addSelectionListener(listenerNAV);

        rerunTimesAndDiscardTextNAV = new Text(compositeNAV, SWT.BORDER | (SWT.CENTER & WidgetGroupFactory.ALIGN_CENTER));
        gridData = new GridData();
        gridData.widthHint = width;
        rerunTimesAndDiscardTextNAV.setLayoutData(gridData);
        rerunTimesAndDiscardTextNAV.setData(CONTROL_PROPERTY_KEY, LoopComponentConstants.CONFIG_KEY_MAX_RERUN_BEFORE_DISCARD_NAV);
        rerunTimesAndDiscardTextNAV.addVerifyListener(new NumericalTextConstraintListener(
            WidgetGroupFactory.GREATER_ZERO | WidgetGroupFactory.ONLY_INTEGER));
        rerunTimesAndDiscardLabelNAV = factory.createLabel(compositeNAV, "time(s) and discard if maximum exceeded");

        failLoopIfAnyRunFailedCheckboxNAV = new Button(compositeNAV, SWT.CHECK);
        labelFinallyFail = createLabel(compositeNAV, TEXT_FINALLY_FAIL, true);
        failLoopIfAnyRunFailedCheckboxNAV.setData(CONTROL_PROPERTY_KEY, LoopComponentConstants.CONFIG_KEY_FINALLY_FAIL_IF_DISCARDED_NAV);

        onlyFailLoopCheckboxNAV = new Button(compositeNAV, SWT.CHECK);
        labelOnlyFailLoop = createLabel(compositeNAV, TEXT_ONLY_FAIL_LOOP, true);
        onlyFailLoopCheckboxNAV.setData(CONTROL_PROPERTY_KEY, LoopComponentConstants.CONFIG_KEY_FAIL_LOOP_ONLY_NAV);

        final Section sectionPropertiesCmpFlr = factory.createSection(parent, Section.TITLE_BAR | Section.EXPANDED);
        sectionPropertiesCmpFlr.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        sectionPropertiesCmpFlr.setText("Fault-tolerance in case of component failure");

        final Composite compositeCmpFlr = factory.createFlatFormComposite(parent);
        compositeCmpFlr.setLayout(new GridLayout(2, false));
        Label labelComponentFailure = factory.createLabel(compositeCmpFlr, "If a component in the loop fails:");
        spanHorizontal(labelComponentFailure, 2);

        BehaviorInCaseOfFailureSelectionListenerCompFailure listenerCmpFlr = new BehaviorInCaseOfFailureSelectionListenerCompFailure();

        failRadioButtonCmpFlr = new Button(compositeCmpFlr, SWT.RADIO);
        createLabel(compositeCmpFlr, "Fail component", false);
        failRadioButtonCmpFlr.setData(LoopComponentConstants.CONFIG_KEY_LOOP_FAULT_TOLERANCE_COMP_FAILURE,
            LoopBehaviorInCaseOfFailure.Fail);
        failRadioButtonCmpFlr.addSelectionListener(listenerCmpFlr);
        failRadioButtonCmpFlr.setData(CONTROL_PROPERTY_KEY, LoopComponentConstants.CONFIG_KEY_LOOP_FAULT_TOLERANCE_COMP_FAILURE);

        discardAndContinueRadioButtonCmpFlr = new Button(compositeCmpFlr, SWT.RADIO);
        labelDiscardAndContinueCmpFlr = createLabel(compositeCmpFlr, TEXT_DISCARD, false);
        discardAndContinueRadioButtonCmpFlr.setData(LoopComponentConstants.CONFIG_KEY_LOOP_FAULT_TOLERANCE_COMP_FAILURE,
            LoopBehaviorInCaseOfFailure.Discard);
        discardAndContinueRadioButtonCmpFlr.addSelectionListener(listenerCmpFlr);

    }

    @Override
    public void setInput(IWorkbenchPart part, ISelection selection) {
        super.setInput(part, selection);
        if (node != null && !listenerRegistered) {
            node.addPropertyChangeListener(this);
            listenerRegistered = true;
        }
    }

    @Override
    protected void beforeTearingDownModelBinding() {
        if (node != null) {
            node.removePropertyChangeListener(this);
        }
        listenerRegistered = false;
        super.beforeTearingDownModelBinding();
    }

    private Label createLabel(Composite parent, String labelText, boolean spanLabel) {
        Label label = new Label(parent, SWT.NONE);
        label.setText(labelText);
        label.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
        if (spanLabel) {
            spanHorizontal(label, 3);
        }
        return label;
    }

    private void spanHorizontal(Control control, int span) {
        GridData gridData = new GridData();
        gridData.horizontalSpan = span;
        control.setLayoutData(gridData);
    }

    @Override
    protected void setWorkflowNode(WorkflowNode workflowNode) {
        super.setWorkflowNode(workflowNode);

        LoopBehaviorInCaseOfFailure loopBehaviorInCaseOfFailureNAV = LoopBehaviorInCaseOfFailure.fromString(
            getProperty(LoopComponentConstants.CONFIG_KEY_LOOP_FAULT_TOLERANCE_NAV));
        LoopBehaviorInCaseOfFailure loopBehaviorInCaseOfFailureCmpFlr = LoopBehaviorInCaseOfFailure.fromString(
            getProperty(LoopComponentConstants.CONFIG_KEY_LOOP_FAULT_TOLERANCE_COMP_FAILURE));
        loopDriverSupportsDiscard = workflowNode.getComponentDescription().getComponentInterface().getLoopDriverSupportsDiscard();

        failRadioButtonNAV.setSelection(loopBehaviorInCaseOfFailureNAV.equals(LoopBehaviorInCaseOfFailure.Fail));
        failRadioButtonCmpFlr.setSelection(loopBehaviorInCaseOfFailureCmpFlr.equals(LoopBehaviorInCaseOfFailure.Fail));
        rerunAndFailRadioButtonNAV.setSelection(loopBehaviorInCaseOfFailureNAV.equals(LoopBehaviorInCaseOfFailure.RerunAndFail));
        rerunTimesAndFailTextNAV.setEnabled(loopBehaviorInCaseOfFailureNAV.equals(LoopBehaviorInCaseOfFailure.RerunAndFail));

        Boolean isNestedLoop = Boolean.valueOf(getProperty(LoopComponentConstants.CONFIG_KEY_IS_NESTED_LOOP));
        boolean onlyFailEnabled = (loopBehaviorInCaseOfFailureNAV.equals(LoopBehaviorInCaseOfFailure.Fail)
            || loopBehaviorInCaseOfFailureNAV.equals(LoopBehaviorInCaseOfFailure.RerunAndFail)) && isNestedLoop;
        onlyFailLoopCheckboxNAV.setEnabled(onlyFailEnabled);
        labelOnlyFailLoop.setEnabled(onlyFailEnabled);

        discardAndContinueRadioButtonNAV.setEnabled(loopDriverSupportsDiscard);
        labelDiscardAndContinue.setEnabled(loopDriverSupportsDiscard);
        discardAndContinueRadioButtonCmpFlr.setEnabled(loopDriverSupportsDiscard);
        labelDiscardAndContinueCmpFlr.setEnabled(loopDriverSupportsDiscard);
        rerunAndDiscardRadioButtonNAV.setEnabled(loopDriverSupportsDiscard);
        labelRerunAndDiscard.setEnabled(loopDriverSupportsDiscard);
        rerunTimesAndDiscardTextNAV.setEnabled(loopDriverSupportsDiscard);
        rerunTimesAndDiscardLabelNAV.setEnabled(loopDriverSupportsDiscard);

        boolean finallyFailEnabled = loopDriverSupportsDiscard && !isNestedLoop;
        failLoopIfAnyRunFailedCheckboxNAV.setEnabled(finallyFailEnabled);
        labelFinallyFail.setEnabled(finallyFailEnabled);

        if (loopDriverSupportsDiscard) {
            discardAndContinueRadioButtonNAV.setSelection(loopBehaviorInCaseOfFailureNAV.equals(LoopBehaviorInCaseOfFailure.Discard));
            discardAndContinueRadioButtonCmpFlr.setSelection(loopBehaviorInCaseOfFailureCmpFlr.equals(LoopBehaviorInCaseOfFailure.Discard));
            rerunAndDiscardRadioButtonNAV.setSelection(loopBehaviorInCaseOfFailureNAV.equals(LoopBehaviorInCaseOfFailure.RerunAndDiscard));
            rerunTimesAndDiscardTextNAV.setEnabled(loopBehaviorInCaseOfFailureNAV.equals(LoopBehaviorInCaseOfFailure.RerunAndDiscard));
            failLoopIfAnyRunFailedCheckboxNAV.setEnabled(failLoopIfAnyRunFailedCheckboxNAV.getEnabled()
                && (loopBehaviorInCaseOfFailureNAV.equals(LoopBehaviorInCaseOfFailure.Discard)
                    || loopBehaviorInCaseOfFailureNAV.equals(LoopBehaviorInCaseOfFailure.RerunAndDiscard)));
        }

        setActivationOfCheckboxes();
    }

    private void setActivationOfCheckboxes() {
        boolean isNestedLoop = Boolean.valueOf(getProperty(LoopComponentConstants.CONFIG_KEY_IS_NESTED_LOOP));
        failLoopIfAnyRunFailedCheckboxNAV
            .setEnabled(!isNestedLoop && (discardAndContinueRadioButtonNAV.getSelection() || rerunAndDiscardRadioButtonNAV.getSelection()));
        onlyFailLoopCheckboxNAV
            .setEnabled(isNestedLoop && (failRadioButtonNAV.getSelection() || rerunAndFailRadioButtonNAV.getSelection()));
        setSelectionOfCheckbox();
    }

    private void setSelectionOfCheckbox() {
        if (!failLoopIfAnyRunFailedCheckboxNAV.isEnabled()) {
            failLoopIfAnyRunFailedCheckboxNAV.setSelection(false);
            setProperty(LoopComponentConstants.CONFIG_KEY_FINALLY_FAIL_IF_DISCARDED_NAV, "false");
        } else {
            String prop = getProperty(LoopComponentConstants.CONFIG_KEY_FINALLY_FAIL_IF_DISCARDED_NAV);
            failLoopIfAnyRunFailedCheckboxNAV.setSelection(Boolean.parseBoolean(prop));
        }
        if (!onlyFailLoopCheckboxNAV.isEnabled()) {
            onlyFailLoopCheckboxNAV.setSelection(false);
            setProperty(LoopComponentConstants.CONFIG_KEY_FAIL_LOOP_ONLY_NAV, "false");
        } else {
            String prop = getProperty(LoopComponentConstants.CONFIG_KEY_FAIL_LOOP_ONLY_NAV);
            onlyFailLoopCheckboxNAV.setSelection(Boolean.parseBoolean(prop));
        }
    }

    /**
     * {@link SelectionListener} for the radio buttons.
     * 
     * @author Doreen Seider
     */
    private class BehaviorInCaseOfFailureSelectionListenerNAV implements SelectionListener {

        @Override
        public void widgetDefaultSelected(SelectionEvent event) {
            widgetSelected(event);
        }

        @Override
        public void widgetSelected(SelectionEvent event) {
            Button button = ((Button) event.getSource());
            setProperty(LoopComponentConstants.CONFIG_KEY_LOOP_FAULT_TOLERANCE_NAV,
                button.getData(LoopComponentConstants.CONFIG_KEY_LOOP_FAULT_TOLERANCE_NAV).toString());
            rerunTimesAndFailTextNAV.setEnabled(button == rerunAndFailRadioButtonNAV);
            rerunTimesAndDiscardTextNAV.setEnabled(button == rerunAndDiscardRadioButtonNAV);
        }

    }

    /**
     * {@link SelectionListener} for the radio buttons.
     * 
     * @author Doreen Seider
     */
    private class BehaviorInCaseOfFailureSelectionListenerCompFailure implements SelectionListener {

        @Override
        public void widgetDefaultSelected(SelectionEvent event) {
            widgetSelected(event);
        }

        @Override
        public void widgetSelected(SelectionEvent event) {
            Button button = ((Button) event.getSource());
            setProperty(LoopComponentConstants.CONFIG_KEY_LOOP_FAULT_TOLERANCE_COMP_FAILURE,
                button.getData(LoopComponentConstants.CONFIG_KEY_LOOP_FAULT_TOLERANCE_COMP_FAILURE).toString());
        }

    }

    @Override
    protected FaultToleranceController createController() {
        return new FaultToleranceController();
    }

    protected class FaultToleranceController extends DefaultController {

        @Override
        public void widgetDefaultSelected(SelectionEvent event) {
            // implementation not needed
        }

        @Override
        public void widgetSelected(SelectionEvent event) {
            Button button = ((Button) event.getSource());

            if (button.getData(CONTROL_PROPERTY_KEY) != null
                && (button.getData(CONTROL_PROPERTY_KEY).equals(LoopComponentConstants.CONFIG_KEY_FINALLY_FAIL_IF_DISCARDED_NAV)
                    || button.getData(CONTROL_PROPERTY_KEY).equals(LoopComponentConstants.CONFIG_KEY_FAIL_LOOP_ONLY_NAV))) {
                super.widgetSelected(event);
            }
        }
    }

    @Override
    protected FaultToleranceUpdater createUpdater() {
        return new FaultToleranceUpdater();
    }

    /**
     * Fault Tolerance {@link DefaultUpdater} implementation of the handler to update the Fault Tolerance UI.
     * 
     * @author scha_kn
     *
     */
    protected class FaultToleranceUpdater extends DefaultUpdater {

        @Override
        public void updateControl(Control control, String propertyName, String newValue, String oldValue) {

            if (control instanceof Text) {
                super.updateControl(control, propertyName, newValue, oldValue);
            }
            if (control instanceof Button && oldValue != null) {
                if (!propertyName.equals(LoopComponentConstants.CONFIG_KEY_FINALLY_FAIL_IF_DISCARDED_NAV)
                    && !propertyName.equals(LoopComponentConstants.CONFIG_KEY_FAIL_LOOP_ONLY_NAV)) {
                    setWorkflowNode(node);
                } else {
                    super.updateControl(control, propertyName, newValue, oldValue);
                }
            }
        }

    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if (evt.getPropertyName().equals(ComponentDescription.PROPERTIES_PREFIX + LoopComponentConstants.CONFIG_KEY_IS_NESTED_LOOP)) {
            setWorkflowNode(node);
        }
    }

}
