package Interface;

import java.awt.Component;
import java.util.Vector;
import java.io.RandomAccessFile;
import java.io.File;
import java.util.TreeMap;
import java.awt.Toolkit;
import java.awt.Image;
import java.awt.MediaTracker;
import java.util.HashMap;

import sema.Element;
import sema.Engine;
import sema.Map;
import sema.Drawing;
import sema.Nature;
import sema.Box;
import sema.World;


/**
 * SIM files reader and writer.
 * The SIM is a special format used to store a simulation.
 */
public class SimFileRW extends Component {
    
    
    private final static Class[] emptyTypes = {};
    private final static Object[] emptyArgs = {};
    private final Toolkit toolkit = getToolkit();
    private MediaTracker m = new MediaTracker(this);
    private TreeMap selftypes;
    private TreeMap elements;
    private Vector elementsLine;
    private TreeMap element2line;
    private int elementI;
    private String beginOfFile = "";
    private HashMap changedBoxes;
    private File currentfile = null;
    private String currentpath = "";
    private int imageIndice = 0;
    private String matrixInputMode = "off";
    private HashMap hasChangedElement = new HashMap();
    
    
    /**
     * Open a SIM file.
     */
    public World open(File f) throws InvalidSIMfile {
        return open(f,0);
    }
    
    /**
     * Edit a SIM file.
     */
    public World edit(File f) throws InvalidSIMfile {
        return open(f,1);
    }
    
