/*
 * #%L
 * IsisFish data
 * %%
 * Copyright (C) 2006 - 2015 Ifremer, CodeLutin
 * %%
 * 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 rules;

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

import scripts.SiMatrix;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.nuiton.math.matrix.*;

import fr.ifremer.isisfish.simulator.MetierMonitor;
import fr.ifremer.isisfish.simulator.SimulationContext;
import fr.ifremer.isisfish.types.TimeStep;
import fr.ifremer.isisfish.types.Month;
import fr.ifremer.isisfish.datastore.RegionStorage;
import fr.ifremer.isisfish.entities.*;
import fr.ifremer.isisfish.rule.AbstractRule;

import fr.ifremer.isisfish.annotations.Doc;
import resultinfos.MatrixNoActivity;

/**
 * CantonnementPreSimu.java
 *
 * Created: 30 novembre 2006
 *
 * @author anonymous &lt;anonymous@labs.libre-entreprise.org&gt;
 * @version $Revision: 1.2 $
 *
 * Last update: $Date: 2007-01-24 18:25:34 $
 * by : $Author: bpoussin $
 */
public class CantonnementPreSimu extends AbstractRule {

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

    @Doc(value="Zone de fermature")
    public Zone param_zone = null;
    @Doc(value="Engin concernés")
    public Gear param_gear = null;
    @Doc(value="Begin month")
    public Month param_beginMonth = Month.JANUARY;
    @Doc(value="End month")
    public Month param_endMonth = Month.DECEMBER;

    protected Map<Month, MatrixND> tableNonActivite = new HashMap<>();
    protected boolean affectNonActivite = false;
    
    protected String [] necessaryResult = {
        // put here all necessary result for this rule
        // example: 
        // MatrixBiomass.NAME,
        // MatrixNetValueOfLandingsPerStrategyMet.NAME
        MatrixNoActivity.NAME
    };

    @Override
    public String[] getNecessaryResult() {
        return this.necessaryResult;
    }

    /**
     * Permet d'afficher a l'utilisateur une aide sur la regle.
     * @return L'aide ou la description de la regle
     */
    @Override
    public String getDescription() throws Exception {
        return "Cantonnement: can be used to Cantonnement with gear if you put gear in parameter";
    }
 
    private void reportNonActivite(SimulationContext context,
            List<Strategy> listestrategies, List<Metier> metiers,
            Metier metier, List<Month> chomageMonth) {
        for(Strategy strategy : listestrategies) {
            SetOfVessels SetOfBateau = strategy.getSetOfVessels();
            EffortDescription effort = SetOfBateau.getPossibleMetiers(metier);
            if (effort != null){
                for (Month month : chomageMonth) {
                    StrategyMonthInfo InfoMois = strategy.getStrategyMonthInfo(month);
                    // this.p.tableNonActivite=Regle_action_lib.reportNonActivite(this.p.tableNonActivite,
                    // listestrategies.get(i), metier, mois, InfoMois.getProportionMetier(metier));                                                 
                     
                    log.info("debut reportNonActivite");
                    // on regarde si on a déjà une entrée pour le mois courant
                    MatrixND matNonActiviteMois=tableNonActivite.get(month);
                    if (matNonActiviteMois == null){
                        matNonActiviteMois = MatrixFactory.getInstance().create(
                                MatrixNoActivity.NAME,
                                new List[]{listestrategies, metiers},
                                new String[]{"Strategies", "Metiers"});
                        tableNonActivite.put(month, matNonActiviteMois);
                    }

                    matNonActiviteMois.setValue(strategy, metier, InfoMois.getProportionMetier(metier));
                    log.info("fin reportNonActivite");
                     
                    InfoMois.setProportionMetier(metier, 0);
                    MetierMonitor metierMon = context.getMetierMonitor();
                    metierMon.addforbiddenMetier(metier, month);
                }
            }
        }
    }
    
