
/**
 * 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.core.data.reference;

import com.vividsolutions.jts.geom.Geometry;
import fr.cenra.rhomeo.api.data.Site;
import fr.cenra.rhomeo.core.RhomeoCore;
import fr.cenra.rhomeo.core.util.GeometryUtils;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import org.apache.sis.internal.system.DefaultFactories;
import org.apache.sis.storage.DataStoreException;
import org.geotoolkit.data.FeatureCollection;
import org.geotoolkit.data.FeatureIterator;
import org.geotoolkit.data.FeatureStore;
import org.geotoolkit.data.query.QueryBuilder;
import org.geotoolkit.data.session.Session;
import org.geotoolkit.filter.DefaultPropertyName;
import org.geotoolkit.geometry.jts.JTS;
import org.geotoolkit.storage.DataStores;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.FilterFactory2;
import org.opengis.util.GenericName;

/**
 *
 * @author Johann Sorel (Geomatys)
 */
public class GeoReferential implements Comparable<GeoReferential>, AutoCloseable{

    private final Site site;
    private final int year;
    private final SimpleBooleanProperty zoneHydro = new SimpleBooleanProperty();
    private final SimpleBooleanProperty tacheUrbaine = new SimpleBooleanProperty();
    private final SimpleBooleanProperty tacheArtif = new SimpleBooleanProperty();
    private final SimpleBooleanProperty rpg = new SimpleBooleanProperty();
    private final SimpleBooleanProperty wfszoneHydro = new SimpleBooleanProperty();
    private final SimpleBooleanProperty wfstacheUrbaine = new SimpleBooleanProperty();
    private final SimpleBooleanProperty wfstacheArtif = new SimpleBooleanProperty();
    private final SimpleBooleanProperty wfsrpg = new SimpleBooleanProperty();
    private final SimpleBooleanProperty download = new SimpleBooleanProperty();

    private FeatureStore store;

    public GeoReferential(Site site, int year) {
        this.site = site;
        this.year = year;
    }

    public int getYear() {
        return year;
    }

    public BooleanProperty zoneHydroLocal() {
        return zoneHydro;
    }

    public BooleanProperty tacheUrbaineLocal() {
        return tacheUrbaine;
    }

    public BooleanProperty tacheArtifLocal() {
        return tacheArtif;
    }

    public BooleanProperty rpgLocal() {
        return rpg;
    }

    public BooleanProperty zoneHydroWFS() {
        return wfszoneHydro;
    }

    public BooleanProperty tacheUrbaineWFS() {
        return wfstacheUrbaine;
    }

    public BooleanProperty tacheArtifWFS() {
        return wfstacheArtif;
    }

    public BooleanProperty rpgWFS() {
        return wfsrpg;
    }

    public BooleanProperty toDownloadProperty() {
        return download;
    }

    public boolean hasLocalTypes(String ... types){
        for(String type : types){
            switch(type.toLowerCase()){
                case GeoReferentials.TYPE_RPG:
                    if(!rpg.get()) return false;
                    break;
                case GeoReferentials.TYPE_ZONE_HYDRO:
                    if(!zoneHydro.get()) return false;
                    break;
                case GeoReferentials.TYPE_TACHE_ARTIF:
                    if(!tacheArtif.get()) return false;
                    break;
                case GeoReferentials.TYPE_TACHE_URBAINE:
                    if(!tacheUrbaine.get()) return false;
                    break;
            }
        }
        return true;
    }

    public boolean hasWFSTypes(String ... types){
        for(String type : types){
            switch(type.toLowerCase()){
                case GeoReferentials.TYPE_RPG:
                    if(!wfsrpg.get()) return false;
                    break;
                case GeoReferentials.TYPE_ZONE_HYDRO:
                    if(!wfszoneHydro.get()) return false;
                    break;
                case GeoReferentials.TYPE_TACHE_ARTIF:
                    if(!wfstacheArtif.get()) return false;
                    break;
                case GeoReferentials.TYPE_TACHE_URBAINE:
                    if(!wfstacheUrbaine.get()) return false;
                    break;
            }
        }
        return true;
    }

    /**
     * Check if given types are available either locally or on configured WFS service.
     * @param types Name of the types to check presence. If null, empty, or if
     * no valid name is present, we consider that predicate cannot be tested, so
     * we return false.
     * @return True if all given names can be found locally or on WFS service.
     * False otherwise, or if input data is null or empty.
     */
    public boolean areAvailable(final String... types) {
        if (types == null || types.length < 1)
            return false;
        boolean validNameFound = false;
        for (final String type : types) {
            if (type != null && !type.isEmpty()) {
                validNameFound = true;
                break;
            }
        }

        if (!validNameFound)
            return false; // No valid name to test.

        for (final String type : types) {
            if (type == null || type.isEmpty())
                continue;
            if (!(hasLocalTypes(type) || hasWFSTypes(type)))
                return false; // One of the given names is not available.
        }

        return true;
    }

