// $Id: StationRegionChooser.java,v 1.3 2004/09/30 00:33:40 caron Exp $
/*
 * Copyright 1997-2004 Unidata Program Center/University Corporation for
 * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
 * support@unidata.ucar.edu.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at
 * your option) any later version.
 *
 * This library 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 Lesser
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package thredds.viewer.ui.station;

import thredds.ui.PopupMenu;
import thredds.viewer.ui.*;
import thredds.viewer.ui.geoloc.*;
import thredds.viewer.ui.event.*;
import thredds.viewer.gis.MapBean;
import thredds.ui.BAMutil;
import thredds.ui.PopupManager;

import ucar.unidata.geoloc.*;
import ucar.util.prefs.ui.*;

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import javax.swing.border.EtchedBorder;
import javax.swing.event.EventListenerList;


/**
 * A Swing widget for THREDDS clients to choose a station and/or a region from navigatable map.
 * <p>
 * Typically a user listens for property change events:
 *  <pre>
 *   stationRegionChooser.addPropertyChangeListener( new PropertyChangeListener() {
        public void propertyChange(java.beans.PropertyChangeEvent e) {
          if (e.getPropertyName().equals("Station")) {
            selectedStation = (StationIF) e.getNewValue();
            ...
          }
          else if (e.getPropertyName().equals("GeoRegion")) {
            geoRegion = (ProjectionRect) e.getNewValue();
            ...
          }
        }
      });
   </pre>
 *
 * @author John Caron
 * @version $Id: StationRegionChooser.java,v 1.3 2004/09/30 00:33:40 caron Exp $
 */

/* implementation note:
 * do we wnat to remove actionSource ? we have setSelectedStation instead.
 */
public class StationRegionChooser extends JPanel {
  private boolean regionSelect = true, stationSelect = true;

  private NavigatedPanel np;
  private JPanel toolPanel;
  private thredds.viewer.ui.Renderer worldMapRender, detailWorldMapRender, detailUSMapRender;
  private thredds.viewer.ui.Renderer mapRender = null;
  private StationRenderer stnRender = null;
  private AffineTransform atI = new AffineTransform();  // identity transform

  // selections and state info
  private StationIF selectedStation;
  private ProjectionRect geoBounds;
  private ProjectionRect geoSelection;

  private boolean geoSelectionMode = false;
  private Color outlineColor = Color.black;
  private int nfracDig = 3;

  // map renderer
  private thredds.ui.PopupMenu mapBeanMenu;
  private int mapBeanCount = 0;

  // prefs
  private Field.Double minLonField, maxLonField, minLatField, maxLatField;

  // events
  private AbstractAction mapSelectAction;
  private EventListenerList listenerList = new EventListenerList();
  private ActionSourceListener actionSource;

  // local caches
  private PopupManager popupInfo = new PopupManager("Station Info");
  private StringBuffer sbuff = new StringBuffer();

  // debugging
  private boolean debugEvent = false;

  /**
   * Default Contructor, allow both region and station selection.
   */
  public StationRegionChooser() {
    this(true, true);
  }