    /**
     * Appelé au démarrage de la simulation, cette méthode permet d'initialiser
     * des valeurs
     * @param context La simulation pour lequel on utilise cette regle
     */
    @Override
    public void init(SimulationContext context) throws Exception {
        SiMatrix siMatrix = SiMatrix.getSiMatrix(context);
        
        ZoneDAO zoneDao = context.getZoneDAO();
        MetierSeasonInfoDAO metierSeasonInfoDao = context.getMetierSeasonInfoDAO();

        List<Month> SaisonFermee = Month.getMonths(param_beginMonth, param_endMonth);
        List<Cell> maillefermee = param_zone.getCell();
        
        FisheryRegion region = RegionStorage.getFisheryRegion(context.getDB());
        List<Strategy> listestrategies= region.getStrategy();
        List<Metier> metiers = region.getMetier();


        for (Metier metier : metiers) {
            log.info("metier examiné " + metier.getName());
            if (param_gear != null && !metier.getGear().equals(param_gear)){
                log.info("l engin du métier n est pas concerné");
            }
            else {
                log.info("l engin du métier est bien concerné");
                // copy list to update metier season info in the loop
                List<MetierSeasonInfo> saisons = new ArrayList<>(metier.getMetierSeasonInfo());
                for (MetierSeasonInfo saisonmetier : saisons){
                    int inter = siMatrix.nbCellInter(saisonmetier.getZone(), param_zone);
                    if (inter != 0) {
                        List<Cell> MailleMetier = siMatrix.getCells(saisonmetier.getZone());
                        
                        // les mailles qui reste dans la zone metier
                        List<Cell> newCellMetier = new ArrayList<>(MailleMetier);
                        newCellMetier.removeAll(maillefermee);
                        
                        List<Month> saison = saisonmetier.getMonths();
                        if (saison.containsAll(SaisonFermee)){
                            log.info("il y a bien intersection entre la zone métier et le cantonnement pour la saison "+saison.toString());
                            // calcule de la zone restante:     
                            boolean ToutFerme;
                            boolean DebInclus=saison.contains(param_beginMonth);
                            boolean FinInclus=saison.contains(param_endMonth);
                            
                            //creation du nouveau secteur de metier
                            Zone secteurResult = zoneDao.create();
                            secteurResult.setName("MetaCantonnement-" + metier.getName() + "-" + saison.toString());
                            secteurResult.setComment("Secteur créé durant la simulation.");                            
                            // on ajoute les mailles restants de la zone metier dans la zone resultante
                            secteurResult.addAllCell(newCellMetier);

                            
                            // test pour savoir si toute la zone de pratique du metier
                            // (reunion de toutes les zones metiers  de metier a date)
                            // est totalement incluse dans la zone Cantonnement                            
                            if (newCellMetier.size() != 0){ //le cas intersection.length==mailleMetier est dans condition
                              // si toute la zone de pratique du metier n'est pas incluse dans zone Cantonnement            
                                ToutFerme=false;
                            } else {
                                log.info("Toute la zone metier est ferme");
                                ToutFerme=true;
                            }

//        1er cas: la saison métier est identique (ou totalement incluse dans la saison fermée, dans ce cas là, on change juste
//        le secteur
                            if (SaisonFermee.containsAll(saison)){
                                log.info("toute la saison metier est incluse dans la saison de fermeture");
                                //si tout est fermé, on met le metier au chômage
                                
                                if (ToutFerme) {
                                    log.info ("toute la zone est fermée, le metier passe donc à chômage pour cette saison");
                                    reportNonActivite(context, listestrategies, metiers, metier, saison);

                                } else {                                    
                                    log.info("tout n'est pas fermée, le secteur de pêche devient " + secteurResult);
                                    for (Month month : SaisonFermee){
                                        metier.getMetierSeasonInfo(month).clearZone();
                                        metier.getMetierSeasonInfo(month).addZone(secteurResult);
                                    }
                                }
                            }

//        2ème cas de figure: moisDeb est inclus mais pas moisFin (ou alors c'est le dernier mois)
                            else if (DebInclus && ((!FinInclus) || param_endMonth.equals(saison.get(saison.size()-1)))) {
                                log.info ("il y a intersection de la saison de fermeture avec la saison métier de " + param_beginMonth + " à " + saison.get(saison.size()-1));
                                //si tout est fermé on met les proportions à 0 pour les mois fermes
                                if (ToutFerme){
                                    log.info ("toute la zone métier est fermée");
                                    List<Month> chomageMonth = new ArrayList<>(SaisonFermee);
                                    chomageMonth.retainAll(saison);
                                    log.info ("de "+chomageMonth.get(0)+" a "+ chomageMonth.get(chomageMonth.size()-1)+ ", le metier est donc mis au chomage");
                                    
                                    reportNonActivite(context, listestrategies, metiers, metier, chomageMonth);
                                } else {
                                    //on commence par crée les nouvelles saisons
                                    saisonmetier.setLastMonth(param_beginMonth.previous());
                                    //on crée un nouveau  infoSaisonMetier
                                    MetierSeasonInfo NouvelInfoSaison= metierSeasonInfoDao.create();
                                    NouvelInfoSaison.setMetier(metier);
                                    NouvelInfoSaison.setFirstMonth(param_beginMonth);
                                    NouvelInfoSaison.setLastMonth(saison.get(saison.size()-1));
                                    NouvelInfoSaison.addZone(secteurResult);
                                    NouvelInfoSaison.setComment("saison crée pendant la simulation") ;
                                    
                                    metier.addMetierSeasonInfo(NouvelInfoSaison);
                                    log.info ("de "+saison.get(0)+" à "+param_beginMonth.previous()+" le metier peche en "+saisonmetier.getZone());
                                    log.info ("de "+param_beginMonth+" à "+saison.get(saison.size()-1)+" le metier peche en "+secteurResult);
                                }
                            }
//        3ème cas de figure: moisFin est inclus mais pas moisDeb (ou alors il est égal au premier mois)
                            else if (((!DebInclus) || param_beginMonth.equals(saison.get(0))) && FinInclus) {
                                log.info ("il y a intersection de la saison de fermeture avec la saison métier de "+param_beginMonth+" à "+saison.get(saison.size()-1));
                                //si tout est fermé on met les proportions à 0 pour les mois fermes
                                if (ToutFerme){
                                    log.info ("toute la zone est fermee");
                                    List<Month> chomageMonth = new ArrayList<>(SaisonFermee);
                                    chomageMonth.retainAll(saison);
                                    reportNonActivite(context, listestrategies, metiers, metier, chomageMonth);
                                }
                                else {
                                    //on commence par crée les nouvelles saisons
                                    saisonmetier.setFirstMonth(param_endMonth.next());
                                    //on crée un nouveau  infoSaisonMetier
                                    MetierSeasonInfo NouvelInfoSaison= metierSeasonInfoDao.create();
                                    NouvelInfoSaison.setMetier(metier);
                                    NouvelInfoSaison.setFirstMonth(saison.get(0));
                                    NouvelInfoSaison.setLastMonth(param_endMonth);
                                    NouvelInfoSaison.addZone(secteurResult);
                                    NouvelInfoSaison.setComment("saison crée pendant la simulation") ;
                                    
                                    metier.addMetierSeasonInfo(NouvelInfoSaison);                                    
                                    log.info ("de "+saison.get(0)+" à "+param_endMonth+" le metier peche en "+secteurResult);
                                    log.info ("de "+param_endMonth.next()+" à "+saison.get(saison.size()-1)+" le metier peche en "+saisonmetier.getZone());
                                }
                            }

//        dernier cas de figure: les deux sont inclus et sont différents des bornes
                            else {
                                //si tout est fermé on met les proportions à 0 pour les mois fermes
                                if (ToutFerme){
                                    log.info("toute la zone métier est fermée");
                                    reportNonActivite(context, listestrategies, metiers, metier, SaisonFermee);
                                }
                                else {
                                    //on commence par crée les nouvelles saisons
//                                    
                                    saisonmetier.setFirstMonth(param_beginMonth.previous());
                                    //on crée un nouveau  infoSaisonMetier
                                    MetierSeasonInfo NouvelInfoSaison = metierSeasonInfoDao.create();
                                    NouvelInfoSaison.setMetier(metier);
                                    NouvelInfoSaison.setFirstMonth(param_beginMonth);
                                    NouvelInfoSaison.setLastMonth(param_endMonth);
                                    NouvelInfoSaison.addZone(secteurResult);
                                    NouvelInfoSaison.setComment("saison crée pendant la simulation") ;                                    
                                    metier.addMetierSeasonInfo(NouvelInfoSaison);

                                    //on crée un nouveau  infoSaisonMetier
                                    MetierSeasonInfo NouvelInfoSaison2 = metierSeasonInfoDao.create();
                                    NouvelInfoSaison2.setMetier(metier);
                                    NouvelInfoSaison2.setFirstMonth(param_endMonth.next());
                                    NouvelInfoSaison2.setLastMonth(saison.get(saison.size()-1));
                                    NouvelInfoSaison2.setZone(saisonmetier.getZone());
                                    NouvelInfoSaison2.setComment("saison crée pendant la simulation") ;                                    
                                    metier.addMetierSeasonInfo(NouvelInfoSaison2);
                                    
                                    log.info ("de "+saison.get(0)+" à "+param_beginMonth.previous()+" le metier peche en "+saisonmetier.getZone());
                                    log.info ("de "+param_beginMonth+" à "+param_endMonth+" le metier peche en "+secteurResult);
                                    log.info ("de "+param_endMonth.next()+" à "+saison.get(saison.size()-1)+" le metier peche en "+saisonmetier.getZone());
                                }
                            }
                        }
                    }
                }
            }
        }

     
        
    }

