package be.iminds.ilabt.jfed.experimenter_gui.timeline.view;

import be.iminds.ilabt.jfed.experimenter_gui.timeline.controller.TimelineController;
import be.iminds.ilabt.jfed.experimenter_gui.timeline.model.TimelineModel;
import be.iminds.ilabt.jfed.experimenter_gui.timeline.util.TimeCoordinateTranslator;
import be.iminds.ilabt.jfed.experimenter_gui.timeline.view.dialogs.AddMultipleCommandDialog;
import be.iminds.ilabt.jfed.experimenter_gui.slice.NodePropertiesDialogFactory;
import be.iminds.ilabt.jfed.experimenter_gui.util.SSHTerminalUtil;
import be.iminds.ilabt.jfed.lowlevel.GeniUserProvider;
import be.iminds.ilabt.jfed.rspec.model.controller.ExperimentBarrierSegment;
import be.iminds.ilabt.jfed.rspec.model.controller.ExperimentCommand;
import be.iminds.ilabt.jfed.rspec.model.controller.Timestamp;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.control.Tooltip;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import org.controlsfx.glyphfont.FontAwesome;
import org.controlsfx.glyphfont.GlyphFont;
import org.controlsfx.glyphfont.GlyphFontRegistry;

import java.util.HashMap;
import java.util.Map;

/**
 * @author Cedric
 */
public class TimelineNode extends Pane implements TimelineComponent {

    private final Line node;
    private final String uniqueNodeId;
    private final Map<Integer, TimelineNodeCommand> commands = new HashMap<>();
    private final Tooltip tooltip;
    private final TimelineModel model;
    private final TimelineController controller;
    private final ExperimentBarrierSegment barrierSegment;
    private final TimeCoordinateTranslator translator;
    private final TimelineBarrierSegment segmentParent;
    private ContextMenu menu;
    //Keeping track where the user last clicked (for adding a command on the right spot)
    private double lastClick = 0;


    private final GeniUserProvider geniUserProvider;
    private final NodePropertiesDialogFactory nodePropertiesDialogFactory;
    private final SSHTerminalUtil sshTerminalUtil;

    public TimelineNode(String uniqueNodeId, TimelineModel model, TimelineController controller,
                        ExperimentBarrierSegment b, TimelineBarrierSegment segmentParent,
                        GeniUserProvider geniUserProvider, NodePropertiesDialogFactory nodePropertiesDialogFactory, SSHTerminalUtil sshTerminalUtil) {
        this.uniqueNodeId = uniqueNodeId;
        this.model = model;
        this.controller = controller;
        this.barrierSegment = b;
        this.segmentParent = segmentParent;
        this.geniUserProvider = geniUserProvider;
        this.nodePropertiesDialogFactory = nodePropertiesDialogFactory;
        this.sshTerminalUtil = sshTerminalUtil;
        translator = TimeCoordinateTranslator.getInstance();

        node = new Line();
        node.getStyleClass().add("node");
        getChildren().add(node);

        tooltip = new Tooltip(uniqueNodeId);
        tooltip.getStyleClass().add("tooltip");

        //setPickOnBounds: only accept mouseEvents if they overlap with non-transparant parts of this pane (e.g. a nodeline)
        setPickOnBounds(false);

        initMenu();
        initClickHandler();
        initTooltipHandlers();

    }