  /**
   * Constructor
   * @param regionSelect
   * @param stationSelect
   */
  public StationRegionChooser(boolean regionSelect, boolean stationSelect) {
    this.regionSelect = regionSelect;
    this.stationSelect = stationSelect;

    np = new NavigatedPanel();
    np.setGeoSelectionMode( regionSelect && geoSelectionMode);
    // setGeoBounds( np.getMapArea());

    if (regionSelect) {
      double defArea = 1.0/8; // default area is 1/4 total
      LatLonRect llbb = np.getProjectionImpl().getDefaultMapAreaLL();
      LatLonPointImpl left = llbb.getLowerLeftPoint();
      LatLonPointImpl right = llbb.getUpperRightPoint();
      double centerLon = llbb.getCenterLon();
      double width = llbb.getWidth();
      double centerLat = (right.getLatitude() + left.getLatitude())/2;
      double height = right.getLatitude() - left.getLatitude();
      right = new LatLonPointImpl( centerLat + height*defArea, centerLon + width*defArea);
      left = new LatLonPointImpl( centerLat - height*defArea, centerLon - width*defArea);
      LatLonRect selected =  new LatLonRect( left, right);
      setGeoSelection( selected);

      // get GeoSelectionEvents from the navigated panel
      np.addGeoSelectionListener( new GeoSelectionListener() {
        public void actionPerformed(GeoSelectionEvent e) {
          setGeoSelection( e.getProjectionRect());
          if (debugEvent) System.out.println("GeoSelectionEvent="+geoSelection);
          firePropertyChangeEvent(geoSelection, "GeoRegion");
          redraw();
        }
      });
    }

    worldMapRender = new thredds.viewer.gis.worldmap.WorldMap();
    mapRender = worldMapRender;
    stnRender = new StationRenderer();

    ProjectionImpl project = np.getProjectionImpl();
    mapRender.setProjection( project);
    stnRender.setProjection( project);

    // get Projection Events from the navigated panel: (latlon seam crossing)
    np.addNewProjectionListener( new NewProjectionListener() {
      public void actionPerformed( NewProjectionEvent e) {
        mapRender.setProjection( e.getProjection());
        stnRender.setProjection( e.getProjection());
        redraw();
      }
    });

    // get NewMapAreaEvents from the navigated panel: pan/zoom
    np.addNewMapAreaListener( new NewMapAreaListener() {
      public void actionPerformed(NewMapAreaEvent e) {
        redraw();
      }
    });

    if (stationSelect) {
      // get Pick events from the navigated panel: mouse click
      np.addPickEventListener( new PickEventListener() {
        public void actionPerformed(PickEvent e) {
          selectedStation = stnRender.pick(e.getLocation());
          if (selectedStation != null) {
            redraw();
            firePropertyChangeEvent(selectedStation, "Station");
            actionSource.fireActionValueEvent( ActionSourceListener.SELECTED, selectedStation);
          }
        }
      });


      // get mouse motion events
      np.addMouseMotionListener( new MouseMotionAdapter() {
        public void mouseMoved(MouseEvent e) {
          Point p = e.getPoint();
          StationRenderer.StationUI sui = stnRender.isOnStation(p);

          if (sui != null) {
            StationIF s = sui.getStation();
            sbuff.setLength(0);
            sbuff.append(s.getName());
            sbuff.append(" ");
            sbuff.append("\n");
            if (null != s.getDescription())
              sbuff.append(s.getDescription()+"\n");
            sbuff.append( LatLonPointImpl.latToString(s.getLatitude(), 4));
            sbuff.append(" ");
            sbuff.append( LatLonPointImpl.lonToString(s.getLongitude(), 4));
            sbuff.append(" ");
            sbuff.append( ucar.unidata.util.Format.d(s.getElevation(),0));
            sbuff.append(" m");

            popupInfo.show(sbuff.toString(), p, StationRegionChooser.this, s);
          }
          else
            popupInfo.hide();
        }
      });

      // get mouse exit events
      np.addMouseListener( new MouseAdapter() {
        public void mouseExited(MouseEvent e) {
          popupInfo.hide();
        }
      });

        // station was selected
      actionSource = new ActionSourceListener("station") {
        public void actionPerformed( ActionValueEvent e) {
          if (debugEvent) System.out.println(" StationdatasetChooser: actionSource event "+e);
          selectedStation = (StationIF) e.getValue();
          redraw();
        }
      };
    }

    makeUI();
  }

