/*
 * #%L
 * IsisFish data
 * %%
 * Copyright (C) 2015 Ifremer, Code Lutin, Benjamin Poussin
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public 
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/gpl-3.0.html>.
 * #L%
 */
package scripts;

import static org.nuiton.i18n.I18n.n;

import fr.ifremer.isisfish.IsisFishException;
import fr.ifremer.isisfish.annotations.ComputeResult;
import fr.ifremer.isisfish.entities.Cell;
import fr.ifremer.isisfish.entities.Gear;
import fr.ifremer.isisfish.entities.Metier;
import fr.ifremer.isisfish.entities.MetierSeasonInfo;
import fr.ifremer.isisfish.entities.Population;
import fr.ifremer.isisfish.entities.PopulationGroup;
import fr.ifremer.isisfish.entities.PopulationSeasonInfo;
import fr.ifremer.isisfish.entities.Selectivity;
import fr.ifremer.isisfish.entities.Strategy;
import fr.ifremer.isisfish.entities.StrategyMonthInfo;
import fr.ifremer.isisfish.entities.Zone;
import fr.ifremer.isisfish.simulator.SimulationContext;
import fr.ifremer.isisfish.types.Month;
import fr.ifremer.isisfish.types.TimeStep;
import resultinfos.MatrixAbundance;
import resultinfos.MatrixCatchPerStrategyMetPerZoneMet;
import resultinfos.MatrixCatchPerStrategyMetPerZonePop;
import resultinfos.MatrixCatchWeightPerStrategyMetPerZoneMet;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuiton.math.matrix.MatrixFactory;
import org.nuiton.math.matrix.MatrixIterator;
import org.nuiton.math.matrix.MatrixND;
import org.nuiton.topia.TopiaException;

/**
 *
 * @author poussin
 * @version $Revision$
 *
 * Last update: $Date$
 * by : $Author$
 */
public class SiMatrixEffortByCell extends SiMatrix {

    /** to use log facility, just put in your code: log.info(\"...\"); */
    static private Log log = LogFactory.getLog(SiMatrixEffortByCell.class);

    public SiMatrixEffortByCell(SimulationContext context) throws TopiaException {
        super(context);
    }

    @Override
    public void computeMonthExtra(TimeStep step, Population pop, MatrixND N) throws TopiaException, IsisFishException {
        MatrixND catchPerStrategyMetPerZoneMet =
                matrixCatchPerStrategyMetPerZoneMet(N, pop, step);
        resManager.addResult(step, pop, catchPerStrategyMetPerZoneMet);

        if (resManager.isEnabled(MatrixCatchWeightPerStrategyMetPerZoneMet.NAME)) {
            MatrixND catchWeightPerStrategyMet =
                    matrixCatchWeightPerStrategyMetPerZoneMet(step,
                            pop, catchPerStrategyMetPerZoneMet);
            resManager.addResult(step, pop, catchWeightPerStrategyMet);
        }
    }

    @ComputeResult(MatrixCatchWeightPerStrategyMetPerZoneMet.NAME)
    protected MatrixND matrixCatchWeightPerStrategyMetPerZoneMet(TimeStep step,
            Population pop, MatrixND matrixCatchPerStrategyMetPerZoneMet)
            throws TopiaException, IsisFishException {

        return matrixToWeightMatrix(step, 2,
                MatrixCatchWeightPerStrategyMetPerZoneMet.NAME,
                matrixCatchPerStrategyMetPerZoneMet);
    }

