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

import be.iminds.ilabt.jfed.experimenter_gui.config.JFedGuiConfig;
import be.iminds.ilabt.jfed.experimenter_gui.util.ui.StyledCheckBoxListCell;
import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.highlevel.model.SfaModel;
import be.iminds.ilabt.jfed.highlevel.model.Slice;
import be.iminds.ilabt.jfed.highlevel.model.Sliver;
import be.iminds.ilabt.jfed.lowlevel.TestbedInfoSource;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.cell.CheckBoxListCell;
import javafx.scene.layout.VBox;
import javafx.util.StringConverter;

import javax.annotation.Nonnull;
import javax.inject.Inject;
import java.util.*;
import java.util.stream.Collectors;

/**
 * User: twalcari
 * Date: 12/5/13
 * Time: 9:21 PM
 */
public class SelectAuthoritiesController {
    private final ObservableList<SelectableAuthority> selectableAuthorities = FXCollections.observableArrayList();

    private final IntegerProperty selectedAuthoritiesCount = new SimpleIntegerProperty();
    private final TestbedInfoSource testbedInfoSource;
    private final JFedGuiConfig jFedGuiConfig;
    private final SfaModel sfaModel;

    @FXML
    private VBox root;
    @FXML
    private Label headerLabel;
    @FXML
    private ListView<SelectableAuthority> authoritiesListView;

    private StringProperty headerLabelText =
            new SimpleStringProperty(
                    "Could not automatically detect which authorities are involved. Please select the authorities which contain (parts of) your experiment:");

    @Inject
    SelectAuthoritiesController(TestbedInfoSource testbedInfoSource, JFedGuiConfig jFedGuiConfig, SfaModel sfaModel) {
        this.testbedInfoSource = testbedInfoSource;
        this.jFedGuiConfig = jFedGuiConfig;
        this.sfaModel = sfaModel;
    }

    @FXML
    private void initialize() {
        headerLabel.textProperty().bindBidirectional(headerLabelText);

        Set<Integer> serverIdInResources = jFedGuiConfig.getAllServersInVisibleResources().stream()
                .map(Server::getId).filter(Objects::nonNull).collect(Collectors.toSet());
        Set<Integer> hiddenServerIds = jFedGuiConfig.getHiddenServers().stream()
                .map(Server::getId).filter(Objects::nonNull).collect(Collectors.toSet());

        Set<Integer> recentlyUsedServerIds = sfaModel.getSlices().stream()
                .map(Slice::getSlivers)
                .flatMap(List::stream)
                .map(Sliver::getServer)
                .map(Server::getId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());

        selectableAuthorities.addAll(
                testbedInfoSource.getServers().stream()
                        .filter(s -> !s.isEdgeVlan())
                        .map(SelectableAuthority::new)
                        .map(sa -> sa.setHidden(hiddenServerIds.contains(sa.getServerId())))
                        .map(sa -> sa.setVisible(serverIdInResources.contains(sa.getServerId())))
                        .map(sa -> sa.setRecentlyUsed(recentlyUsedServerIds.contains(sa.getServerId())))
                        .sorted(
                                Comparator.<SelectableAuthority>comparingInt(
                                        SelectableAuthority::getSortInt)
                                        .thenComparing(
                                                SelectableAuthority::getServerName,
                                                String.CASE_INSENSITIVE_ORDER
                                        )
                        )
                        .collect(Collectors.toList()));

        authoritiesListView.setItems(selectableAuthorities);


        authoritiesListView.setCellFactory(
                StyledCheckBoxListCell.forListView(
                        SelectableAuthority::selectedProperty,
                        new StringConverter<SelectableAuthority>() {
                            @Override
                            public String toString(SelectableAuthority selectableAuthority) {
                                return selectableAuthority.getExactSfaAuthority().getName()
//                                        +" ("+selectableAuthority.getExactSfaAuthority().getId()+")" //with ID for debugging
                                        ;
                            }

                            @Override
                            public SelectableAuthority fromString(String s) {
                                return null;
                            }
                        },
                        new StyledCheckBoxListCell.StyleProvider<SelectableAuthority>() {
                            @Nonnull
                            @Override
                            public List<String> getStyleClasses(SelectableAuthority item) {
                                List<String> res = new ArrayList<>();
                                if (item.isRecentlyUsed())
                                    res.add("styled-checkbox-listcell-strong");
                                if (item.isHidden())
                                    res.add("styled-checkbox-listcell-strike");
                                if (!item.isVisible())
                                    res.add("styled-checkbox-listcell-fade");

                                if (res.isEmpty()) {
                                    return Arrays.asList("styled-checkbox-listcell-normal");
                                } else {
                                    return res;
                                }
                            }
                        }
                )
        );

    }

    private class SelectableAuthority {
        private final BooleanProperty selected = new SimpleBooleanProperty(false);
        private final Server sfaAuthority;
        private boolean visible;
        private boolean recentlyUsed;
        private boolean hidden;

        private SelectableAuthority(Server sfaAuthority) {
            this.sfaAuthority = sfaAuthority;

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

            visible = true;
            recentlyUsed = false;
            hidden = false;
        }

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

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

        public BooleanProperty selectedProperty() {
            return selected;
        }

        public Server getExactSfaAuthority() {
            return sfaAuthority;
        }

        public Integer getServerId() {
            return sfaAuthority == null ? null : sfaAuthority.getId();
        }


        public String getServerName() {
            return sfaAuthority == null ? null : sfaAuthority.getName();
        }

        public boolean isVisible() {
            return this.visible;
        }

        public SelectableAuthority setVisible(boolean visible) {
            this.visible = visible;
            return this;
        }

        public boolean isHidden() {
            return this.hidden;
        }

        public SelectableAuthority setHidden(boolean hidden) {
            this.hidden = hidden;
            return this;
        }

        public boolean isRecentlyUsed() {
            return this.recentlyUsed;
        }

        public SelectableAuthority setRecentlyUsed(boolean recentlyUsed) {
            this.recentlyUsed = recentlyUsed;
            return this;
        }

        /** get int for sorting. recently used go first, then others, finally, invisible+hidden */
        public int getSortInt() {
            if (recentlyUsed) return 0;
            if (hidden || !visible) return 2;
            return 1;
        }
    }

    public VBox getRoot() {
        return this.root;
    }

    public int getSelectedAuthoritiesCount() {
        return selectedAuthoritiesCount.get();
    }

    public IntegerProperty selectedAuthoritiesCountProperty() {
        return selectedAuthoritiesCount;
    }

    public String getHeaderLabelText() {
        return this.headerLabelText.get();
    }

    public StringProperty headerLabelTextProperty() {
        return this.headerLabelText;
    }

    public void setHeaderLabelText(String headerLabelText) {
        this.headerLabelText.set(headerLabelText);
    }

    public Set<Server>  getSelectedServers(){
        return selectableAuthorities.stream()
                .filter(SelectableAuthority::getSelected)
//                        .map(SelectableAuthority::getUsedSfaAuthority) caller has to worry about which auth to use, not us!
                .map(SelectableAuthority::getExactSfaAuthority)
                .collect(Collectors.toSet());
    }
}
