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

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.data.reference.GeoReferential;
import fr.cenra.rhomeo.core.data.reference.GeoReferentials;
import fr.cenra.rhomeo.core.util.GeometryUtils;
import fr.cenra.rhomeo.protocol.p09.I13;
import fr.cenra.rhomeo.protocol.p09.I13Process;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javafx.beans.property.SimpleObjectProperty;
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.geotoolkit.gui.javafx.util.TaskManager;
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.core.util.ExportUtils;
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.scene.control.ChoiceDialog;
import javafx.scene.layout.GridPane;
import org.geotoolkit.internal.GeotkFX;

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

    public static final String SITE_NAME = "site_name";
    public static final String AVAILABLE_YEARS = "available_years";
    private static final String TYPE_SELECTION = "type_selection";
    public static final String YEAR_SELECTION = "year_selection";

    private static final String[] TYPES = new String[]{
        null,
        "01 - Blé tendre",
        "02 - Maïs grain et ensilage",
        "03 - Orge",
        "04 - Autres céréales",
        "05 - Colza",
        "06 - Tournesol",
        "07 - Autres oleagineux",
        "08 - Protéagineux",
        "09 - Plantes à fibres",
        "10 - Semences",
        "11 - Gel (surfaces gelées sans production)",
        "12 - Gel industriel",
        "13 - Autres gels",
        "14 - Riz",
        "15 - Légumineuses à grains",
        "16 - Fourrage",
        "17 - Estives landes",
        "18 - Prairies permanentes",
        "19 - Prairies temporaires",
        "20 - Vergers",
        "21 - Vignes",
        "22 - Fruits à coque",
        "23 - Oliviers",
        "24 - Autres cultures industrielles",
        "25 - Légumes-fleurs",
        "26 - Canne à sucre",
        "27 - Arboriculture",
        "28 - Divers"
    };

    @Autowired
    private Session session;
    @Autowired
    private GeoReferentials geoRef;
    @Autowired
    @Qualifier(I13.NAME)
    private Indicator indicator;

    @FXML
    private Button next;

    @FXML
    private Button uiExport;

    @FXML
    private HBox yearBoxes;
    @FXML
    private GridPane typeBoxes;

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

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

    private BitSet activatedYears;
    private BitSet activatedTypes;

    private final Preferences prefs;

    public FXP09ProcessPane() {
        Rhomeo.loadFXML(this);
        prefs = Preferences.userNodeForPackage(FXP09ProcessPane.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_RPG))
                    .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(SITE_NAME, "").equals(siteName)
                && ByteBuffer.wrap(prefs.getByteArray(AVAILABLE_YEARS, new byte[0])).asIntBuffer().equals(years);

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

        final byte[] typeSelection;
        if (restore && (typeSelection = prefs.getByteArray(TYPE_SELECTION, null)) != null) {
            activatedTypes = BitSet.valueOf(typeSelection);
        } else {
            activatedTypes = new BitSet(TYPES.length);
            activatedTypes.set(17, true);
            activatedTypes.set(18, true);
            activatedTypes.set(19, true);
        }

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

        // Init UI. For checkboxes, each time selection change, we update preferences.
        for (GeoReferential ref : refs) {
            final CheckBox check = new CheckBox(String.valueOf(ref.getYear()));
            final int index = yearCheckBoxes.size();
            check.setSelected(activatedYears.get(index));
            check.selectedProperty().addListener((obs, oldValue, newValue) -> {
                yearSelectionChanged(index, newValue == null? false : newValue);
            });
            yearBoxes.getChildren().add(check);
            yearCheckBoxes.add(check);
        }

        double best = 0;
        typeCheckBoxes.add(null);
        for(int i=1;i<29;i++){
            final CheckBox box = new CheckBox(TYPES[i]);
            box.setSelected(activatedTypes.get(i));
            typeCheckBoxes.add(box);
            GridPane.setColumnIndex(box, (i-1)%4);
            GridPane.setRowIndex(box, ((i-1)-((i-1)%4)));
            typeBoxes.getChildren().add(box);
            best = Math.max(best, box.getWidth());
            final int index = i;
            box.selectedProperty().addListener((obs, oldValue, newValue) -> {
                boxSelectionChanged(index, newValue == null? false : newValue);
            });
        }
    }

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

    private void boxSelectionChanged(final int index, final boolean value) {
        activatedTypes.set(index, value);
        prefs.putByteArray(TYPE_SELECTION, activatedTypes.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 : yearCheckBoxes) {
            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 : yearCheckBoxes){
            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 I13Process process = (I13Process) processes.get(0);

            //configure selected years
            for(CheckBox c : yearCheckBoxes){
                if(c.isSelected())process.getYears().add(Integer.valueOf(c.getText()));
            }
            //configure excluded types
            for(int i=1;i<29;i++){
                if(typeCheckBoxes.get(i).isSelected()){
                    process.getNoCount().add(i);
                }
            }

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

            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);
        }
    }

}
