/**
 * 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.api.process.Indicator;
import fr.cenra.rhomeo.api.result.DashboardResultItem;
import fr.cenra.rhomeo.api.result.Index;
import fr.cenra.rhomeo.api.result.IndicatorValuesByYearItem;
import fr.cenra.rhomeo.core.CSVDecoder;
import fr.cenra.rhomeo.core.RhomeoCore;
import fr.cenra.rhomeo.core.result.ResultStorage;
import fr.cenra.rhomeo.core.result.SimpleLocationIndex;
import fr.cenra.rhomeo.core.result.SimpleYearIndex;
import java.beans.IntrospectionException;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.VBox;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

/**
 * UI to display detailed information about all computed information for a given
 * year and {@link Indicator}. This includes the value of the main indicator
 * {@link Index} and the potential ones.
 *
 * @author Samuel Andrés (Geomatys) <samuel.andres at geomatys.com>
 */
@Component("fr.cenra.rhomeo.fx.FXFullSpecificResultPane")
@Lazy
@Scope("prototype")
public class FXFullSpecificResultPane extends VBox {

    private final IndicatorValuesByYearItem selectedItem;

    private final Indicator indicator;

    private final String siteName;

    @Autowired
    private ResultStorage resultStorage;

    public FXFullSpecificResultPane(final IndicatorValuesByYearItem selectedItem, final Indicator indicator, final String siteName) throws IOException, IntrospectionException, InstantiationException, IllegalAccessException {
        this.selectedItem = selectedItem;
        this.indicator = indicator;
        this.siteName = siteName;
        setPadding(new Insets(10.));
        setSpacing(10.);
        setPrefSize(USE_COMPUTED_SIZE, USE_COMPUTED_SIZE);
    }

    @PostConstruct
    private void postConstruct() throws IOException, IntrospectionException, InstantiationException, IllegalAccessException {
        final String indicatorName = indicator.getName();
        final IndicatorValuesByYearItem.PublishedValue pub = selectedItem.getValueForIndicator(indicatorName);

        if (pub != null) {
            final DashboardResultItem dItem = new DashboardResultItem(selectedItem.getYear(), siteName, indicatorName, pub.getValue(), pub.isPublished());
            final ArrayList<? extends SimpleYearIndex> result = new ArrayList<>();
            final Optional<Path> mainResults = resultStorage.getMainResults(dItem);
            if (mainResults.isPresent()) {
                result.addAll((List) readResult(mainResults.get()));
            }

            final Optional<Path> otherResults = resultStorage.getAdditionalResults(dItem);
            if (otherResults.isPresent()) {
                result.addAll((List) readResult(otherResults.get()));
            }

            buildTables(result);
        }
    }

    /**
     * Create the table view containing the result value list mapping their class.
     *
     * @param result
     * @throws IntrospectionException
     */
    private void buildTables(List<? extends SimpleYearIndex> result) throws IntrospectionException {
        final List<SimpleLocationIndex> locValues = new ArrayList<>();
        final Iterator<? extends SimpleYearIndex> it = result.iterator();
        SimpleYearIndex current;
        while (it.hasNext()) {
            current = it.next();
            if (current instanceof SimpleLocationIndex) {
                locValues.add((SimpleLocationIndex) current);
                it.remove();
            }
        }

        if (!result.isEmpty()) {
            final TableView<SimpleYearIndex> table = buildSimpleTableView(FXCollections.observableList((List)result));
            getChildren().add(table);
        }

        if (!locValues.isEmpty()) {
            final TableView<SimpleLocationIndex> table = buildLocationTableView(FXCollections.observableList(locValues));
            getChildren().add(table);
        }
    }

    /**
     * Builds a {@link TableView} for tuples indexed by year.
     *
     * @param tuples
     * @return
     * @throws IntrospectionException
     * @see SimpleYearIndex
     */
    private TableView<SimpleYearIndex> buildSimpleTableView(ObservableList<SimpleYearIndex> values) throws IntrospectionException {
        final TableView<SimpleYearIndex> table = new TableView<>();
        addCommonColumns(table);
        table.setItems(values);
        initResizePolicy(table);
        return table;
    }

