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

import com.vividsolutions.jts.geom.Geometry;
import fr.cenra.rhomeo.api.Version;
import fr.cenra.rhomeo.api.process.Indicator;
import fr.cenra.rhomeo.api.process.Process;
import fr.cenra.rhomeo.api.result.Index;
import fr.cenra.rhomeo.core.data.ReferenceManager;
import fr.cenra.rhomeo.protocol.TaxonOperator;
import java.beans.IntrospectionException;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.collection.IntegerList;
/**
 * Calcul des indices de sténéocie brute et relative : Rapport du
 * nombre d'espèces observées sur le nombre d'espèces référencées (département
 * ou grille SHF).
 *
 * @author Alexis Manin (Geomatys)
 */
public class StenoProcess extends Process {

    final I11 indic;
    final TaxonOperator<P07Statement> op;

    StenoProcess(final I11 indic, final TaxonOperator op) {
        super(op.getProcessContext());
        ArgumentChecks.ensureNonNull("Indicator", indic);
        this.indic = indic;
        this.op = op;
    }

    @Override
    public Indicator getIndicator() {
        return indic;
    }

    @Override
    protected Set<? extends Index> call() throws Exception {
        updateTitle("Calcul sténoècie");
        final int totalProgress = 3;

        updateProgress(0, totalProgress);
        updateMessage("Lecture des réferentiels");

        final HashSet<Index> result = new HashSet<>();

        final Set<Integer> refSpeciesCounty = getSpeciesCounty();
        /**
         * Only species marked as important by CenRA (the ones listed in species
         * by county) are kept from SHF.
         */
        final Set<Integer> refSpeciesSHF = getSpeciesSHF();
        refSpeciesSHF.retainAll(refSpeciesCounty);

        if (refSpeciesSHF.isEmpty() && refSpeciesCounty.isEmpty()) {
            return result;// We cannot compute any indice with no species referred.
        }

        updateProgress(1, totalProgress);
        updateMessage("Analyse des données");

        final List<P07Statement> data = op.getProcessData();

        // Split data by year. In the mean time, we'll count number of distinct
        // viewed species for each year, to save an iteration.
        final HashSet<Integer> distinctSpecies = new HashSet();
        final IntegerList years = new IntegerList(10, Short.MAX_VALUE);
        final IntegerList obsSpeciesCounty = new IntegerList(10, Short.MAX_VALUE);
        final IntegerList obsSpeciesShf = new IntegerList(10, Short.MAX_VALUE);

        P07Statement st = data.get(0);
        int year = st.getDate().getYear();
        distinctSpecies.add(op.getCdRef(st.getCd_nom()));
        int obsCounty, obsShf;
        for (int i = 1 ; i < data.size() ; i++) {
            st = data.get(i);
            if (st.getDate().getYear() > year) {
                years.add(year);
                obsCounty = obsShf = 0;
                for (final Integer str : distinctSpecies) {
                    if (refSpeciesCounty.contains(str))
                        obsCounty++;
                    if (refSpeciesSHF.contains(str))
                        obsShf++;
                }
                obsSpeciesCounty.add(obsCounty);
                obsSpeciesShf.add(obsShf);

                year = st.getDate().getYear();
                distinctSpecies.clear();
            }

            distinctSpecies.add(op.getCdRef(st.getCd_nom()));
        }

        years.add(year);
        obsCounty = obsShf = 0;
        for (final Integer ds : distinctSpecies) {
            if (refSpeciesCounty.contains(ds))
                obsCounty++;
            if (refSpeciesSHF.contains(ds))
                obsShf++;
        }
        obsSpeciesCounty.add(obsCounty);
        obsSpeciesShf.add(obsShf);

        updateProgress(2, totalProgress);
        updateMessage("Calcul des indices");

        // Compute indices for each year.
        int nbObs;
        final boolean computeByCounty = refSpeciesCounty.size() > 0;
        final boolean computeByShf = refSpeciesSHF.size() > 0;
        for (int i = 0; i < years.size(); i++) {
            year = years.get(i);

            if (computeByCounty) {
                nbObs = obsSpeciesCounty.get(i);
                if (nbObs < 1) // not enough observations to compute index
                    result.add(indic.sbSpi.createIndex(0d, year));
                else
                    result.add(indic.sbSpi.createIndex(nbObs / (double) refSpeciesCounty.size(), year));
            }

            if (computeByShf) {
                nbObs = obsSpeciesShf.get(i);
                if (nbObs < 1) // not enough observations to compute index
                    result.add(indic.srSpi.createIndex(0d, year));
                else
                    result.add(indic.srSpi.createIndex(nbObs / (double) refSpeciesSHF.size(), year));
            }
        }

        return result;
    }

    /**
     *
     * @return Distinct species (cd_ref) referred by {@link AmpSpeciesByCounty}
     * for the county referred by the currently edited site.
     */
    private Set<Integer> getSpeciesCounty() throws IntrospectionException, ReflectiveOperationException {
        final String countyCode;
        final Version version = ctx.getDataCtx().getReferences().get(AmpSpeciesByCounty.class);
        if (version == null || ctx.getDataCtx() == null || ctx.getDataCtx().getSite() == null || (countyCode = ctx.getDataCtx().getSite().getCountyCode()) == null)
            return Collections.EMPTY_SET;

        final List<AmpSpeciesByCounty> values = ReferenceManager.getOrCreate(indic.ampDescription).getValues(version);
        final HashSet<Integer> distinctSpecies = new HashSet();
        for (final AmpSpeciesByCounty ref : values) {
            if (countyCode.equals(ref.getId_dept()) && ref.getValue() != 3)
                distinctSpecies.add(op.getCdRef(ref.getCd_nom()));
        }

        return distinctSpecies;
    }

    /**
     *
     * @return Distinct species (cd_ref) referred by {@link Shf} for the
     * currently edited site.
     */
    private Set<Integer> getSpeciesSHF() throws IntrospectionException, ReflectiveOperationException {
        final Geometry geom;
        final Version version = ctx.getDataCtx().getReferences().get(Shf.class);
        if (version == null || ctx.getDataCtx() == null || ctx.getDataCtx().getSite() == null || (geom = ctx.getDataCtx().getSite().getGeometry()) == null)
            return Collections.EMPTY_SET;

        Integer cdRef;
        final List<Shf> values = ReferenceManager.getOrCreate(indic.shfDescription).getValues(version);
        final HashSet<Integer> distinctSpecies = new HashSet();
        for (final Shf ref : values) {
            cdRef = op.getCdRef(ref.getCd_nom());
            // Check if we've already got the ref name, to avoid testing costly intersection for nothing.
            if (!distinctSpecies.contains(cdRef) && ref.getGeometry()!= null && ref.getGeometry().intersects(geom))
                distinctSpecies.add(cdRef);
        }

        return distinctSpecies;
    }
}
