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

import be.iminds.ilabt.jfed.experiment.ExperimentController;
import be.iminds.ilabt.jfed.experimenter_gui.util.WaitForTaskExecutionsTask;
import be.iminds.ilabt.jfed.highlevel.model.SfaModel;
import be.iminds.ilabt.jfed.highlevel.model.Slice;
import be.iminds.ilabt.jfed.highlevel.tasks.HighLevelTaskFactory;
import be.iminds.ilabt.jfed.highlevel.tasks.ListSliceMembersTask;
import be.iminds.ilabt.jfed.highlevel.tasks.ListSubAuthorityMembersTask;
import be.iminds.ilabt.jfed.lowlevel.GeniUserProvider;
import be.iminds.ilabt.jfed.ui.javafx.FXMLUtil;
import be.iminds.ilabt.jfed.ui.javafx.GlyphUtils;
import be.iminds.ilabt.jfed.util.GeniUrn;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.control.cell.CheckBoxListCell;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Window;
import javafx.util.Callback;
import org.controlsfx.glyphfont.FontAwesome;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.stream.Collectors;

/**
 */
public class ShareSliceDialog extends VBox {
    private static final Logger LOG = LoggerFactory.getLogger(ShareSliceDialog.class);

    private final Slice slice;
    private final ExperimentController experimentController;

    private final GeniUserProvider geniUserProvider;
    private final SfaModel sfaModel;
    private final HighLevelTaskFactory hltf;


    @FXML
    private ListView<SelectableUser> usersList;

    @FXML
    private HBox initializingBox;

    @FXML
    private CheckBox addSshKeysCheckBox;

    @FXML
    private Label helpLabel;

    private final Dialog<ButtonType> dialog;
    private final ButtonType shareType = new ButtonType("Share", ButtonBar.ButtonData.OK_DONE);

    ShareSliceDialog(Window parentWindow, ExperimentController experimentController,
                     GeniUserProvider geniUserProvider, SfaModel sfaModel, HighLevelTaskFactory hltf) {
        this.experimentController = experimentController;
        this.geniUserProvider = geniUserProvider;
        this.sfaModel = sfaModel;
        this.hltf = hltf;
        this.slice = experimentController.getExperiment().getSlice();

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


        usersList.setCellFactory(new Callback<ListView<SelectableUser>, ListCell<SelectableUser>>() {
            @Override
            public ListCell<SelectableUser> call(ListView<SelectableUser> param) {
                return new CheckBoxListCell<SelectableUser>(SelectableUser::selectedProperty) {
                    @Override
                    public void updateItem(SelectableUser item, boolean empty) {
                        super.updateItem(item, empty);

                        if (item != null) {
                            setDisable(item.isFixed());
                        }
                    }
                };
            }
        });

        WaitForTaskExecutionsTask waitTask = new WaitForTaskExecutionsTask() {

            @Override
            public void onTaskExecutionsFinished() {
                updateUsersList();

                initializingBox.setVisible(false);
            }
        };

        if (experimentController.getExperiment().getSlice().getUsers().isEmpty()) {
            ListSliceMembersTask listSliceMembersTask =
                    this.hltf.listSliceMembers(experimentController.getExperiment().getSlice());

            waitTask.addTaskExecution(this.hltf.submitTask(listSliceMembersTask));
        }

        if (sfaModel.getUsersBySubAuthority(experimentController.getExperiment().getSubAuthName()).isEmpty()) {
            ListSubAuthorityMembersTask listSubAuthorityMembersTask =
                    this.hltf.listSubAuthorityMembers(experimentController.getExperiment().getSubAuthName());

            waitTask.addTaskExecution(this.hltf.submitTask(listSubAuthorityMembersTask));
        }


        new Thread(null, waitTask, "ShareSlice-WaitForBTaskExecutionsTasks").start();

        final String sliceProject = slice.getSubAuthorityName();
        final String authName = geniUserProvider.getLoggedInGeniUser().getUserAuthorityServer().getName();

        String helpText =
                "Sharing an experiment has some limitations:\n" +
                        "- You can only share with users of the same authority (" + authName + ")\n";

        if (sliceProject != null)
            helpText +=
                    "- You can only share with users that are member of the \"" + sliceProject + "\" project in which this experiment was created.\n";

        helpLabel.setText(helpText);

        //initialize dialog
        dialog = new Dialog<>();
        dialog.initOwner(parentWindow);
        dialog.setTitle("Share Experiment");
        dialog.setHeaderText("Share your experiment");
        dialog.setGraphic(GlyphUtils.createDialogGlyph(FontAwesome.Glyph.AMBULANCE, Color.GREEN));
        dialog.getDialogPane().setContent(this);
        dialog.getDialogPane().getButtonTypes().setAll(shareType, ButtonType.CANCEL);

        Node shareButton = dialog.getDialogPane().lookupButton(shareType);
        shareButton.disableProperty().bind(selectedNewUsersCount.isEqualTo(0));

        dialog.resultProperty().addListener(observable -> {
            if (dialog.getResult() == shareType) {
                List<GeniUrn> selectedUsers = usersList.getItems().stream()
                        .filter(SelectableUser::getSelected)
                        .map(SelectableUser::getUrn)
                        .collect(Collectors.toList());

                //remove all existing users
                selectedUsers.removeAll(experimentController.getExperiment().getSlice().getUsers());

                if (!selectedUsers.isEmpty())
                    experimentController.shareWithUsers(selectedUsers, addSshKeysCheckBox.isSelected());
                else
                    LOG.warn("Unexpectedly got an empty list of new project members");
            }
        });

    }

