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

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.*;
import java.util.stream.Collectors;

/**
 * StringArgumentChooser
 */
public class ChApi1MatchArgumentChooser extends CommandArgumentChooser<Map<String, Object>> {
    class MyEntry {
        public final StringProperty key = new SimpleStringProperty();
        public final StringProperty value = new SimpleStringProperty();

        public MyEntry(String key, String value) {
            this.key.set(key);
            this.value.set(value);
        }

        public void addListeners() {
            this.key.addListener(changeListener);
            this.value.addListener(changeListener);
        }

        public void removeListeners() {
            this.key.removeListener(changeListener);
            this.value.removeListener(changeListener);
        }
    }

    private final ChangeListener<String> changeListener = (observableValue, oldString, newString) -> updateMap();

    @FXML
    private TableView<MyEntry> table;

    final ObservableList<MyEntry> argument = FXCollections.observableArrayList();
    private final ObjectProperty<Map<String, Object>> valueProperty = new SimpleObjectProperty<>();

    public ChApi1MatchArgumentChooser(Map<String, String> defaultArguments) {
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("ChApi1MatchArgumentChooser.fxml"));
        fxmlLoader.setRoot(this);
        fxmlLoader.setController(this);

        try {
            fxmlLoader.load();

            assert table != null;
            if (defaultArguments != null)
                argument.addAll(defaultArguments.entrySet().stream()
                        .map(e -> new MyEntry(e.getKey(), e.getValue()))
                        .collect(Collectors.toList()));
            table.setItems(this.argument);

            table.setEditable(true);
//            table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);

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

            TableColumn<MyEntry,String> keyCol = new TableColumn<>("Field");
            keyCol.setMinWidth(100);
            keyCol.setCellValueFactory(myEntryStringCellDataFeatures -> myEntryStringCellDataFeatures.getValue().key);
            keyCol.setCellFactory(cellFactory);
            keyCol.setEditable(true);


            TableColumn<MyEntry,String> valueCol = new TableColumn<>("Required value");
            valueCol.setMinWidth(200);
            valueCol.setCellFactory(cellFactory);
            valueCol.setCellValueFactory(myEntryStringCellDataFeatures -> myEntryStringCellDataFeatures.getValue().value);
            valueCol.setEditable(true);

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

//            table.setTableMenuButtonVisible(true);

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

                //not the best way to link this, but I want to keep "value" in CommandArgumentChooser as it is
                updateMap();
            });

            //init argument: updateMap and listen to all it's entries
            updateMap();
            argument.forEach(MyEntry::addListeners);

            value = valueProperty;
            assert value != null;
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }
    }

    private void updateMap() {
//        System.out.println("DEBUG updateMap(): ");
        Map<String, Object> newMap = new HashMap<>();
        for (MyEntry e : argument) {
//            System.out.println("                   entry="+e+"  "+e.key.get()+" -> "+e.value.get());
            String value = e.value.get();
            if (value.contains(" OR ")) {
                String[] parts = value.split(" OR ");
                List<String> oredValues = new ArrayList<>();
                Collections.addAll(oredValues, parts);
                newMap.put(e.key.get(), oredValues);
            } else
                newMap.put(e.key.get(), value);
        }
        valueProperty.set(newMap);
    }

    public void add() {
        argument.add(new MyEntry("field name", "required value"));
    }

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


    private static class EditingCell extends TableCell<MyEntry, 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, oldIsFocused, newIsFocused) -> {
                    if (!newIsFocused) {
                        System.out.println("OnFocusLost Committing edit: " + textField.getText());
                        commitEdit(textField.getText());
                    }
                });
                textField.setOnAction(actionEvent -> {
                    System.out.println("OnAction Committing edit: " + textField.getText());
                    commitEdit(textField.getText());
                });
            }
        }

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

}
