/*
 * #%L
 * IsisFish data
 * %%
 * Copyright (C) 2009 - 2014 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 simulators;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import fr.ifremer.isisfish.simulator.SimulationContext;
import scripts.SiMatrix;
import scripts.SiMatrixEffortByCell;
import scripts.SiMatrixEffortByCell_Sid;

import org.nuiton.topia.TopiaException;
import fr.ifremer.isisfish.IsisFishException;

import fr.ifremer.isisfish.datastore.StorageException;
import fr.ifremer.isisfish.entities.Metier;
import fr.ifremer.isisfish.entities.Population;
import fr.ifremer.isisfish.entities.PopulationSeasonInfo;
import fr.ifremer.isisfish.rule.Rule;
import fr.ifremer.isisfish.simulator.MetierMonitor;
import fr.ifremer.isisfish.simulator.PopulationMonitor;
import fr.ifremer.isisfish.simulator.ResultManager;
import fr.ifremer.isisfish.simulator.RuleMonitor;
import fr.ifremer.isisfish.simulator.SimulationControl;
import fr.ifremer.isisfish.simulator.SimulationParameter;
import fr.ifremer.isisfish.simulator.ResultManager;
import fr.ifremer.isisfish.simulator.Simulator;
import fr.ifremer.isisfish.types.Month;
import fr.ifremer.isisfish.types.TimeStep;
import org.nuiton.math.matrix.MatrixFactory;
import org.nuiton.math.matrix.MatrixND;
import org.nuiton.topia.TopiaContext;
import org.nuiton.topia.TopiaException;
import scripts.GravityModel;
import scripts.ResultName;

import java.util.List;

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


/**
 * Simulateur qui fixe la valeur de effortByCell a true pour force
 * l'utilisation des calculs par cellule plutot que par zone.
 *
 * @author poussin
 */
public class SimulatorEffortByCell_Sid extends DefaultSimulator {
    
    /** to use log facility, just put in your code: log.info("..."); */
    static private Log log = LogFactory.getLog(SimulatorEffortByCell_Sid.class);

    protected SiMatrixEffortByCell_Sid siMatrix;

    @Override
    protected SiMatrix newSiMatrix(SimulationContext context) throws TopiaException {
    	   siMatrix = new SiMatrixEffortByCell_Sid(context);
        return siMatrix;
    }