    @FXML
    private void addCustomUser() {

        TextInputDialog dialog = new TextInputDialog();

        dialog.setHeaderText("Share experiment to custom user");
        dialog.setContentText("Username:");

        dialog.getEditor().textProperty().addListener((observable, oldValue, newValue) -> {
            String strippedValue = newValue.replaceAll("[^A-Za-z0-9_-]", "");

            if (!Objects.equals(strippedValue, newValue)) {
                dialog.getEditor().setText(strippedValue);
            }
        });


        dialog.showAndWait().ifPresent(username -> {
            final GeniUrn curUserUrn = geniUserProvider.getLoggedInGeniUser().getUserUrn();
            GeniUrn newUserUrn =
                    GeniUrn.createGeniUrnFromEncodedParts(curUserUrn.getEncodedTopLevelAuthority(), "user", username);

            Optional<SelectableUser> existingUser =
                    usersList.getItems().stream().filter(su -> Objects.equals(su.getUrn(), newUserUrn)).findFirst();

            if (!existingUser.isPresent()) {
                final SelectableUser newSelectableUser = new SelectableUser(newUserUrn, true, false);
                usersList.getItems().add(newSelectableUser);
                usersList.scrollTo(newSelectableUser);
            } else {
                new Alert(Alert.AlertType.ERROR, "The user already exists in the list!").showAndWait();
                usersList.scrollTo(existingUser.get());
            }

        });

    }

    private void updateUsersList() {
        assert !experimentController.getExperiment().getSlice().getUsers().isEmpty();
        assert !sfaModel.getUsersBySubAuthority(experimentController.getExperiment().getSubAuthName()).isEmpty();

        usersList.getItems().clear();

        final Collection<GeniUrn> currentMembers = experimentController.getExperiment().getSlice().getUsers();
        for (GeniUrn currentMember : currentMembers) {
            usersList.getItems().addAll(new SelectableUser(currentMember, true, true));
        }

        List<GeniUrn> candidateMembers =
                new ArrayList<>(sfaModel.getUsersBySubAuthority(experimentController.getExperiment().getSubAuthName()));
        candidateMembers.removeAll(currentMembers);

        for (GeniUrn candidateMember : candidateMembers) {
            usersList.getItems().addAll(new SelectableUser(candidateMember, false, false));
        }

        usersList.getItems().sort(Comparator.comparing(SelectableUser::isFixed).reversed()
                .thenComparing(SelectableUser::getSelected)
                .thenComparing(su -> su.getUrn().getResourceName().toLowerCase()));

    }


    public void showDialog() {
        dialog.show();
    }

    private final IntegerProperty selectedNewUsersCount = new SimpleIntegerProperty(0);

    private final class SelectableUser {

        private final GeniUrn urn;
        private final BooleanProperty selected = new SimpleBooleanProperty(false);
        private final boolean fixed;


        public SelectableUser(GeniUrn urn, boolean selected, boolean fixed) {
            this.fixed = fixed;

            if (!fixed) {
                this.selected.addListener((observable, oldValue, newValue) -> {
                    if (oldValue != newValue) {
                        if (newValue) {
                            selectedNewUsersCount.set(selectedNewUsersCount.get() + 1);
                        } else {
                            selectedNewUsersCount.set(selectedNewUsersCount.get() - 1);
                        }
                    }
                });
            }

            this.selected.set(selected);
            this.urn = urn;
        }

        public boolean isFixed() {
            return fixed;
        }

        public boolean getSelected() {
            return selected.get();
        }

        public BooleanProperty selectedProperty() {
            return selected;
        }

        public void setSelected(boolean selected) {
            this.selected.set(selected);
        }

        public GeniUrn getUrn() {
            return urn;
        }

        @Override
        public String toString() {
            return urn.getResourceName();
        }
    }
}
