// $Id: RangeDateSelector.java,v 1.3 2004/09/24 03:26:34 caron Exp $
/*
 * Copyright 2002-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.ui;

import thredds.datatype.*;
import thredds.datatype.prefs.*;
import thredds.datatype.*;
import thredds.viewer.ui.event.*;

import ucar.util.prefs.ui.*;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import java.beans.*;

/**
 * Widget to select a point or a range from a date range.
 */

public class RangeDateSelector extends JPanel {
  static private final int SLIDER_RESOLUTION = 1000;

  private String title, helpMessage;
  private boolean acceptButton, isPointOnly;

  private DateType minLimit, maxLimit;
  private DateRange dateRange;

  private JSlider minSlider, maxSlider;
  private PrefPanel pp;
  private JLabel minLabel, maxLabel;
  private DateField minField, maxField;
  private DurationField durationField;
  private Scale scale;
  private JButton helpButton;
  private HelpWindow helpWindow;

    // event management
  private ActionSourceListener actionSource;
  private String actionName = "rangeDateSelection";
  private boolean eventOK = true;

  private static boolean debugEvent = false;

  /**
   * Constructor using info from thredds DQC.
   *
   * @param title widget title displayed to user, may be null
   * @param start starting date as a string
   * @param end ending date as a string
   * @param durationS duration as a String
   * @param minInterval minimum useful interval as a String, may be null.
   * @param acceptButton add an acceptButton
   * @param help optional help text
   * @param pointOnly if user can only select one point, otherwise can select a range of dates.
   * @throws java.text.ParseException
   */
  public RangeDateSelector(String title, String start, String end, String durationS, String minInterval,
      boolean acceptButton, String help, boolean pointOnly) throws java.text.ParseException {

    this( title,
          new DateRange( new DateType(start, null, null), new DateType(end, null, null),
                         new TimeDuration(durationS), new TimeDuration(minInterval)),
          acceptButton, help, pointOnly);
  }

  /**
   * Constructor.
   * @param title widget title displayed to user, may be null
   * @param range range that the user can select from
   * @param acceptButton add an acceptButton
   * @param help optional help text
   * @param pointOnly if user can only select one point, otherwise can select a range of dates.
   */
  public RangeDateSelector(String title, DateRange range, boolean acceptButton, String help, boolean pointOnly) {
    this.title = title;
    this.dateRange = range;
    this.acceptButton = acceptButton;
    this.helpMessage = help;
    this.isPointOnly = pointOnly;

    init();
  }

