package mapper;

import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.*;
import java.util.*;

import ec.*;
import geolocale.*;
import misc.*;


/**
 * This class is a custom component that supports mapping.  It also has
 * a popup menu that allows. . .
 */

public class Mapper extends JPanel implements ECEGenerator
{
   private MapperPanel mapperPanel = null;
   private MPController mpController = null;
   //private ControlMenu controlMenu = null;
   private ControlMenu controlMenu = null;
   
   private geolocale.Locale locale = null;
   private static final int POLBOUNDSETTAG = 101;
   private static final int VERTEXSETTAG = 202;
   private static final int INLANDWATERSETTAG = 302;
   private static final int COCSETTAG = 102;
   private static final int ROADSETTAG = 201;
   private static final int BRIDGESETTAG = 203;
   private static final int RAILSETTAG = 401;
   private static final int CONTOURSETTAG = 501;
   private static final int RIVERSETTAG = 301;
   
   private Layer bgFaces = new Layer();
   
   private Hashtable mapItems = new Hashtable();


   public final static Color TILECOLOR          = Color.black;    // color of tile frame and text
   public final static Color WAYPOINTSCOLOR     = Color.black;
   public final static Color FOVCOLOR           = Color.magenta;
   public final static Color FORCOLOR           = Color.cyan;
   public final static Color FLYOUTCOLOR        = Color.yellow;
   public final static Color DEADFLYOUTCOLOR    = Color.darkGray;
   public final static Color LANDCOLOR          = new Color(96,156,128);
   public final static Color ROADCOLOR          = Color.red;
   public final static Color DRAINCOLOR         = Color.blue;
   public final static Color RAILCOLOR          = Color.black;
   public final static Color CONTOURCOLOR       = new Color(255, 100, 100);
   public final static Color OCEANCOLOR         = new Color(175,200,240);

   public final static Color[] COLORS = {new Color(96,156,128), new Color(163,218,35),
                                         new Color(61,192,150), new Color(96,128,64),
                                         new Color(128,128,64), new Color(64,128,64),
                                         Color.green, Color.blue, Color.orange};

   private int colorcursor = 0;
   private Hashtable countryColors = new Hashtable();
   Runtime rt = Runtime.getRuntime();
   
   
   // In the long run, this should be replaced with some sort of dynamic 
   // mechanism for setting color preferences.
   private Hashtable defaultColors = new Hashtable();
   private void setColors()
   {
      defaultColors.put("Roads", ROADCOLOR);
      defaultColors.put("Rivers", DRAINCOLOR);
      defaultColors.put("Countours", CONTOURCOLOR);
      defaultColors.put("Rail Roads", RAILCOLOR);
   }
   
/*
   public void repaint()
   {
      //super.repaint();
      mapperPanel.repaint();
   }
   
   public void update(Graphics g) {
      paint(g);
   }
*/

   public Mapper()
   {
      super();
      setDoubleBuffered(false);

      setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));
      
      // layout stuff
      GridBagLayout gbl = new GridBagLayout();
      //setLayout(gbl);
      setLayout(new BorderLayout());
      GridBagConstraints gbc;
      
      mapperPanel = new MapperPanel(0, 0, 2);
      gbc= new GridBagConstraints();
      gbc.fill = GridBagConstraints.BOTH;
      gbc.anchor = GridBagConstraints.CENTER;
      gbc.gridx = 0;  gbc.gridy = 0;
      //add(mapperPanel, gbc);
      add(mapperPanel, BorderLayout.CENTER);
      
      mpController = new MPController(mapperPanel);
      mpController.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
      gbc.fill = GridBagConstraints.BOTH;
      gbc.anchor = GridBagConstraints.EAST;
      gbc.gridx = 0;  gbc.gridy = 1;
      //gbc.weighty = 0.1;
      //add(mpController, gbc);
      add(mpController, BorderLayout.SOUTH);
 
      controlMenu = new ControlMenu(this, mapperPanel);
      add(controlMenu);
      if(bettygui.BettyGui.DEBUG.equals("1")) { System.out.println("Mapper: controlMenu:" + controlMenu + "mapperPanel: " + mapperPanel + "mpController: " + mpController);}
      mapperPanel.setEditMode(true);
      setColors();
        }

   public void setLocale(geolocale.Locale l)
   {

      locale = l;
      showMapItem("Political Boundaries", null, true);
      showMapItem("Inland Water", OCEANCOLOR, true);
      
      String[] descrips = l.getDescriptions();
      for(int i = 0; i < descrips.length; i++)
      {
         if(bettygui.BettyGui.DEBUG.equals("1")) { System.out.println("Mapper setlocal: descrips[i]" + descrips[i] + "controlMenu: " + controlMenu);}

         if(descrips[i].equals("Political Boundaries"))
         {
            controlMenu.addMapItem(descrips[i], true);
         }
         else if(descrips[i].equals("Inland Water"))
         {
            controlMenu.addMapItem(descrips[i], true);
         }
         else
            controlMenu.addMapItem(descrips[i], false);
      }
   }

   public void addMouseListener(MouseAdapter ma) { mapperPanel.addMouseListener(ma); }

   public void setDrawable(Object e, boolean drawable) {
      mapperPanel.setDrawable((ProtoElement)e, drawable);
   }

   public void setLocation(Object e, Vec pos)/*cth*/
   {
      mapperPanel.setLocation((ProtoElement)e, pos);
   }

   public void setLocation(Object e, Vec pos, Vec v)/*cth*/
   {
      mapperPanel.setLocation((ProtoElement)e, pos, v);
   }

   public void setLocation(Object e, Vec pos, Vec v, boolean drawable)/*cth*/
   {
      mapperPanel.setLocation((ProtoElement)e, pos, v, drawable);
   }
   
   
   
   
