/*
 * StationNearestView.java
 *
 * Created on Aug 5, 2009, 11:56:48 AM
 */
package usda.weru.weps.location.chooser;

import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import javax.measure.Measurable;
import javax.measure.quantity.Length;
import javax.measure.unit.Unit;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.ListCellRenderer;
import org.jscience.geography.coordinates.LatLong;
import usda.weru.gis.GISUtil;
import usda.weru.weps.location.Station;

/**
 *
 * @author Joseph Levin <joelevin@weru.ksu.edu>
 */
public class StationChoiceView extends JPanel implements StationChooser.ViewHandler,
        ListCellRenderer<Object>, ItemListener, PropertyChangeListener {

    private static final long serialVersionUID = 1L;

    private StationChooser c_chooser;
    private final InternalComboBoxModel c_model;
    private final NumberFormat c_format = new DecimalFormat("0.0");

    /** Creates new form StationNearestView */
    public StationChoiceView() {
        initComponents();
        combo.setRenderer(this);
        c_model = new InternalComboBoxModel();
        combo.setModel(c_model);

    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        renderer = new javax.swing.JPanel();
        stationName = new javax.swing.JLabel();
        stationDistance = new javax.swing.JLabel();
        combo = new javax.swing.JComboBox<Station>();

        stationName.setFont(stationName.getFont().deriveFont(stationName.getFont().getStyle() & ~java.awt.Font.BOLD));
        stationName.setText("displayName");
        stationName.setPreferredSize(null);

        stationDistance.setFont(stationDistance.getFont().deriveFont(stationDistance.getFont().getStyle() & ~java.awt.Font.BOLD));
        stationDistance.setText("100");

        javax.swing.GroupLayout rendererLayout = new javax.swing.GroupLayout(renderer);
        renderer.setLayout(rendererLayout);
        rendererLayout.setHorizontalGroup(
            rendererLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, rendererLayout.createSequentialGroup()
                .addComponent(stationName, javax.swing.GroupLayout.DEFAULT_SIZE, 161, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(stationDistance)
                .addContainerGap())
        );
        rendererLayout.setVerticalGroup(
            rendererLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(stationDistance, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
            .addComponent(stationName, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        );

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(combo, 0, 165, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(combo, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE)
        );
    }// </editor-fold>//GEN-END:initComponents

    @Override
    public Component install(StationChooser chooser) {
/**
                 * Note:  Assertions are not enabled.  These will be useless items
                 * unless assertions are enabled.  Thus, they will be commented out unless
                 * the user wishes to enable specific assertions (feed the virtual machine 
                 * the -ea argument).
                 */
//        assert EventQueue.isDispatchThread();
        c_chooser = chooser;
        enqueueUpdate();
        updateDistance();
        c_chooser.addPropertyChangeListener(this);
        return this;
    }

    @Override
    public void uninstall(StationChooser chooser) {
        JLabel subLabel = c_chooser.getSubLabel();
        if (subLabel != null) {
            subLabel.setText("");
        }
        c_chooser.removePropertyChangeListener(this);
        c_chooser = null;
    }

    /**
     *
     * @param evt
     */
    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        switch (evt.getPropertyName()) {
            case StationChooser.PROP_DISTANCE_UNITS:
                //only need to repaint because the cell renderer will calculate the distance on each paint
                repaint();
                updateDistance();
                break;
            case StationChooser.PROP_LATLONG:
                //only need to repaint because the cell renderer will calculate the distance on each paint
                repaint();
                break;
            case StationChooser.PROP_STATION_CHOICES:
                //the choices changed, upate the model
                enqueueUpdate();
                break;
            case StationChooser.PROP_SELECTED_STATION:
                //the selected station changed'
                setStation(c_chooser.getSelectedStation());
                break;
            case StationChooser.PROP_DISTANCE:
                updateDistance();
                break;
        }

    }

    private void updateDistance() {
        JLabel subLabel = c_chooser.getSubLabel();
        if (subLabel != null) {
            Measurable<Length> distance = c_chooser.getDistance();
            Unit<Length> unit = c_chooser.getDistanceUnits();
            if (distance != null && unit != null) {
                subLabel.setText("< " + c_format.format(distance.doubleValue(unit)) + " " + unit.toString());
            } else {
                subLabel.setText("");
            }
        }

    }

    /**
     *
     * @param station
     */
    public void setStation(Station station) {
        assert EventQueue.isDispatchThread();
        setItemListenerActive(false);
        if (station != null) {
            combo.setSelectedItem(station);
        } else {
            combo.setSelectedIndex(-1);
        }
        setItemListenerActive(true);
    }

    /**
     *
     * @param enabled
     */
    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
        combo.setEnabled(enabled);
    }

    /**
     * Enables or disables the listener so that changes can be made without
     * notifying the whole world.
     * @param active
     */
    private void setItemListenerActive(boolean active) {
        if (active) {
            combo.addItemListener(this);
        } else {
            combo.removeItemListener(this);
        }
    }

    /**
     *
     * @param e
     */
    @Override
    public void itemStateChanged(ItemEvent e) {
        if (ItemEvent.SELECTED == e.getStateChange()) {
            c_chooser.setSelectedStation((Station) combo.getSelectedItem());
        }
    }

    /**
     * Schedules an update of the data in the backing model.  Current implementation
     * just executes the update.  Future updates will collapse requests within a
     * window and have the work done on a swing worker.
     */
    private synchronized void enqueueUpdate() {
        c_model.update();
    }
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JComboBox<Station> combo;
    private javax.swing.JPanel renderer;
    private javax.swing.JLabel stationDistance;
    private javax.swing.JLabel stationName;
    // End of variables declaration//GEN-END:variables

    /**
     *
     * @param list
     * @param value
     * @param index
     * @param isSelected
     * @param cellHasFocus
     * @return
     */
    @Override
    @SuppressWarnings("rawtypes")
    public Component getListCellRendererComponent(JList list, Object value, int index,
            boolean isSelected, boolean cellHasFocus) {
        if (value != null && value instanceof Station) {
            Station station = (Station) value;

            if (isSelected) {
                renderer.setBackground(list.getSelectionBackground());
            } else {
                // list is coming up gray, so use the combo
                renderer.setBackground(Color.WHITE);
            }

            stationName.setText(station.getDisplayName());

            double distanceMeters = 0;
            //TODO: can the chooser actually ever be null?
            if (c_chooser != null && c_chooser.getLatLong() != null) {
                //TODO: perhaps cache the distances?  Is this a costly calculation?
                LatLong latlong1 = c_chooser.getLatLong();
                LatLong latlong2 = station.getLatLong();

                Measurable<Length> distance = GISUtil.distanceBetweenCoordinates(latlong1, latlong2);

                if (distance != null && c_chooser.getDistanceUnits() != null) {
                    distanceMeters = distance.doubleValue(c_chooser.getDistanceUnits());
                    stationDistance.setText(c_format.format(distanceMeters));
                } else {
                    stationDistance.setText("");
                }
            } else {
                stationDistance.setText("");
            }
        } else {
            stationName.setText("");
            stationDistance.setText("");
        }

        if (index == -1) {
            Unit units = c_chooser.getDistanceUnits();
            stationDistance.setText(stationDistance.getText() + " " + (units != null ? units.toString() : ""));
        }
        return renderer;
    }

    private class InternalComboBoxModel extends DefaultComboBoxModel<Station> {

        private static final long serialVersionUID = 1L;

        public InternalComboBoxModel() {
        }

        public void update() {
            setItemListenerActive(false);
            try {
                combo.setEnabled(false);
                removeAllElements();
                for (Station station : c_chooser.getStationChoices()) {
                    addElement(station);
                }

                // The station model always dictates what is selected.
                setSelectedItem(c_chooser.getSelectedStation());

            } finally {
                combo.setEnabled(true);
                setItemListenerActive(true);
            }

        }
    }
}