     /**
      * Use custom "siMatrix" instead of provided _siMatrix.
      */
	protected void computeMonth(SimulationContext context, SiMatrix _siMatrix,
            TimeStep step, Population pop) throws IsisFishException, TopiaException {
        // to add result
        ResultManager resManager = context.getResultManager();

        PopulationMonitor popMon = context.getPopulationMonitor();
        MatrixND N = popMon.getN(pop);

        if (log.isInfoEnabled()) {
            log.info("====================== begin " + step + " - " + pop
                    + " =========================== "
                    + System.currentTimeMillis());
            //log.info("N: " + N);
        }

        // add N and biomass result now, before computation
        // N is reassigned during computation        
        resManager.addResult(step, pop, N);

        MatrixND biomass = siMatrix.matrixBiomass(N, pop, step);
        if (resManager.isEnabled(ResultName.MATRIX_BIOMASS)) {
            resManager.addResult(step, pop, biomass);
        }

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

        // group change
        MatrixND CA;
        if (step.getStep() == 0) {
            CA = MatrixFactory.getInstance().matrixId(
                    pop.sizePopulationGroup() * pop.sizePopulationZone());
        } else {
            CA = info.getGroupChangeMatrix(month);
        }

        log.debug("CA: " + CA);

        //migration
        MatrixND M = info.getMigrationMatrix(month, N);
        //log.info(M);
        //log.debug("M: " + M);
        //emigration
        MatrixND EM = info.getEmigrationMatrix(month, N);
        //log.debug("EM: " + EM);
        //immigration
        MatrixND IM = info.getImmigrationMatrix(month, N).transpose();
        //log.debug("IM: " + IM);

        // pour les premiers calculs on met N en une matrice 1D
        MatrixND N1D = pop.N2DToN1D(N);
        //log.debug("N1D: " + N1D);

        MatrixND tmp0 = N1D.mult(CA);
        MatrixND tmp1 = M.minus(EM);
        MatrixND tmp2 = tmp0.mult(tmp1);
        MatrixND tmp3 = tmp2.add(IM);

        //log.debug("N1D after mig: " + tmp3);

        // On reconvertie en une matrice Semantique
        N = pop.split2D(tmp3);

        //log.debug("N after mig: " + N);

        // reproduction
        MatrixND R = info.getReproductionMatrix(month, N);
        //log.debug("R: " + R);

        // ajout de la matrice R dans le suivi de la pop comme etant
        // la reproduction pour le mois courant.
        popMon.setReproduction(step, pop, N, biomass, R);

        // recrutement
        MatrixND recruitment = popMon.getRecruitment(step, pop);
        //log.debug("recruitment: " + recruitment);

        // mortalite de la reproduction
        popMon.applyReproductionMortality(pop);

        N = N.add(recruitment);
        //log.info("rec matrice"+recruitment);
        //log.debug("N after recru: " + N);

        if (resManager.isEnabled(ResultName.MATRIX_ABUNDANCE_BEGIN_MONTH)) {
            MatrixND abondanceBM = siMatrix.matrixAbondanceBeginMonth(N, pop,
                    step);
            resManager.addResult(step, pop, abondanceBM);
        }

        if (resManager.isEnabled(ResultName.MATRIX_BIOMASS_BEGIN_MONTH)) {
            MatrixND biomassBM = siMatrix.matrixBiomassBeginMonth(N, pop, step);
            resManager.addResult(step, pop, biomassBM);
        }

        MatrixND abundance;
        // compute fishing matrix only if there are one or more strategy
        if (siMatrix.getStrategies(step).size() > 0) {
            // compute some Matrix and add result

            // this matrix is necessary for PopulationMonitor.holdCatch (reused in rule)
            MatrixND catchPerStrategyMetPerZonePop;

            if (isEffortByCell(context)) {
                abundance = siMatrix.matrixAbundance(N, pop, step);
                catchPerStrategyMetPerZonePop = siMatrix
                        .matrixCatchPerStrategyMetPerZonePop(N, pop, step);
            } else { // en zone
                MatrixND matrixFishingMortality = siMatrix
                        .matrixFishingMortality(step, pop);
                resManager.addResult(step, pop, matrixFishingMortality);

                abundance = siMatrix.matrixAbundance(N, pop, step,
                        matrixFishingMortality);

                // this matrix is necessary for matrixCatchPerStrategyMet
                MatrixND catchRatePerStrategyMet = siMatrix
                        .matrixCatchRatePerStrategyMetPerZone(pop, step,
                                matrixFishingMortality);
                resManager.addResult(step, pop, catchRatePerStrategyMet);

                catchPerStrategyMetPerZonePop = siMatrix
                        .matrixCatchPerStrategyMetPerZone(N, pop, step,
                                catchRatePerStrategyMet);

            }

            popMon.holdCatch(pop, catchPerStrategyMetPerZonePop);
            resManager.addResult(step, pop, catchPerStrategyMetPerZonePop);

            if (resManager
                    .isEnabled(ResultName.MATRIX_CATCH_WEIGHT_PER_STRATEGY_MET_PER_ZONE_POP)) {
                MatrixND catchWeightPerStrategyMet = siMatrix
                        .matrixCatchWeightPerStrategyMetPerZonePop(step, pop,
                                catchPerStrategyMetPerZonePop);
                resManager.addResult(step, pop, catchWeightPerStrategyMet);
                  }

            if (isEffortByCell(context)) {
                MatrixND catchPerStrategyMetPerZoneMet = siMatrix
                        .matrixCatchPerStrategyMetPerZoneMet(N, pop, step);
                resManager.addResult(step, pop, catchPerStrategyMetPerZoneMet);

                if (resManager
                        .isEnabled(ResultName.MATRIX_CATCH_WEIGHT_PER_STRATEGY_MET_PER_ZONE_MET)) {
                    MatrixND catchWeightPerStrategyMet = siMatrix
                            .matrixCatchWeightPerStrategyMetPerZoneMet(step,
                                    pop, catchPerStrategyMetPerZoneMet);
                    resManager.addResult(step, pop, catchWeightPerStrategyMet);
                }
            }

            if (resManager.isEnabled(ResultName.MATRIX_FISHING_MORTALITY_PER_GROUP) ||
                    resManager.isEnabled(ResultName.MATRIX_TOTAL_FISHING_MORTALITY)) {
                MatrixND fishingMortalityPerGroup = siMatrix.fishingMortalityPerGroup(step,
                        pop, context.getSimulationStorage().getResultStorage());

                if (resManager.isEnabled(ResultName.MATRIX_FISHING_MORTALITY_PER_GROUP)) {
                    resManager.addResult(step, pop, fishingMortalityPerGroup);
                }
            
                if (resManager.isEnabled(ResultName.MATRIX_TOTAL_FISHING_MORTALITY)) {
                    MatrixND totalFishingMortality = siMatrix.totalFishingMortality(step, pop, fishingMortalityPerGroup);
                    resManager.addResult(step, pop, totalFishingMortality);
                }
            }

        } else { // no strategies
            // compute only if fishing mortality =0 to apply Natural Mortality
            abundance = siMatrix.matrixAbundanceSsF(N, pop, step);
        }
        
        log.debug("abundance: " + abundance);
        
        if (resManager.isEnabled(ResultName.MATRIX_ABUNDANCE_END_MONTH)) {
            MatrixND abondanceEM = siMatrix.matrixAbondanceEndMonth(abundance, pop,
                    step);
            resManager.addResult(step, pop, abondanceEM);
        }
         if (resManager.isEnabled(ResultName.MATRIX_BIOMASS_END_MONTH)) {
            MatrixND biomassEM = siMatrix.matrixBiomassEndMonth(abundance, pop, step);
            resManager.addResult(step, pop, biomassEM);
        }
        // Keep new N
        popMon.setN(pop, abundance);


        log.debug("====================== end " + step + " - " + pop
                + " ===========================");

    }
	
	
	
	
	
}
