/* Author: Clauirton Siebra <c.siebra@ed.ac.uk>
 * Updated: Mon Aug 22 12:08:43 2005 by Clauirton Siebra
 * Copyright: (c) 2005, AIAI, University of Edinburgh
 */

package ix.ip2.map;

import javax.swing.*;
import java.awt.Color;
import java.awt.event.*;
import java.awt.BorderLayout;
import java.util.*;
import java.io.FileInputStream;

import ix.util.*;
import ix.util.lisp.*;
import ix.ip2.StateViewer;
import ix.iface.ui.HelpFrame;
import ix.iface.ui.AboutFrame;
import ix.iface.util.CatchingActionListener;
import ix.iface.util.ToolFrame;
import ix.iface.util.IconImage;

import com.bbn.openmap.*;
import com.bbn.openmap.util.ColorFactory;
import com.bbn.openmap.gui.*;
import com.bbn.openmap.layer.shape.ShapeLayer;
import com.bbn.openmap.event.SelectMouseMode;
import com.bbn.openmap.event.CenterEvent;
import com.bbn.openmap.proj.*;

/**
 * Class that manages the creation and use of layers.
 */

public class MapTool extends ToolFrame implements ActionListener, KeyListener, MouseListener {

    MapBean map = null;
    WorldStateLayer wLayer;
    JLabel coordinate = new JLabel("coordinates");
    boolean ctrlDown = false;
    Vector layers = new Vector();

