/**
 * 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 fr.cenra.rhomeo.api.Version;
import fr.cenra.rhomeo.api.process.Indicator;
import fr.cenra.rhomeo.api.result.Index;
import java.util.Set;
import fr.cenra.rhomeo.api.process.Process;
import fr.cenra.rhomeo.core.RhomeoCore;
import fr.cenra.rhomeo.core.data.ReferenceManager;
import fr.cenra.rhomeo.protocol.TaxonOperator;
import java.beans.IntrospectionException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.apache.sis.util.ArgumentChecks;

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

    final I11 indic;
    final StenoProcess steneoProcess;
    final SimpsonProcess simpsonProcess;

    final TaxonOperator<P07Statement> op;

    public I2paProcess(final I11 indic, final TaxonOperator<P07Statement> op, final StenoProcess steneoProcess, final SimpsonProcess simpsonProcess) {
        super(op.getProcessContext());
        ArgumentChecks.ensureNonNull("Indicator", indic);
        ArgumentChecks.ensureNonNull("SB and SR indice provider", steneoProcess);
        ArgumentChecks.ensureNonNull("Simpson indice provider", simpsonProcess);
        this.indic = indic;
        this.steneoProcess = steneoProcess;
        this.simpsonProcess = simpsonProcess;
        this.op = op;
    }

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

    /**
     * The I2PA index is measured over previously computed Simpson and steneocie
     * indices (see {@link SimpsonProcess} and {@link StenoProcess}).
     * For each year, the I2PA is the sum of each of the above indices on the
     * same year, + a bonus computed from observed species.
     *
     * The process follows the procedure :
     * 1 - Get Simpson and steneocie process results.
     * 2 - Check we've got at least one index of each type to base our computing on.
     * 3 - Compute the bonus value by analysing input dataset.
     * 4 - Group indices by year
     * 5 - For each year, compute I2PA using above indices.
     *
     * @return A set of I2PA indices for requested years.
     * @throws Exception
     */
    @Override
    protected Set<? extends Index> call() throws Exception {
        updateTitle("Calcul de l'I2PA");

        final int totalProgress = 3;
        updateMessage("Analyse des données");
        updateProgress(0, totalProgress);

        // 1
        final Set<? extends Index> simpsonResult;
        final Set<? extends Index> steneoResult;
        try {
            steneoResult = steneoProcess.get();
            simpsonResult = simpsonProcess.get();
        } catch (Exception e) {
            RhomeoCore.LOGGER.log(Level.FINE, "Cannot complete process due to error on data preparation.", e);
            cancel(false);
            return null;
        }

        // 2
        if (simpsonResult == null || simpsonResult.isEmpty() || steneoProcess == null || steneoResult.isEmpty()) {
            return Collections.EMPTY_SET;
        }

        updateMessage("Calcul du bonus");
        updateProgress(1, totalProgress);

        // 3
        final List<P07Statement> data = op.getProcessData();
        final Map<Integer, Double> bonuses = new HashMap<>();
        final Set<Integer> bonusSpecies = getBonusSpecies();
        final Set<Integer> obsBonus = new HashSet<>();
        int cdRef;
        P07Statement st = data.get(0);
        int stYear = st.getDate().getYear();
        for (int i = 0; i < data.size(); i++) {
            st = data.get(i);
            if (stYear < st.getDate().getYear()) {
                bonuses.put(stYear, obsBonus.size() * 0.1);
                stYear = st.getDate().getYear();
                obsBonus.clear();
            }

            cdRef = op.getCdRef(st.getCd_nom());
            if (bonusSpecies.contains(cdRef)) {
                obsBonus.add(cdRef);
            }
        }

        bonuses.put(stYear, obsBonus.size() * 0.1);

        updateMessage("Calcul de l'indice");
        updateProgress(2, totalProgress);

        // 4
        final HashSet<Index<Double>> result = new HashSet<>(simpsonResult.size());
        final Map<Integer, I2paComputer> indicesByYear = new HashMap<>(simpsonResult.size());
        for (final Index<Double> index : simpsonResult) {
            indicesByYear.computeIfAbsent(index.getYear(), year -> new I2paComputer(bonuses.getOrDefault(year, 0d))).simpson = index;
        }

        for (final Index<Double> index : steneoResult) {
            final I2paComputer container = indicesByYear.computeIfAbsent(index.getYear(), year -> new I2paComputer(bonuses.getOrDefault(year, 0d)));
            if (index.getSpi() == indic.sbSpi)
                container.sb = index;
            else if (index.getSpi() == indic.srSpi)
                container.sr = index;
        }

        // 5
        for (final Map.Entry<Integer, I2paComputer> entry : indicesByYear.entrySet()) {
            result.add(indic.i2paSpi.createIndex(entry.getValue().compute(), entry.getKey()));
        }

        return result;
    }

    /**
     *
     * @return Set of species {@link AmpSpeciesByCounty#cd_nom } for which
     * {@link AmpSpeciesByCounty#value } is 3 in the site county.
     */
    private Set<Integer> getBonusSpecies() 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 Set<Integer> bonusSpecies = new HashSet<>();
        final List<AmpSpeciesByCounty> values = ReferenceManager.getOrCreate(indic.ampDescription).getValues(version);
        for (final AmpSpeciesByCounty ref : values) {
            if (countyCode.equals(ref.getId_dept()) && (ref.value == 3 || ref.value == 2))
                bonusSpecies.add(op.getCdRef(ref.getCd_nom()));
        }
        return bonusSpecies;
    }

    /**
     * Compute I2PA index from a bonus value and parameterised steneocy/simpson
     * indices.
     */
    private static class I2paComputer {
        final double bonus;
        public Index<Double> simpson;
        public Index<Double> sb;
        public Index<Double> sr;

        I2paComputer(final double bonus) {
            this.bonus = bonus;
        }

        double compute() {
            return (simpson == null? 0 : simpson.getValue())
                    + (sb == null? 0 : sb.getValue())
                    + (sr == null? 0 : sr.getValue())
                    + bonus;
        }
    }
}
