package org.javafxdata.control.cell;

import javafx.beans.property.BooleanProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.util.Callback;

/**
 * <p>A class containing a {@link ListCell} implementation that draws a {@link CheckBox}
 * node inside the cell, optionally with a label to indicate what the checkbox represents.
 * 
 * <img src="http://www.javafxdata.org/screenshots/CheckBoxCell-ListView.PNG" 
 * style="float: right; padding: 0px 0px 0px 3px;"/>
 * 
 * <p>The CheckBoxListCell is rendered with a CheckBox on the left-hand side of the {@link ListView}, 
 * and the text related to the list item taking up all remaining horizontal space. 
 * 
 * <p>To construct an instance of this class, it is necessary to provide a {@link Callback} that, given an 
 * object of type T, will return a {@code ObservableValue<Boolean>} that represents whether
 * the given item is selected or not. This ObservableValue will be bound bidirectionally (meaning that the 
 * CheckBox in the cell will set/unset this property based on user interactions, and the CheckBox will reflect
 * the state of the ObservableValue<Boolean>, if it changes externally).
 * 
 * @param <T> The type of the elements contained within the ListView.
 * @author <a href="http://www.jonathangiles.net">Jonathan Giles</a>
 */
public class CheckBoxListCell<T> extends ListCell<T> {
    private final CheckBox checkBox;
    
    private final Callback<T, String> toString;

    private final Callback<T, ObservableValue<Boolean>> getSelectedProperty;
    private ObservableValue<Boolean> booleanProperty;
    
    /**
     * Creates a default CheckBoxListCell.
     * 
     * @param getSelectedProperty A {@link Callback} that will return an {@code ObservableValue<Boolean>}
     *      given an item from the ListView.
     */
    public CheckBoxListCell(Callback<T, ObservableValue<Boolean>> getSelectedProperty) {
        this(getSelectedProperty, CheckBoxCell.<T>defaultToString());
    }
    
    /**
     * Creates a CheckBoxListCell with a custom string converter.
     * 
     * @param getSelectedProperty A {@link Callback} that will return an {@code ObservableValue<Boolean>} 
     *      given an item from the ListView.
     * @param toString A Callback that, give an object of type T, will return a String that can be used 
     *      to represent the object visually.
     */
    public CheckBoxListCell(Callback<T, ObservableValue<Boolean>> getSelectedProperty, Callback<T, String> toString) {
        if (getSelectedProperty == null) {
            throw new NullPointerException("getSelectedProperty can not be null");
        }
        this.getSelectedProperty = getSelectedProperty;
        this.toString = toString;
        
        this.checkBox = new CheckBox();
        
        setAlignment(Pos.CENTER_LEFT);
        setContentDisplay(ContentDisplay.LEFT);
        setGraphic(checkBox);
    }
    
    /** {@inheritDoc} */
    @Override public void updateItem(T item, boolean empty) {
        super.updateItem(item, empty);
        
        if (! empty) {
            setText(toString == null ? (item == null ? "" : item.toString()) : toString.call(item));
            setGraphic(checkBox);
            
            if (booleanProperty != null) {
                checkBox.selectedProperty().unbindBidirectional((BooleanProperty)booleanProperty);
            }
            booleanProperty = getSelectedProperty.call(item);
            if (booleanProperty != null) {
                checkBox.selectedProperty().bindBidirectional((BooleanProperty)booleanProperty);
            }
        } else {
            setText(null);
            setGraphic(null);
        }
    }
}