package be.iminds.ilabt.jfed.ui.javafx.choosers;

import be.iminds.ilabt.jfed.fedmon.webapi.service.json.Server;
import be.iminds.ilabt.jfed.lowlevel.TestbedInfoSource;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.SortedList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.ComboBox;
import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.VBox;
import javafx.util.StringConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import java.text.Collator;
import java.util.Comparator;

public class ServerChooser {
    private static final Logger LOG = LoggerFactory.getLogger(ServerChooser.class);

    @FXML
    private VBox root;
    @FXML
    private ComboBox<Server> comboBox;
    @FXML
    private MenuButton configViewMenuButton;

    private final TestbedInfoSource testbedInfoSource;

    @Inject
    public ServerChooser(TestbedInfoSource testbedInfoSource) {
        this.testbedInfoSource = testbedInfoSource;
    }

    public static class ServerChooserViewConfig {
        public enum SortOrder { SERVER_ID, TESTBED_ID, TESTBED_NAME }

        public enum NameShown { SERVER_NAME, TESTBED_NAME, TESTBED_ID, SERVER_ID, TESTBED_BOTH, SERVER_BOTH }

        private final SortOrder sortOrder;
        private final NameShown nameShown;

        public ServerChooserViewConfig(SortOrder sortOrder, NameShown nameShown) {
            this.sortOrder = sortOrder;
            this.nameShown = nameShown;
        }
        public ServerChooserViewConfig() {
//            this.sortOrder = SortOrder.SERVER_ID;
//            this.nameShown = NameShown.TESTBED_NAME;
            this.sortOrder = SortOrder.TESTBED_NAME;
            this.nameShown = NameShown.TESTBED_NAME;
        }

        public String getShownName(Server server) {
            assert server != null;
            assert server.getTestbed() != null;
            switch (nameShown) {
                case SERVER_NAME: return server.getName()+"";
                case TESTBED_NAME: return server.getTestbed().getLongName()+"";
                case TESTBED_ID: return server.getTestbed().getId()+"";
                case SERVER_ID: return server.getId()+"";
                case TESTBED_BOTH: return server.getTestbed().getLongName()+" ("+server.getTestbed().getId()+")";
                case SERVER_BOTH: return server.getName()+" ("+server.getId()+")";
                default: throw new RuntimeException("NameShown not supported: "+nameShown);
            }
        }

        public SortOrder getSortOrder() {
            return sortOrder;
        }

        public Comparator<Server> getComparator() {
            return (s1, s2) -> {
                Collator c = Collator.getInstance();
                switch (sortOrder) {
                    case SERVER_ID: return s1.getId().compareTo(s2.getId());
                    case TESTBED_ID: return c.compare(s1.getTestbed().getId(), s2.getTestbed().getId());
                    case TESTBED_NAME: return c.compare(s1.getTestbed().getLongName(), s2.getTestbed().getLongName());
                    default: throw new RuntimeException("SortOrder not supported: "+sortOrder);
                }
            };
        }
    }
    private ServerChooserViewConfig viewConfig = new ServerChooserViewConfig();

    private ObservableList<Server> serverObservableList;

    @FXML
    private void initialize() {
//        configViewMenuButton.setOnAction(new EventHandler<ActionEvent>() {
//            @Override
//            public void handle(ActionEvent event) {
//                LOG.debug("configViewMenuButton onAction " + event.toString());
//            }
//        });
//        for (MenuItem menuItem : configViewMenuButton.getItems()) {
//            menuItem.on
//        }

        comboBox.setConverter(new StringConverter<Server>() {
            @Override
            public String toString(Server server) {
                assert server != null;
                assert server.getTestbed() != null;
                return viewConfig.getShownName(server);
            }

            @Override
            public Server fromString(String s) {
                throw new RuntimeException("unused");
            }
        });

        editable.addListener((observableValue, oldBoolean, newBoolean) -> comboBox.disableProperty().set(!newBoolean));

        showEditListButton.addListener((observableValue, oldBoolean, newBoolean) -> {
            configViewMenuButton.setVisible(newBoolean);
            configViewMenuButton.setManaged(newBoolean);
        });

        assert (testbedInfoSource != null);
        serverObservableList = FXCollections.observableArrayList(testbedInfoSource.getServers());
        comboBox.setItems(new SortedList<>(serverObservableList, viewConfig.getComparator()));
        comboBox.getSelectionModel().selectFirst();
    }


    public Server getSelectedServer() {
        return comboBox.getSelectionModel().getSelectedItem();
    }

    public ReadOnlyObjectProperty<Server> selectedAuthorityProperty() {
        return comboBox.getSelectionModel().selectedItemProperty();
    }