    /**
     * Open a simulation file, with the specified box factor.
     * The box factor allows to modify the number of boxes
     * in a simulation, from an existing map.
     * The higher it is, the more the simulation wil be accurate.
     * Usually in the range of [1-10].
     */
    public World open(File f, int boxFactor) throws InvalidSIMfile { //crée un monde à partir d'un fichier .SIM (renvoie une exception si fichier invalide)
        
        currentfile = f;
        currentpath = f.getParent();
        selftypes = new TreeMap();
        elements = new TreeMap();
        elementsLine = new Vector();
        element2line = new TreeMap();
        elementI = 1;
        beginOfFile = "";
        changedBoxes = new HashMap();
        imageIndice = 0;
        m = new MediaTracker(this);
        matrixInputMode = "off";
        hasChangedElement = new HashMap();
        
        
        class Open { //classe interne gérant la lecture ligne à ligne du fichier, en sautant les lignes vides et en maintenant à jour un numéro de ligne
            public Open(File f) throws Exception {
                ch = new RandomAccessFile(f,"r"); //ouverture du fichier en lecture
                int lineNumber = 0;
                String line = "";
            }
            private RandomAccessFile ch;
            public int lineNumber;  //numéro de la ligne courante
            //lit la ligne suivante du fichier en sautant les lignes vides et les commentaires
            public void close(){
                try {
                    ch.close();} catch (Exception e) {}
            }
            private String inputline() throws Exception {
                String line = ch.readLine();
                lineNumber++;
                while (line.trim().equals("") || line.charAt(0)=='/') {
                    line = ch.readLine();
                    lineNumber++;
                }
                return(line);
            }
        }
        
        //attribution de valeurs par défauts aux paramètres de la simulation
        String name = "A simulation";
        String comments = "No comments";
        String engineClass = "sema.Engine";
        String mapClass = "sema.Map";
        String natureClass = "sema.Nature";
        String drawingClass = "sema.Drawing";
        String modelisationClass = "sema.World";
        String defaultMboxClass = "sema.Box(float 0,String \"DefaultBox\", Image \"\")";
        Float minZoom = new Float(0);
        Float maxZoom = new Float(10);
        Float minTfactor = new Float(0);
        Float maxTfactor = new Float(100);
        Float tFactor = new Float(1);
        Float zoomFactor = new Float(1);
        Float centerX = null;
        Float centerY = null;
        Integer virtualDisplayDelay = new Integer(20000);
        Integer realDisplayDelay = new Integer(200);
        float pixelsByMbox = 80;
        float mBoxLength = 1;
        int xMboxes = 10;
        int yMboxes = 10;
        matrixInputMode = "off";
        Engine engine = null;
        Map map = null;
        Nature nature = null;
        Drawing drawing = null;
        World world = null;
        String oldBeginOfFile = "";
        Open sim = null;
        String line = null;
        
        
        try { //ouverture du fichier
            sim = new Open(currentfile);
            line = sim.inputline();
        } catch (Exception e) {throw new InvalidSIMfile("Unable to open file");}
        
        
        try {
            
            // 1)LECTURE DES BALISES "BALISE=value" redéfinissant les valeurs par défaut de la simulation, ou définissant des variables
            while (line!=null) {
                oldBeginOfFile = beginOfFile;
                beginOfFile = beginOfFile+line+"\n"; //on mémorise le début du fichier.
                String[] balises = readBalises(line);
                String s1 = balises[0], s2 = balises[1]; //"BALISE=value" donne s1="BALISE" et s2="value". On ne respecte pas la casse pour 'balise'
                if (s1.equals("NAME")) {
                    name = s2;
                } else
                    if (s1.equals("COMMENTS")) {
                    comments = s2;
                    } else
                        if (s1.equals("ENGINE")) {
                    engineClass = s2;
                        } else
                            if (s1.equals("MAP")) {
                    mapClass = s2;
                            } else
                                if (s1.equals("NATURE")) {
                    natureClass = s2;
                                } else
                                    if (s1.equals("MODELISATION")) {
                    modelisationClass = s2;
                                    } else
                                        if (s1.equals("MBOXLENGTH")) {
                    mBoxLength = Float.parseFloat(s2);
                    if (mBoxLength<=0) throw new Exception("negative value for MBOXLENGTH");
                                        } else
                                            if (s1.equals("BOXFACTOR")) {
                    if (boxFactor==0) { //on ne prend en compte le boxFactor du .SIM que si celui passé en argument est nul...
                        boxFactor = Integer.parseInt(s2);
                        if (boxFactor<=0) throw new Exception("negative value for BOXFACTOR");
                    }
                                            } else
                                                if (s1.equals("DRAWING")) {
                    drawingClass = s2;
                                                } else
                                                    if (s1.equals("REALDISPLAYDELAY")) {
                    realDisplayDelay = new Integer(Integer.parseInt(s2));
                    if (realDisplayDelay.floatValue()<0) throw new Exception("negative value for REALDISPLAYDELAY");
                                                    } else
                                                        if (s1.equals("VIRTUALDISPLAYDELAY")) {
                    virtualDisplayDelay = new Integer(Integer.parseInt(s2));
                    if (virtualDisplayDelay.floatValue()<=0) throw new Exception("negative value for VIRTUALDISPLAYDELAY");
                                                        } else
                                                            if (s1.equals("TIMEFACTOR")) {
                    tFactor = new Float(Float.parseFloat(s2));
                    if (tFactor.floatValue()<0) throw new Exception("negative value for TIMEFACTOR");
                                                            } else
                                                                if (s1.equals("ZOOM")) {
                    zoomFactor = new Float(Float.parseFloat(s2));
                    if (zoomFactor.floatValue()<0) throw new Exception("negative value for ZOOM");
                                                                } else
                                                                    if (s1.equals("CENTERX")) {
                    centerX = new Float(Float.parseFloat(s2));
                    if (centerX.floatValue()<0) throw new Exception("negative value for CENTERX");
                                                                    } else
                                                                        if (s1.equals("CENTERY")) {
                    centerY = new Float(Float.parseFloat(s2));
                    if (centerY.floatValue()<0) throw new Exception("negative value for CENTERY");
                                                                        } else
                                                                            if (s1.equals("MINZOOM")) {
                    minZoom = new Float(Float.parseFloat(s2));
                    if (minZoom.floatValue()<0) throw new Exception("negative value for MINZOOM");
                                                                            } else
                                                                                if (s1.equals("MAXZOOM")) {
                    maxZoom = new Float(Float.parseFloat(s2));
                    if (maxZoom.floatValue()<=0) throw new Exception("negative value for MAXZOOM");
                                                                                } else
                                                                                    if (s1.equals("MINTFACTOR")) {
                    minTfactor = new Float(Float.parseFloat(s2));
                    if (minTfactor.floatValue()<0) throw new Exception("negative value for MINTFACTOR");
                                                                                    } else
                                                                                        if (s1.equals("MAXTFACTOR")) {
                    maxTfactor = new Float(Float.parseFloat(s2));
                    if (maxTfactor.floatValue()<0) throw new Exception("negative value for MAXTFACTOR");
                                                                                        } else
                                                                                            if (s1.equals("PIXELSBYMBOX")) {
                    pixelsByMbox = Float.parseFloat(s2);
                    if (pixelsByMbox<=0) throw new Exception("negative value for PIXELSBYMBOX");
                                                                                            } else
                                                                                                if (s1.equals("XMBOXES")) {
                    xMboxes = Integer.parseInt(s2);
                    if (xMboxes<=0) throw new Exception("negative value for XMBOXES");
                                                                                                } else
                                                                                                    if (s1.equals("YMBOXES")) {
                    yMboxes = Integer.parseInt(s2);
                    if (yMboxes<=0) throw new Exception("negative value for YMBOXES");
                                                                                                    } else
                                                                                                        if (s1.equals("MATRIXINPUTMODE")) {
                    if (s2.toLowerCase().equals("on"))
                        matrixInputMode = "on";
                    else
                        if (s2.toLowerCase().equals("off"))
                            matrixInputMode = "off";
                        else throw new Exception("invalid value for MATRIXINPUTMODE (should be on or off)");
                    
                                                                                                        } else
                                                                                                            if (s1.equals("DEFAULTBOX")) {
                    defaultMboxClass = s2;
                                                                                                            } else
                                                                                                                if (s1.equals("BOXES") || s1.equals("ELEMENTS")) {
                    //2) LES BALISES BOXES ou ELEMENTS LANCENT LA CREATION DU MONDE
                    beginOfFile = oldBeginOfFile; //on ne mémorise pas le marqueur qui vient d'être lu.
                    //ON INSTANCIE LES DIFFERENTES CLASSES DE LA SIMULATION
                    try {
                        //instanciation du moteur
                        engine = (Engine)readClass(emptyTypes,emptyArgs,engineClass);
                        selftypes.put("ENGINE",engine);
                    } catch (Exception e) {throw new Exception("impossible to load engine class "+e);}
                    
                    int xBoxes = boxFactor*xMboxes;
                    int yBoxes = boxFactor*yMboxes;
                    float boxLength = mBoxLength/(float)boxFactor;
                    float pixelsByUnit = pixelsByMbox/mBoxLength;
                    Box[][] boxes = new Box[xBoxes][yBoxes];
                    if (centerX==null) centerX = new Float(xBoxes*boxLength/2);
                    if (centerY==null) centerY = new Float(yBoxes*boxLength/2);
                    Object[] drawingArgs = {new Float((pixelsByMbox/(float)boxFactor)/boxLength),new Float(boxLength),minZoom,maxZoom,minTfactor,maxTfactor,tFactor, zoomFactor, virtualDisplayDelay, realDisplayDelay, centerX, centerY, new Integer(boxFactor)};
                    Class[] drawingTypes = {Float.TYPE,Float.TYPE,Float.TYPE,Float.TYPE,Float.TYPE,Float.TYPE, Float.TYPE, Float.TYPE,Integer.TYPE, Integer.TYPE, Float.TYPE, Float.TYPE, Integer.TYPE};
                    try {
                        drawing = (Drawing)readClass(drawingTypes,drawingArgs,drawingClass);
                        selftypes.put("DRAWING",drawing);
                    } catch (Exception e) {throw new Exception("impossible to load drawing class "+e);}
                    
                    Object[] mapArgs = {new Integer(xBoxes),new Integer(yBoxes),new Float(boxLength), boxes};
                    Class[] mapTypes = {Integer.TYPE,Integer.TYPE,Float.TYPE,boxes.getClass()};
                    try {
                        //création de la carte
                        map = (Map)readClass(mapTypes,mapArgs,mapClass);
                        selftypes.put("MAP",map);
                    } catch (Exception e) {throw new Exception("impossible to load map class "+e);}
                    
                    //3) ON LIT LES BOXES
                    if (matrixInputMode.equals("off")) { //mode par défaut pour représenter la carte dans le fichier .SIM
                        changedBoxes.put("default",defaultMboxClass);
                        for (int i=0; i<xBoxes; i++) { //on initialise toutes les cases à la case par défaut
                            for (int j=0; j<yBoxes; j++) {
                                Object[] args0 = {map,drawing,new Integer(i), new Integer(j)};
                                Class[] types0 = {Map.class,Drawing.class, Integer.TYPE, Integer.TYPE};
                                try {
                                    Box b = (Box)readClass(types0, args0, defaultMboxClass); //on crée la matrice de cases par défaut
                                    boxes[i][j] = b;
                                } catch (Exception e) {throw new Exception("impossible to load defaultbox class");}
                            }
                            
                        }
                        if (s1.equals("BOXES")) { //si on a lu la balise BOXES, on lit les cases qui suivent en attendant la balise ELEMENTS
                            try {line = sim.inputline();} catch (Exception e) {line = null;};
                            while (line!=null) { //tant qu'on ne rencontre pas la balise ELEMENTS, on est en train de lire des cases, que l'on rajoute
                                if (!line.equals("ELEMENTS:")) {
                                    if (!line.trim().equals("")) {
                                        if (line.charAt(0)=='§') {//directive de setProperties pour boxes[i][j]
                                            try {
                                                Vector ij = formatString(line.substring(1));
                                                int i = Integer.parseInt((String)ij.get(0));
                                                int j = Integer.parseInt((String)ij.get(1));
                                                String prop = sim.inputline().substring(1);
                                                String val = sim.inputline().substring(1);
                                                boxes[i][j].setProperties(prop,val);
                                            } catch (Exception e) {throw new Exception("Invalid $order. "+e.toString());}
                                        } else {
                                            String[] v = readBalises(line);
                                            String c1 = v[0], c2=v[1];
                                            Vector v2 = formatString(c1); //i,j=boxclass()
                                            int i0 = Integer.parseInt((String)v2.get(0)), j0 = Integer.parseInt((String)v2.get(1));
                                            for (int i = i0*boxFactor; i<(i0+1)*boxFactor; i++) { //ici on fait la conversion entre mBox et Box
                                                for (int j = j0*boxFactor; j<(j0+1)*boxFactor; j++) {
                                                    Object[] args0 = {map,drawing,new Integer(i), new Integer(j)};
                                                    Class[] types0 = {Map.class,Drawing.class, Integer.TYPE, Integer.TYPE};
                                                    Box b = (Box)readClass(types0,args0,c2);
                                                    changedBoxes.put(b,c2);}
                                            }
                                        }
                                    }
                                    
                                } else {break;}
                                try {line = sim.inputline();} catch (Exception e) {line = null;};
                            }
                        }
                    } else { //maxtrixInputMode = "on"
                        //ENTREE MATRICIELLE DES CASES, plus visuelle
                        if (s1.equals("ELEMENTS")) {throw new Exception("BOXES:, followed by boxes shorcuts and matrix, is necessary before ELEMENTS: when MATRIXINPUTMODE is on");}
                        try {
                            line = sim.inputline();
                            while (line!=null) { //on lit les raccourcis #B=box()
                                if (!line.trim().equals("")) {
                                    if (line.charAt(0)=='#') {
                                        String[] v = readBalises(line.substring(1));
                                        String c1 = v[0], c2=v[1];
                                        selftypes.put(c1,c2);
                                    } else {break;}
                                }
                                line = sim.inputline();
                            }
                            
                            while (line!=null & (line.trim().equals(""))) { //on saute les lignes jusqu'à tomber sur la matrice de raccourcis
                                try {line = sim.inputline();} catch (Exception e) {line = null;};
                            }
                            //ICI ON LIT UNE MATRICE DE RACCOURCIS
                            for (int j0=0; j0<yMboxes; j0++) {
                                Vector tempBoxes = formatString(line);
                                for (int i0=0; i0<xMboxes; i0++) {
                                    for (int i = i0*boxFactor; i<(i0+1)*boxFactor; i++) {
                                        for (int j = j0*boxFactor; j<(j0+1)*boxFactor; j++) {
                                            Object[] args0 = {map,drawing,new Integer(i), new Integer(j)};
                                            Class[] types0 = {Map.class,Drawing.class, Integer.TYPE, Integer.TYPE};
                                            String boxCode = (String)selftypes.get(((String)tempBoxes.get(i0)).toUpperCase());
                                            Box b = (Box)readClass(types0,args0, boxCode);
                                            changedBoxes.put(b, boxCode);
                                        }
                                    }
                                }
                                try {line = sim.inputline();} catch (Exception e) {line = null;};
                            }
                            while (line!=null & !(line.equals("ELEMENTS:"))) {
                                try {line = sim.inputline();} catch (Exception e) {line = null;};
                            }
                        } catch (Exception e) {throw new Exception("invalid matrix input for boxes.\n"+e);}
                    };
                    //ON CREE LES DERNIERES CLASSES DE LA SIMULATION
                    Object[] natureArgs = {engine,map};
                    Class[] natureTypes = {Engine.class,Map.class};
                    try {
                        nature = (Nature)readClass(natureTypes,natureArgs,natureClass);
                        selftypes.put("NATURE",nature);
                    } catch (Exception e) {throw new Exception("impossible to load nature class "+e);}
                    Object[] worldArgs = {engine,map,nature,drawing,name,comments};
                    Class[] worldTypes = {Engine.class,Map.class,Nature.class,Drawing.class, String.class, String.class};
                    try {
                        world = (World)readClass(worldTypes,worldArgs,modelisationClass);
                        selftypes.put("WORLD",world);
                    } catch (Exception e) {throw new Exception("impossible to load world class " +e);}
                    
                    //4) ET POUR FINIR, ON LIT LES ELEMENTS DU MONDE
                    Object[] args0 = {world};
                    Class[] types0 = {World.class};
                    try {line = sim.inputline();} catch (Exception e) {line = null;};
                    while (line!=null) {
                        if (!line.trim().equals("")) {
                            if (line.equals("$WORLD$")) {//directive de setProperties pour World
                                try {
                                    String prop = sim.inputline().substring(1);
                                    String val = sim.inputline().substring(1);
                                    world.setProperties(prop,val);
                                } catch (Exception e) {throw new Exception("Invalid $order. "+e.toString());}
                            } else
                                if (line.charAt(0)== '$') {//directive de setProperties "$nomElement\npropertName\nValue".
                                try {
                                    Element el = (Element)elements.get(line.trim().substring(1));
                                    hasChangedElement.put(el,"");
                                    String prop = sim.inputline().substring(1);
                                    String val = sim.inputline().substring(1);
                                    el.setProperties(prop,val);
                                } catch (Exception e) {throw new Exception("Invalid $order. "+e.toString());}
                                } else
                                    if (line.charAt(0)== '@') {
                                elementI = 1+Integer.parseInt(line.substring(1)); //prochain indice disponible
                                    } else
                                        if (line.charAt(0)!= '#') {
                                Element e = (Element)readClass(types0, args0,line);
                                String nameEl = (new Integer(elementI++)).toString();
                                elements.put(nameEl,e); //enregistrement pour édition éventuelle
                                Object[] temp = new Object[2];
                                temp[0] = line;
                                temp[1] = nameEl;
                                elementsLine.add(temp);
                                element2line.put(e,line);
                                        } else {
                                //si la ligne de définition de l'élément commence par #, alors c'est #SHORCUT=element(...) pour passer l'élément en argument à d'autres
                                String[] a = readBalises(line.substring(1));
                                Element e = (Element)readClass(types0, args0,a[1]);
                                selftypes.put(a[0],e);
                                elements.put(a[0],e);
                                Object[] temp = new Object[2];
                                temp[0] = a[1];
                                temp[1] = a[0];
                                elementsLine.add(temp);
                                element2line.put(e,line);
                                        }
                        }
                        try {line = sim.inputline();} catch (Exception e) {line = null;};
                    }
                                                                                                                } else //sinon "BALISE=..." est interprêté comme un raccourci sémantique (définition de variable)
                                                                                                                    //raccourci de type "VARIABLE=string" où String est le chemin d'accès de l'image à charger&partager
                                                                                                                    try {
                                                                                                                        selftypes.put(s1,readImage(s2)); } catch (Exception e) {
                                                                                                                            throw new Exception("cannot load image file (implicit image loading synthax on this line)");}
                
                try {line = sim.inputline();} catch (Exception e) {line = null;}; //lecture de la ligne suivante et on recherche à nouveau une balise...
            }
            if (world!=null) {
                try {
                    m.waitForAll();
                } catch( InterruptedException e ) {
                }
                sim.close();
                return world;
            } else throw new Exception("cannot find BOXES: or ELEMENTS:");
        } catch (Exception e) {
            sim.close();
            throw new InvalidSIMfile("Invalid SIM file. Error on line "+sim.lineNumber+": "+e+"\n (Line beginning with: "+((line!=null)? (line.length()<=50? line : line.substring(0,50)+" ... "): "")+")");}
        //on affiche au plus les 50 premiers caractères de la ligne erronnée
    }
    