    /**
     * The default constructor for the map tool. It loads the property file that contains the layers to be
     * created and sets the parameters to each of them.
     * @param sv reference to the state viewer and communication channel between the map tool and IP2 agent.
     * @param title name to the map tool frame.
     */
    public MapTool(StateViewer sv, String title) {
        //super(Parameters.getParameter("display-name")+" Map");
        super(title);
        wLayer = new WorldStateLayer(sv);
        getContentPane().setLayout(new BorderLayout());
        setJMenuBar(makeMenuBar());
        setIconImage(IconImage.getIconImage("ip2-map-icon.gif"));
        Properties properties = loadProperties();
    
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
            setVisible(false);
            }
        });

        try {

            //Allows all the components to find each other if they are added to it
            MapHandler mapHandler = new MapHandler();

            // Create the map
            map = new MapBean();

            //Display the coordinates via mouse event
            map.addMouseListener(new MouseAdapter() {
                public void mouseClicked(MouseEvent e) {
                    coordinate.setText((map.getCoordinates(e)).toString());
                }
            });

            map.addKeyListener(this);
            map.addMouseListener(this);
    
            // Add the MapBean to the MapHandler.
            mapHandler.add(map);

            // Add the map to the JFrame
            getContentPane().add(map, BorderLayout.CENTER);

            //Creating Layers
            Layer[] layers = getLayers(properties); 
            LayerHandler layerHandler = new LayerHandler();
            for (int i = 0; i < layers.length; i++) {
                layers[i].setVisible(true);
                layerHandler.addLayer(layers[i]);
            }
        
            //Set the properties of the map: center, lat, log, ...
            setMapProperties(properties,layers);

            // Mouse delegator to handle mouse events on the map
            mapHandler.add(new MouseDelegator());
            mapHandler.add(new SelectMouseMode());

            // Manages all layers, on or off.
            mapHandler.add(layerHandler);

            //add a GUI to control the layers
            mapHandler.add(new LayersPanel());

            // Some pre-built tools
            ToolPanel toolPanel = new ToolPanel();
            mapHandler.add(toolPanel);
            OMToolSet ots = new OMToolSet();
            ots.getNavigatePanel().setDefaultCenter(map.getCenter().getLatitude(),
                                                    map.getCenter().getLongitude());

            mapHandler.add(ots);   //mapHandler.add(new OMToolSet());

            toolPanel.add(coordinate);
            getContentPane().add(toolPanel, BorderLayout.SOUTH);

        } catch(MultipleSoloMapComponentException e){}
    }

   /**
    * Gets the contents of the map.layers property, which is space-separated list of names.
    * @param p properties that contains the information about the layers.
    * @return layers.
    */
    protected Layer[] getLayers(Properties p) {
        String layersValue = p.getProperty("map.layers");
        if (layersValue == null) {
            System.err.println("No property <<map.layers>> found in properties file.");
            return null;
        }

        StringTokenizer tokens = new StringTokenizer(layersValue, " ");
        Vector layerNames = new Vector();
        while (tokens.hasMoreTokens()) {
            layerNames.addElement(tokens.nextToken());
        }
        int nLayerNames = layerNames.size();
        //Vector layers = new Vector(); //Vector layers = new Vector(nLayerNames);
        layers.addElement(wLayer);  

        // For each layer marker name, find that layer's properties.
        // The marker name is used to scope those properties that
        // apply to a particular layer.  If you parse the layers'
        // properties from a file, you can add/remove layers from the
        // application without re-compiling.  You could hard-code all
        // the properties being set if you'd rather...

        for (int i = 0; i < nLayerNames; i++) {
            String layerName = (String)layerNames.elementAt(i);

            // Find the .class property to know what kind of layer to create.
            String classProperty = layerName + ".class";
            String className = p.getProperty(classProperty);
            if (className == null) {
                // Skip it if you don't find it.
                System.err.println("Failed to locate property \""+ classProperty + "\"");
                System.err.println("Skipping layer \"" + layerName + "\"");
                continue;
            }
            try {
                // Create it if you do...
                Object obj = java.beans.Beans.instantiate(null, className);
                if (obj instanceof Layer) {
                    Layer l = (Layer) obj;
                    // All layers have a setProperties method, and
                    // should intialize themselves with proper
                    // settings here.  If a property is not set, a
                    // default should be used, or a big, graceful
                    // complaint should be issued.
                    l.setProperties(layerName, p);
                    layers.addElement(l);
                }
            } catch (java.lang.ClassNotFoundException e) {
                System.err.println("Layer class not found: \""+ className + "\"");
                System.err.println("Skipping layer \"" + layerName + "\"");
            } catch (java.io.IOException e) {
                System.err.println("IO Exception instantiating class \""   + className + "\"");
                System.err.println("Skipping layer \"" + layerName + "\"");
            }
        }
        int nLayers = layers.size();

        if (nLayers == 0) {
            return null;
        } else {
            Layer[] value = new Layer[nLayers];
            layers.copyInto(value);
            return value;
        }
    }

   /**
    * Method called by the IP2 agent. All layer that is dealing with I-X objects needs to implement this function
    * to keep the representation consistent with the agent. In this case the method deletes all the objects.
    */
    public void reset(){
	wLayer.reset();

	Enumeration en = layers.elements();
	Object temp;
	while(en.hasMoreElements()) {
	    temp = en.nextElement();
	    //if(temp instanceof IxRouterLayer)
	    //((IxRouterLayer)temp).reset();
	}
    }

    /**
     * Method called by the IP2 agent. All layer that is dealing with I-X components needs to implement this function
     * to keep the representation consistent with the agent. In this case the method adds a pattern-value pair.
     * @param pattern pattern.
     * @param value value.
     */
    public void addPatternValue(LList pattern, Object value){
        wLayer.addPatternValue(pattern, value);
    } 

    /**
     * Method called by the IP2 agent. All layer that is dealing with I-X components needs to implement this function
     * to keep the representation consistent with the agent. In this case the method changes a pattern value pair.
     * @param pattern pattern.
     * @param value value.
     */
    public void changePatternValue(LList pattern, Object value){
        wLayer.changePatternValue(pattern, value);
    }

    /**
     * Method called by the IP2 agent. All layer that is dealing with I-X components needs to implement this function
     * to keep the representation consistent with the agent. In this case the method deletes a pattern-value pair.
     * @param pattern pattern.
     * @param value value.
     */
    public void deletePatternValue(LList pattern, Object value){
        wLayer.deletePatternValue(pattern, value);
    }

    /**
     * Loads the content of a property file. The file path is specified by the "map-properties" parameter in the
     * scripts of initialization.
     * @return list of properties.
     */
    private Properties loadProperties(){
        Properties properties = new Properties();
        try{
            if(Parameters.haveParameter("map-properties")) {
		try {
		    properties.load(new FileInputStream(Parameters.getParameter("map-properties")));
		}catch(Exception e) {
		     System.err.println("Error in loading path " + Parameters.getParameter("map") + ". Default value will be used.");
		     String s = (Util.class.getClassLoader().getResource("resources/map/world-map-default/map.props")).getFile();
		     properties.load(new FileInputStream(s));
		}
	    }
            else {
		String s = (Util.class.getClassLoader().getResource("resources/map/world-map-default/map.props")).getFile();
		properties.load(new FileInputStream(s));
            }
        }
        catch (Exception e) {
            System.err.println("ERROR: Problems in loading the inbuilt default map properties. Possibly the file <ix-directory>apps/addon/map/resources/map/world-map/map.props does not exist or the path was changed.");
        }
        return properties;
    }

    /**
     * Sets the properties of the map. This method can be extended to consider any other property. The current are:<p>
     *      Width           = width size of the map tool frame; <p>
     *      Height          = height size of the map tool frame; <p>
     *      Latitude        = initial camera (user view) latitude; <p>
     *      Longitude       = initial camera (user view) longitude; <p>
     *      Scale           = map scale (1:x); <p>
     *      Projection      = initial kind of projection; <p>
     *      BackgroundColor = background color; <p>
     *      startUpLayers   = layers that are visible when the application starts. If not specified, all layers are visible.
     * @param properties properties
     * @param layers current set of layers of the application.
     */
    protected void setMapProperties(Properties properties,Layer[] layers){
    
        // Set the map's size property
        if(properties.containsKey("map.Width") && properties.containsKey("map.Height"))
            setSize(Integer.parseInt(properties.getProperty("map.Width")),Integer.parseInt(properties.getProperty("map.Height")));
        else
	    setSize(800,600);
            //setSize(530,410);

        // Set the map's center property
        if(properties.containsKey("map.Latitude") && properties.containsKey("map.Longitude"))
            map.setCenter(new LatLonPoint(Float.parseFloat(properties.getProperty("map.Latitude")),
                                          Float.parseFloat(properties.getProperty("map.Longitude"))));
	else
	    map.setCenter(new LatLonPoint(0f,0f));
    
        // Set a projection type for the map
        if(properties.containsKey("map.Projection")) {    
            //String[] projs = ProjectionFactory.getAvailableProjections();
            //for(int x=0;x<projs.length;x++)
            //System.out.println(">>>>>>>>>>>>>>>> "+projs[x]);

            String projName = properties.getProperty("map.Projection");
            int projType = ProjectionFactory.getProjType(projName);
            map.setProjectionType(projType);
        }       

	// Set the map' scale
        if(properties.containsKey("map.Scale")) {
	    float sc = Float.parseFloat(properties.getProperty("map.Scale"));
	    if(sc<(map.getProjection()).getMinScale())
		//sc = (map.getProjection()).getMinScale();
		sc = ((map.getProjection()).getMaxScale() - (map.getProjection()).getMinScale())/2;
	    else if (sc>(map.getProjection()).getMaxScale())
		//sc = (map.getProjection()).getMaxScale();
		sc = ((map.getProjection()).getMaxScale() - (map.getProjection()).getMinScale())/2;
            map.setScale(sc);
	}
	else
	    map.setScale(((map.getProjection()).getMaxScale() - (map.getProjection()).getMinScale())/2);

        // An ARGB integer to use for the background (see) color.
        if(properties.containsKey("map.BackgroundColor"))
            map.setBackgroundColor(ColorFactory.parseColor(properties.getProperty("map.BackgroundColor")));
    
        // Set the layers that will be visible
        if(properties.containsKey("map.startUpLayers")) {
            for (int i = 1; i < layers.length; i++)
                layers[i].setVisible(false);

            String layersOn = properties.getProperty("map.startUpLayers");
            if (layersOn != null) {
                StringTokenizer tokens = new StringTokenizer(layersOn, " ");
                Layer l;
                while (tokens.hasMoreTokens()) {
                    l = getLayer(tokens.nextToken(),layers);
                    if (l!= null) l.setVisible(true);
                }
            }
        }
    }

   /**
    * Returns a layers whose name is specified in the input attributes.
    * @param name layer name.
    * @param l list of current layers of theh application.
    * @return layer.
    */
    protected Layer getLayer(String name, Layer[] l) {
        for (int i = 1; i < l.length; i++)
            if ((l[i].getPropertyPrefix()).equals(name))
                return l[i];
        return null;
    }

   /**
    * Creates a menu bar in the map tool frame.
    */
    protected JMenuBar makeMenuBar() {
        JMenuBar bar = new JMenuBar();

        JMenuItem closeI = new JMenuItem("Close");
        closeI.addActionListener(CatchingActionListener.listener(MapTool.this));

        JMenuItem refreshI = new JMenuItem("Refresh");
        refreshI.addActionListener(CatchingActionListener.listener(MapTool.this));  

        JMenu fileMenu = new JMenu("File");
        bar.add(fileMenu);
        fileMenu.add(refreshI);
        fileMenu.addSeparator();
        fileMenu.add(closeI);

        JMenu viewMenu = new JMenu("View");
        bar.add(viewMenu);
        JMenu projectionMenu = new JMenu("Projection");
        viewMenu.add(projectionMenu);

        ButtonGroup group = new ButtonGroup();
        JRadioButtonMenuItem meI,cyI,azI,orI;
        meI = new JRadioButtonMenuItem("Mercator");
        meI.addActionListener(CatchingActionListener.listener(MapTool.this));
        meI.setSelected(true);
        group.add(meI);
        projectionMenu.add(meI);
        cyI = new JRadioButtonMenuItem("Cylindrical (CADRG)");
        cyI.setActionCommand("CADRG");
        cyI.addActionListener(CatchingActionListener.listener(MapTool.this));
        group.add(cyI);
        projectionMenu.add(cyI);
        //llI = new JRadioButtonMenuItem("Lat/Lon ratios = pixel ratios");
        //llI.setActionCommand("LLXY");
        //llI.addActionListener(CatchingActionListener.listener(MapTool.this));
        //group.add(llI);
        //projectionMenu.add(llI);
        azI = new JRadioButtonMenuItem("Azimuth (Gnomonic)");
        azI.setActionCommand("Gnomonic");
        azI.addActionListener(CatchingActionListener.listener(MapTool.this));
        group.add(azI);
        projectionMenu.add(azI);
        orI = new JRadioButtonMenuItem("Orthographic");
        orI.addActionListener(CatchingActionListener.listener(MapTool.this));
        group.add(orI);
        projectionMenu.add(orI);

        JMenuItem helpI = new JMenuItem("Help");
        helpI.addActionListener(CatchingActionListener.listener(MapTool.this));

        JMenuItem aboutI = new JMenuItem("About");
        aboutI.addActionListener(CatchingActionListener.listener(MapTool.this));

        JMenu helpMenu = new JMenu("Help");
        bar.add(helpMenu);
        helpMenu.add(helpI);
        helpMenu.add(aboutI);

        return bar;
    }

    public void actionPerformed(ActionEvent e) {
        String command = e.getActionCommand();
        Debug.noteln("Map Tool action:", command);
        if (command.equals("Close")) {
            this.setVisible(false);
        }
        else if (command.equals("Refresh")) {
            if(wLayer != null)
                wLayer.refresh();
        }
        else if (command.equals("Help")) {
            (new HelpFrame(Util.class.getClassLoader().getResource("resources/html/object-whiteboards.html"))).setVisible(true);
        }
        else if (command.equals("About")) {
            (new AboutFrame("I-X Map Tool version 4.4, 26-Jun-06")).setVisible(true);
        }
        else
            map.setProjectionType(ProjectionFactory.getProjType(command));
        //Debug.noteln("Nothing to do", command);
    }

   /**
    * Returns the latitude-longitude point of a position where the mouse was clicked on the map.
    */
    public LatLonPoint getCoordinates(MouseEvent e){
        coordinate.setText((map.getCoordinates(e)).toString());
        return map.getCoordinates(e);
    }

   /**
    * Centralises the view around the position where the mouse was clicked
    */
    public void setCenterView(MouseEvent e){
       LatLonPoint ll = map.getCoordinates(e);
       map.center(new CenterEvent(this,ll.getLatitude(),ll.getLongitude()));
    }

    // KEYBOARD AND MOUSE STUFF

    public void keyPressed(KeyEvent e){
    if (KeyEvent.getKeyModifiersText(e.getModifiers()).equals("Ctrl"))
        ctrlDown = true;
    }

    public void keyReleased(KeyEvent e){
        ctrlDown = false;
    }

    public void keyTyped(KeyEvent e){}

    public void mouseExited(MouseEvent e) {}
    public void mouseEntered(MouseEvent e) {/*map.requestFocus();*/}
    public void mouseClicked(MouseEvent e) {

	//int click = e.getClickCount();
	//System.out.println("clicks = "+click);
	//if(click == 1) System.out.println(">>>> Entrou no click=1: "+click);
	//else if(click > 1) System.out.println(">>>> Entrou no click>1: "+click);

}
    public void mouseReleased(MouseEvent e) {}

    public void mousePressed(MouseEvent e) {
        map.requestFocus();
        if(e.getButton()==e.BUTTON2)
            setCenterView(e);
        else if (ctrlDown && e.getButton()==e.BUTTON1)
            setCenterView(e);
    }
}

// javac -classpath ../../../../ix.jar:../imports/openmap.jar:. ix/ip2/map/MapTool.java
