package be.iminds.ilabt.jfed.experimenter_gui.util.ui;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.util.Callback;
import javafx.util.StringConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * StyledCheckBoxListCell
 */
public class StyledCheckBoxListCell<T> extends ListCell<T> {
    private static final Logger LOG = LoggerFactory.getLogger(StyledCheckBoxListCell.class);

    private final CheckBox checkBox;
    private ObservableValue<Boolean> booleanProperty;
    private ObjectProperty<StringConverter<T>> converter;
    private ObjectProperty<StyledCheckBoxListCell.StyleProvider<T>> styleProvider;
    private ObjectProperty<Callback<T, ObservableValue<Boolean>>> selectedStateCallback;

    private List<String> baseStyleClasses;

    public interface StyleProvider<T> {
        @Nonnull
        List<String> getStyleClasses(T item);
    }

    public static <T> Callback<ListView<T>, ListCell<T>> forListView(
            Callback<T, ObservableValue<Boolean>> getSelectedProperty,
            StyledCheckBoxListCell.StyleProvider<T> styleProvider) {
        return forListView(getSelectedProperty, (StringConverter<T>)defaultStringConverter, styleProvider);
    }

    public static <T> Callback<ListView<T>, ListCell<T>> forListView(
            Callback<T, ObservableValue<Boolean>> getSelectedProperty,
            StringConverter<T> converter,
            StyledCheckBoxListCell.StyleProvider<T> styleProvider) {
        return (list) -> {
            return new StyledCheckBoxListCell(getSelectedProperty, converter, styleProvider);
        };
    }

    public StyledCheckBoxListCell() {
        this((Callback)null, null);
    }

    public StyledCheckBoxListCell(Callback<T, ObservableValue<Boolean>> getSelectedProperty,
                                  StyledCheckBoxListCell.StyleProvider<T> styleProvider) {
        this(getSelectedProperty, (StringConverter<T>)defaultStringConverter, styleProvider);
    }

    public StyledCheckBoxListCell(Callback<T, ObservableValue<Boolean>> getSelectedProperty,
                                  StringConverter<T> converter,
                                  StyledCheckBoxListCell.StyleProvider<T> styleProvider) {
        this.converter = new SimpleObjectProperty(this, "converter");
        this.styleProvider = new SimpleObjectProperty(this, "styleProvider");
        this.selectedStateCallback = new SimpleObjectProperty(this, "selectedStateCallback");

        this.getStylesheets().add(getClass().getResource("StyledCheckBoxListCell.css").toExternalForm());
        this.getStyleClass().add("check-box-list-cell");
        this.baseStyleClasses = Collections.unmodifiableList(new ArrayList<>(this.getStyleClass()));

        this.setSelectedStateCallback(getSelectedProperty);
        this.setConverter(converter);
        this.setStyleProvider(styleProvider);
        this.checkBox = new CheckBox();
        this.setAlignment(Pos.CENTER_LEFT);
        this.setContentDisplay(ContentDisplay.LEFT);
        this.setGraphic((Node)null);
    }

    public final ObjectProperty<StringConverter<T>> converterProperty() {
        return this.converter;
    }

    public final void setConverter(StringConverter<T> value) {
        this.converterProperty().set(value);
    }

    public final StringConverter<T> getConverter() {
        return (StringConverter)this.converterProperty().get();
    }

    public StyledCheckBoxListCell.StyleProvider getStyleProvider() {
        return styleProvider.get();
    }

    public ObjectProperty<StyledCheckBoxListCell.StyleProvider<T>> styleProviderProperty() {
        return styleProvider;
    }

    public void setStyleProvider(StyledCheckBoxListCell.StyleProvider styleProvider) {
        this.styleProvider.set(styleProvider);
    }

    public final ObjectProperty<Callback<T, ObservableValue<Boolean>>> selectedStateCallbackProperty() {
        return this.selectedStateCallback;
    }

    public final void setSelectedStateCallback(Callback<T, ObservableValue<Boolean>> value) {
        this.selectedStateCallbackProperty().set(value);
    }

    public final Callback<T, ObservableValue<Boolean>> getSelectedStateCallback() {
        return (Callback)this.selectedStateCallbackProperty().get();
    }

    public void updateItem(T item, boolean empty) {
        super.updateItem(item, empty);
        if(!empty) {
            StringConverter c = this.getConverter();
            Callback callback = this.getSelectedStateCallback();
            if(callback == null) {
                throw new NullPointerException("The StyledCheckBoxListCell selectedStateCallbackProperty can not be null");
            }

            this.setGraphic(this.checkBox);
            this.setText(c != null?c.toString(item):(item == null?"":item.toString()));
            if(this.booleanProperty != null) {
                this.checkBox.selectedProperty().unbindBidirectional((BooleanProperty)this.booleanProperty);
            }

            List<String> styleClasses = new ArrayList<>(baseStyleClasses.size()+1);
            styleClasses.addAll(baseStyleClasses);
            if (styleProvider.isNotNull().get()) {
                styleClasses.addAll(styleProvider.get().getStyleClasses(item));
            }
//            LOG.debug("updateItem(item, empty={}) set styleClass->{}", empty, styleClasses);
            this.getStyleClass().setAll(styleClasses);

            this.booleanProperty = (ObservableValue)callback.call(item);
            if(this.booleanProperty != null) {
                this.checkBox.selectedProperty().bindBidirectional((BooleanProperty)this.booleanProperty);
            }
        } else {
            this.setGraphic((Node)null);
            this.setText((String)null);
        }

    }



    private static final StringConverter<?> defaultStringConverter = new StringConverter() {
        public String toString(Object t) {
            return t == null?null:t.toString();
        }

        public Object fromString(String string) {
            return string;
        }
    };
}