    /**
     * Configure input table resize strategy, by setting preferred height
     * property to follow table item number.
     *
     * @param table The modified table.
     */
    private void initResizePolicy(final TableView table) {
        table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
        table.setMinHeight(50);
        table.setFixedCellSize(30);
        table.prefHeightProperty().bind(Bindings.createDoubleBinding(() -> table.getFixedCellSize() * table.getItems().size() + 30, table.getItems(), table.fixedCellSizeProperty()));
    }

    private void addCommonColumns(final TableView<? extends SimpleYearIndex> table) {
        final TableColumn<SimpleYearIndex, String> titleColumn = new TableColumn<>("Nom");
        titleColumn.setEditable(false);
        titleColumn.setCellValueFactory(FXFullSpecificResultPane::getTitle);

        final TableColumn<SimpleYearIndex, Number> valueColumn = new TableColumn<>("Valeur");
        valueColumn.setEditable(false);
        valueColumn.setCellFactory(RhomeoCore.ROUNDING_CELL_FACTORY);
        valueColumn.setCellValueFactory(FXFullSpecificResultPane::getValue);

        table.getColumns().add((TableColumn)titleColumn);
        table.getColumns().add((TableColumn)valueColumn);
    }

    /**
     * Builds a {@link TableView} for tuples indexed by year and location.
     *
     * @param tuples
     * @return
     * @throws IntrospectionException
     * @see SimpleLocationIndex
     */
    private TableView<SimpleLocationIndex> buildLocationTableView(ObservableList<SimpleLocationIndex> values) throws IntrospectionException {
        final TableView<SimpleLocationIndex> table = new TableView<>();
        addCommonColumns(table);

        final TableColumn<SimpleLocationIndex, String> locColumn = new TableColumn<>();
        if (indicator.getProtocol().getName().equalsIgnoreCase("P04"))
            locColumn.setText("Habitat");
        else
            locColumn.setText("Point de suivi");
        locColumn.setCellValueFactory(FXFullSpecificResultPane::getLocation);

        table.getColumns().add(1, locColumn);

        table.setItems(values);
        initResizePolicy(table);
        return table;
    }

    /**
     * Get the results stored in the file which path is given as a parameter.
     *
     * The results are retrieved as a list of either instances of {@link SimpleLocationIndex}
     * or instances of {@link SimpleYearIndex}. The method returns an entry which
     * the value it the list of results, and the key is the instances' class.
     *
     * @param resultsPath
     * @return
     * @throws IOException
     * @throws IntrospectionException
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    private static List<? extends SimpleYearIndex> readResult(final Path resultsPath) throws IOException, IntrospectionException, InstantiationException, IllegalAccessException {
        try {
            return new CSVDecoder(resultsPath, SimpleLocationIndex.class).decode();
        } catch (Exception ex) {
            RhomeoCore.LOGGER.info("Impossible de lire en tant que résultat localisé. Essai de lecture en résultat non localisé.");
            return new CSVDecoder(resultsPath, SimpleYearIndex.class).decode();
        }
    }

    private static ObservableValue<String> getTitle(final TableColumn.CellDataFeatures<SimpleYearIndex, String> cdf) {
        final SimpleYearIndex index = cdf.getValue();
        if (index == null)
            return null;
        return new SimpleStringProperty(index.getName());
    }

    private static ObservableValue<Number> getValue(final TableColumn.CellDataFeatures<SimpleYearIndex, Number> cdf) {
        final SimpleYearIndex index = cdf.getValue();
        if (index == null)
            return null;
        return new SimpleObjectProperty<>(index.getValue());
    }

    private static ObservableValue<String> getLocation(final TableColumn.CellDataFeatures<SimpleLocationIndex, String> cdf) {
        final SimpleLocationIndex index = cdf.getValue();
        if (index == null)
            return null;
        return new SimpleStringProperty(index.getLocation());
    }
}
