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

import be.iminds.ilabt.jfed.experimenter_gui.config.AdvertisementFetchManager;
import be.iminds.ilabt.jfed.experimenter_gui.util.StringConverters;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.lowlevel.JFedConnectivityException;
import be.iminds.ilabt.jfed.lowlevel.TestbedInfoSource;
import be.iminds.ilabt.jfed.rspec.model.Channel;
import be.iminds.ilabt.jfed.rspec.model.ModelRspecType;
import be.iminds.ilabt.jfed.rspec.model.javafx_impl.FXModelRspec;
import be.iminds.ilabt.jfed.rspec.model.javafx_impl.FXRspecChannel;
import be.iminds.ilabt.jfed.ui.javafx.FXMLUtil;
import be.iminds.ilabt.jfed.ui.javafx.dialogs.JFDialogs;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.util.Comparator;
import java.util.Objects;

/**
 * User: twalcari
 * Date: 4/23/2015
 * Time: 16:46
 */
public class ChannelPropertiesDialog extends BorderPane {

    private final FXRspecChannel channel;
    private final FXModelRspec modelRspec;

    private final AdvertisementFetchManager advertisementFetchManager;


    private final ObjectProperty<AdvertisementFetchManager.Resources.RefreshAdvertisementTask> activeRefreshTask =
            new SimpleObjectProperty<>(null);

    @FXML
    private ComboBox<Server> serverComboBox;

    @FXML
    private ComboBox<Channel> channelComboBox;

    @FXML
    private ProgressBar advertisementFetchProgressBar;
    @FXML
    private VBox advertisementFetchOverlay;

    @FXML
    private Button saveButton;
    @FXML
    private Button cancelButton;
    private boolean success = false;

    ChannelPropertiesDialog(FXRspecChannel channel, FXModelRspec modelRspec,
                            TestbedInfoSource testbedInfoSource, AdvertisementFetchManager advertisementFetchManager) {
        this.channel = channel;
        this.modelRspec = modelRspec;
        this.advertisementFetchManager = advertisementFetchManager;

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

        testbedInfoSource.getServers().stream()
                .filter(server -> server.hasFlag(Server.Flag.featureWirelessChannelReservation))
                .forEach(serverComboBox.getItems()::add);

        serverComboBox.setConverter(StringConverters.SERVER_STRING_CONVERTER);
        channelComboBox.setConverter(StringConverters.CHANNEL_CONVERTER);

        serverComboBox.getSelectionModel().selectedItemProperty().addListener((obj) -> updateChannels());


        advertisementFetchOverlay.visibleProperty().bind(activeRefreshTask.isNotNull());
        activeRefreshTask.addListener((observable, oldValue, newValue) -> {
            if (oldValue != null) {
                advertisementFetchProgressBar.progressProperty().unbind();
            }

            if (newValue != null) {
                advertisementFetchProgressBar.progressProperty().bind(newValue.progressProperty());
            }
        });

        //load values from the provided channel

        if (channel.getComponentManagerId() != null) {
            Server server = testbedInfoSource.getByUrnExact(channel.getComponentManagerId());
            serverComboBox.setValue(server);
        } else {
            //try to find the most probable CMI that will be used
            serverComboBox.getItems().sort(
                    Comparator.comparing(this::getNumberOfNodesInRspec).reversed().thenComparing(server -> server.getName().toLowerCase().trim())
            );

            serverComboBox.getSelectionModel().selectFirst();
        }

        if (channel.getComponentId() != null) {
            channelComboBox.setValue(
                    channelComboBox.getItems().stream()
                            .filter(ch -> Objects.equals(ch.getComponentId(), channel.getComponentId()))
                            .findAny().orElse(channel)
            );
        }

    }

    private int getNumberOfNodesInRspec(Server server) {
        return modelRspec.getNodes()
                .filtered(node -> node.getComponentManagerId() != null &&
                        Objects.equals(node.getComponentManagerId(), server.getDefaultComponentManagerAsGeniUrn()))
                .size();
    }

    private void updateChannels() {
        Server server = serverComboBox.getSelectionModel().getSelectedItem();

        if (server != null) {
            //do we have the manifest available?
            AdvertisementFetchManager.Resources resources = advertisementFetchManager.getAllResources(server);
            if (resources.getAdvertisementRspecSource() == null) {
                resources = advertisementFetchManager.getAvailableResources(server);
            }

            if (resources.getAdvertisementRspecSource() == null) {
                onRefreshAdvertisementAction();
            } else {
                final FXModelRspec modelRspec = resources.getAdvertisementRspecSource().getModelRspec(ModelRspecType.FX);
                assert modelRspec != null;
                channelComboBox.getItems().setAll(modelRspec.getChannels());
            }
        }
    }

    @FXML
    private void onRefreshAdvertisementAction() {
        Server server = serverComboBox.getSelectionModel().getSelectedItem();
        assert server != null;

        final AdvertisementFetchManager.Resources.RefreshAdvertisementTask updateAdvTask =
                advertisementFetchManager.getAllResources(server).refreshAdvertisement();

        activeRefreshTask.set(updateAdvTask);

        final EventHandler<WorkerStateEvent> removeOnFinished = value -> {
            if (activeRefreshTask.get() == updateAdvTask) {
                activeRefreshTask.set(null);
            }

            updateChannels();
        };
        updateAdvTask.setOnSucceeded(removeOnFinished);
        updateAdvTask.setOnCancelled(removeOnFinished);

        updateAdvTask.setOnFailed(workerStateEvent -> {
            removeOnFinished.handle(workerStateEvent);

            //only show messages if pane is still visible
            if (ChannelPropertiesDialog.this.getScene() != null) {
                if (isConnectivityException(updateAdvTask.getException())) {
                    JFDialogs.create()
                            .owner(ChannelPropertiesDialog.this)
                            .message(String.format("A connectivity exception occurred while fetching the advertisement RSpec from %s.\n\n" +
                                            "Verify your internet connection, and consider enabling a proxy in the jFed Preferences.",
                                    server.getName()))
                            .masthead("Fetching advertisement RSpec failed")
                            .title("Fetching advertisement RSpec failed")
                            .showException(updateAdvTask.getException());


                } else {
                    JFDialogs.create()
                            .owner(ChannelPropertiesDialog.this)
                            .message(String.format("An error occurred while fetching the advertisement RSpec from %s.",
                                    server.getName()))
                            .masthead("Fetching advertisement RSpec failed")
                            .title("Fetching advertisement RSpec failed")
                            .showException(updateAdvTask.getException());

                }
            }
        });

    }

    private static boolean isConnectivityException(Throwable t) {

        while (t != null) {
            if (t.getCause() instanceof JFedConnectivityException)
                return true;
            else
                t = t.getCause();
        }
        return false;
    }


    @FXML
    public void onSaveAction(ActionEvent actionEvent) {

        if (channelComboBox.getValue() == null) {
            JFDialogs.create()
                    .owner(this)
                    .message("Please select a channel from the dropdown list.")
                    .masthead("No channel selected.")
                    .showError();
            return;
        }

        Channel selectedChannel = channelComboBox.getValue();
        channel.setComponentManagerId(selectedChannel.getComponentManagerId());
        channel.setComponentId(selectedChannel.getComponentId());
        channel.setComponentName(selectedChannel.getComponentName());

        success = true;

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

    }


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

    public boolean isSuccess() {
        return success;
    }
}
