/*
 * Copyright (C) 2011 chatellier
 *
 * 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package rules;

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

import scripts.ResultName;

import java.io.*;
import java.util.*;
import java.util.regex.Pattern;

import fr.ifremer.isisfish.*;
import fr.ifremer.isisfish.util.Doc;
import fr.ifremer.isisfish.simulator.SimulationContext;
import fr.ifremer.isisfish.types.Date;
import fr.ifremer.isisfish.entities.*;
import fr.ifremer.isisfish.rule.AbstractRule;
import fr.ifremer.isisfish.datastore.SimulationStorage;
import fr.ifremer.isisfish.datastore.ResultStorage;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.collections.primitives.ArrayIntList;
import org.apache.commons.collections.primitives.IntList;
import org.nuiton.math.matrix.*;
import org.nuiton.topia.*;
import org.nuiton.topia.persistence.*;

/**
 * TestImportMatrixNDRule.java
 *
 * Created: 30 mai 2011
 *
 * @author chatellier <user.name@vcs.hostName>
 * @version $Revision: 1545 $
 * Last update: $Date: 30 mai 2011 $
 * by : $Author: chatellier $
 */
public class TestImportMatrixNDRule extends AbstractRule {

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

    public String [] necessaryResult = {
        // put here all necessary result for this rule
	    // example: 
	    // ResultName.MATRIX_BIOMASS,
	    // ResultName.MATRIX_NET_VALUE_OF_LANDINGS_PER_STRATEGY_MET,
    };

    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
     */
    public String getDescription() throws Exception {
        // TODO
        return _("TODO description rule");
    }

    /**
     * Appel�� au d��marrage de la simulation, cette m��thode permet d'initialiser
     * des valeurs
     * @param simulation La simulation pour lequel on utilise cette regle
     */
    public void init(SimulationContext context) throws Exception {

        MatrixND matrix3D = MatrixFactory.getInstance().create(new int[]{3, 1, 3});

        // In simulation context : 
        String matrixcontent = "[3, 1, 3], 0.0\n" +
            "String: 2009, 2010, 2011\n" +
            "Population: nephrops\n" +
            "Zone: zone L21E8, zone L21E7, zone L22E7\n" +
            "0;0;0;-4e7\n" +
            "1;0;1;42\n" +
            "2;0;2;3";

    	   try {
            importCSVND(matrix3D, new StringReader(matrixcontent));
    	   } catch (IOException ex) {
    	   	  log.error("Can't import matrix", ex);
    	   }
        log.info("matrix 3D in Rule " + matrix3D);
    }

    /**
     * La condition qui doit etre vrai pour faire les actions
     * @param simulation La simulation pour lequel on utilise cette regle
     * @return vrai si on souhaite que les actions soit faites
     */
    public boolean condition(SimulationContext context, Date date, Metier metier) throws Exception {

        return false;
    }

    /**
     * Si la condition est vrai alors cette action est execut��e avant le pas
     * de temps de la simulation.
     * @param simulation La simulation pour lequel on utilise cette regle
     */
    public void preAction(SimulationContext context, Date date, Metier metier) throws Exception {

    }

    /**
     * Si la condition est vrai alors cette action est execut��e apres le pas
     * de temps de la simulation.
     * @param simulation La simulation pour lequel on utilise cette regle
     */
    public void postAction(SimulationContext context, Date date, Metier metier) throws Exception {

    }






 
    protected static final Pattern NUMBER = Pattern.compile(" *[+-]?[0-9]*\\.?[0-9]+([eE][+-]?[0-9]+)? *");

    /**
     * Import CSV file defined in Matrix ND format.
     * 
     * @param reader reader containing content
     * @param origin not used
     * @throws IOException 
     */
    protected void importCSVND(MatrixND currentMatrix, Reader reader) throws IOException {

        int c = -1;
        StringBuffer number = new StringBuffer(20);
        IntList coordinates = new ArrayIntList();

        // read dimension
        reader.read(); // skip [
        while ((c = reader.read()) != -1) {
            if (c == ' ') {
                // skip space
            } else if (c == ',' || c == ']') {
                if (NUMBER.matcher(number.toString()).matches()) {
                    int coord = Integer.parseInt(number.toString());
                    coordinates.add(coord);
                }
                number.setLength(0);
                
                if (c == ']') {
                    break;
                }
            } else {
                number.append((char) c);
            }
        }
        int[] dimensions = coordinates.toArray();
        coordinates.clear();
        // / read dimension

        // read defaut value
        while ((c = reader.read()) != -1) {
            if (c == ',' || c == ' ') {
                // skip
            }
            else if (c == '\n' || c == '\r') {
                break;
            }
            else {
                number.append((char) c);
            }
        }
        double defaultValue = 0.0;
        if (NUMBER.matcher(number.toString()).matches()) {
            defaultValue = Double.parseDouble(number.toString());
            MatrixHelper.fill(currentMatrix, defaultValue);
        }
        number.setLength(0);
        // / read default value

        List[] semantics = new List[dimensions.length];
        for (int indexDim = 0 ; indexDim < dimensions.length ; indexDim++) {
            List dimension = importCSVNDReadDimension(reader);
            if (dimension != null && dimension.size() != dimensions[indexDim]) {
                throw new MatrixException(String.format("Semantics %d count not equals to semantics dimension, excepted %d, got %d",
                        indexDim, dimensions[indexDim], dimension.size()));
            }
            semantics[indexDim] = dimension;
        }

        if (ArrayUtils.contains(semantics, null)) {
            throw new MatrixException("Wrong semantics definition : " + Arrays.toString(semantics));
        }
        MatrixND matrix = MatrixFactory.getInstance().create(semantics);
        MatrixHelper.fill(matrix, defaultValue);
        do {
            c = reader.read();
            if (c == ' ') {
                // skip space
            } else if (c == ';') {
                if (NUMBER.matcher(number.toString()).matches()) {
                    int coord = Integer.parseInt(number.toString());
                    coordinates.add(coord);
                }
                number.setLength(0);
            } else if (c == -1 || c == '\n' || c == '\r' || c == -1) {
                // is line return or equivalent char because space is already
                // skiped
                // or end of stream

                // at end of line, we must see if the leave number*
                Double val = null;
                if (NUMBER.matcher(number.toString()).matches()) {
                    val = Double.valueOf(number.toString());
                }
                number.setLength(0);

                if (!coordinates.isEmpty()) {
                    int[] coords = coordinates.toArray();
                    matrix.setValue(coords, val);
                    coordinates.clear();
                    
                }
            } else {
                number.append((char) c);
            }
        } while (c != -1);

        // finally paste loaded matrix into this
        currentMatrix.paste(matrix);
    }