    /**
     *
     * @param typeName
     * @return
     * @throws DataStoreException
     */
    public FeatureCollection getFeatureCollection(String typeName, Geometry intersectGeom) throws DataStoreException{
        if (store == null) {
            Path folder = RhomeoCore.REFERENCIELS_GEO_PATH.resolve(GeometryUtils.getSha1(site.getGeometry())).resolve(String.valueOf(year));
            if (!Files.isDirectory(folder)) {
                folder = RhomeoCore.REFERENCIELS_GEO_PATH.resolve(site.getName()).resolve(String.valueOf(year));
            }
            final Map parameters = new HashMap();
            parameters.put("identifier", "shapefile-folder");
            parameters.put("path", folder.toUri());
            parameters.put("namespace", "no namespace");
            store = (FeatureStore) DataStores.open(parameters);
        }

        Set<GenericName> names = store.getNames();
        if (names == null || names.isEmpty())
            throw new DataStoreException("No geo-reference available !");
        GenericName name = null;
        final String lcTypeName = typeName.toLowerCase();
        for (final GenericName tmpName : names) {
            if (tmpName.tip().toString().toLowerCase().startsWith(lcTypeName)) {
                name = tmpName;
            }
        }

        if (name == null)
            throw new IllegalArgumentException("No reference available for name ".concat(typeName));

        final Session session = store.createSession(true);
        final QueryBuilder qb = new QueryBuilder();
        qb.setTypeName(name);

        if(intersectGeom!=null){
            final FilterFactory2 ff = ((FilterFactory2)DefaultFactories.forBuildin(FilterFactory.class));
            qb.setFilter(ff.intersects(new DefaultPropertyName("the_geom"), ff.literal(intersectGeom)));
        }

        return session.getFeatureCollection(qb.buildQuery());
    }

    /**
     * Compute a geometry which is the union of all geometries in specified
     * referential which intersect the geometry given as input.
     * @param typeName Name of the geo-referential to use to get geometies to merge.
     * @param source Filter geometry.
     * @return Computed union, or nothing if we cannot find any geometry intercepting given source.
     * @throws IllegalArgumentException If given type name is not available locally.
     * @throws DataStoreException If an error occurs while accessing reference data.
     */
    public Optional<Geometry> unionIntersecting(final String typeName, final Geometry source) throws DataStoreException {
        final FeatureCollection refData = getFeatureCollection(GeoReferentials.TYPE_ZONE_HYDRO, source);
        Geometry union = null;
        try (FeatureIterator ite = refData.iterator()) {
            if (ite.hasNext())
            while (ite.hasNext()) {
                final Geometry tmp = (Geometry) ite.next().getDefaultGeometryProperty().getValue();
                union = union==null ? tmp : tmp.union(union);
            }
        }

        if (union != null) {
            JTS.setCRS(union, refData.getFeatureType().getCoordinateReferenceSystem());
        }
        return Optional.ofNullable(union);
    }

    @Override
    public int compareTo(GeoReferential o) {
        return year - o.year;
    }

    @Override
    public void close() throws Exception {
        if(store!=null){
            store.close();
        }
    }

    public BooleanExpression completed(final String... types) {
        if (types == null || types.length < 1)
            return new ReadOnlyBooleanWrapper(false).getReadOnlyProperty();

        final ArrayList<BooleanProperty> deps = new ArrayList<>(types.length);
        for (final String type : types) {
            if (type == null || type.isEmpty())
                continue;
            switch (type.toLowerCase()) {
                case GeoReferentials.TYPE_RPG:
                    deps.add(rpg);
                    break;
                case GeoReferentials.TYPE_ZONE_HYDRO:
                    deps.add(zoneHydro);
                    break;
                case GeoReferentials.TYPE_TACHE_ARTIF:
                    deps.add(tacheArtif);
                    break;
                case GeoReferentials.TYPE_TACHE_URBAINE:
                    deps.add(tacheUrbaine);
                    break;
            }
        }

        if (deps.isEmpty())
            return new ReadOnlyBooleanWrapper(false).getReadOnlyProperty();

        BooleanExpression result = deps.get(0);
        for (int i = 1 ; i < deps.size(); i++) {
            result = result.and(deps.get(i));
        }

        return result;
    }
}
