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

import be.iminds.ilabt.jfed.experimenter_gui.config.AdvertisementFetchManager;
import be.iminds.ilabt.jfed.experimenter_gui.config.JFedGuiConfig;
import be.iminds.ilabt.jfed.experimenter_gui.tasks.DeleteDiskImageTask;
import be.iminds.ilabt.jfed.experimenter_gui.tasks.TasksFactory;
import be.iminds.ilabt.jfed.experimenter_gui.ui.progressbar.TaskService;
import be.iminds.ilabt.jfed.experimenter_gui.util.StringConverters;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Resource;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.highlevel.controller.TaskThread;
import be.iminds.ilabt.jfed.highlevel.tasks.HighLevelTaskFactory;
import be.iminds.ilabt.jfed.highlevel.tasks.ListDiskImagesTask;
import be.iminds.ilabt.jfed.highlevel.util.AggregateManagerWrapperFactory;
import be.iminds.ilabt.jfed.lowlevel.TestbedInfoSource;
import be.iminds.ilabt.jfed.lowlevel.api_wrapper.AggregateManagerWrapper;
import be.iminds.ilabt.jfed.rspec.model.DiskImage;
import be.iminds.ilabt.jfed.ui.javafx.dialogs.JFDialogs;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import java.util.Optional;

/**
 * User: twalcari
 * Date: 9/11/2014
 * Time: 11:57
 */
public class SelectDiskImageController {
    private static final Logger LOG = LoggerFactory.getLogger(SelectDiskImageDialog.class);
    private final BooleanProperty isLoadingUserImages = new SimpleBooleanProperty(false);
    private final JFedGuiConfig config;
    private final TestbedInfoSource testbedInfoSource;
    private final AdvertisementFetchManager advertisementFetchManager;
    private final HighLevelTaskFactory hltf;
    private final TaskThread tt;
    private final TasksFactory tasksFactory;
    private final TaskService taskService;
    private final AggregateManagerWrapperFactory aggregateManagerWrapperFactory;
    @FXML
    private VBox root;
    /*
         Items for public disk images
     */
    @FXML
    private RadioButton publicDiskImagesRadioButton;
    @FXML
    private ComboBox<DiskImage> publicDiskImagesComboBox;
    @FXML
    private VBox publicFetchOverlay;
    @FXML
    private ProgressBar publicAdvertisementFetchProgressBar;
    private Server currentPublicDiskImagesServer = null;
    /*
        Items for private disk images
     */
    @FXML
    private RadioButton privateDiskImagesRadioButton;
    @FXML
    private ComboBox<AggregateManagerWrapper.ImageInfo> privateDiskImagesComboBox;
    @FXML
    private VBox privateFetchOverlay;
    @FXML
    private ProgressBar privateAdvertisementFetchProgressBar;
    @FXML
    private Button deletePrivateDiskImageButton;
    /*
    Advanced
     */
    @FXML
    private CheckBox advancedCheckBox;
    @FXML
    private VBox advancedVBox;
    @FXML
    private ComboBox<Server> sourceTestbedComboBox;
    private Server currentPrivateDiskImagesServer = null;
    private Resource nodeResource = null;
    private InvalidationListener publicRefreshTaskInvalidationListener;

    @Inject
    public SelectDiskImageController(JFedGuiConfig config, TestbedInfoSource testbedInfoSource,
                                     AdvertisementFetchManager advertisementFetchManager,
                                     HighLevelTaskFactory hltf, TaskThread tt, TasksFactory tasksFactory, TaskService taskService,
                                     AggregateManagerWrapperFactory aggregateManagerWrapperFactory) {
        this.config = config;
        this.testbedInfoSource = testbedInfoSource;
        this.advertisementFetchManager = advertisementFetchManager;
        this.hltf = hltf;
        this.tt = tt;
        this.tasksFactory = tasksFactory;
        this.taskService = taskService;
        this.aggregateManagerWrapperFactory = aggregateManagerWrapperFactory;

    }