    /**
     * Raised if the SIM file is not valid.
     * Contains the line number and the message error.
     */
    public static class InvalidSIMfile extends Exception{

        public InvalidSIMfile(String message){
            super(message);
        }
    }
    
    
    /**
     * Save the current world in the specified file.
     * @param world the world to save.
     * @throws java.io.IOException
     * @throws java.io.FileNotFoundException
     */
    public void save(File f, World world) throws java.io.IOException, java.io.FileNotFoundException {
        currentfile = f;
        currentpath = f.getParent();
        currentfile.delete();
        currentfile.createNewFile();
        String toWrite = beginOfFile; //SAUVEGARDE DES BALISES
        RandomAccessFile ch = new RandomAccessFile(currentfile,"rw");
        if (matrixInputMode.equals("off"))
            toWrite = toWrite+"\nBOXES:\n";
        else toWrite = toWrite+"MATRIXINPUTMODE=off\n\nBOXES:\n";
        HashMap temp = (HashMap)changedBoxes.clone();
        for (int i = 0; i<world.map.xBoxes; i++) { //SAUVEGARDE DES BOXES
            for (int j = 0; j<world.map.yBoxes; j++) {
                Box b = world.map.boxes[i][j];
                if (temp.containsKey(b)) {
                    toWrite = toWrite+i+","+j+"="+(String)temp.get(b)+"\n";
                    String[][] bprop = world.map.boxes[i][j].getProperties();
                    for (int k = 0; k<bprop.length; k++) {
                        String prop = bprop[k][0];
                        String value = bprop[k][1];
                        toWrite = toWrite+"§"+i+","+j+"\n~"+prop+"\n~"+value+"\n";
                    }
                }
            }
        }
        toWrite = toWrite+"\nELEMENTS:\n"; //SAUVEGARDE DES ELEMENTS
        toWrite = toWrite+"@"+(elementI-1)+"\n";
        for (int i = 0; i<elementsLine.size(); i++){
            Object[] temp2 = (Object[])elementsLine.get(i);
            String line = (String)temp2[0];
            String name = (String)temp2[1];
            Element el = (Element)elements.get(name);
            if (world.id2element(el.id)!=null) //si l'élément est encore dans le monde...
                toWrite = toWrite + "#" +name+"="+line+"\n";
            String[][] elprop = el.getProperties();
            if (hasChangedElement.containsKey(el)) //si l'élément a changé de puis son instanciation
                for (int j = 0; j<elprop.length; j++) {
                String prop = elprop[j][0];
                String value = elprop[j][1];
                toWrite = toWrite+"$"+name+"\n~"+prop+"\n~"+value+"\n";
                }
        }
        toWrite = toWrite+"\n"; //SAUVEGARDES DES PROPRIETES DU MONDE
        String[][] bprop = world.getProperties();
        for (int k = 0; k<bprop.length; k++) {
        toWrite = toWrite+"$WORLD$\n";
            String prop = bprop[k][0];
            String value = bprop[k][1];
            toWrite = toWrite+"£"+prop+"\n£"+value+"\n";
        }
        
        ch.writeBytes("//$$Gener@ted by Sem@$$\n"+toWrite);
        ch.close();
    }
    
    
    /**
     * Used to notify that a box has changed since the last file opening,
     * and so will have to be saved.
     * @param b the box which has changed.
     */
    protected void boxChanged(Box b){
        String boxCode;
        if (changedBoxes.containsKey(b))
            boxCode = (String)changedBoxes.get(b);
        else boxCode = (String)changedBoxes.get("default");
        changedBoxes.put(b,boxCode);
    }
    /**
     * Used to notify that an element has changed since the last file opening,
     * and so will have to be saved.
     * @param e the element which has changed.
     */
    protected void elementChanged(Element e){
        hasChangedElement.put(e,"");
    }
    