    @ComputeResult(MatrixCatchPerStrategyMetPerZoneMet.NAME)
    protected MatrixND matrixCatchPerStrategyMetPerZoneMet(MatrixND N,
            Population pop, TimeStep step) throws TopiaException, IsisFishException {

        MatrixND matrixFishingMortalityPerCell = matrixFishingMortalityPerCell(
                step, pop);
        MatrixND matrixCatchRatePerStrategyMetPerCell = matrixCatchRatePerStrategyMetPerCell(
                pop, step, matrixFishingMortalityPerCell);
        MatrixND matrixCatchPerStrategyMetPerCell = matrixCatchPerStrategyMetPerCell(
                N, pop, step, matrixCatchRatePerStrategyMetPerCell);

        List<Strategy> strategies = getStrategies(step);
        List<Metier> metiers = getMetiers(step);
        List<PopulationGroup> groups = (List<PopulationGroup>)matrixCatchPerStrategyMetPerCell
                .getSemantic(2);
        List<Zone> zones = getZones(step);

        Set<Cell> cellPops = new HashSet(matrixCatchPerStrategyMetPerCell
                .getSemantic(4));

        MatrixND result = MatrixFactory.getInstance().create(
                MatrixCatchPerStrategyMetPerZoneMet.NAME,
                new List[] { strategies, metiers, groups, zones },
                new String[] { n("Strategies"), n("Metiers"), n("Groups"),
                        n("Zones") });

        // matrice temporaire ou les zones pops sont sommees
        MatrixND tmp = matrixCatchPerStrategyMetPerCell.sumOverDim(3);
        tmp = tmp.reduceDims(3);

        for (Strategy str : strategies) {
            for (PopulationGroup group : groups) {
                // on iter que sur les metiers qui ont une proportion non 0
                // car la matrice pour les autres metiers, contient 0.
                List<Metier> metiersNot0 = getMetiers(str, step);
                for (Metier metier : metiersNot0) {
                    MetierSeasonInfo infoMet = metier.getMetierSeasonInfo(step
                            .getMonth());
                    Collection<Zone> zoneMet = infoMet.getZone();
                    for (Zone z : zoneMet) {
                        double value = 0;
                        List<Cell> cells = z.getCell();
                        for (Cell cell : cells) {
                            if (cellPops.contains(cell)) {
                                // les cells de la matrice sont les cells des
                                // zones pops, donc seul les intersections avec
                                // les cells des metiers sont des cells valides
                                value += tmp.getValue(str, metier, group, cell);
                            }
                        }
                        result.setValue(str, metier, group, z, value);
                    }
                }
            }
        }

        return result;
    }

    @Override
    @ComputeResult(MatrixCatchPerStrategyMetPerZonePop.NAME)
    public MatrixND matrixCatchPerStrategyMetPerZone(MatrixND N,
            Population pop, TimeStep step) throws TopiaException, IsisFishException {
        MatrixND matrixFishingMortalityPerCell = matrixFishingMortalityPerCell(
                step, pop);
        MatrixND matrixCatchRatePerStrategyMetPerCell = matrixCatchRatePerStrategyMetPerCell(
                pop, step, matrixFishingMortalityPerCell);
        MatrixND matrixCatchPerStrategyMetPerCell = matrixCatchPerStrategyMetPerCell(
                N, pop, step, matrixCatchRatePerStrategyMetPerCell);

        // on somme sur les cellules
        MatrixND result = matrixCatchPerStrategyMetPerCell.sumOverDim(4);
        result = result.reduceDims(4);
        result.setName(MatrixCatchPerStrategyMetPerZonePop.NAME);

        return result;
    }


    /**
     * Utilise pour le calcule en Cell.
     *
     * @param N
     * @param pop
     * @param step
     * @return
     * @throws IsisFishException
     * @throws TopiaException
     */
    @Override
    @ComputeResult(MatrixAbundance.NAME)
    public MatrixND matrixAbundance(MatrixND N, Population pop, TimeStep step)
            throws TopiaException, IsisFishException {

        MatrixND matrixFishingMortalityPerCell = matrixFishingMortalityPerCell(
                step, pop);

        MatrixND result = matrixAbundancePerCell(N, pop, step,
                matrixFishingMortalityPerCell);
        result = result.sumOverDim(2);
        result = result.reduceDims(2);
        result.setName(MatrixAbundance.NAME);

        return result;
    }