    public void select(Server server) {
        assert server != null : "must always select something";
        comboBox.getSelectionModel().select(server);
    }

    public void setSort(ActionEvent e) {
        MenuItem menuItem = (MenuItem) e.getSource();
        ServerChooserViewConfig.SortOrder newSortOrder = null;
        if (menuItem.getText().contains("Server ID"))
            newSortOrder = ServerChooserViewConfig.SortOrder.SERVER_ID;
        if (menuItem.getText().contains("Testbed ID"))
            newSortOrder = ServerChooserViewConfig.SortOrder.TESTBED_ID;
        if (menuItem.getText().contains("Testbed Name"))
            newSortOrder = ServerChooserViewConfig.SortOrder.TESTBED_NAME;
        if (newSortOrder == null)
            throw new RuntimeException("BUG: did not find sort order: \""+menuItem.getText()+"\"");
        viewConfig = new ServerChooserViewConfig(newSortOrder, viewConfig.nameShown);
        configView();
    }

    public void setNameShown(ActionEvent e) {
        MenuItem menuItem = (MenuItem) e.getSource();
        ServerChooserViewConfig.NameShown newNameShown = null;
        if (menuItem.getText().contains("Server Name"))
            newNameShown = ServerChooserViewConfig.NameShown.SERVER_NAME;
        if (menuItem.getText().contains("Server ID"))
            newNameShown = ServerChooserViewConfig.NameShown.SERVER_ID;
        if (menuItem.getText().contains("Testbed Name"))
            newNameShown = ServerChooserViewConfig.NameShown.TESTBED_NAME;
        if (menuItem.getText().contains("Testbed ID"))
            newNameShown = ServerChooserViewConfig.NameShown.TESTBED_ID;
        if (menuItem.getText().contains("Server Name & ID"))
            newNameShown = ServerChooserViewConfig.NameShown.SERVER_BOTH;
        if (menuItem.getText().contains("Testbed Name & ID"))
            newNameShown = ServerChooserViewConfig.NameShown.TESTBED_BOTH;
        if (newNameShown == null)
            throw new RuntimeException("BUG: did not find name to show: \""+menuItem.getText()+"\"");
        viewConfig = new ServerChooserViewConfig(viewConfig.sortOrder, newNameShown);
        configView();
    }

    @FXML
    public void configView() {
        Server selected = comboBox.getSelectionModel().getSelectedItem();

        //StringConverter will automatically adapt
        comboBox.setItems(FXCollections.emptyObservableList());
        comboBox.setItems(new SortedList<>(serverObservableList, viewConfig.getComparator()));

        if (selected == null)
            comboBox.getSelectionModel().clearSelection();
        else
            comboBox.getSelectionModel().select(selected);
    }

    private final BooleanProperty editable = new SimpleBooleanProperty(true);

    public BooleanProperty editableProperty() {
        return editable;
    }

    private final BooleanProperty showEditListButton = new SimpleBooleanProperty(true);

    public BooleanProperty showEditListButtonProperty() {
        return showEditListButton;
    }

    private final ObservableList<Server> filteredAuthorities = FXCollections.observableArrayList();
    private ServerFilter serverFilter;
//    private ListChangeListener<Server> listChangeListener;

    public void setServerFilter(final ServerFilter serverFilter) {
        assert testbedInfoSource.getServers() != null;

        if (serverFilter == null) {
            this.serverFilter = null;
            comboBox.setItems(FXCollections.observableArrayList(testbedInfoSource.getServers()));
            comboBox.getSelectionModel().selectFirst();
            return;
        }
        this.serverFilter = serverFilter;

        filteredAuthorities.clear();
        for (Server server : testbedInfoSource.getServers()) {
            if (serverFilter.includeAuthority(server))
                filteredAuthorities.add(server);
        }
        comboBox.setItems(filteredAuthorities);

        //init and set listener only once. It keeps listening, but it doesn't do anything when there is no filter
//        if (listChangeListener != null) {
//            listChangeListener = change -> {
//                if (AuthorityChooser.this.serverFilter == null)
//                    return;
//
//                while (change.next()) {
//                    if (change.wasAdded()) {
//                        for (Server a : change.getAddedSubList())
//                            if (AuthorityChooser.this.serverFilter.includeAuthority(a))
//                                filteredAuthorities.add(a);
//                    }
//                    if (change.wasRemoved())
//                        filteredAuthorities.removeAll(change.getRemoved());
//                }
//            };
//            testbedInfoSource.authorityInfosProperty().addListener(listChangeListener);
//        }
        comboBox.getSelectionModel().selectFirst();
    }

    public interface ServerFilter {
        boolean includeAuthority(Server server);
    }

    public VBox getRoot() {
        return root;
    }
}
