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

import fr.cenra.rhomeo.api.data.TrackingPoint;
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.protocol.TaxonOperator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 *
 * @author Alexis Manin (Geomatys)
 */
public class RichnessProcess extends Process {

    final I10 indic;
    final TaxonOperator<OdonataStatement> operator;

    protected RichnessProcess(final TaxonOperator operator, final I10 indic) {
        super(operator.getProcessContext());
        this.indic = indic;
        this.operator = operator;
    }

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

    @Override
    protected Set<? extends Index> call() throws Exception {
        updateTitle("Calcul ".concat(indic.annualRateRichness.getTitle()));
        final int total = 3;
        updateProgress(0, total);
        updateMessage("Calcul du nombre de points de suivi par an.");
        final Map<Integer, Integer> nPerYear = computePointNumberPerYear();

        updateProgress(1, total);
        updateMessage("Calcul d'abondance");
        Map<Integer, Map<Integer, Integer>> abundancePerYear = computeAbundance();

        updateProgress(2, total);
        updateMessage("Calcul de richesse");
        final Set<Index> indices = new HashSet<>(nPerYear.keySet().size());
        Map<Integer, Integer> abundance;
        int sobs;
        long uniqueAppearance;
        double strue;
        for (final Map.Entry<Integer, Integer> entry : nPerYear.entrySet()) {
            abundance = abundancePerYear.get(entry.getKey());
            sobs = abundance.size();
            uniqueAppearance = abundance.values().stream().filter(i -> i == 1).count();
            strue = sobs + (entry.getValue() - 1) / (double) entry.getValue() *  uniqueAppearance;
            indices.add(indic.annualRateRichness.createIndex(sobs / strue * 100, entry.getKey()));
        }

        return indices;
    }

    /**
     *
     * @return Number of tracking points found for each year.
     */
    private Map<Integer, Integer> computePointNumberPerYear() {
        final ArrayList<TrackingPoint> tps = new ArrayList<>(ctx.getTrackingPoints());
        tps.sort((o1, o2) -> o1.getYear() - o2.getYear());
        final Map<Integer, Integer> nPerYear = new HashMap<>();
        int year = tps.get(0).getYear();
        int n = 0;
        for (final TrackingPoint tp : tps) {
            if (year < tp.getYear()) {
                nPerYear.put(year, n);
                n = 1;
                year = tp.getYear();
            } else n++;
        }

        nPerYear.put(year, n);
        return nPerYear;
    }

    /**
     * Compute abundance for each year. It is the number tracking points in which
     * a species appears in a single year.
     * @return A map whose keys are the available years, and values are maps
     * giving for each species found its cd_ref (key) along with the number of
     * tracking points it appears into (value).
     * @throws Exception
     */
    private Map<Integer, Map<Integer, Integer>> computeAbundance() throws Exception {
        final List<OdonataStatement> statements = operator.getProcessData();
        OdonataStatement st = statements.get(0);
        int year = st.getDate().getYear();
        Map<Integer, Map<Integer, Integer>> abundancePerYear = new HashMap<>();
        Map<Integer, Set<String>> trackingPointsPerCdRef = new HashMap<>();
        for (int i = 0 ; i < statements.size(); i++) {
            st = statements.get(i);
            if (year < st.getDate().getYear()) {
                abundancePerYear.put(year, countValues(trackingPointsPerCdRef));
                trackingPointsPerCdRef.clear();
                year = st.getDate().getYear();
            }

            trackingPointsPerCdRef.computeIfAbsent(operator.getCdRef(st.getCd_nom()), (key) -> new HashSet<>()).add(st.getTrackingPoint());
        }

         abundancePerYear.put(year, countValues(trackingPointsPerCdRef));

        return abundancePerYear;
    }

    private Map<Integer, Integer> countValues(final Map<Integer, ? extends Collection> toCount) {
        final Map<Integer, Integer> result = new HashMap(toCount.size());
        for (final Map.Entry<Integer, ? extends Collection> entry : toCount.entrySet())
            result.put(entry.getKey(), entry.getValue().size());
        return result;
    }
}