  private void makeUI() {

    AbstractAction incrFontAction = new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        stnRender.incrFontSize();
        redraw();
      }
    };
    BAMutil.setActionProperties( incrFontAction, "FontIncr", "increase font size", false, 'I', -1);

    AbstractAction decrFontAction = new AbstractAction() {
      public void actionPerformed(ActionEvent e) {
        stnRender.decrFontSize();
        redraw();
      }
    };
    BAMutil.setActionProperties( decrFontAction, "FontDecr", "decrease font size", false, 'D', -1);

    JCheckBox declutCB = new JCheckBox("Declutter", true);
    declutCB.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        setDeclutter(((JCheckBox) e.getSource()).isSelected());
      }
    });

    AbstractAction bbAction = new AbstractAction() {
       public void actionPerformed(ActionEvent e) {
         geoSelectionMode = !geoSelectionMode;
         np.setGeoSelectionMode( geoSelectionMode);
         redraw();
       }
     };
     BAMutil.setActionProperties( bbAction, "geoselect", "select geo region", true, 'B', -1);
     bbAction.putValue(BAMutil.STATE, geoSelectionMode ? Boolean.TRUE : Boolean.FALSE );

     // the fields use a PrefPanel
     PrefPanel pp = new PrefPanel( null, null);
     minLonField = pp.addDoubleField("minLon", "minLon", geoSelection.getMinX(), nfracDig, 0, 0, null);
     maxLonField = pp.addDoubleField("maxLon", "maxLon", geoSelection.getMaxX(), nfracDig, 2, 0, null);
     minLatField = pp.addDoubleField("minLat", "minLat", geoSelection.getMinY(), nfracDig, 4, 0, null);
     maxLatField = pp.addDoubleField("maxLat", "maxLat", geoSelection.getMaxY(), nfracDig, 6, 0, null);

     pp.finish(false, BorderLayout.EAST);
     pp.addActionListener(new ActionListener() {
       public void actionPerformed(ActionEvent e) {
         // "Apply" was called
         double minLon = minLonField.getDouble();
         double minLat = minLatField.getDouble();
         double maxLon = maxLonField.getDouble();
         double maxLat = maxLatField.getDouble();
         LatLonRect llbb = new LatLonRect( new LatLonPointImpl(minLat, minLon),
                                           new LatLonPointImpl(maxLat, maxLon));
         setGeoSelection( llbb);
         redraw();
       }
     });

    // assemble
    setLayout(new BorderLayout());
    toolPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
    toolPanel.add(declutCB);
    if (regionSelect) BAMutil.addActionToContainer( toolPanel, bbAction);

    mapBeanMenu = MapBean.makeMapSelectButton();
    toolPanel.add( mapBeanMenu.getParentComponent());

    if (stationSelect) BAMutil.addActionToContainer( toolPanel, incrFontAction);
    if (stationSelect) BAMutil.addActionToContainer( toolPanel, decrFontAction);
    toolPanel.add(np.getNavToolBar());
    toolPanel.add(np.getMoveToolBar());
    if (regionSelect) BAMutil.addActionToContainer(toolPanel, np.setReferenceAction);

    JPanel upperPanel = new JPanel(new BorderLayout());
    if (regionSelect) upperPanel.add(pp, BorderLayout.NORTH);
    upperPanel.add(toolPanel, BorderLayout.SOUTH);

    JPanel statusPanel = new JPanel(new BorderLayout());
    statusPanel.setBorder(new EtchedBorder());
    JLabel positionLabel = new JLabel("position");
    statusPanel.add(positionLabel, BorderLayout.CENTER);

    np.setPositionLabel(positionLabel);
    add(upperPanel, BorderLayout.NORTH);
    add(np, BorderLayout.CENTER);
    add(statusPanel, BorderLayout.SOUTH);
  }


         // add a MapBean to the User Interface
  public void addMapBean( thredds.viewer.gis.MapBean mb) {
    mapBeanMenu.addAction( mb.getActionDesc(), mb.getIcon(), mb.getAction());

    // first one is the "default"
    if (mapBeanCount == 0) {
      mapRender = mb.getRenderer();
      mapRender.setProjection( np.getProjectionImpl());
    }
    mapBeanCount++;

   mb.addPropertyChangeListener( new PropertyChangeListener() {
     public void propertyChange( java.beans.PropertyChangeEvent e) {
       if (e.getPropertyName().equals("Renderer")) {
         mapRender = (thredds.viewer.ui.Renderer) e.getNewValue();
         mapRender.setProjection( np.getProjectionImpl());
         redraw();
       }
     }
   });

  }

  /**
   * Add a PropertyChangeListener. Throws a PropertyChangeEvent:
   * <ul>
   *   <li>propertyName = "Station", getNewValue() = StationIF
   *   <li>propertyName =  "GeoRegion", getNewValue() = ProjectionRect
   * </ul>
   */
  public void addPropertyChangeListener( PropertyChangeListener l) {
    listenerList.add(PropertyChangeListener.class, l);
  }

  /**
   * Remove a PropertyChangeEvent Listener.
   */
  public void removePropertyChangeListener( PropertyChangeListener l) {
    listenerList.remove(PropertyChangeListener.class, l);
  }

  /**
   * Add an action to the toolbar.
   */
  public void addToolbarAction(AbstractAction act) {
    BAMutil.addActionToContainer( toolPanel, act);
  }

  // Notify all listeners that have registered interest for
  // notification on this event type.  The event instance
  // is lazily created using the parameters passed into
  // the fire method.
  // see addPropertyChangeListener for list of valid arguments
  private void firePropertyChangeEvent(Object newValue, String propertyName) {
    PropertyChangeEvent event = null;
    Object[] listeners = listenerList.getListenerList();
    // Process the listeners last to first
    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == PropertyChangeListener.class) {
        if (event == null) // Lazily create the event:
          event = new PropertyChangeEvent(this, propertyName, null, newValue);
        // send event
        ((PropertyChangeListener)listeners[i+1]).propertyChange(event);
      }
    }
  }

      /** add ActionValueListener listener */
  public void addActionValueListener( ActionValueListener l) { actionSource.addActionValueListener(l); }
    /** remove ActionValueListener listener */
  public void removeActionValueListener( ActionValueListener l) { actionSource.removeActionValueListener(l); }

    /** better way to do event management */
  public ActionSourceListener getActionSourceListener() { return actionSource; }

  public void setMapArea(ProjectionRect ma) {
    np.getProjectionImpl().setDefaultMapArea( ma);
    //np.setMapArea(ma);
  }

  /**
   * Set the list of Stations.
   * @param stns list of StationIF
   */
  public void setStations(java.util.List stns) {
    stnRender.setStations( stns);
  }

  /**
   * Looks for the station with givemn id. If found, makes it current. Redraws.
   * @param id must match stationIF.getID().
   */
  public void setSelectedStation( String id) {
    stnRender.setSelectedStation( id);
    selectedStation = stnRender.getSelectedStation();
    np.setLatLonCenterMapArea( selectedStation.getLatitude(), selectedStation.getLongitude());
    redraw();
  }

  /**
   * Get currently selected station, or null if none.
   * @return
   */
  public StationIF getSelectedStation( ) { return selectedStation; }


  /**
   * Access to the navigated panel.
   * @return  navigated panel object
   */
  public NavigatedPanel getNavigatedPanel() { return np; }

  /**
   *  Change the state of decluttering
   */
  public void setDeclutter(boolean declut) {
    stnRender.setDeclutter(declut);
    redraw();
  }

  /**
   * Get the state of the declutter flag.
   */
  public boolean getDeclutter() { return stnRender.getDeclutter(); }

  /**
   * set the Projection used in the navigation.
   * @param p: projection.
   */
  public void setProjectionImpl( ProjectionImpl p) {
    mapRender.setProjection( p);
    stnRender.setProjection( p); // ??
    np.setProjectionImpl(p);
    redraw();
  }

  public ProjectionImpl getProjectionImpl() { return np.getProjectionImpl(); } // ??

  // return the state of using the detailed map */
  //public boolean useDetailedMapRenderer() { return mapRender == detailMapRender; }


  /**
   *  Redraw the graphics on the screen.
   */
  public void redraw() {
    java.awt.Graphics2D gNP = np.getBufferedImageGraphics();
    if (gNP == null) // panel not drawn on screen yet
        return;

      // clear it
    gNP.setBackground(np.getBackgroundColor());
    java.awt.Rectangle r = gNP.getClipBounds();
    gNP.clearRect(r.x, r.y, r.width, r.height);

    if (regionSelect && geoSelectionMode) {
      if (geoSelection != null) drawBB( gNP, geoSelection, Color.cyan);
      if (geoBounds != null) drawBB( gNP, geoBounds, null);
      // System.out.println("GeoRegionChooser.redraw geoBounds= "+geoBounds);

      if (geoSelection != null) {
        // gNP.setColor( Color.orange);
        Navigation navigate = np.getNavigation();
        double handleSize = RubberbandRectangleHandles.handleSizePixels / navigate.getPixPerWorld();
        RubberbandRectangleHandles.drawHandledRect( gNP, geoSelection, handleSize);
        if (debug) System.out.println("GeoRegionChooser.drawHandledRect="+handleSize+" = "+geoSelection);
      }
    }

    mapRender.draw(gNP, atI);
    if (stationSelect) stnRender.draw(gNP, atI);

    gNP.dispose();

    // copy buffer to the screen
    np.repaint();
  }

   private void drawBB(java.awt.Graphics2D g, ProjectionRect bb, Color fillColor) {
    if ( null != fillColor) {
      g.setColor(fillColor);
      g.fill(bb);
    }
    g.setColor(outlineColor);
    g.draw(bb);
  }
  private boolean debug = false;

  public void setGeoBounds( LatLonRect llbb) {
    np.setMapArea( llbb);
    geoBounds = np.getProjectionImpl().latLonToProjBB( llbb);
    np.getProjectionImpl().setDefaultMapArea( geoBounds);
  }
  public void setGeoBounds( ProjectionRect bb) {
    geoBounds = new ProjectionRect( bb);
    np.setMapArea( bb);
    np.getProjectionImpl().setDefaultMapArea( geoBounds);
  }
  public void setGeoSelection( LatLonRect llbb) {
    np.setGeoSelection( llbb);
    setGeoSelection( np.getGeoSelection());
  }
  public void setGeoSelection( ProjectionRect bb) {
    geoSelection = bb;
    if (minLonField != null) {
      minLonField.setDouble(geoSelection.getMinX());
      minLatField.setDouble(geoSelection.getMinY());
      maxLonField.setDouble(geoSelection.getMaxX());
      maxLatField.setDouble(geoSelection.getMaxY());
    }
    np.setGeoSelection( geoSelection);
  }

  public LatLonRect getGeoSelectionLL() { return np.getGeoSelectionLL(); }
  public ProjectionRect getGeoSelection() { return np.getGeoSelection(); }
  public boolean getGeoSelectionMode() { return geoSelectionMode; }

  /** Wrap this in a JDialog component.
   *
   * @param parent      JFrame (application) or JApplet (applet) or null
   * @param title       dialog window title
   * @param modal     is modal
   */
  public JDialog makeDialog( RootPaneContainer parent, String title, boolean modal) {
    return new Dialog( parent, title, modal);
  }

  private class Dialog extends JDialog {

    private Dialog(RootPaneContainer parent, String title, boolean modal) {
      super(parent instanceof Frame ? (Frame) parent : null, title, modal);

      // L&F may change
      UIManager.addPropertyChangeListener( new PropertyChangeListener() {
        public void propertyChange( PropertyChangeEvent e) {
          if (e.getPropertyName().equals("lookAndFeel"))
            SwingUtilities.updateComponentTreeUI( StationRegionChooser.Dialog.this);
        }
      });

      // add a dismiss button
      JPanel buttPanel = new JPanel();
      JButton dismissButton = new JButton("Dismiss");
      dismissButton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent evt) {
          setVisible(false);
        }
      });
      buttPanel.add(dismissButton, null);

     // add it to contentPane
      Container cp = getContentPane();
      cp.setLayout(new BorderLayout());
      cp.add( StationRegionChooser.this, BorderLayout.CENTER);
      cp.add( buttPanel, BorderLayout.SOUTH);
      pack();
    }
  }

  public static void main(String[] args) {
    StationRegionChooser slm = new StationRegionChooser();
    slm.setBounds( new Rectangle( 10, 10, 400, 200));

    JFrame frame = new JFrame("StationRegionChooser Test");
    frame.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        System.exit(0);
      }
    });
    frame.getContentPane().add(slm);
    frame.pack();
    frame.show();
  }
}

/* Change History:
   $Log: StationRegionChooser.java,v $
   Revision 1.3  2004/09/30 00:33:40  caron
   *** empty log message ***

   Revision 1.2  2004/09/28 21:39:11  caron
   *** empty log message ***

   Revision 1.1  2004/09/24 03:26:42  caron
   merge nj22

   Revision 1.2  2004/05/21 05:57:33  caron
   release 2.0b

*/
