
/**
 * 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.p08;

import com.vividsolutions.jts.geom.Geometry;
import fr.cenra.rhomeo.Rhomeo;
import fr.cenra.rhomeo.api.data.Site;
import fr.cenra.rhomeo.api.process.Indicator;
import fr.cenra.rhomeo.api.result.Index;
import fr.cenra.rhomeo.core.Session;
import fr.cenra.rhomeo.core.WorkflowStep;
import fr.cenra.rhomeo.core.util.ExportUtils;
import fr.cenra.rhomeo.core.util.GeometryUtils;
import fr.cenra.rhomeo.protocol.p08.I12;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import fr.cenra.rhomeo.api.process.Process;
import fr.cenra.rhomeo.fx.p09.FXP09ProcessPane;
import fr.cenra.rhomeo.core.data.reference.GeoReferential;
import fr.cenra.rhomeo.core.data.reference.GeoReferentials;
import fr.cenra.rhomeo.protocol.p08.I12Process;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.BitSet;
import java.util.Optional;
import java.util.prefs.Preferences;
import javafx.application.Platform;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.control.ChoiceDialog;
import org.geotoolkit.gui.javafx.util.TaskManager;
import org.geotoolkit.internal.GeotkFX;

/**
 *
 * @author Johann Sorel (Geomatys)
 */
@Component
@Scope("prototype")
public class FXP08ProcessPane extends VBox {

    @Autowired
    private Session session;

    @Autowired
    private GeoReferentials geoRef;

    @Autowired
    @Qualifier(I12.NAME)
    private Indicator indicator;

    @FXML
    private Button next;

    @FXML
    private Button uiExport;

    @FXML
    private HBox yearBoxes;

    private final List<CheckBox> checkBoxes = new ArrayList<>();

    private final SimpleObjectProperty<Task<Set<Index>>> globalTask = new SimpleObjectProperty<>();

    private BitSet activatedYears;

    private final Preferences prefs;

    public FXP08ProcessPane(){
        Rhomeo.loadFXML(this);
        prefs = Preferences.userNodeForPackage(FXP08ProcessPane.class);
    }

    @PostConstruct
    private void postConstruct() {
        //liste available referential state for this site
        final ObservableList<GeoReferential> refs;
        try {
            refs = geoRef.getLocalReferentials(session.getDataContext().getSite())
                    .filtered(ref -> ref.hasLocalTypes(
                            GeoReferentials.TYPE_ZONE_HYDRO,
                            GeoReferentials.TYPE_TACHE_ARTIF,
                            GeoReferentials.TYPE_TACHE_URBAINE)
                    )
                    .sorted();
        } catch (IllegalArgumentException | IOException ex) {
            // If we cannot load georeferentials, it's no use to go further.
            GeotkFX.newExceptionDialog("Impossible de déterminer la liste des réferentiels disponibles. La lecture sur le système a échouée.", ex).show();
            session.requestWorkflowStep(WorkflowStep.DATASET);
            return;
        }

        final ByteBuffer yearsAsBytes = ByteBuffer.wrap(new byte[refs.size() * (Integer.SIZE / Byte.SIZE)]);
        final IntBuffer years = yearsAsBytes.asIntBuffer();
        for (final GeoReferential ref : refs) {
            years.put(ref.getYear());
        }
        years.position(0);

        /* Restore selection state from serialized bitset if possible. To
         * determine if restoration is possible, we ensure there's data stored
         * in preferences, and also that they match current edited site / years.
         * Otherwise, we set a new bitset, with default selection on types 17
         * and 18.
         * We do the same with years, except that by default, they're all selected.
         */
        final String siteName = session.getDataContext().getSite().getName();
        final boolean restore = prefs.get(FXP09ProcessPane.SITE_NAME, "").equals(siteName)
                && ByteBuffer.wrap(prefs.getByteArray(FXP09ProcessPane.AVAILABLE_YEARS, new byte[0])).asIntBuffer().equals(years);

        final byte[] yearSelection;
        if (restore && (yearSelection = prefs.getByteArray(FXP09ProcessPane.YEAR_SELECTION, null)) != null) {
            activatedYears = BitSet.valueOf(yearSelection);
        } else {
            activatedYears = new BitSet(refs.size());
            activatedYears.set(0, activatedYears.length());
        }

        // A new session is starting. We initialize preferences for it.
        if (!restore) {
            prefs.put(FXP09ProcessPane.SITE_NAME, siteName);
            prefs.putByteArray(FXP09ProcessPane.AVAILABLE_YEARS, yearsAsBytes.array());
        }

        for (GeoReferential ref : refs) {
            final CheckBox check = new CheckBox(String.valueOf(ref.getYear()));
            final int index = checkBoxes.size();
            check.setSelected(activatedYears.get(index));
            check.selectedProperty().addListener((obs, oldValue, newValue) -> {
                yearSelectionChanged(index, newValue == null? false : newValue);
            });
            yearBoxes.getChildren().add(check);
            checkBoxes.add(check);
        }
    }

