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

import be.iminds.ilabt.jfed.experimenter_gui.events.StartExperimentEvent;
import be.iminds.ilabt.jfed.experimenter_gui.util.ui.UIUtils;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.lowlevel.GeniUserProvider;
import be.iminds.ilabt.jfed.lowlevel.authority.AuthorityFinder;
import be.iminds.ilabt.jfed.rspec.model.ModelRspecType;
import be.iminds.ilabt.jfed.rspec.model.RspecNode;
import be.iminds.ilabt.jfed.rspec.model.javafx_impl.FXModelRspec;
import be.iminds.ilabt.jfed.rspec.rspec_source.RequestRspecSource;
import be.iminds.ilabt.jfed.ui.javafx.GlyphUtils;
import be.iminds.ilabt.jfed.ui.javafx.GuiceFXMLUtil;
import be.iminds.ilabt.jfed.util.GeniUrn;
import com.google.common.eventbus.EventBus;
import javafx.event.ActionEvent;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.paint.Color;
import javafx.stage.Window;
import org.controlsfx.glyphfont.FontAwesome;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.stream.Collectors;

/**
 * User: twalcari
 * Date: 2/24/14
 * Time: 4:03 PM
 */
public class StartExperimentDialog extends Dialog<ButtonType> {
    private static final Logger LOG = LoggerFactory.getLogger(StartExperimentDialog.class);

    private final RequestRspecSource requestRspecSource;

    private final StartExperimentController controller;
    private final AuthorityFinder authorityFinder;
    private final GeniUserProvider geniUserProvider;
    private final EventBus eventBus;



    public StartExperimentDialog(GuiceFXMLUtil guiceFXMLUtil, Window parentWindow, RequestRspecSource requestRspecSource,
                                 AuthorityFinder authorityFinder, GeniUserProvider geniUserProvider, EventBus eventBus) {
        this.authorityFinder = authorityFinder;
        this.geniUserProvider = geniUserProvider;
        this.requestRspecSource = requestRspecSource;
        this.eventBus = eventBus;

        GuiceFXMLUtil.Result<?, StartExperimentController> result =
                guiceFXMLUtil.createFromFXML(StartExperimentController.class);

        this.controller = result.getController();
        controller.setMaximumSliceNameLength(getMaximumSliceNameLength());


        initOwner(parentWindow);
        setTitle("Start an experiment run");
        setGraphic(GlyphUtils.createDialogGlyph(FontAwesome.Glyph.PLAY, Color.GREEN));
        setHeaderText("Enter experiment run details");
        //TODO: is manual decoration pane wrapping still needed?
        getDialogPane().setContent(UIUtils.wrapWithDecorationPane(result.getRoot()));
        setResizable(false);

        ButtonType startExperimentAction =
                new ButtonType("Start Experiment", ButtonBar.ButtonData.OK_DONE); //this::checkAndStartExperiment);
        getDialogPane().getButtonTypes().setAll(startExperimentAction, ButtonType.CANCEL);

        Button startExperimentButton = (Button) getDialogPane().lookupButton(startExperimentAction);
        startExperimentButton.disableProperty().bind(controller.getValidationSupport().invalidProperty());
        startExperimentButton.addEventFilter(ActionEvent.ACTION, event -> {
            checkAndStartExperiment();
            event.consume();
        });

        controller.getNameTextField().setOnAction(event -> checkAndStartExperiment());
    }

    /**
     * The length of a slice-name is limited because of the maximum hostname-length under Linux,
     * which must be less than 63 characters.
     * <p/>
     * Emulab-based testbeds use the following convention to generate hostnames:
     * <nodename>.<slicename>.<user-authority-without-subauthority>.<testbed-authority>
     * <p/>
     * For example:
     * Emulab.net-node with a GENI-account: node0.slicename.ch-geni-net.emulab.net
     * <p/>
     * <p/>
     * planetlab used to have another constraint (due to login name) but that is gone now
     *
     * @return
     */
    private int getMaximumSliceNameLength() {
        if (requestRspecSource.isXmlBased())
            return 8; //fallback in case we cannot parse the Rspec

        final FXModelRspec modelRspec = requestRspecSource.getModelRspec(ModelRspecType.FX);
        assert modelRspec != null;
        List<RspecNode> emulabBasedNodes = modelRspec.getNodes()
                .stream()
                .filter(rspecNode -> {
                    GeniUrn compManId = rspecNode.getComponentManagerId();
                    if (compManId == null) return false;
                    Server nodeAuth =
                            authorityFinder.findByUrn(compManId, AuthorityFinder.Purpose.CREATE_SLIVER);
                    if (nodeAuth == null) return false;
                    return nodeAuth.isServerType("emulab") || nodeAuth.isServerType("instageni");
                }).collect(Collectors.toList());
        LOG.debug("getMaximumSliceNameLength() emulabBasedNodes.size()=" + emulabBasedNodes.size());

        int res = 10; //we set a short max length in any case
        if (!emulabBasedNodes.isEmpty()) {
            //at least one emulab based node
            int nodeNameAndTestBedAuthorityLength = emulabBasedNodes.stream()
                    .mapToInt(rspecNode -> {
                        int totalLength = 0;
                        if (rspecNode.getClientId() != null) {
                            totalLength += rspecNode.getClientId().length();
                        }
                        if (rspecNode.getComponentManagerId() != null) {
                            totalLength += rspecNode.getComponentManagerId().getEncodedTopLevelAuthority().length();
                        }
                        return totalLength;
                    })
                    .max().orElse(15);//compute length of first and last part of slice-name.

            final Server userAuthorityServer = geniUserProvider.getLoggedInGeniUser().getUserAuthorityServer();
            assert userAuthorityServer != null;
            String userTld = userAuthorityServer.getDefaultComponentManagerAsGeniUrn().getEncodedTopLevelAuthority();

            int maxLength = 63 - 3/* number of dots in the hostname */;
            maxLength -= nodeNameAndTestBedAuthorityLength;
            maxLength -= userTld.length();

            if (maxLength < res)
                res = maxLength;
        }

        LOG.debug("getMaximumSliceNameLength() res=" + res);

        return res;
    }


    /**
     * This method uses the inputs from the dialog to start the experiment.
     * These inputs should be validated before calling this function!
     */
    private void startExperiment() {
        hide();

        StartExperimentEvent startExperimentEvent = new StartExperimentEvent(controller.getSliceName(), controller.getSubAuthName(), controller.getExpirationTime(),
                        requestRspecSource, controller.isIncludeSshKeys(), controller.isRequestSla());
        LOG.info("User pressed \"Start Exeriment\" button: {}", startExperimentEvent);
        eventBus.post(startExperimentEvent);
    }


    public void checkAndStartExperiment() {
        if (controller.getValidationSupport().isInvalid()) {
            LOG.warn("Cannot start because validationSupport is currently invalid");
            return;
        }

        final String sliceName = controller.getSliceName();
        LOG.info("Trying to make a slice with sliceName '{}'", sliceName);
        startExperiment();
    }
}