    /**
     * Read a line and convert line to semantic value.
     * 
     * Use:
     *  - mapper to convert semantics values
     *  - return null if line is empty
     *  
     * @param reader reader to read
     * @return semantics for readed line
     * @throws IOException 
     */
    protected List importCSVNDReadDimension(Reader reader) throws IOException {
        
        StringBuffer buffer = new StringBuffer();
        int c = -1;
        while ((c = reader.read()) != -1) {
            if (c == '\n') {
                break;
            }
            else {
                buffer.append((char)c);
            }
        }

        // read must be in form:
        // type : PK1, PK2, PK3...
        List sems = null;
        if (buffer.length() > 0) {
            sems = new ArrayList();
            // get type
            int twodIndex = buffer.indexOf(":");
            if (twodIndex == -1) {
                throw new MatrixException("Can't parse semantics line as 'Type: ids'");
            }
            String type = buffer.substring(0, twodIndex).trim();
            Class typeClass = new IsisSemanticMapper().getType(type);
            
            // get semantics value
            String semanticsStrings = buffer.substring(twodIndex +1).trim();
            String[] semanticsPKs = semanticsStrings.split("\\s*,\\s*");
            for (String semanticsPK : semanticsPKs) {
                Object value = new IsisSemanticMapper().getValue(typeClass, semanticsPK);
                sems.add(value);
            }
        }
        return sems;
    }

    class IsisSemanticMapper {
	    /**
	     * Return class for type identified by typeName.
	     * 
	     * For example : "Population" can return "fr.ifremer.entities.Population.class"
	     * 
	     * Return {@code String} by default.
	     * 
	     * @param typeName type to get class.
	     * @return type for typeId
	     */
	    public Class getType(String typeName) {

	        // In simulation context :
	        Class clazz = null;
	        try {
	        	  clazz = Class.forName("fr.ifremer.isisfish.entities." + typeName);
	        } catch (Exception ex) {
	        	  log.warn("Can't find class for " + typeName, ex);
	        	  clazz = String.class;
	        }
	        return clazz;
	    }

	    /**
	     * Return value identified by valueId and type {@code type}.
	     * 
	     * Return {@code valueId} by default;
	     * 
	     * @param type
	     * @param valueId
	     * @return value identified by {valueId}
	     */
	    public Object getValue(Class type, String valueId) {

             // In simulation context :
             Object value = null;
             try {
                 TopiaContext context = SimulationContext.get().getDB();
                 TopiaDAO dao = IsisFishDAOHelper.getDAO(context, type);
                 value = dao.findByProperty("name", valueId);
             } catch (Exception ex) {
             	  log.warn("Can't get value for " + valueId, ex);
             	  value = valueId;
             }
	        return value;

	        //return valueId;
	    }
    }



















    /**
     * Main a lancer via "Evaluer".
     */
    public static void main(String... args) {
    	   new TestImportMatrixNDRule().test();
    }

    public void test() {
    	   MatrixND matrix3D = MatrixFactory.getInstance().create(new int[]{3, 3, 3});

        String matrixcontent = "[3, 3, 3], 0.0\n" +
            "String: 2009, 2010, 2011\n" +
            "String: Anchois, Sardine, Langoustine\n" +
            "String: Zone1, Zone2, Zone3\n" +
            "0;0;0;-4e7\n" +
            "1;2;1;42\n" +
            "2;2;2;3";

    	   try {
            importCSVND(matrix3D, new StringReader(matrixcontent));
    	   } catch (IOException ex) {
    	   	  log.error("Can't import matrix", ex);
    	   }
        System.out.println("matrix 3D" +matrix3D);

        /*
        In simulation context : 
        String matrixcontent = "[3, 1, 3], 0.0\n" +
            "String: 2009, 2010, 2011\n" +
            "Population: nephrops\n" +
            "Zone: zone L21E8, zone L21E7, zone L22E7\n" +
            "0;0;0;-4e7\n" +
            "1;0;1;42\n" +
            "2;0;2;3";

    	   try {
            importCSVND(matrix3D, new StringReader(matrixcontent));
    	   } catch (IOException ex) {
    	   	  log.error("Can't import matrix", ex);
    	   }
        System.out.println("matrix 3D" +matrix3D); */
    }

}