    @FXML
    public void initialize() {
        //initialize advanced options first (as it contains the source testbed-combobox)
        advancedVBox.visibleProperty().bind(advancedCheckBox.selectedProperty());
        deletePrivateDiskImageButton.visibleProperty().bind(advancedCheckBox.selectedProperty());


        this.publicRefreshTaskInvalidationListener = observable -> {
            if (currentPublicDiskImagesServer != null) {
                assert publicAdvertisementFetchProgressBar != null;

                AdvertisementFetchManager.Resources resources =
                        advertisementFetchManager.getAllResources(currentPublicDiskImagesServer);
                if (resources.getCurrentRefreshAdvertisementTask() == null) {
                    publicAdvertisementFetchProgressBar.progressProperty().unbind();

                    updatePublicDiskImagesList();
                } else {
                    publicAdvertisementFetchProgressBar.progressProperty().bind(
                            resources.getCurrentRefreshAdvertisementTask().progressProperty());
                }
            } else {
                LOG.debug("currentPublicDiskImagesServer == null -> doing nothing");
            }
        };

        ObservableList<Server> allServers = FXCollections.observableArrayList(testbedInfoSource.getServers());
        sourceTestbedComboBox.setItems(new FilteredList<>(allServers, s -> s.hasFlag(Server.Flag.featurePoaExtDiskImageOps)));
        sourceTestbedComboBox.setConverter(StringConverters.SERVER_STRING_CONVERTER);

        assert publicRefreshTaskInvalidationListener != null;

        this.sourceTestbedComboBox.getSelectionModel().selectedItemProperty().addListener(observable -> {
            //check if private disk images are available
            if (!areUserDiskImagesAvailable()) {
                publicDiskImagesRadioButton.setSelected(true);
            }
            privateDiskImagesRadioButton.setDisable(!areUserDiskImagesAvailable());

            //update the correct combobox
            if (publicDiskImagesRadioButton.isSelected()) {
                updatePublicDiskImagesComponentManagerInfo();
            } else {
                updatePrivateDiskImagesComponentManagerInfo(false /* no forced refresh */);
            }
        });

        publicDiskImagesComboBox.disableProperty().bind(publicDiskImagesRadioButton.selectedProperty().not());
        publicDiskImagesComboBox.setConverter(StringConverters.DISK_IMAGE_STRING_CONVERTER);

        privateDiskImagesComboBox.disableProperty().bind(privateDiskImagesRadioButton.selectedProperty().not());
        privateDiskImagesComboBox.setConverter(StringConverters.IMAGEINFO_STRING_CONVERTER);
        privateFetchOverlay.visibleProperty().bind(isLoadingUserImages);

        deletePrivateDiskImageButton.disableProperty().bind(privateDiskImagesComboBox.getSelectionModel().selectedItemProperty().isNull());
        deletePrivateDiskImageButton.setOnAction(this::deleteCurrentPrivateDiskImage);

        privateDiskImagesRadioButton.selectedProperty().addListener(observable -> updatePrivateDiskImagesComponentManagerInfo(false));
    }

    public void setResource(Resource nodeResource) {
        this.nodeResource = nodeResource;
        sourceTestbedComboBox.getSelectionModel().select(nodeResource.getServer());

        //public is selected by default, so update that list
        updatePublicDiskImagesComponentManagerInfo();
        updatePublicDiskImagesList();
    }

    private void deleteCurrentPrivateDiskImage(ActionEvent actionEvent) {

        AggregateManagerWrapper.ImageInfo imageInfo = privateDiskImagesComboBox.getSelectionModel().getSelectedItem();

        Optional<ButtonType> answer =
                JFDialogs.create()
                        .title("Delete disk image")
                        .message(String.format("Are you sure you want to permanently delete disk image '%s' from %s?",
                                StringConverters.IMAGEINFO_STRING_CONVERTER.toString(imageInfo),
                                currentPrivateDiskImagesServer.getName()))
                        .showConfirm();

        if (answer.isPresent() && answer.get() == ButtonType.YES) {
            DeleteDiskImageTask deleteDiskImageTask =
                    tasksFactory.createDeleteDiskImageTask(currentPrivateDiskImagesServer, imageInfo);
            deleteDiskImageTask.setOnSucceeded(event -> updatePrivateDiskImagesComponentManagerInfo(true));

            taskService.submitTask(deleteDiskImageTask);


        }
    }

