package be.iminds.ilabt.jfed.experimenter_gui.editor.properties;

import be.iminds.ilabt.jfed.experimenter_gui.editor.ModelRspecEditor;
import be.iminds.ilabt.jfed.experimenter_gui.util.ui.FXUtils;
import be.iminds.ilabt.jfed.ui.javafx.util.StageUtils;
import be.iminds.ilabt.jfed.rspec.model.javafx_impl.DelayRspecNode;
import be.iminds.ilabt.jfed.rspec.model.javafx_impl.FXLinkSetting;
import be.iminds.ilabt.jfed.ui.javafx.FXMLUtil;
import be.iminds.ilabt.jfed.ui.javafx.dialogs.JFDialogs;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.stage.Window;
import javafx.util.converter.NumberStringConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This dialog is meant to act upon the FXLinkSetting-variables.
 * In practice however, these settings do not work. Because of that, this dialog is not in use.
 * <p/>
 * User: twalcari
 * Date: 11/28/13
 * Time: 5:03 PM
 */
public class DelayNodePropertiesDialog extends BorderPane {
    private static final Logger LOG = LoggerFactory.getLogger(DelayNodePropertiesDialog.class);

    private final DelayRspecNode node;
    private final ModelRspecEditor modelRspecEditor;

    private final ListProperty<FXLinkSetting> impairmentList;
    private final LongProperty capacity = new SimpleLongProperty();
    private final IntegerProperty latency = new SimpleIntegerProperty();
    private final DoubleProperty packetLoss = new SimpleDoubleProperty();
    @FXML
    private Button saveButton;
    @FXML
    private Button cancelButton;
    @FXML
    private TextField nameTextField;
    @FXML
    private ToggleGroup impairmentToggleGroup;
    @FXML
    private RadioButton simpleLinkImpairmentRadioButton;
    @FXML
    private TextField capacityTextField;
    @FXML
    private TextField latencyTextField;
    @FXML
    private TextField packetLossTextField;
    @FXML
    private CheckBox capacityCheckBox;
    @FXML
    private CheckBox latencyCheckBox;
    @FXML
    private CheckBox packetLossCheckBox;

    @FXML
    private RadioButton advancedLinkImpairmentRadioButton;
    @FXML
    private TableView<FXLinkSetting> advancedImpairmentTableView;

    @FXML
    private TableColumn<FXLinkSetting, String> fromTableColumn;
    @FXML
    private TableColumn<FXLinkSetting, String> toTableColumn;
    @FXML
    private TableColumn<FXLinkSetting, Number> capacityTableColumn;
    @FXML
    private TableColumn<FXLinkSetting, Number> latencyTableColumn;
    @FXML
    private TableColumn<FXLinkSetting, Number> packetLossTableColumn;