    /**
     * Changes the (x,y) box of the world, instanciating the new box
     * from a string given in argument, and that follows the BoxFormat specifications.
     */
    protected Box changeBox(World world,int x, int y, String boxCode) throws Exception {
        Object[] args0 = {world.map,world.drawing,new Integer(x), new Integer(y)};
        Class[] types0 = {sema.Map.class,sema.Drawing.class, Integer.TYPE, Integer.TYPE};
        Box b = (Box)readClass(types0,args0,boxCode);
        changedBoxes.put(b,boxCode);
        return b;
    }
    
    
    /**
     * Returns the string corresponding to the
     * command line of the SIM file, line which
     * was used to instanciate the given box.
     */
    protected String codeOf(Box b){
        if (changedBoxes.containsKey(b))
            return (String)changedBoxes.get(b);
        else return(String)changedBoxes.get("default");
    }
    
    /**
     * Returns the string corresponding to the
     * command line of the SIM file, line which
     * was used to instanciate the given element.
     */
    protected String codeOf(Element e){
        String code = "";
        try {
            String line = (String)element2line.get(e);
            if (line.charAt(0)!= '#') code = line;
            //sinon, ligne de la forme #machin=instanciation
            String[] a = readBalises(line);
            code =a[1];} catch (Exception ee) {//cas impossible en principe, on rattrape au cas où}
            }
        return code;
    }
    