    /**
     * La condition qui doit etre vrai pour faire les actions.
     * 
     * @param context la simulation pour lequel on utilise cette regle
     * @param step le pas de temps courant
     * @param metier le metier concerné
     * @return vrai si on souhaite que les actions soit faites
     */
    @Override
    public boolean condition(SimulationContext context, TimeStep step, Metier metier) throws Exception {
        boolean result = false;
        MatrixND mat = tableNonActivite.get(step.getMonth());
        if (mat != null && !affectNonActivite) {
            result = true;
        }
        return result;
        // fin
    }
 
    /**
     * Si la condition est vrai alors cette action est executee avant le pas
     * de temps de la simulation.
     * 
     * @param context la simulation pour lequel on utilise cette regle
     * @param step le pas de temps courant
     * @param metier le metier concerné
     */
    @Override
    public void preAction(SimulationContext context, TimeStep step, Metier metier) throws Exception {
        // on ne doit le faire qu'une seul fois quelque soit le nombre de metier
        affectNonActivite=true;
        MatrixND mat = tableNonActivite.get(step.getMonth());
        
        MetierMonitor metierMon = context.getMetierMonitor();
        MatrixND noActivity = metierMon.getNoActivity(step);

        if (noActivity == null){
            metierMon.setNoActivity(step, mat.copy());
        }
        else{
            noActivity.add(mat);
        }
    }
 
    /**
     * Si la condition est vrai alors cette action est executée apres le pas
     * de temps de la simulation.
     * 
     * @param context La simulation pour lequel on utilise cette regle
     * @param step le pas de temps courant
     * @param metier le metier concerné
     */
    @Override
    public void postAction(SimulationContext context, TimeStep step, Metier metier) throws Exception {
        affectNonActivite = false;
    }

}
