package be.iminds.ilabt.jfed.ui.javafx.probe_gui.command_arguments;

import be.iminds.ilabt.jfed.lowlevel.api.FederationSliceAuthorityApi2;
import be.iminds.ilabt.jfed.util.GeniUrn;
import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.util.Callback;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * ChApiMemberArgumentChooser
 * <p>
 * TODO: make it work for both API 1 and 2
 */
public class ChApiMemberArgumentChooser extends CommandArgumentChooser<List<FederationSliceAuthorityApi2.UrnRoleTuple>> {
    class MyMemberTuple {
        public final StringProperty urn = new SimpleStringProperty();
        public final StringProperty role = new SimpleStringProperty();

        public MyMemberTuple(String urn, String role) {
            this.urn.set(urn);
            this.role.set(role);
        }

        public void addListeners() {
            this.urn.addListener(changeListener);
            this.role.addListener(changeListener);
        }

        public void removeListeners() {
            this.urn.removeListener(changeListener);
            this.role.removeListener(changeListener);
        }
    }

    private final ChangeListener<String> changeListener = (observableValue, s, s1) -> updateMembers();

    @FXML
    private TableView<MyMemberTuple> table;

    final ObservableList<MyMemberTuple> argument = FXCollections.observableArrayList();
    private final ObjectProperty<List<FederationSliceAuthorityApi2.UrnRoleTuple>> valueProperty =
            new SimpleObjectProperty<>();

    public ChApiMemberArgumentChooser(List<FederationSliceAuthorityApi2.UrnRoleTuple> defaultArguments) {
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("ChApiMemberArgumentChooser.fxml"));
        fxmlLoader.setRoot(this);
        fxmlLoader.setController(this);

        try {
            fxmlLoader.load();

            assert table != null;
            if (defaultArguments != null)
                defaultArguments.stream().map(e -> new MyMemberTuple(e.urn.toString(), e.role))
                .forEach(argument::add);
            table.setItems(this.argument);

            table.setEditable(true);

            Callback<TableColumn<MyMemberTuple, String>, TableCell<MyMemberTuple, String>> cellFactory =
                    p -> new EditingCell();

            TableColumn<MyMemberTuple, String> keyCol = new TableColumn<>("Member URN");
            keyCol.setMinWidth(100);
            keyCol.setCellValueFactory(myEntryStringCellDataFeatures -> myEntryStringCellDataFeatures.getValue().urn);
            keyCol.setCellFactory(cellFactory);

            TableColumn<MyMemberTuple, String> valueCol = new TableColumn<>("Role");
            valueCol.setMinWidth(200);
            valueCol.setCellFactory(cellFactory);
            valueCol.setCellValueFactory(myEntryStringCellDataFeatures -> myEntryStringCellDataFeatures.getValue().role);

            table.getColumns().add(keyCol);
            table.getColumns().add(valueCol);

//            table.setTableMenuButtonVisible(true);

            argument.addListener((ListChangeListener<MyMemberTuple>) change -> {
                while (change.next()) {
                    change.getRemoved().forEach(MyMemberTuple::removeListeners);
                    change.getAddedSubList().forEach(MyMemberTuple::addListeners);
                }

                //not the best way to link this, but I want to keep "value" in CommandArgumentChooser as it is
                updateMembers();
            });
            //init
            updateMembers();
            value = valueProperty;
            assert value != null;
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }
    }

    private void updateMembers() {
        List<FederationSliceAuthorityApi2.UrnRoleTuple> newVal = new ArrayList<>();
        for (MyMemberTuple e : argument)
            try {
                newVal.add(new FederationSliceAuthorityApi2.UrnRoleTuple("SLICE", new GeniUrn(e.urn.get()), e.role.get()));
            } catch (GeniUrn.GeniUrnParseException e1) {
                System.err.println("Ignoring invalid urn: " + e.urn.get());
                //ignore
            }
        valueProperty.set(newVal);
    }

    public void add() {
        argument.add(new MyMemberTuple("member_urn", "role"));
    }

    public void remove() {
        MyMemberTuple r = table.getSelectionModel().getSelectedItem();
        if (r != null)
            argument.remove(r);
    }


    private static class EditingCell extends TableCell<MyMemberTuple, String> {
        private TextField textField;

        public EditingCell() {
        }

        @Override
        public void startEdit() {
//            System.out.println("startEdit");
            if (!isEmpty()) {
                super.startEdit();
                createTextField();
                setText(null);
                setGraphic(textField);
                Platform.runLater(() -> {
                    //bug: somehow the caret isn't always shown, which is confusing
                    textField.requestFocus();
                    textField.selectAll();
                    textField.end();

                    Platform.runLater(() -> {
                        textField.requestFocus();
                        textField.end();
                    });
                });
            }
        }

        @Override
        public void cancelEdit() {
            super.cancelEdit();

            setText(getItem());
            setGraphic(null);
        }

        @Override
        public void updateItem(String item, boolean empty) {
//            System.out.println("updateItem "+item);
            super.updateItem(item, empty);

            if (empty) {
                setText(null);
                setGraphic(null);
            } else {
                if (isEditing()) {
                    if (textField != null) {
                        textField.setText(getString());
                    }
                    setText(null);
                    setGraphic(textField);
                } else {
                    setText(getString());
                    setGraphic(null);
                }
            }
        }

        private void createTextField() {
            if (textField == null) {
                textField = new TextField(getString());
                textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
                textField.focusedProperty().addListener((arg0, arg1, arg2) -> {
                    if (!arg2) {
                        commitEdit(textField.getText());
                    }
                });
            }
        }

        private String getString() {
            return getItem() == null ? "" : getItem();
        }
    }

}