    /**
     * Add an element to the world, element corresponding
     * to the string, following the ElementFormat specifications.
     */
    protected Element addElement(String line, World world) throws InvalidSIMfile{
        try {
            Object[] args0 = {world};
            Class[] types0 = {World.class};
            Element e = null;
            if (line.charAt(0)!= '#') {
                e = (Element)readClass(types0, args0,line);
                String nameEl = (new Integer(elementI++)).toString();
                elements.put(nameEl,e); //enregistrement pour édition éventuelle
                Object[] temp = new Object[2];
                String lin2 = line.substring(0);
                temp[0] = lin2; //copie de la chaîne pour ne pas la partager avec d'autres éléments
                temp[1] = nameEl;
                elementsLine.add(temp);
                element2line.put(e,lin2);
            } else {
                //si la ligne de définition de l'élément commence par #, alors c'est #SHORCUT=element(...) pour passer l'élément en argument à d'autres
                String[] a = readBalises(line.substring(1));
                e = (Element)readClass(types0, args0,a[1]);
                selftypes.put(a[0],e);
                elements.put(a[0],e);
                Object[] temp = new Object[2];
                temp[0] = a[1];
                temp[1] = a[0];
                elementsLine.add(temp);
                element2line.put(e,line);
            }
            return e;} catch (Exception e) {throw new InvalidSIMfile("invalid element declaration");}
    }
    
    
    
