package be.iminds.ilabt.jfed.experimenter_gui.slice;

import be.iminds.ilabt.jfed.experimenter_gui.canvas.ExperimentCanvas;
import be.iminds.ilabt.jfed.experimenter_gui.util.BrowserUtil;
import be.iminds.ilabt.jfed.experimenter_gui.util.ImageUtil;
import be.iminds.ilabt.jfed.ui.javafx.GlyphUtils;
import be.iminds.ilabt.jfed.util.ExecuteOnNotNull;
import be.iminds.jfed.gts_highlevel.jobs.gts_parts.GtsExperimentPart;
import be.iminds.jfed.gts_highlevel.model.GtsXmlReservation;
import be.iminds.jfed.gts_highlevel.model.GtsXmlResource;
import be.iminds.jfed.gts_highlevel.model.GtsXmlResource.Type;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.FlowPane;
import org.controlsfx.glyphfont.FontAwesome;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * User: twalcari
 * Date: 10/14/2015
 * Time: 17:13
 */
public class GTSDetailsView extends FlowPane {
    private static final Logger LOG = LoggerFactory.getLogger(GTSDetailsView.class);

    private final GtsExperimentPart experimentPart;
    private final BrowserUtil browserUtil;

    private final Map<GtsXmlResource, Button> buttons = new HashMap<>();

    private final InvalidationListener updateListener = observable -> Platform.runLater(this::update);

    {
        setPrefWrapLength(300);
        setHgap(5);
        setVgap(5);
    }

    public GTSDetailsView(GtsExperimentPart experimentPart, BrowserUtil browserUtil) {
        this.experimentPart = experimentPart;
        this.browserUtil = browserUtil;

        assert experimentPart.getReservation() != null;
        experimentPart.getReservation().statusProperty().addListener(new WeakInvalidationListener(updateListener));
        experimentPart.getReservation().getChildren().addListener(new WeakInvalidationListener(updateListener));

        new ExecuteOnNotNull<GtsXmlResource>(experimentPart.resourceProperty()){
            @Override
            public void run() {
                experimentPart.getResource().statusProperty().addListener(new WeakInvalidationListener(updateListener));
                experimentPart.getResource().reservationsProperty().addListener(new WeakInvalidationListener(updateListener));

                Platform.runLater(GTSDetailsView.this::update);
            }
        };

        update();
    }

    public void update() {
        assert experimentPart.getReservation() != null;
        LOG.debug("Processing nodes in reservation {}", experimentPart.getReservation().getId());

        final Set<GtsXmlResource> resourcesInReservation = experimentPart.getResourcesInReservation();

        LOG.debug("Found {} resources in  reservation: {}", resourcesInReservation.size(),
                resourcesInReservation.stream().map(Object::toString).collect(Collectors.joining(", ")));

        resourcesInReservation.stream()
                .filter(resource -> resource.getType() != Type.Testbed)
                .filter(resource -> !buttons.containsKey(resource))
                .forEach(this::addGtsResource);
    }


    private void addGtsResource(GtsXmlResource resource) {
        Button button = new Button();
        button.getStylesheets().add(ExperimentCanvas.class.getResource("canvas.css").toExternalForm());
        button.getStyleClass().add("canvas-node");

        Node graphic = null;
        switch (resource.getType()) {
            case Host: {
                final ImageView imageView =
                        new ImageView(new Image(ImageUtil.class.getResource("/images/gts_types/32/gts-node.png").toExternalForm()));
                imageView.setFitWidth(16);
                imageView.setFitHeight(16);
                graphic = imageView;
                break;
            }
            case Link:
                graphic = GlyphUtils.createGlyph(FontAwesome.Glyph.ARROWS_H);
                break;
            case OFX: {
                final ImageView imageView =
                        new ImageView(new Image(ImageUtil.class.getResource("/images/gts_types/32/gts-ofx.png").toExternalForm()));
                imageView.setFitWidth(16);
                imageView.setFitHeight(16);
                graphic = imageView;
                break;
            }
            case ExternalDomain: {
                final ImageView imageView =
                        new ImageView(new Image(ImageUtil.class.getResource("/images/gts_types/32/gts-network.png").toExternalForm()));
                imageView.setFitWidth(16);
                imageView.setFitHeight(16);
                graphic = imageView;
                break;
            }
        }
        button.setGraphic(graphic);
        button.setText(resource.getId());

        resource.getReservations().statusProperty().addListener(observable -> updateGtsStyleClass(button, resource));
        updateGtsStyleClass(button, resource);

        if (resource.getType() == Type.Host) {
            button.setOnAction(event -> {
                URL vncUrl = null;
                if (resource.getCapabilities() != null && resource.getCapabilities().containsKey("console")) {
                    try {
                        vncUrl = new URL(resource.getCapabilities().get("console"));
                        LOG.info("Found login-url {} for GTS resource {} (providerId: {})",
                                vncUrl, resource.getId(), resource.getProviderId());
                    } catch (MalformedURLException e) {
                        LOG.warn("Got invalid URL {} while processing GTS resource {}",
                                resource.getCapabilities().get("console"), resource.getProviderId(), e);
                    }
                }

                if (vncUrl != null) {
                    browserUtil.openUrlInBrowser(vncUrl);
                } else {
                    new Alert(Alert.AlertType.WARNING, "No login info available").show();
                }
            });
        }

        buttons.put(resource, button);
        getChildren().add(button);

        LOG.debug("Added visualisation of GTSResource {}", resource);
    }

    private static void updateGtsStyleClass(Button button, GtsXmlResource resource) {
        //remove all old styleclasses
        button.getStyleClass().removeAll("canvas-node-unknown",
                "canvas-node-ready",
                "canvas-node-changing",
                "canvas-node-unallocated",
                "canvas-node-uninitialised");


        //apply new styleclasses
        button.getStyleClass().add(statusToNodeStyleClass(resource.getReservations().getStatus(),
                resource.getType() != Type.Host || (
                        resource.getCapabilities() != null && resource.getCapabilities().containsKey("console"))));
    }

    private static String statusToNodeStyleClass(GtsXmlReservation.Status status, boolean hasLoginInfo) {
        if (status == null)
            return "canvas-node-unknown";
        switch (status) {
            case ACTIVE:
                if (hasLoginInfo)
                    return "canvas-node-ready";
                else
                    return "canvas-node-ready-nologin";
            case ACTIVATING:
            case DEACTIVATING:
            case RESERVING:
            case RELEASING:
                return "canvas-node-changing";
            case RESERVED:
                return "canvas-node-unallocated";
            case DEACTIVED:
                return "canvas-node-uninitialised";
            case FAILURE:
                return "canvas-node-failed";
            default:
                throw new IllegalArgumentException("Unknown status: " + status);
        }
    }

}
