/**
 * Copyright © CENRA (2016)
 *
 * remi.clement@espaces-naturels.fr
 * 
 * This software is a multi-platform and portable application based on 
 * the scientifical toolbox to monitor wetlands. With this toolbox it’s 
 * possible to calculate indicators «RhoMéO» to monitor quality, 
 * fonctionalities and urban and agricultural pressures of wetlands. 
 * Indicators are calculated using naturalist data entered and/or imported,
 * and geographic data downloaded from a geographic server.
 * 
 * This software is governed by the CeCILL license under French law and
 * abiding by the rules of distribution of free software.  You can  use, 
 * modify and/ or redistribute the software under the terms of the CeCILL
 * license as circulated by CEA, CNRS and INRIA at the following URL
 * "http://www.cecill.info". 
 * 
 * As a counterpart to the access to the source code and  rights to copy,
 * modify and redistribute granted by the license, users are provided only
 * with a limited warranty  and the software's author,  the holder of the
 * economic rights,  and the successive licensors  have only  limited
 * liability. 
 * 
 * In this respect, the user's attention is drawn to the risks associated
 * with loading,  using,  modifying and/or developing or reproducing the
 * software by the user in light of its specific status of free software,
 * that may mean  that it is complicated to manipulate,  and  that  also
 * therefore means  that it is reserved for developers  and  experienced
 * professionals having in-depth computer knowledge. Users are therefore
 * encouraged to load and test the software's suitability as regards their
 * requirements in conditions enabling the security of their systems and/or 
 * data to be ensured and,  more generally, to use and operate it in the 
 * same conditions as regards security. 
 * 
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL license and that you accept its terms.
*/
package fr.cenra.rhomeo.fx;

import fr.cenra.rhomeo.Rhomeo;
import fr.cenra.rhomeo.api.Version;
import fr.cenra.rhomeo.api.data.DataContext;
import fr.cenra.rhomeo.api.data.Reference;
import fr.cenra.rhomeo.api.data.ReferenceDescription;
import fr.cenra.rhomeo.core.RhomeoRuntimeException;
import fr.cenra.rhomeo.core.Session;
import fr.cenra.rhomeo.core.data.ReferenceManager;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.ToggleGroup;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import org.geotoolkit.gui.javafx.util.FXTableView;
import org.geotoolkit.gui.javafx.util.TaskManager;

import java.beans.IntrospectionException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * Handle available versions for a reference.
 *
 * @param <R> the reference to display available versions.
 * @author Cédric Briançon (Geomatys)
 */
public class FXProtocolReferencesPane<R extends Reference> extends BorderPane {
    @FXML
    private Label uiRefNameLbl;

    private final FXTableView<ReferenceLineItem> referencesTable;

    private final ReferenceDescription<R> description;

    private static final String CSS_BUTTON_CLASS = "install-button";

    public FXProtocolReferencesPane(final ReferenceDescription<R> description) throws IntrospectionException {
        this.description = description;

        Rhomeo.loadFXML(this);
        uiRefNameLbl.setText("Référentiel "+ description.getTitle());

        referencesTable = new FXTableView<>();
        referencesTable.setEditable(true);
        referencesTable.setMinHeight(90);
        referencesTable.setPrefHeight(getMinHeight());
        referencesTable.setMaxHeight(280);
        referencesTable.setFixedCellSize(25);

        final TableColumn<ReferenceLineItem,String> availableCol = new TableColumn<>("Versions disponibles");
        availableCol.setCellValueFactory(value -> new SimpleStringProperty(String.valueOf(value.getValue().getVersion())));
        referencesTable.getColumns().add(availableCol);
        referencesTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);

        final TableColumn<ReferenceLineItem,Boolean> installedCol = new TableColumn<>("Installées");
        installedCol.setCellValueFactory(value -> value.getValue().installedProperty);
        installedCol.setCellFactory(param ->
            new TableCell<ReferenceLineItem, Boolean>() {
                @Override
                protected void updateItem(Boolean item, boolean empty) {
                    setText(null);
                    setGraphic(null);
                    setAlignment(Pos.CENTER);
                    if (item != null && !item) {
                        final Button installBtn = new Button("Installer");
                        installBtn.getStyleClass().add(CSS_BUTTON_CLASS);
                        installBtn.setOnAction(event -> {
                            final ReferenceManager<R> referenceManager;
                            try {
                                referenceManager = ReferenceManager.getOrCreate(description);
                            } catch (IntrospectionException ex) {
                                throw new RhomeoRuntimeException(ex);
                            }

                            final Task installTask = referenceManager.install(((ReferenceLineItem)getTableRow().getItem()).getVersion());
                            installTask.setOnSucceeded(success -> {
                                ((ReferenceLineItem)getTableRow().getItem()).setInstalled(true);
                            });
                            installBtn.disableProperty().bind(installTask.runningProperty());
                            TaskManager.INSTANCE.submit(installTask);
                        });
                        setGraphic(installBtn);
                    } else if (!empty && item != null){
                        setGraphic(new ImageView(Rhomeo.ICON_CHECK_GREEN));
                    }
                }
            }
        );
        referencesTable.getColumns().add(installedCol);