    private String[] readBalises(String s) throws Exception { //méthode de lecture de balise de type BALISE=valeur avec d'éventuels espaces à enlever avant valeur
        for(int i = 0; i<s.length(); i++) {
            if (s.charAt(i) == '=' || s.charAt(i) == ':') {
                String s1 = s.substring(0,i).trim();
                String s2 = s.substring(i+1, s.length()).trim();
                String[] a = {s1.toUpperCase(),s2};
                return(a);
            }
        }
        if (s.trim().equals("")) { //on ignore la chaine de caractères si elle est vide (pour autoriser les sauts de ligne dans un fichier
            String[] a = {s,s};
            return(a);
        }
        throw new java.lang.Exception("invalid definition syntax");
    }
    
    
    //formate une chaine de caractère pour obtenir les arguments: "class(type1 args1, type2 arg2, ....)" -> {type1, arg1, type2, arg2, ...}
    //avec argi qui peut lui même être une instanciation de classe de la meme forme.
    private Vector formatString(String s) throws Exception {
        Vector v = new Vector();
        int last_indice = 0;
        boolean isFirstBracket = true; //la première parenthèse ouvrante (et donc la dernière fermante) seront ignorées
        boolean isLastBracket = true; //la première parenthèse ouvrante (et donc la dernière fermante) seront ignorées
        int lastBracketi = -1;
        int waitingForBracket = 0; //lecture des lexèmes (...) pour passage d'arguments à une classe
        boolean waitForEndString = false; //lecture des chaînes de caractères
        for (int i = 0; i<s.length(); i++) {
            if (s.charAt(i)=='\"') {
                if (waitForEndString) {
                    waitForEndString = false;
                    v.add(s.substring(last_indice, i));
                    last_indice = i+1;
                } else if (waitingForBracket==0){
                    last_indice = i+1;
                    waitForEndString = true;
                }
            } else
                if (s.charAt(i)=='(') {
                if (isFirstBracket){
                    isFirstBracket = false;
                    if (last_indice<i)
                        v.add(s.substring(last_indice, i));
                    last_indice = i+1;
                } else {
                    if (!waitForEndString) {
                        waitingForBracket++;
                        if (waitingForBracket==1) {
                            lastBracketi = i;}
                        if (last_indice<i)
                            v.add(s.substring(last_indice, i));
                        last_indice = i+1;
                        
                    }
                }
                } else
                    if (s.charAt(i)==')') {
                if (waitingForBracket==0 && !isLastBracket)
                    throw new Exception("bad string to format (unmatched '(')");
                else {
                    if (waitingForBracket==0) {
                        isLastBracket = false;
                        if (last_indice<i)
                            v.add(s.substring(last_indice, i));
                        last_indice = i+1;
                    } else if (waitingForBracket==1) {
                        String sTemp = (String)v.get(v.size()-1);
                        v.remove(v.size()-1);
                        v.add(sTemp.concat(s.substring(lastBracketi,i+1)));
                        waitingForBracket--;
                        last_indice = i+1;
                    }
                }
                    } else
                        if(!waitForEndString && waitingForBracket ==0) {
                if (s.charAt(i)==' ' || s.charAt(i)==',') //séparateurs: , et espace
                {
                    if (i == last_indice) { //on ignore un sépérateur seul, ce qui permet de considérer ", " comme un unique séparateur
                        last_indice++;
                    } else {
                        if (last_indice<i)
                            v.add(s.substring(last_indice, i)); //on prend la chaine entre les deux séparateurs
                        last_indice = i+1;
                    }
                }
                        }
        }
        if (last_indice < s.length()) {
            v.add(s.substring(last_indice,s.length()));
        }
        if (waitForEndString) throw new Exception("bad string to format (unmatched \")");
        if (waitingForBracket>0) throw new Exception("bad string to format (unmatched '(')");
        if (waitingForBracket<0) throw new Exception("bad string to format (unmatched ')')");
        return(v);
    }
    