    /**
     * @param N
     * @param pop
     * @param step
     * @return
     * @throws IsisFishException
     * @throws TopiaException
     */
    protected MatrixND matrixAbundancePerCell(MatrixND N, Population pop,
            TimeStep step, MatrixND matrixFishingMortality) throws TopiaException,
            IsisFishException {

        List<PopulationGroup> groups = pop.getPopulationGroup();
        List<Zone> zones = pop.getPopulationZone();
        List<Cell> allCells = getCells(zones);

        MatrixND result = MatrixFactory.getInstance().create(
                MatrixAbundance.NAME + "_PER_CELL",
                new List[] { groups, zones, allCells },
                new String[] { n("Groups"), n("Zones"), n("Cells") });

        for (int g = 0; g < groups.size(); g++) {
            PopulationGroup group = groups.get(g);
            for (int z = 0; z < zones.size(); z++) {
                Zone zone = zones.get(z);
                List<Cell> cells = zone.getCell();
                for (int c = 0; c < cells.size(); c++) {
                    Cell cell = cells.get(c);
                    double value = survivalRatePerCell(step, group, zone, cell,
                            matrixFishingMortality);
                    double n = N.getValue(g, z) / zone.sizeCell();
                    value *= n;
                    result.setValue(g, z, c, value);
                }
            }
        }

        return result;
    }

    /**
     * @param step
     * @param group
     * @param zone
     * @return
     * @throws IsisFishException
     * @throws TopiaException
     */
    protected double survivalRatePerCell(TimeStep step, PopulationGroup group,
            Zone zone, Cell cell, MatrixND matrixFishingMortalityPerCell)
            throws TopiaException, IsisFishException {

        double F = totalFishingMortalityPerCell(step,
                matrixFishingMortalityPerCell).getValue(group, zone, cell); //totalFishingMortality(step, group, zone);  // rem perf: totalFishingMortality a deja ete calcule
        double M = getTotalDeathRate(step, group, zone)
        / (double) Month.NUMBER_OF_MONTH;
        double result = Math.exp(-(F + M));

        return result;
    }