// Mapper code:

   public void setCenter(float lat, float lon)
   {
      mapperPanel.setCenter(lat, lon);
   }
   
   // For backward compatibility only:
   public void setExtents(float minLat, float minLon, float maxLat, float maxLon)
   {
      setCenter(minLat + (maxLat - minLat)/2, minLon + (maxLon - minLon)/2);
   }

   public void setZoom(int pixelsPerDegree)
   {
      mapperPanel.setZoom(pixelsPerDegree);
   }
   
   public void setZoomAndCenter(int pixelsPerDegree, float lat, float lon)
   {
      mapperPanel.setZoomAndCenter(pixelsPerDegree, lat, lon);
   }

   public void zoomIn(int multiplier)
   {
      mapperPanel.zoomIn(multiplier);
   }

   public void zoomOut(int divisor)
   {
      mapperPanel.zoomOut(divisor);
   }
   
   public float[] getCenter()
   {
      return mapperPanel.getCenter();
   }
   
   public int getZoomLevel()
   {
      return mapperPanel.getZoomLevel();
   }

   /**
    * Load the map item described by 'description' and store it in the 'mapItems'
    * Hashtable.
    *
    * @param   description    The String selected from the ones provided by Locale.getDescriptions.
    */
   private void loadMapItem(String description, Color color)
   {
      try
      {
         try
         {
            LocaleDataSegment lds = locale.getByDescription(description);
            if(color == null)
            {
               color = (Color)defaultColors.get(description);
               if(color == null) color = Color.black;
            }
            if(lds instanceof LineSet)
            {
               geolocale.LineSet ls = (geolocale.LineSet)lds;
               MapItem mi = new MapItem(MapItem.LINESET, ls.getLines(), color);
               mapItems.put(description, mi);
            }
            else if(lds instanceof AreaSet)
            {
               geolocale.AreaSet as = (geolocale.AreaSet)lds;
               MapItem mi = new MapItem(MapItem.AREASET, as.getAreas(), color);
               if(bettygui.BettyGui.DEBUG.equals("1")) { System.out.println("Mapper: Registering MapItem with description: \"" + description + "\"");}
               mapItems.put(description, mi);
            }
            else if(lds instanceof PolBoundSet)
            {
               PolBoundSet pbs = (PolBoundSet)lds;
               Enumeration polbnds = pbs.elements();
               Hashtable faces = new Hashtable();
               while(polbnds.hasMoreElements())
               {
                  PolBoundary p = (PolBoundary)polbnds.nextElement();
                  String cd = p.getCountryDesignator();
                  if(!countryColors.containsKey(cd))
                  {
                     countryColors.put(cd, COLORS[colorcursor++]);
                     if(bettygui.BettyGui.DEBUG.equals("1")) { System.out.println("Mapper: setBackground: Color assigned to " + cd + ": "+
                       COLORS[colorcursor - 1]);}
                     colorcursor = (COLORS.length > colorcursor ? colorcursor : 0);
                  }
                  float[][] vertices = p.getVertices();
                  Color c = (Color)countryColors.get(cd);
                  faces.put(vertices, c);
               }
               MapItem mi = new MapItem(MapItem.POLBNDSET, faces);
               if(bettygui.BettyGui.DEBUG.equals("1")) { System.out.println("Mapper: Registering MapItem with description: \"" + description + "\"");}
               mapItems.put(description, mi);
            }
            else if(lds instanceof VertexSet)
            {
               geolocale.VertexSet vs = (geolocale.VertexSet)lds;
               MapItem mi = new MapItem(MapItem.LINESET, vs.getEdges(), color);
               mapItems.put(description, mi);
            }
            else if(lds instanceof PointSet)
            {
               geolocale.PointSet ps = (geolocale.PointSet)lds;
               String iconsDir = System.getProperty("sim.root") + "/" + System.getProperty("icons.dir");
               MapItem mi = new MapItem(MapItem.POINTSET, ps.getPoints(), ImageBroker.getImage(iconsDir + "/" + "red-ball-small.gif"));
               mapItems.put(description, mi);
            }
         }
         catch(geolocale.TagNotFoundException tnf)
         {
            if(bettygui.BettyGui.DEBUG.equals("1")) { System.out.print("Mapper: setBackground: No \"" + description + "\" available: ");}
            System.out.println(tnf.getMessage());
         }
      }
      catch(Exception e)
      {
         if(locale == null) System.out.println("Mapper: No Locale specified.");
         else e.printStackTrace();
      }
   }
   
   public void showMapItem(String description, boolean trueMeansShow)
   {
      MapItem mi = (MapItem)mapItems.get(description);
      if(mi == null)
      {
         loadMapItem(description, null);
         mi = (MapItem)mapItems.get(description);
      }
      if(mi == null) return;
      showMapItem(mi, trueMeansShow);
   }
      
   
   public void showMapItem(String description, Color color, boolean trueMeansShow)
   {
      MapItem mi = (MapItem)mapItems.get(description);
      if(mi == null)
      {
         loadMapItem(description, color);
         mi = (MapItem)mapItems.get(description);
      }
      if(mi == null) return;
      mi.color = color;
      showMapItem(mi, trueMeansShow);
   }
   
   private void showMapItem(MapItem mItem, boolean trueMeansShow)
   {
      if(trueMeansShow)
      {
         if(mItem.visible == true) return;
         switch(mItem.type)
         {
            case MapItem.LINESET:
               float[][][] lines = (float[][][])mItem.data;
               bgFaces.addLineset(lines, mItem.color);
               break;
            case MapItem.AREASET:
               float[][][] faces = (float[][][])mItem.data;
               for(int i = 0; i < faces.length; i++)
               {
                  bgFaces.addFace(faces[i], mItem.color);
               }
               break;
            case MapItem.POLBNDSET:
               Hashtable bnds = (Hashtable)mItem.data;
               Enumeration polbnd = bnds.keys();
               while(polbnd.hasMoreElements())
               {
                  float[][] face = (float[][])polbnd.nextElement();
                  bgFaces.addFace(face, (Color)bnds.get(face));
               }
               break;
            case MapItem.POINTSET:
               float[][] set = (float[][])mItem.data;
               bgFaces.addPointset(set, mItem.icon);
               break;
            default:
               break;
         }
         //if(!bgFaces.isEmpty()) 
         mapperPanel.setLayerAt(bgFaces, 0);
         mItem.visible = true;
      }
      else
      {
         if(mItem.visible == false) return;
         switch(mItem.type)
         {
            case MapItem.LINESET:
               bgFaces.removeLineset((float[][][])mItem.data);
               break;
            case MapItem.AREASET:
               float[][][] faces = (float[][][])mItem.data;
               for(int i = 0; i < faces.length; i++)
               {
                  bgFaces.removeFace(faces[i]);
               }
               break;
            case MapItem.POLBNDSET:
               Hashtable bnds = (Hashtable)mItem.data;
               Enumeration polbnds = bnds.keys();
               while(polbnds.hasMoreElements())
               {
                  float[][] face = (float[][])polbnds.nextElement();
                  bgFaces.removeFace(face);
               }
               break;
            case MapItem.POINTSET:
               float[][] set = (float[][])mItem.data;
               bgFaces.removePointset(set);
               break;
            default:
               break;
         }
         //if(!bgFaces.isEmpty()) 
         mapperPanel.setLayerAt(bgFaces, 0);
         mItem.visible = false;
      }
   }

   public void toggleMapItem(String description)
   {
      if(bettygui.BettyGui.DEBUG.equals("1")) { System.out.println("Mapper: Retrieving MapItem with description: \"" + description + "\"");}
      MapItem mi = (MapItem)mapItems.get(description);
      if(mi == null) showMapItem(description, true);
      else
      {
         if(bettygui.BettyGui.DEBUG.equals("1")) { System.out.println("Mapper: Found MapItem, calling showMapItem with " + !mi.visible);}
         showMapItem(mi, !mi.visible);
      }
   }

   protected float[][][] concatenate(float[][][] f1, float[][][] f2)
   {
      float[][][] result = null;
      try
      {
         result = new float[f1.length + f2.length][][];
      }
      catch(NullPointerException npe)
      {
         if(f1 == null) return f2;  // If one of the arguments was null,
         if(f2 == null) return f1;  // return the other -- it may be null too.
         throw npe;         // Something else is (horribly) wrong.
      }
      System.arraycopy(f1, 0, result, 0, f1.length);
      System.arraycopy(f2, 0, result, f1.length, f2.length);
      return result;
   }
   
   


   public void removeListeners() {mapperPanel.removeListeners();}

   public void removeMouseListener(MouseAdapter ma) {mapperPanel.removeMouseListener(ma);}

   public void restoreListeners() {mapperPanel.restoreListeners();}

   public void activateLink(Object src, Object dest) {
      mapperPanel.activateLink((ProtoElement)src, (ProtoElement)dest);
   }

   public void deactivateLink(Object src, Object dest) {
      mapperPanel.deactivateLink((ProtoElement)src, (ProtoElement)dest);
   }


   public ProtoElement getSelectedElement() {
      return mapperPanel.getSelectedElement();
   }

   public void setSelectedElement(Object e) {
      mapperPanel.setSelectedElement((ProtoElement)e);
   }

   public ProtoElement addElement(String name, Vec v)
   {
      return mapperPanel.addElement(name, v);
   }

   public ProtoElement addElement(String name, float lat, float lon) {
      return mapperPanel.addElement(name, lat, lon);
   }

   public ProtoElement addElement(String name, int x, int y) {
      return mapperPanel.addElement(name, x, y);
   }

   public ProtoElement addElement(String name, Vec v, Image icon)
   {
      return mapperPanel.addElement(name, v, icon);
   }

   public ProtoElement addElement(String name, float lat, float lon, Image icon, boolean drawable) {
      return mapperPanel.addElement(name, lat, lon, icon, drawable);
   }

   public ProtoElement addElement(String name, float lat, float lon, Image icon) {
      return mapperPanel.addElement(name, lat, lon, icon);
   }

   public ProtoElement addElement(String name, int x, int y, Image icon) {
      return mapperPanel.addElement(name, x, y, icon);
   }

   public void empty() {mapperPanel.empty();}

   public void addLink(Object src, Object dest) {
      mapperPanel.addLink((ProtoElement)src, (ProtoElement)dest);
   }

   public void hideIcon(Object element) {
      mapperPanel.hideIcon((ProtoElement)element);
   }

   public void removeLink(Object src, Object dest) {
      mapperPanel.removeLink((ProtoElement)src, (ProtoElement)dest);
   }

   public void subElementUpdate(Object element, PolygonSubElement sub, Vec[] vertices) {
      mapperPanel.subElementUpdate((ProtoElement)element, sub, vertices);
   }