    /**
     * Lit une image (dans le répertoire du fichier SIM si pas de répertoire spécifié).
     * Attend que l'image ait fini de se charger avant de repasser la main à l'interface graphique
     * (utilisation d'un MediaTracker).
     */
    protected Image readImage(String location) { //
        Image im;
        File imagefile = new File(currentpath,location.trim());
        if (imagefile.exists())
            im= toolkit.getImage(imagefile.toString()); //par défaut, on essaye dans le répertoire du fichier SIM
        else
            im= toolkit.getImage(location); //sinon on essaye avec uniquement l'image
        m.addImage( im, imageIndice++ );
        return im;
    }
    
    
    /**
     * Instancie un objet à partir d'une chaîne de caractères formatée correctement (syntaxe très proche de celle des constructeurs Java).
     */
    private Object readClass(Class[] types0, Object[] args0, String s) throws Exception {
        //fonction (technique) qui transforme une String "myclass(type1 arg1,...,typeN argN)" en une instance de 'myclass':
        //args0 et types0 sont les arguments supplémentaires et leurs types, que l'on ajoute au début des paramètres du constructeur de 'myclass'
        
        try {
            Vector v = formatString(s);
            int n = args0.length; //nombre d'arguments supplémentaires
            
            Class[] types = new Class[n + v.size()/2];
            for (int i=0; i<n; i++) {
                types[i] = types0[i];
            }
            Vector args = new Vector();
            for (int i=0; i<n; i++) {
                args.add(args0[i]);
            }
            
            for (int i=1; i< v.size();i=i+2) { //on détermine le type des arguments dans la chaine (pas de respect de la casse pour les types de base + String)
                String vi = ((String)v.get(i)).toLowerCase().trim();
                if (vi.equals("float")) {
                    types[n+i/2] = Float.TYPE;
                    args.add(new Float(Float.parseFloat((String)v.get(i+1))));
                } else
                    if (vi.equals("double")) {
                    types[n+i/2] = Double.TYPE;
                    args.add(new Double(Double.parseDouble((String)v.get(i+1))));
                    } else
                        if (vi.equals("int")) {
                    types[n+i/2] = Integer.TYPE;
                    args.add(new Integer(Integer.parseInt((String)v.get(i+1))));
                        } else
                            if (vi.equals("string")) {
                    types[n+i/2] = String.class;
                    args.add(v.get(i+1));
                            } else
                                if (vi.equals("boolean")) {
                    types[n+i/2] = Boolean.TYPE;
                    args.add(Boolean.valueOf((String)v.get(i+1)));
                                } else
                                    if (vi.equals("long")) {
                    types[n+i/2] = Long.TYPE;
                    args.add(new Long(Long.parseLong((String)v.get(i+1))));
                                    } else
                                        if (vi.equals("short")) {
                    types[n+i/2] = Short.TYPE;
                    args.add(new Short(Short.parseShort((String)v.get(i+1))));
                                        } else
                                            if (vi.equals("byte")) {
                    types[n+i/2] = Byte.TYPE;
                    args.add(new Byte(Byte.parseByte((String)v.get(i+1))));
                                            } else
                                                if (vi.equals("char")) {
                    types[n+i/2] = Character.TYPE;
                    args.add(new Character(((String)v.get(i+1)).charAt(0)));
                                                }  else
                                                    if (vi.equals("image")) {
                    types[n+i/2] = Image.class;
                    if (selftypes.containsKey(((String)v.get(i+1)).trim().toUpperCase())) {
                        args.add(selftypes.get(((String)v.get(i+1)).trim().toUpperCase()));} else {
                        args.add(readImage(((String)v.get(i+1))));}
                                                    } else
                                                        if (vi.equals("selftype")) {
                    String temp = (((String)v.get(i+1)).trim().toUpperCase()); //on ne respecte pas la casse pour les noms de variables
                    //on regarde si le selftype désigne la carte ou le monde. Sinon, il désigne un élément précédemment défini par #name=value.
                    if (temp.equals("MAP")) {
                        types[n+i/2] = Map.class;
                        args.add(selftypes.get("MAP"));} else
                            if (temp.equals("WORLD")) {
                        types[n+i/2] = World.class;
                        args.add(selftypes.get("WORLD"));} else {
                        Object var = selftypes.get(temp);
                        types[n+i/2] = Element.class;
                        args.add(var);
                        }
                                                        } else {
                    //sinon, le type est suivi d'une lecture de la classe: "classtype class(...)"
                    Object o = readClass(emptyTypes, emptyArgs,(String)v.get(i+1)); //appel récursif
                    args.add(o);
                    types[n+i/2] = Class.forName((String)v.get(i));
                                                        }
            }
            Class cl = Class.forName((String)v.get(0)); //on récupère l'objet Class
            java.lang.reflect.Constructor constr = cl.getConstructor(types); //son constructeur
            Object o = constr.newInstance(args.toArray()); //on instancie l'objet
            return(o);} catch (Exception e) {throw new Exception("readClass cannot create instance of "+s+"\n("+e+")");}
    }
    
}