        final TableColumn<ReferenceLineItem,Boolean> selectedCol = new TableColumn<>("Sélectionnées");
        selectedCol.setCellValueFactory(value -> value.getValue().selectedProperty);
        final ToggleGroup group = new ToggleGroup();
        selectedCol.setCellFactory(param -> new RadioButtonTableCell(group));
        selectedCol.setEditable(true);
        referencesTable.getColumns().add(selectedCol);

        final DataContext dc = Session.getInstance().getDataContext();
        final ReferenceManager<R> referenceManager = ReferenceManager.getOrCreate(description);

        final Set<Version> installedVersions = referenceManager.getInstalledVersions();
        final Set<Version> distantVersions = referenceManager.getDistantVersions();
        final HashSet<Version> allVersions = new HashSet<>(installedVersions);
        allVersions.addAll(distantVersions);

        final Version selectedVersion = dc.getReferences().get(description.getReferenceType());
        final ObservableList<ReferenceLineItem> items = FXCollections.observableArrayList();
        for (final Version ver : allVersions) {
            items.add(new ReferenceLineItem(ver, installedVersions.contains(ver), ver.equals(selectedVersion)));
        }
        Collections.sort(items);
        referencesTable.setItems(FXCollections.observableList(items));
        referencesTable.setPrefHeight((items.size() + 1) * referencesTable.getFixedCellSize());

        setCenter(referencesTable);
    }

    public ObservableList<ReferenceLineItem> getItems() {
        return referencesTable.itemsProperty().get();
    }

    public Class<R> getReferenceClass() {
        return description.getReferenceType();
    }

    private class RadioButtonTableCell extends TableCell<ReferenceLineItem,Boolean> {
        private RadioButton radioButton;
        private BooleanProperty selProperty;

        /**
         * Cell containing a radio button in the given group.
         *
         * @param group
         */
        public RadioButtonTableCell(final ToggleGroup group) {
            radioButton = new RadioButton();
            if (group != null) {
                radioButton.setToggleGroup(group);
            }
        }

        @Override
        protected void updateItem(Boolean item, boolean empty) {
            super.updateItem(item, empty);

            if (empty) {
                setText(null);
                setGraphic(null);
            } else {
                // First remove potential binding
                if (selProperty != null) {
                    radioButton.selectedProperty().unbindBidirectional(selProperty);
                }
                setGraphic(radioButton);
                setAlignment(Pos.CENTER);
                radioButton.visibleProperty().bind(((ReferenceLineItem)getTableRow().getItem()).installedProperty);

                final ObservableValue<?> obsValue = getTableColumn().getCellObservableValue(getIndex());
                if (obsValue instanceof BooleanProperty) {
                    selProperty = (BooleanProperty)obsValue;
                    // ensures the radio button selected value will follow the item value
                    radioButton.selectedProperty().bindBidirectional(selProperty);
                }
            }
        }
    }

    class ReferenceLineItem implements Comparable<ReferenceLineItem> {
        private Version version;
        private BooleanProperty installedProperty;
        private BooleanProperty selectedProperty;

        public ReferenceLineItem() {
        }

        public ReferenceLineItem(Version version, boolean installed, boolean selected) {
            this.version = version;
            this.installedProperty = new SimpleBooleanProperty(installed);
            this.selectedProperty = new SimpleBooleanProperty(selected);
        }

        public Version getVersion() {
            return version;
        }

        public void setVersion(Version version) {
            this.version = version;
        }

        public boolean isInstalled() {
            return installedProperty.get();
        }

        public void setInstalled(boolean installed) {
            this.installedProperty.set(installed);
        }

        public boolean isSelected() {
            return selectedProperty.get();
        }

        public void setSelected(boolean selected) {
            this.selectedProperty.set(selected);
        }

        @Override
        public int compareTo(ReferenceLineItem o) {
            if (version == null) {
                return 1;
            }

            if (o == null) {
                return -1;
            }

            return version.compareTo(o.getVersion());
        }
    }
}