//   public void subElementUpdate(Object element, IconSubElement sub, Vec vertices) {
//      mapperPanel.subElementUpdate((ProtoElement)element, sub, vertices);
//   }

   public void subElementUpdate(Object element, PolygonSubElement sub, Vec[] vertices, int status) {
      mapperPanel.subElementUpdate((ProtoElement)element, sub, vertices, status);
   }

   public void subElementUpdate(Object element, SubElement sub, boolean state) {
      mapperPanel.subElementUpdate((ProtoElement)element, sub, state);
   }

   public void subElementUpdate(Object element, SubElement sub, int status) {
      mapperPanel.subElementUpdate((ProtoElement)element, sub, status);
   }

   public void subElementUpdate(Object element, SubElement sub, Vec v) {
      mapperPanel.subElementUpdate((ProtoElement)element, sub, v);
   }

   public PolygonSubElement addPolygonSubElement(Object element, int status) {
      return mapperPanel.addPolygonSubElement((ProtoElement)element, status);
   }

   public PolygonSubElement addPolylineSubElement(Object element, int status) {
      return mapperPanel.addPolylineSubElement((ProtoElement)element, status);
   }

   public CrossHairSubElement addCrossHairSubElement(Object element, int status) {
      return mapperPanel.addCrossHairSubElement((ProtoElement)element, status);
   }

   public CircleSubElement addCircleSubElement(Object element, float x, float y, float width, float height) {
      return mapperPanel.addCircleSubElement((ProtoElement)element, x, y, width, height);
   }

   public AbsoluteCircleSubElement addAbsoluteCircleSubElement(Object element){
      return mapperPanel.addAbsoluteCircleSubElement((ProtoElement)element);
   }

   public IconSubElement addIconSubElement(Object element, Image icon,boolean absolute){
      return mapperPanel.addIconSubElement((ProtoElement)element, icon, absolute);
   }


   public void subElementUpdate(ProtoElement proto, AbsoluteCircleSubElement sub, float x, float y, float width, float height){
      mapperPanel.subElementUpdate(proto, sub, x, y, width, height);
   }   

   public void processECEvent(ECEvent e) {
      mapperPanel.processECEvent(e);
   }

   public void addECEListener(ECEListener l){
      mapperPanel.addECEListener(l);
   }

   public void removeECEListener(ECEListener l){
      mapperPanel.addECEListener(l);
   }
   
   public void setEditMode(boolean mode) {
      mapperPanel.setEditMode(mode);
   }

   public void removeElement(Object e) {
      mapperPanel.removeElement((ProtoElement)e);
   }

   public Vec getLocationLatLon(Object e)/*cth*/
   {
      return mapperPanel.getLocationLatLon((ProtoElement)e);
   }
   
   public void addMenuItem(String label, Object target, Method functionPtr)
   {
      if(controlMenu != null)
      {
         controlMenu.addMenuItem(label, target, functionPtr);
      }
   }
   
   MouseAdapter ma;
   
   public void rigForDragAndDrop()
   {
      removeListeners();
      if (ma == null)
      {
         ma = new MouseAdapter() {
            public void mousePressed(MouseEvent me)
            {
               float[] coords = mapperPanel.xy2LatLon(me.getX(), me.getY());
               processECEvent(new ECEvent(ECEvent.DRAG_CAUGHT, null, (double)coords[0], (double)coords[1]));
            }
         };
         addMouseListener(ma);
      }
   }
   
   public void recoverFromDragAndDrop()
   {
          removeMouseListener(ma);
          ma = null;
      restoreListeners();
   }

   /**
    * For unit testing.
    */
   public static void main(String[] args)
   {
      Runtime rt = Runtime.getRuntime();
      System.out.println("Memory:  Total = " + rt.totalMemory() + "   Free = " + rt.freeMemory());
      Frame f = new Frame("Mapper Test");
      f.setSize(600, 400);
      f.addWindowListener(
                    new java.awt.event.WindowAdapter()
                    {
                        public void windowClosing(WindowEvent e)
                        {
                            System.exit(0);
                        }
                    }
                                                          );
      System.out.println("Mapper Memory:  Total = " + rt.totalMemory() + "   Free = " + rt.freeMemory());
                                                          
      Mapper m = new Mapper();
      
      // Use either setExtents or setCenter.  setExtents just passes
      // the center of the extents to setCenter.
      
      //m.setExtents((float)25, 32, (float)40, 60);     // SWA
      //m.setExtents(35, 120, 45, 140);     // NEA
      //m.setExtents((float)-8, (float)-43, (float)-0, (float)-38);  // Brazil
      //m.setExtents((float)32, (float)-112, (float)34, (float)-110);  // noamer?
      
      //m.setCenter((float)41, (float)-85);     // Great Lakes
      //m.setCenter((float)40, (float)130);
      
      //m.setZoom(40);
      
      java.io.File loc = null;
      
      if(args.length == 0)
      {
         FileDialog fd = new FileDialog(f, "Choose a \"locale\" file:", FileDialog.LOAD);
         fd.show();
         loc = new java.io.File(fd.getDirectory(), fd.getFile());
      }
      else
      {
         loc = new java.io.File(args[0]);
      }
         
      try
      {
         geolocale.Locale l = LocaleBroker.getLocale(loc);
         m.setLocale(l);
      }
      catch(Exception e)
      {
         e.printStackTrace();
      }
      f.add(m);
      f.show();
      System.out.println("Mapper Memory:  Total = " + rt.totalMemory() + "   Free = " + rt.freeMemory());
   }
}