    private void initMenu() {
        GlyphFont fontAwesome = GlyphFontRegistry.font("FontAwesome");

        menu = new ContextMenu();
        MenuItem mnAdd = new MenuItem("Add command");
        mnAdd.setOnAction(t -> AddMultipleCommandDialog.showSimpleAddDialog(getScene().getWindow(), controller,
                model.getNodes(), model.getRspecNode(uniqueNodeId), barrierSegment,
                new Timestamp(translator.translateWidthToTime(lastClick))));
        mnAdd.setGraphic(fontAwesome.create(FontAwesome.Glyph.PLUS).color(Color.GREEN));

        MenuItem mnOutput = new MenuItem("Show output");
        mnOutput.setOnAction(t -> controller.showNodeOutput(model.getRspecNode(uniqueNodeId)));
        mnOutput.setGraphic(fontAwesome.create(FontAwesome.Glyph.REORDER).color(Color.BLUE));

        MenuItem mnSaveOutput = new MenuItem("Save output");
        mnSaveOutput.setOnAction(t -> controller.saveOutput(model.getRspecNode(uniqueNodeId).getCommands()));
        mnSaveOutput.setGraphic(fontAwesome.create(FontAwesome.Glyph.SAVE).color(Color.BLUE));

        menu.getItems().addAll(mnAdd, mnOutput, mnSaveOutput);

        //disable configure node from this GUI
//        if (model.getModelRspecEditor() != null) {
//            final MenuItem mnProperties = new MenuItem("Configure node");
//            mnProperties.setOnAction(new EventHandler<ActionEvent>() {
//
//                @Override
//                public void handle(ActionEvent t) {
//                    AdaptivePropertiesDialog.showPropertiesDialog(ControllerNode.this.getScene().getWindow(),
//                            model.getModelRspecEditor(), model.getRspecNode(uniqueNodeId));
//                }
//            });
//            mnProperties.setGraphic(fontAwesome.create(FontAwesome.Glyph.PENCIL).color(Color.BLACK));
//            menu.getItems().add(mnProperties);
//        }

        if (!model.isOfflineEditor()) {
            menu.getItems().add(new SeparatorMenuItem());
            MenuItem mnSSH =
                    new MenuItem("Open SSH terminal", fontAwesome.create(FontAwesome.Glyph.TERMINAL));
            mnSSH.setOnAction(actionEvent -> {
                assert (model.getExperiment() != null);
                sshTerminalUtil.launchSSHTerminal(model.getRspecNode(uniqueNodeId), model.getExperiment().getSlice());
            });

            BooleanBinding nodeHasLoginInfoExpression = Bindings.isEmpty(model.getRspecNode(uniqueNodeId).getLoginServices()).not();

            mnSSH.disableProperty().bind(nodeHasLoginInfoExpression);
            menu.getItems().add(mnSSH);

            String username = null;
            if (geniUserProvider != null && geniUserProvider.getLoggedInGeniUser() != null)
                username =
                        geniUserProvider.getLoggedInGeniUser().getUserUrn().getEncodedResourceName();
            if (username != null) {
                MenuItem mnSSHForced =
                        new MenuItem(String.format("Open SSH terminal with user \"%s\"", username),
                                fontAwesome.create(FontAwesome.Glyph.TERMINAL));
                mnSSHForced.setOnAction(actionEvent -> {
                    assert (model.getExperiment() != null);
                    sshTerminalUtil.launchSSHTerminalForcedLocalUser(model.getRspecNode(uniqueNodeId), model.getExperiment().getSlice());
                });

                mnSSHForced.disableProperty().bind(nodeHasLoginInfoExpression);

                menu.getItems().add(mnSSHForced);
            }


            final MenuItem mnInfo = new MenuItem("Show node info");
            mnInfo.setOnAction(t -> nodePropertiesDialogFactory.showSimplePropertiesDialog(model.getExperiment().getSlice(), model.getRspecNode(uniqueNodeId)));
            mnInfo.setGraphic(fontAwesome.create(FontAwesome.Glyph.INFO).color(Color.BLACK));
            mnInfo.disableProperty().bind(nodeHasLoginInfoExpression);
            menu.getItems().add(mnInfo);
        }

        mnOutput.setDisable(model.isOfflineEditor());
        mnSaveOutput.setDisable(model.isOfflineEditor());
    }

    public void addCommand(ExperimentCommand command) {
        if (commands.containsKey(command.getID())) return;

        TimelineNodeCommand comm =
                new TimelineNodeCommand(command.getID(), model, controller, TimelineNode.this, segmentParent);
        commands.put(comm.getID(), comm);

        comm.setCenterX(translator.translateTimeToWidth(command.getStartingTime().toMillis()));
        comm.setCenterY(node.getStartY());
    }

    /*
     * getX: will get the right X-coordinate for the command-placement
     *      > taking into account how much has been scrolled and etc
     * getScreenX: will get the right X-coordinate for showing the menu
     */
    private void initClickHandler() {
        addEventHandler(MouseEvent.MOUSE_CLICKED, me -> {
            if (me.getButton() == MouseButton.SECONDARY) {
                lastClick = me.getX();
                menu.show(TimelineNode.this, me.getScreenX(), me.getScreenY());
                me.consume();
            }
        });

        addEventFilter(MouseEvent.MOUSE_CLICKED, t -> menu.hide());
    }

    private void initTooltipHandlers() {
        node.setOnMouseEntered(t -> {
            tooltip.show(node
                    , t.getScreenX() + 5
                    , t.getScreenY() + node.getStrokeWidth());
            highight();
        });

        node.setOnMouseExited(t -> {
            tooltip.hide();
            unHighlight();
        });
    }

    public void highight() {
        node.getStyleClass().add("nodeHighlight");
    }

    public void unHighlight() {
        node.getStyleClass().removeAll("nodeHighlight");
    }

    public void setStartX(double d) {
        node.setStartX(d);
    }

    public void setStartY(double d) {
        node.setStartY(d);

        for (TimelineNodeCommand t : commands.values()) {
            t.setCenterY(d);
        }
    }

    public void setEndX(double d) {
        node.setEndX(d);
    }

    public void setEndY(double d) {
        node.setEndY(d);
    }

    public Line getNodeLine() {
        return node;
    }

    @Override
    public void update(UpdateEvent event, Object o) {
        if (event == UpdateEvent.UPDATE_TIME || event == UpdateEvent.UPDATE_ZOOM || event == UpdateEvent.REWIND_TIME) {
            for (TimelineNodeCommand c : commands.values()) {
                c.update(event, o);
            }
        } else if (event == UpdateEvent.CLEAR) {
            for (TimelineNodeCommand rnc : commands.values()) {
                rnc.update(event, o);
            }
            commands.clear();
        } else if (event == UpdateEvent.ADD_COMMAND) {
            addCommand((ExperimentCommand) o);
        } else if (event == UpdateEvent.DELETE_COMMAND) {
            ExperimentCommand c = (ExperimentCommand) o;
            commands.get(c.getID()).update(UpdateEvent.CLEAR, null);
            commands.remove(c.getID());
        }
    }

}