  private void init() {

    // UI
    JPanel sliderPanel = new JPanel();

     // optional top panel
    JPanel topPanel = null;
    if ((title != null) || (helpMessage != null)) {
      topPanel = new JPanel(new BorderLayout());
      JPanel butts = new JPanel();

      if (title != null)
        topPanel.add(BorderLayout.WEST, new JLabel("  "+title+":"));

      if (helpMessage != null) {
        helpButton = new JButton("help");
        helpButton.addActionListener(new AbstractAction() {
          public void actionPerformed(ActionEvent e) {
            if (helpWindow == null)
              helpWindow = new HelpWindow(null, "Help on " + title, helpMessage);
            helpWindow.show(helpButton);
          }
        });
        butts.add(helpButton);
      }

      if (acceptButton) {
        JButton okButton = new JButton("accept");
        okButton.addActionListener(new AbstractAction() {
          public void actionPerformed(ActionEvent e) {
            pp.accept();
            sendEvent();
          }
        });

        butts.add(okButton);
        acceptButton = false; // dont need it in prefpanel
      }

      topPanel.add(BorderLayout.EAST, butts);
    }

    // the sliders
    sliderPanel.setLayout( new BoxLayout(sliderPanel, BoxLayout.Y_AXIS));
    sliderPanel.setBorder(new LineBorder(Color.black, 1, true));

    minSlider = new JSlider(JSlider.HORIZONTAL, 0, SLIDER_RESOLUTION, 0);
    maxSlider = new JSlider(JSlider.HORIZONTAL, 0, SLIDER_RESOLUTION, SLIDER_RESOLUTION);

    Border b = BorderFactory.createEmptyBorder(0,15,0,15);
    minSlider.setBorder(b);
    maxSlider.setBorder(b);

    // set this so we can call setDateRange();
    minLabel = new JLabel();
    maxLabel = new JLabel();
    minField = new DateField("min", isPointOnly ? "value" : "start", dateRange.getStart(), null);
    maxField = new DateField("max", "end", dateRange.getEnd(), null);
    durationField = new DurationField("duration", "duration", dateRange.getDuration(), null);

    setDateRange(dateRange);

    JPanel labelPanel = new JPanel( new BorderLayout());
    labelPanel.add( minLabel, BorderLayout.WEST);
    labelPanel.add( maxLabel, BorderLayout.EAST);

    // the fields use a PrefPanel
    pp = new PrefPanel( null, null);
    int row = 0;
    //if (tit != null) {
    //  pp.addComponent(new JLabel(tit), col, 0, null);
    //  col+=2;
    //}
    if (isPointOnly) {
      pp.addField( minField, 0, row++, null);
    } else {
      pp.addField( minField, 0, row++, null);
      pp.addField( maxField, 0, row++, null);
      pp.addField( durationField, 0, row++, null);
    }
    pp.finish(acceptButton, BorderLayout.EAST);

    // overall layout
    if (topPanel != null)
     sliderPanel.add( topPanel);
    sliderPanel.add( pp);
    if (!isPointOnly) sliderPanel.add( maxSlider);
    sliderPanel.add( minSlider);
    sliderPanel.add( labelPanel);

    setLayout(new BorderLayout()); // allow width expansion
    add( sliderPanel, BorderLayout.NORTH);

    /// event management

    // listen for changes from user manupulation
    maxSlider.addChangeListener( new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
        if (!eventOK) return;

        int pos = maxSlider.getValue();
        dateRange.setEnd( scale.slider2world(pos));

        // change value of the field
        eventOK = false;
        maxField.setEditValue( dateRange.getEnd());
        durationField.setEditValue( dateRange.getDuration());
        eventOK = true;

       if (dateRange.isPoint())
          minSlider.setValue(pos); // drag min along */
     }
    });

    minSlider.addChangeListener( new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
        if (!eventOK) return;

        int pos = minSlider.getValue();
        dateRange.setStart( scale.slider2world(pos));

        // change value of the field
        eventOK = false;
        minField.setEditValue( dateRange.getStart());
        if (!isPointOnly)
          durationField.setEditValue( dateRange.getDuration());
        eventOK = true;

        if (dateRange.isPoint() && !isPointOnly)
          maxSlider.setValue(pos); // drag max along
     }
    });

    minField.addPropertyChangeListener(new PropertyChangeListener() {
      public void propertyChange( PropertyChangeEvent e) {
        if (debugEvent) System.out.println("minField event= "+e.getNewValue()+" "+e.getNewValue().getClass().getName());
        if (!eventOK) return;

        DateType val = (DateType) minField.getEditValue();
        if (maxLimit.after(val.getDate()) && minLimit.before(val.getDate())) {
          eventOK = false;
          minSlider.setValue( scale.world2slider( val));
          dateRange.setStart( val);
          minField.setEditValue(dateRange.getStart());
          if (!isPointOnly)
            durationField.setEditValue( dateRange.getDuration());
          eventOK = true;
        } else
          minField.setEditValue( dateRange.getStart());
      }
    });

    if (maxField != null) {
      maxField.addPropertyChangeListener(new PropertyChangeListener() {
        public void propertyChange(PropertyChangeEvent e) {
          if (debugEvent) System.out.println("maxField event= "+e.getNewValue());
          if (!eventOK) return;

        DateType val = (DateType) maxField.getEditValue();
        if (maxLimit.after(val.getDate()) && minLimit.before(val.getDate())) {
          eventOK = false;
          maxSlider.setValue( scale.world2slider( val));
          dateRange.setEnd( val);
          maxField.setEditValue(dateRange.getEnd());
          if (!isPointOnly)
            durationField.setEditValue( dateRange.getDuration());

          eventOK = true;
        } else
          maxField.setEditValue( dateRange.getEnd());
        }
      });
    }

      // listen for outside changes
    actionSource = new ActionSourceListener(actionName) {
      public void actionPerformed( ActionValueEvent e) {
        if (debugEvent) System.out.println(" actionSource event "+e);
        //?? setSelectedByName( e.getValue().toString());
      }
    };

        // catch resize events on the slider
    /* minSlider.addComponentListener( new ComponentAdapter() {
      public void componentResized( ComponentEvent e) {
        setLabels();
      }
    }); */
  }

  public void setDateRange(DateRange dateRange) {
    this.minLimit = new DateType( dateRange.getStart());
    this.maxLimit = new DateType( dateRange.getEnd());
    this.scale = new Scale( dateRange);

    minLabel.setText(" "+minLimit.getText()+" ");
    maxLabel.setText(" "+maxLimit.getText()+" ");

    if (isPointOnly) {
      minField.setEditValue( dateRange.getStart());
    } else {
      minField.setEditValue( dateRange.getStart());
      maxField.setEditValue( dateRange.getEnd());
      durationField.setEditValue( dateRange.getDuration());
    }
  }

  public DateRange getSelectedDateRange() {
    dateRange.setStart( minField.getDate());
    dateRange.setEnd( maxField.getDate());
    return dateRange;
  }

  public void sendEvent() {
        // gotta do this after the dust settles
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        System.out.println("event range= "+dateRange);
        actionSource.fireActionValueEvent(actionName, this);
      } // run
    }); // invokeLater */
  }

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

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

  private class Scale {
    private double min; // secs
    private double scale;  // pixels / secs

    Scale( DateRange dateRange) {
      this.min = .001 * dateRange.getStart().getDate().getTime();
      //this.max = .001 * dateRange.getEnd().getDate().getTime();
      //scale = SLIDER_RESOLUTION / (this.max - this.min);

      scale = SLIDER_RESOLUTION / dateRange.getDuration().getSeconds();
      // System.out.println("slider scale= "+scale);
    }

    private int world2slider(DateType val) {
      double msecs = .001 * val.getDate().getTime() - min;
      return (int) (scale * msecs);
    }

    private DateType slider2world(int pval) {
      double val = pval / scale; // secs
      //double floor = Math.floor(val / resolution);
      //double incr = floor * resolution;
      double msecs = 1000 * (min + val);
      return new DateType(false, new java.util.Date( (long) msecs));
    }
  }

  public static void main(String args[]) throws java.text.ParseException {

    JFrame frame = new JFrame("Test Date Range Selector");
    frame.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {System.exit(0);}
    });


    RangeDateSelector rs1 = new RangeDateSelector("Date Range", "1990-01-01T01:00:00", "1990-01-01T02:00:00",
       null, "15 minute", true, "i think im fallin", false);
    RangeDateSelector rs2 = new RangeDateSelector("Date", "1990-01-01", "1991-01-01", null, "1 day", true,
       "i think im fallin\n in love with youuuu ", false);
    RangeDateSelector rs3 = new RangeDateSelector("Date", "1990-01-01", "1991-01-01", null, "10 days", true,
       null, true);
    RangeDateSelector rs4 = new RangeDateSelector("Date", null, "present", "10 days", "1 day", true,
       null, false);

    Box main = new Box( BoxLayout.Y_AXIS);

    frame.getContentPane().add(main);
    //main.setPreferredSize(new Dimension(400, 200));
    //main.add( new JSlider(), BorderLayout.NORTH);
    main.add( rs1);
    main.add( rs2);
    main.add( rs3);
    main.add( rs4);

    frame.pack();
    frame.setLocation(400, 300);
    frame.setVisible(true);
  }

}

/* Change History:
   $Log: RangeDateSelector.java,v $
   Revision 1.3  2004/09/24 03:26:34  caron
   merge nj22

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

*/