    private void updatePublicDiskImagesComponentManagerInfo() {
        final Server server = sourceTestbedComboBox.getSelectionModel().getSelectedItem();
        if (server == null)
            return;

        if (publicDiskImagesRadioButton.isSelected() && (currentPublicDiskImagesServer != server)) {

            if (currentPublicDiskImagesServer != null) {
                config.getDiskImages(currentPublicDiskImagesServer).removeListener(publicRefreshTaskInvalidationListener);
            }

            currentPublicDiskImagesServer = server;

            assert currentPublicDiskImagesServer != null;
            AdvertisementFetchManager.Resources allResources =
                    advertisementFetchManager.getAllResources(currentPublicDiskImagesServer);
            AdvertisementFetchManager.Resources availableResources =
                    advertisementFetchManager.getAvailableResources(currentPublicDiskImagesServer);
            assert allResources != null;
            assert availableResources != null;

            publicFetchOverlay.visibleProperty().unbind();
            publicFetchOverlay.visibleProperty().bind(allResources.currentRefreshAdvertisementTaskProperty().isNotNull());

            assert allResources.currentRefreshAdvertisementTaskProperty() != null;
            assert publicRefreshTaskInvalidationListener != null;
            allResources.currentRefreshAdvertisementTaskProperty().addListener(publicRefreshTaskInvalidationListener);
            publicRefreshTaskInvalidationListener.invalidated(null); //force invalidation
        }

        if (currentPublicDiskImagesServer == null)
            return;

        assert currentPublicDiskImagesServer != null;

        AdvertisementFetchManager.Resources allResources =
                advertisementFetchManager.getAllResources(currentPublicDiskImagesServer);
        AdvertisementFetchManager.Resources availableResources =
                advertisementFetchManager.getAvailableResources(currentPublicDiskImagesServer);


        if (!allResources.isComponentIdsAvailable() && !availableResources.isComponentIdsAvailable())
            allResources.refreshAdvertisement();
        else if (config.getDiskImages(currentPublicDiskImagesServer).isEmpty()) {
            JFDialogs.create()
                    .owner(root.getScene().getWindow())
                    .message(String.format("'%s' did not provide any available disk images", currentPublicDiskImagesServer.getName()))
                    .showWarning();
        }
    }


    private void updatePublicDiskImagesList() {
        publicDiskImagesComboBox.getSelectionModel().clearSelection();
        publicDiskImagesComboBox.setItems(config.getDiskImages(sourceTestbedComboBox.getSelectionModel().getSelectedItem()));
    }


    private void updatePrivateDiskImagesComponentManagerInfo(boolean forceRefresh) {
        final Server server = sourceTestbedComboBox.getSelectionModel().getSelectedItem();

        if (forceRefresh ||
                privateDiskImagesRadioButton.isSelected() && currentPrivateDiskImagesServer != server && !isLoadingUserImages.get()) {
            LOG.debug("Getting private disk images from {}", server.getName());
            isLoadingUserImages.set(true);

            final ListDiskImagesTask listDiskImagesTask = hltf.listDiskImages(server);
            tt.addTask(listDiskImagesTask, (task, te, state) ->
                    Platform.runLater(() -> {
                        privateDiskImagesComboBox.getItems().clear();
                        if (listDiskImagesTask.getDiskImageInfos() != null)
                            privateDiskImagesComboBox.getItems().setAll(listDiskImagesTask.getDiskImageInfos());

                        if (!privateDiskImagesComboBox.getItems().isEmpty()) {
                            privateDiskImagesComboBox.getSelectionModel().selectFirst();
                        }

                        LOG.debug("Updating private images list with {} items from authority {}",
                                listDiskImagesTask.getDiskImageInfos() != null
                                        ? listDiskImagesTask.getDiskImageInfos().size() : "null",
                                server.getName());

                        currentPrivateDiskImagesServer = server;
                        isLoadingUserImages.setValue(false);
                    }));

        }
    }

    public String getDiskImageString() {
        /*
            When the disk image resides on the same testbed as the node, we can use the URN.
            Otherwise, we have to use the URL.

             If we would use the URL all the time, this would incur a performance hit.
             Emulab-testbeds will always copy a URL-provided image locally, even if it is already present locally!
        */

        if (publicDiskImagesRadioButton.isSelected() && !publicDiskImagesComboBox.getSelectionModel().isEmpty()) {
            DiskImage selectedDiskImage = publicDiskImagesComboBox.getSelectionModel().getSelectedItem();

            if (nodeResource != null && nodeResource.getServer() == currentPublicDiskImagesServer) {
                return selectedDiskImage.getName();
            } else {
                return selectedDiskImage.getUrl();
            }
        } else if (privateDiskImagesRadioButton.isSelected() && !privateDiskImagesComboBox.getSelectionModel().isEmpty()) {
            AggregateManagerWrapper.ImageInfo imageInfo =
                    privateDiskImagesComboBox.getSelectionModel().getSelectedItem();


            if (nodeResource != null && nodeResource.getServer() == currentPrivateDiskImagesServer) {
                return imageInfo.getUrn();
            } else {
                return imageInfo.getUrl();
            }
        } else {
            return null;
        }
    }

    private boolean areUserDiskImagesAvailable() {
        return aggregateManagerWrapperFactory.getAggregateManagerWrapper(sourceTestbedComboBox.getSelectionModel().getSelectedItem())
                .areUserDiskImageFunctionsSupported();
    }
}