    /**
     * Matrice fishing mortality dim [ Strategy x Metier x Classe x zonePop x
     * cellPop]
     *
     * Nouvelle implantation suite a demande Steph et Sigrid: 20080208 (NouvellesEquationsfev2008ParCelluleAvecAlgo.odt)
     *
     * @param pop
     * @param step
     * @return
     * @throws TopiaException
     * @throws IsisFishException
     */
    protected MatrixND matrixFishingMortalityPerCell(TimeStep step, Population pop)
            throws TopiaException, IsisFishException {
        List<Strategy> strategies = getStrategies(step);
        List<Metier> metiers = getMetiers(step);
        List<PopulationGroup> groups = pop.getPopulationGroup();
        List<Zone> zones = pop.getPopulationZone();
        List<Cell> cells = getCells(zones);

        // default value in matrix is 0
        MatrixND result = MatrixFactory.getInstance().create(
                "matrixFishingMortalityPerCell",
                new List[] { strategies, metiers, groups, zones, cells },
                new String[] { n("Strategies"), n("Metiers"), n("Groups"),
                    n("Zones"), n("Cells") });

        Month month = step.getMonth();
        PopulationSeasonInfo infoPop = pop.getPopulationSeasonInfo(month);

        for (Strategy str : strategies) {
            StrategyMonthInfo smi = str.getStrategyMonthInfo(month);
            double nbTrip = smi.getNumberOfTrips();
            double propSetOfVessels = str.getProportionSetOfVessels();
            int nbOfVessels = str.getSetOfVessels().getNumberOfVessels();

            // dans le calcul de l'effort si une de ces donnees est 0
            // alors le result dans la matrice est 0
            // donc autant ne pas faire tous les autres calculs couteux
            if (nbTrip * propSetOfVessels * nbOfVessels != 0) {
                for (PopulationGroup group : groups) {

                    // getCapturability is check matrix validity and create matrix if needed and get one value in
                    double capturability = infoPop.getCapturability(group);
                    if (capturability != 0) { // check 0, this prevent next call, for default value

                        // et de la meme facon si la proportion est 0 alors
                        // le resultat dans la matrice sera 0, donc on boucle que
                        // sur ceux qui ont une proportion
                        List<Metier> metiersNot0 = getMetiers(str, step);
                        for (Metier metier : metiersNot0) {

                            MetierSeasonInfo infoMet = metier
                                    .getMetierSeasonInfo(month);
                            // getTargetFactor seem to be simple
                            double ciblage = infoMet.getTargetFactor(group);

                            if (ciblage != 0) { // check 0, this prevent next call, for default value

                                Gear gear = metier.getGear();
                                Selectivity selectivity = gear.getPopulationSelectivity(pop);
                                if (selectivity != null) {

                                    // getCoefficient is equation evaluation
                                    double coeff = selectivity.getCoefficient(pop,group, metier);
                                    if (coeff != 0) { // check 0, this prevent next call, for default value

                                        // l'effort d'une cellule du metier
                                        double effort = effortPerStrategyPerCell(
                                                str, metier, infoMet, step);
                                        if (effort > 0) { // put value only if <> 0
                                            for (Zone zone : zones) {
                                                Set<Cell> cellPops = new HashSet<>(zone.getCell());
                                                for (Cell cellMet : infoMet.getCells()) {
                                                    if (cellPops.contains(cellMet)) {
                                                        // Reduction d'effort (generic):
                                                        double effortReduction = getEffortReduction(step, gear, cellMet);

                                                        double value = (1.0 - effortReduction) * coeff * capturability * ciblage * effort;
                                                        result.setValue(
                                                                new Object[] { str,
                                                                    metier,
                                                                    group,
                                                                    zone,
                                                                    cellMet },
                                                                value);
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        return result;
    }

    protected double getEffortReduction(TimeStep step, Gear gear, Cell cellMet) {
        return 0.0;
    }

    /**
     * Matrice des captures en nombre dim [ Strategy x Metier x Classe x zonePop ]
     *
     * @param N
     *            l'abondance sous forme de matrice 2D [class x zone]
     * @param pop
     * @param step
     * @return
     * @throws TopiaException
     */
    protected MatrixND matrixCatchPerStrategyMetPerCell(MatrixND N, Population pop, TimeStep step,
            MatrixND matrixCatchRatePerStrategyMetPerCell) throws TopiaException {
        int groupDim = 2;
        int zoneDim = 3;

        int NGroupDim = 0;
        int NZoneDim = 1;

        MatrixND result = matrixCatchRatePerStrategyMetPerCell.copy();
        result.setName("matrixCatchPerStrategyMetPerCell");

        List<PopulationGroup> groups = (List<PopulationGroup>)result.getSemantic(groupDim);
        List<Zone> zones = (List<Zone>)result.getSemantic(zoneDim);

        double[] sizeCell = new double[zones.size()];
        int cpt = 0;
        for (Zone z : zones) {
            sizeCell[cpt++] = z.sizeCell();
        }

        // on suppose que les semantics de N et matrixCatchRatePerStrategyMetPerCell
        // pour les PopulationGroup et Zone sont les memes
        // mais on verifie tout de meme
        if (groups.equals(N.getSemantic(NGroupDim)) &&
                zones.equals(N.getSemantic(NZoneDim))) {

            // on place les effectifs dans un tableau pour un acces rapide
            // on divise directement par le nombre de cell de la zone pour eviter
            // de le faire beaucoup plus souvent dans l'autre boucle
            double[][] NArray = new double[groups.size()][zones.size()];
            for (MatrixIterator i=N.iteratorNotZero(); i.next();) {
                int[] pos = i.getCoordinates();
                double val = i.getValue();

                NArray[pos[NGroupDim]][pos[NZoneDim]] = val / sizeCell[pos[NZoneDim]];
            }

            // calcul de la matrice resultat
            for (MatrixIterator i=result.iteratorNotZero(); i.next();) {
                int[] pos = i.getCoordinates();
                double val = i.getValue();
                val *= NArray[pos[groupDim]][pos[zoneDim]];
                i.setValue(val);
            }

        } else {
            // N et matrixCatchRatePerStrategyMetPerCell non pas les meme semantics
            // on fait moins rapide
            for (MatrixIterator i=result.iteratorNotZero(); i.next();) {
                int[] pos = i.getCoordinates();
                Object[] sems = i.getSemanticsCoordinates();
                PopulationGroup group = (PopulationGroup)sems[groupDim];
                Zone zone = (Zone)sems[zoneDim];
                double val = i.getValue();
                val *= N.getValue(group, zone) / sizeCell[pos[zoneDim]];
                i.setValue(val);
            }
        }

        return result;
    }

    /**
     * Matrice des captures en poids dim [ Strategy x Metier x Classe x zonePop ]
     *
     * @param pop
     * @param step
     * @return
     * @throws TopiaException
     * @throws IsisFishException
     */
    protected MatrixND matrixCatchRatePerStrategyMetPerCell(Population pop,
            TimeStep step, MatrixND matrixFishingMortalityPerCell)
            throws TopiaException, IsisFishException {

        MatrixND totalFishingMortalityPerCell = totalFishingMortalityPerCell(
                step, matrixFishingMortalityPerCell);

        MatrixND result = matrixFishingMortalityPerCell.copy();
        result.setName("matrixCatchRatePerStrategyMetPerCell");

        for (MatrixIterator i=result.iteratorNotZero(); i.next();) {
            Object[] sems = i.getSemanticsCoordinates();

            PopulationGroup group = (PopulationGroup)sems[2];
            Zone zone = (Zone)sems[3];
            Cell cell = (Cell)sems[4];

            double totalFishingMortality = totalFishingMortalityPerCell.getValue(
                    group, zone, cell);

            if (totalFishingMortality != 0) {

                double fishingMortalityPerCell = i.getValue();
                double totalCatchRatePerCell = totalCatchRatePerCell(step, group, zone,
                        totalFishingMortality);

                double value = fishingMortalityPerCell / totalFishingMortality
                        * totalCatchRatePerCell;

                i.setValue(value);
            }
        }

        return result;
    }

    /**
     * @param step
     * @param group
     * @param zone
     * @param totalFishingMortalityPerCell
     * @return
     * @throws TopiaException
     */
    private double totalCatchRatePerCell(TimeStep step, PopulationGroup group, Zone zone, double totalFishingMortalityPerCell)
            throws TopiaException {
        double M = getTotalDeathRate(step, group, zone) / Month.NUMBER_OF_MONTH;
        if (M == 0) {
            // normalement il devrait y avoir de la mortalite naturelle
            if (log.isWarnEnabled()) {
                log.warn("Pas de mortalite naturelle pour: " + group);
            }
        }
        double F = totalFishingMortalityPerCell;

        double result = 0;
        if (M != 0 || F != 0) {
            result = F / (F + M) * (1 - Math.exp(-(F + M)));
        }

        return result;
    }

    /**
     * Returne une matrice de mortalite group x zone, donc somme sur str et
     * metier
     *
     * @param step
     * @param matrixFishingMortalityPerCell
     * @return
     */
    protected MatrixND totalFishingMortalityPerCell(TimeStep step,
            MatrixND matrixFishingMortalityPerCell) {
        MatrixND result = matrixFishingMortalityPerCell.sumOverDim(0);
        result = result.sumOverDim(1);
        result = result.reduceDims(0, 1);
        return result;
    }

}