    private void yearSelectionChanged(final int index, final boolean value) {
        activatedYears.set(index, value);
        prefs.putByteArray(FXP09ProcessPane.YEAR_SELECTION, activatedYears.toByteArray());
    }

    @FXML
    void exportGeometries(ActionEvent event) {
        final Site site = session.getDataContext().getSite();
        final FileChooser.ExtensionFilter zipFilter = new FileChooser.ExtensionFilter("Archive zip", "*.zip");
        final File outputFile = Rhomeo.askOutputFilePopup(site.getName()+".zip", this, zipFilter);

        if (outputFile == null) {
            return;
        }

        int tmpSelectedYear = Integer.MIN_VALUE;
        for(final CheckBox c : checkBoxes) {
            if (c.isSelected()) {
                tmpSelectedYear = Math.max(tmpSelectedYear, Integer.parseInt(c.getText()));
            }
        }

        final int selectedYear = tmpSelectedYear;

        final ChoiceDialog<ExportUtils.ExportType> dialog = new ChoiceDialog<>(ExportUtils.ExportType.GJSON, ExportUtils.ExportType.values());
        dialog.setTitle("Format d'export");
        dialog.setHeaderText("Choisissez un format de sortie");
        dialog.setResizable(true);
        dialog.showAndWait().ifPresent(type -> {
            Task<Boolean> t = TaskManager.INSTANCE.submit("Export", () -> {
                final Geometry buffer = GeometryUtils.computeBuffer(site.getGeometry());
                final Optional<Geometry> territory;
                if (selectedYear > Integer.MIN_VALUE) {
                    territory = geoRef.getLocalReferential(site, selectedYear).unionIntersecting(GeoReferentials.TYPE_ZONE_HYDRO, site.getGeometry());
                } else {
                    territory = Optional.empty();
                }
                ExportUtils.exportSiteAndBuffer(outputFile, site, buffer, territory, type);
                return true;
            });
            uiExport.disableProperty().unbind();
            uiExport.disableProperty().bind(t.runningProperty());
        });
    }

    @FXML
    void nextStep(ActionEvent event) {

        //check there is at least one year of data available
        int nbAvailable = 0;
        for(CheckBox c : checkBoxes){
            if(c.isSelected()) nbAvailable++;
        }

        if (nbAvailable==0) {
            Rhomeo.showAlert(Alert.AlertType.WARNING, "Impossible de passer à l'étape suivante : veuillez sélectionner une année.");
            return;
        }

        final Computer computer = new Computer();
        globalTask.set(computer);

        computer.stateProperty().addListener(obs -> {
            if (computer.isDone()) {
                setDisable(false);
                switch (computer.getState()) {
                    case FAILED:
                        Rhomeo.showAlert(Alert.AlertType.ERROR, "process-failed");
                        break;
                    case SUCCEEDED:
                        session.setResults(computer.getValue());
                        globalTask.set(null);
                        session.requestWorkflowStep(WorkflowStep.RESULT);
                        break;
                }
            }
        });

        TaskManager.INSTANCE.submit(computer);

    }


    private class Computer extends Task<Set<Index>> {

        private final List<Process> processes = new ArrayList<>();

        @Override
        protected Set<Index> call() throws Exception {
            processes.addAll(indicator.createProcesses(session.getProcessContext()));
            final I12Process process = (I12Process) processes.get(0);

            //configure selected years
            for(CheckBox c : checkBoxes){
                if(c.isSelected())process.getYears().add(Integer.valueOf(c.getText()));
            }

            updateProgress(-1, -1);
            updateTitle("I12");

            Platform.runLater(() -> {
                process.titleProperty().addListener((obs) -> updateTitle(process.getTitle()));
                process.messageProperty().addListener((obs) -> updateTitle(process.getMessage()));
                process.workDoneProperty().addListener((obs) -> updateProgress(process.getWorkDone(), process.getTotalWork()));
            });

            process.run();
            return (Set) process.get();
        }

        @Override
        public boolean cancel(boolean mayInterruptIfRunning) {
            for (final Process p : processes) {
                p.cancel(true);
            }
            return super.cancel(mayInterruptIfRunning);
        }
    }

}