    private DelayNodePropertiesDialog(ModelRspecEditor modelRspecEditor, DelayRspecNode node) {
        this.modelRspecEditor = modelRspecEditor;
        this.node = node;

        impairmentList = node.getLinkSettings();

        //initialize the Scene
        FXMLUtil.injectFXML(this);

        nameTextField.setText(node.getClientId());

        //initialize simple values on first LinkSetting-object values

        FXLinkSetting ls = node.getLinkSettings().get(0);

        capacity.setValue(ls.getCapacity_Kbps());
        latency.setValue(ls.getLatency_ms());
        packetLoss.setValue(ls.getPacketLoss());

        if (hasAdvancedImpairmentSettings())
            impairmentToggleGroup.selectToggle(advancedLinkImpairmentRadioButton);


        //initialize simple values
        capacityTextField.textProperty().bindBidirectional(capacity, new NumberStringConverter());

        capacityTextField.focusedProperty().addListener(observable -> {
            if (!capacityTextField.isFocused()) {
                if (capacity.get() < 0) {
                    JFDialogs.create()
                            .owner(advancedImpairmentTableView.getScene().getWindow())
                            .message("Capacity must be a non-negative value")
                            .masthead("Invalid Capacity Loss value")
                            .showError();
                    capacity.setValue(0);
                }

            }
        });
        capacityCheckBox.selectedProperty().bind(Bindings.notEqual(0, capacity));

        capacityCheckBox.selectedProperty().addListener(observable -> LOG.debug("new checkbox value:" + capacityCheckBox.isSelected()));

        latencyTextField.textProperty().bindBidirectional(latency, new NumberStringConverter());

        latencyTextField.focusedProperty().addListener(observable -> {
            if (!latencyTextField.isFocused()) {
                if (latency.get() < 0) {
                    JFDialogs.create()
                            .owner(advancedImpairmentTableView.getScene().getWindow())
                            .message("Latency must be a non-negative value")
                            .masthead("Invalid Latency value")
                            .showError();
                    latency.setValue(0);
                }

            }
        });

        latencyCheckBox.selectedProperty().bind(Bindings.notEqual(0, latency));

        packetLossTextField.textProperty().bindBidirectional(packetLoss, new NumberStringConverter());

        packetLossTextField.focusedProperty().addListener(observable -> {
            if (!packetLossTextField.isFocused()) {
                if (packetLoss.get() < 0 || packetLoss.get() > 1) {
                    JFDialogs.create()
                            .owner(advancedImpairmentTableView.getScene().getWindow())
                            .message("Packet loss must be between 0 and 1")
                            .masthead("Invalid Packet Loss value")
                            .showError();
                    packetLoss.setValue(0);
                }

            }
        });
        packetLossCheckBox.selectedProperty().bind(Bindings.notEqual(0, packetLoss));

        //populate advancedDelayRspecNode.LinkSettingTableView
        fromTableColumn.setCellValueFactory(p -> p.getValue().getFromIface().clientIdProperty());
        toTableColumn.setCellValueFactory(p -> p.getValue().getToIface().clientIdProperty());

        capacityTableColumn.setCellFactory(TextFieldTableCell.forTableColumn(new NumberStringConverter()));

        capacityTableColumn.setOnEditCommit(e -> {
            if (e.getNewValue().intValue() < 0) {

                JFDialogs.create()
                        .owner(advancedImpairmentTableView.getScene().getWindow())
                        .message("Capacity must be a non-negative value")
                        .masthead("Invalid Capacity value")
                        .showError();

                return;
            }
            e.getRowValue().capacity_KbpsProperty().setValue(e.getNewValue());
        });

        capacityTableColumn.setCellValueFactory(p -> p.getValue().capacity_KbpsProperty());

        latencyTableColumn.setCellFactory(TextFieldTableCell.forTableColumn(new NumberStringConverter()));

        latencyTableColumn.setOnEditCommit(e -> {
            if (e.getNewValue().intValue() < 0) {

                JFDialogs.create()
                        .owner(advancedImpairmentTableView.getScene().getWindow())
                        .message("Latency must be a non-negative value")
                        .masthead("Invalid Latency value")
                        .showError();
                return;
            }
            e.getRowValue().latency_msProperty().setValue(e.getNewValue());
        });

        latencyTableColumn.setCellValueFactory(p -> p.getValue().latency_msProperty());

        packetLossTableColumn.setCellFactory(TextFieldTableCell.forTableColumn(new NumberStringConverter()));

        packetLossTableColumn.setOnEditCommit(e -> {
            if (e.getNewValue().doubleValue() < 0 || e.getNewValue().doubleValue() > 1) {
                JFDialogs.create()
                        .owner(advancedImpairmentTableView.getScene().getWindow())
                        .message("Packet loss must be between 0 and 1")
                        .masthead("Invalid Packet Loss value")
                        .showError();
                return;
            }

            e.getRowValue().setPacketLoss(e.getNewValue().doubleValue());
        });

        packetLossTableColumn.setCellValueFactory(p -> p.getValue().packetLossProperty());


        capacityTableColumn.setCellFactory(TextFieldTableCell.forTableColumn(new NumberStringConverter()));
        packetLossTableColumn.setCellFactory(TextFieldTableCell.forTableColumn(new NumberStringConverter()));
        latencyTableColumn.setCellFactory(TextFieldTableCell.forTableColumn(new NumberStringConverter()));


        advancedImpairmentTableView.setItems(impairmentList);

        latencyTextField.disableProperty().bind(Bindings.not(simpleLinkImpairmentRadioButton.selectedProperty()));
        capacityTextField.disableProperty().bind(Bindings.not(simpleLinkImpairmentRadioButton.selectedProperty()));
        packetLossTextField.disableProperty().bind(Bindings.not(simpleLinkImpairmentRadioButton.selectedProperty()));

        advancedImpairmentTableView.disableProperty().bind(Bindings.not(advancedLinkImpairmentRadioButton.selectedProperty()));


        //add link between simple and advanced impairment fields
        capacity.addListener(observable -> {
            for (FXLinkSetting ls13 : DelayNodePropertiesDialog.this.node.getLinkSettings()) {
                ls13.setCapacity_Kbps(capacity.get());
            }
        });

        packetLoss.addListener(observable -> {
            for (FXLinkSetting ls12 : DelayNodePropertiesDialog.this.node.getLinkSettings()) {
                ls12.setPacketLoss(packetLoss.get());
            }
        });

        latency.addListener(observable -> {
            for (FXLinkSetting ls1 : DelayNodePropertiesDialog.this.node.getLinkSettings()) {
                ls1.setLatency_ms(latency.get());
            }
        });


        //add warning when switching from advanced to simple

        impairmentToggleGroup.selectedToggleProperty().addListener((observableValue, oldValue, newValue) -> {
            if (newValue == simpleLinkImpairmentRadioButton && hasAdvancedImpairmentSettings()) {
                if (JFDialogs.create()
                        .owner(DelayNodePropertiesDialog.this)
                        .message("Switching to simple impairment-mode will cause your advanced settings to reverse. Are you sure?")
                        .title("Switching to simple impairment mode").showConfirm().filter(r -> r == ButtonType.YES).isPresent()) {
                    resetAdvancedImpairmentSettings();
                } else {
                    impairmentToggleGroup.selectToggle(advancedLinkImpairmentRadioButton);
                }
            }
        });

    }

    public static void showNodePropertiesDialog(Window parentWindow, ModelRspecEditor modelRspecEditor,
                                                DelayRspecNode link) {

        Scene scene = new Scene(new DelayNodePropertiesDialog(modelRspecEditor, link));

        Stage dialogStage = new Stage();
        StageUtils.setJFedStageIcons(dialogStage);
        dialogStage.setScene(scene);

        dialogStage.setTitle("Properties of " + link.getClientId());
        dialogStage.setResizable(false);
        FXUtils.centerToOwner(parentWindow, dialogStage);
        dialogStage.showAndWait();
    }

    private void resetAdvancedImpairmentSettings() {
        for (FXLinkSetting ls : node.getLinkSettings()) {
            ls.setCapacity_Kbps(capacity.get());
            ls.setLatency_ms(latency.get());
            ls.setPacketLoss(packetLoss.get());
        }

    }

    private boolean hasAdvancedImpairmentSettings() {
        for (FXLinkSetting ls : node.getLinkSettings()) {
            if (ls.getLatency_ms() != latency.get())
                return true;
            if (ls.getCapacity_Kbps() != capacity.get())
                return true;
            if (ls.getPacketLoss() != packetLoss.get())
                return true;
        }
        return false;
    }

    @FXML
    public void onSaveAction(ActionEvent actionEvent) {

        if (!isValidLinkName(nameTextField.getText())) {
            JFDialogs.create()
                    .owner(this)
                    .message("A link name may only contain alphanumerical characters, " +
                            "and is maximum " + getMaximumLinkNameLength() + " characters long.")
                    .masthead("The entered link name is invalid.")
                    .showError();
            return;
        }


        node.setClientId(nameTextField.getText());

        Stage stage = (Stage) saveButton.getScene().getWindow();
        stage.close();
    }

    private int getMaximumLinkNameLength() {
        return 25;
    }

    private boolean isValidLinkName(String text) {
        if (text.length() > getMaximumLinkNameLength())
            return false;

        for (char c : text.toCharArray()) {
            boolean validChar = (c >= 'a' && c <= 'z')
                    || (c >= 'A' && c <= 'Z')
                    || (c >= '0' && c <= '9');

            if (!validChar) {
                return false;
            }
        }

        return true;
    }

    @FXML
    public void onCancelAction(ActionEvent actionEvent) {
        Stage stage = (Stage) cancelButton.getScene().getWindow();
        stage.close();
    }
}

