/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/*
 * Das2ServerDataSourceEditorPanel.java
 *
 * Created on Oct 16, 2009, 12:59:27 PM
 */

package org.das2.datasource;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTree;
import javax.swing.ListCellRenderer;
import javax.swing.SwingUtilities;
import javax.swing.text.DefaultEditorKit;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.das2.DasException;
import org.das2.client.DasServer;
import org.das2.datum.Datum;
import org.das2.datum.DatumRange;
import org.das2.datum.DatumRangeUtil;
import org.das2.datum.TimeUtil;
import org.das2.system.RequestProcessor;
import org.das2.util.LoggerManager;
import org.das2.util.monitor.ProgressMonitor;
import org.autoplot.datasource.AutoplotSettings;
import org.autoplot.datasource.DataSetURI;
import org.autoplot.datasource.DataSourceEditorPanel;
import org.autoplot.datasource.RecentComboBox;
import org.autoplot.datasource.TimeRangeTool;
import org.autoplot.datasource.URISplit;
import org.das2.client.Das2ServerGUI;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 *
 * @author jbf
 */
public class Das2ServerDataSourceEditorPanel extends javax.swing.JPanel implements DataSourceEditorPanel {

    private static final Logger logger= LoggerManager.getLogger("apdss.das2server");

    private static final char EXAMPLE_TIMERANGE_LABEL_DELIM = '|';

    //DANGER: NB gui code doesn't use this...
    private static final String EXAMPLE_TIME_RANGES = "<html><i>Example Time Ranges</i>";


    private final String DEFAULT_TIMERANGE="2001-01-01";
    private String dsdfContent;

    /** Creates new form Das2ServerDataSourceEditorPanel */
    public Das2ServerDataSourceEditorPanel() {
        initComponents();
        recentComboBox1.setPreferenceNode(RecentComboBox.PREF_NODE_TIMERANGE);
    }

    /**
     * list of servers we know about
     */
    private List<String> d2ss;
	 
    /** 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() {

        das2ServerComboBox = new javax.swing.JComboBox();
        jLabel1 = new javax.swing.JLabel();
        jScrollPane1 = new javax.swing.JScrollPane();
        jTree1 = new javax.swing.JTree();
        jLabel2 = new javax.swing.JLabel();
        jLabel3 = new javax.swing.JLabel();
        jLabel4 = new javax.swing.JLabel();
        jScrollPane2 = new javax.swing.JScrollPane();
        readerParamsTextArea = new javax.swing.JTextArea();
        jLabel5 = new javax.swing.JLabel();
        tcaTextField = new javax.swing.JTextField();
        jLabel6 = new javax.swing.JLabel();
        viewDsdfButton = new javax.swing.JButton();
        validRangeLabel = new javax.swing.JLabel();
        discoveryCb = new javax.swing.JCheckBox();
        examplesComboBox = new javax.swing.JComboBox();
        jLabel7 = new javax.swing.JLabel();
        descriptionLabel = new javax.swing.JLabel();
        timeRangeTool = new javax.swing.JButton();
        intrinsicCb = new javax.swing.JCheckBox();
        recentComboBox1 = new org.autoplot.datasource.RecentComboBox();
        itemsComboBox = new javax.swing.JComboBox<>();
        jButton1 = new javax.swing.JButton();

        setName("das2serverDataSourceEditorPanel"); // NOI18N

        das2ServerComboBox.setEditable(true);
        das2ServerComboBox.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "https://planet.physics.uiowa.edu/das/das2Server", "https://jupiter.physics.uiowa.edu/das/server" }));
        das2ServerComboBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                das2ServerComboBoxActionPerformed(evt);
            }
        });

        jLabel1.setText("Das2 Server URL:");

        javax.swing.tree.DefaultMutableTreeNode treeNode1 = new javax.swing.tree.DefaultMutableTreeNode("Loading DataSets List...");
        jTree1.setModel(new javax.swing.tree.DefaultTreeModel(treeNode1));
        jTree1.addTreeSelectionListener(new javax.swing.event.TreeSelectionListener() {
            public void valueChanged(javax.swing.event.TreeSelectionEvent evt) {
                jTree1ValueChanged(evt);
            }
        });
        jScrollPane1.setViewportView(jTree1);

        jLabel2.setText("Data Set Id:");

        jLabel3.setText("Time Range:");

        jLabel4.setText("Reader Parameters:");
        jLabel4.setToolTipText("Special parameters for the reader that implements the data source.  ");

        readerParamsTextArea.setColumns(20);
        readerParamsTextArea.setRows(5);
        jScrollPane2.setViewportView(readerParamsTextArea);

        jLabel5.setText("Sampling Interval (sec):");
        jLabel5.setToolTipText("<html> Interval (in seconds) to use for arbitrary resolution data.<br>Typically used for spacecraft positions, leave blank for most datasets.<br> </html> ");

        tcaTextField.setText(" ");
        tcaTextField.setToolTipText("<html> Interval (in seconds) to use for TCA (ephemeris) data.<br> Leave blank for most datasets.<br> </html> ");

        jLabel6.setText("TCA Item:");
        jLabel6.setToolTipText("The optional item number for TCAs.");

        viewDsdfButton.setText("View DSDF");
        viewDsdfButton.setToolTipText("View the DSDF configuration file on the server");
        viewDsdfButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                viewDsdfButtonActionPerformed(evt);
            }
        });

        validRangeLabel.setFont(new java.awt.Font("DejaVu LGC Sans", 0, 10)); // NOI18N
        validRangeLabel.setText("<html><i>no valid range for dataset provided</i></html>");

        discoveryCb.setText("require example time");
        discoveryCb.setToolTipText("Show only datasets that have identified example times.  These should be a higher quality, and can be tested by a machine.");
        discoveryCb.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                discoveryCbActionPerformed(evt);
            }
        });

        examplesComboBox.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "<html><i>Example Time Ranges</i>", " " }));
        examplesComboBox.setToolTipText("Example times specified in the data set descriptor file");
        examplesComboBox.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                examplesComboBoxActionPerformed(evt);
            }
        });

        jLabel7.setText("Description:");
        jLabel7.setToolTipText("Description provided by the server (in its dsdf file)");

        descriptionLabel.setText(" ");

        timeRangeTool.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/das2/datasource/calendar.png"))); // NOI18N
        timeRangeTool.setToolTipText("Time Range Tool");
        timeRangeTool.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                timeRangeToolActionPerformed(evt);
            }
        });

        intrinsicCb.setText("Force Intrinsic Resolution");
        intrinsicCb.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                intrinsicCbActionPerformed(evt);
            }
        });

        recentComboBox1.addItemListener(new java.awt.event.ItemListener() {
            public void itemStateChanged(java.awt.event.ItemEvent evt) {
                recentComboBox1ItemStateChanged(evt);
            }
        });

        itemsComboBox.setEditable(true);
        itemsComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { " ", " " }));

        jButton1.setText("Edit Params");
        jButton1.setToolTipText("The DSDF can be used to describe the parameters, and this experimental GUI will be generated.");
        jButton1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton1ActionPerformed(evt);
            }
        });

        org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(layout.createSequentialGroup()
                .addContainerGap()
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                    .add(das2ServerComboBox, 0, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .add(layout.createSequentialGroup()
                        .add(jLabel2)
                        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .add(discoveryCb))
                    .add(jScrollPane1)
                    .add(layout.createSequentialGroup()
                        .add(jLabel7)
                        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                        .add(descriptionLabel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                    .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup()
                        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                            .add(layout.createSequentialGroup()
                                .add(21, 21, 21)
                                .add(validRangeLabel, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 267, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                                .add(examplesComboBox, 0, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                            .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup()
                                .add(jLabel3)
                                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                                .add(recentComboBox1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                                .add(timeRangeTool))
                            .add(org.jdesktop.layout.GroupLayout.TRAILING, jScrollPane2))
                        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                            .add(viewDsdfButton)
                            .add(jButton1)))
                    .add(layout.createSequentialGroup()
                        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                            .add(jLabel1)
                            .add(jLabel4)
                            .add(layout.createSequentialGroup()
                                .add(intrinsicCb)
                                .add(28, 28, 28)
                                .add(jLabel5)
                                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                                .add(tcaTextField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 70, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                                .add(26, 26, 26)
                                .add(jLabel6)
                                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                                .add(itemsComboBox, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)))
                        .add(0, 27, Short.MAX_VALUE)))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(layout.createSequentialGroup()
                .addContainerGap()
                .add(jLabel1)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(das2ServerComboBox, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                .add(5, 5, 5)
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                    .add(jLabel2)
                    .add(discoveryCb))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 240, Short.MAX_VALUE)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                    .add(jLabel7)
                    .add(descriptionLabel))
                .add(7, 7, 7)
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                    .add(viewDsdfButton)
                    .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                        .add(jLabel3)
                        .add(recentComboBox1, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
                    .add(org.jdesktop.layout.GroupLayout.TRAILING, timeRangeTool))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                    .add(examplesComboBox, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                    .add(validRangeLabel, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(jLabel4)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                    .add(layout.createSequentialGroup()
                        .add(jScrollPane2, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 53, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                            .add(jLabel5)
                            .add(tcaTextField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                            .add(jLabel6)
                            .add(intrinsicCb)
                            .add(itemsComboBox, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)))
                    .add(jButton1))
                .addContainerGap())
        );
    }// </editor-fold>//GEN-END:initComponents

    private DatumRange validTimeRange= null; // some timerange believed to be valid.
    
    private final Map<String,String> readerParams= new HashMap(); // from dataset url to params.
            
    private final Map<String,String> otherParams= new HashMap(); // other params which are not supported by this dialog, like qubeSubset
    
    private String userTimeRange= null;
    
    private final Map<String,String> tcaItem= new HashMap(); // from ID to selected TCA item.
        
    private static class Example {
        private String timeRange;
        private String label="";
        private String params="";
    }
    
    private final LinkedHashMap<String,Example> theExamples= new LinkedHashMap<>();
    
    /**
     * populate the timeRange and label parts of the Example.
     * @param s the string 
     * @param e null or the existing Example
     */
    private static Example parseExample( String s, Example e ) {
        if ( e==null ) e= new Example();
        int j= s.indexOf( EXAMPLE_TIMERANGE_LABEL_DELIM );
        if ( j>-1 ) {
            e.timeRange= s.substring(0,j);
            e.label= s.substring(j+1).trim();
        } else {
            e.timeRange= s;
        }
        return e;
    }
    
    private static Example addParamsToExample( String s, Example e ) {
        if ( e==null ) e= new Example();
        e.params= s;
        return e;
    }
    
    /**
     * open a connection to retrieve the URL, possibly handling one redirect.
     * @param url the URL, such as http://jupiter.physics.uiowa.edu/das/server?server=logo
     * @return the input stream.
     * @throws IOException 
     */
    private InputStream openConnection( URL url ) throws IOException {
        HttpURLConnection httpConn= (HttpURLConnection)url.openConnection();
        int nStatus= httpConn.getResponseCode();
        if ( nStatus==301 ) {
            String newUrl= httpConn.getHeaderField("Location");
            httpConn.disconnect(); //TODO: this is sloppy.  The buffer needs to be emptied.
            if ( newUrl==null ) {
                throw new IllegalArgumentException("301 response but no new location");
            }
            httpConn=  (HttpURLConnection) new URL(newUrl).openConnection();
            nStatus= ((HttpURLConnection) httpConn).getResponseCode();
            //if ( conn.getHeaderField("Strict-Transport-Security"))
        }
        return httpConn.getInputStream();        
    }
    
    /**
     * this is called off the event thread for the web transaction, then hop back on it to populate the GUI.
     * @param url
     */
    private void updateDataSetSelected( final URL url ) {
        InputStream in= null;
        try {
            in= openConnection(url);
            StringBuilder sb = new StringBuilder();
            int by = in.read();
            while (by != -1) {
                sb.append((char) by);
                by = in.read();
            }
            in.close();
            String s = sb.toString();
            final int packetTagLength=10;

            int contentLength = Integer.parseInt(s.substring(4, packetTagLength )); // "[00]000192<stream > <properties validRange="1999-228 to 2010-359" server="http://planet.physics.uiowa.edu/das-test/das2Server" das2Stream="0" qstream="1" exampleRange="2010-001 to 2010-002" /> </stream>"
            String sxml = s.substring( packetTagLength, packetTagLength + contentLength);
            Reader xin = new BufferedReader(new StringReader(sxml));
            DocumentBuilder builder;
            builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            InputSource source = new InputSource(xin);
            final Document document = builder.parse(source);

            Runnable run = new Runnable() {
                @Override
                public void run() {
                    try {
                    validTimeRange= null;
                    boolean isTca= false;

                    XPathFactory factory = XPathFactory.newInstance();
                    XPath xpath = (XPath) factory.newXPath();

                    String curr= Das2ServerDataSourceEditorPanel.this.recentComboBox1.getText();

                    Node description= (Node) xpath.evaluate( "/stream/properties/@description", document, XPathConstants.NODE );
                    descriptionLabel.setText( description==null ? "" : description.getNodeValue() );

                    NodeList exs=  (NodeList) xpath.evaluate( "/stream/properties/@*", document, XPathConstants.NODESET );
                    Example example= null;
                    //String exampleParams= null;
                    Map<String,String> items= new HashMap<>();
                    Pattern itemPattern= Pattern.compile("item_(\\d\\d)");
                    for ( int i=0; i<exs.getLength(); i++ ) {
                        Node ex= exs.item(i);
                        String name= ex.getNodeName();
                        if ( name.startsWith("exampleRange")) {
                            String s= ex.getNodeValue();
                            name= name.replace("exampleRange","example");
                            Example e = theExamples.get(name);
                            theExamples.put( name, parseExample( s, e ) );
                            example= parseExample( s, e );
                        } else if ( name.startsWith("exampleParams") ) {
                            String s=ex.getNodeValue();
                            name=name.replace("exampleParams","example");
                            Example e = theExamples.get(name);
                            theExamples.put( name, addParamsToExample( s, e ) );
                            addParamsToExample( s, example );
                        } else if ( name.equals("items") ) {
                            isTca= true;
                        } else if ( name.equals("requiresInterval") ) {
                            isTca= !ex.getNodeValue().equals("0");
                        } else if ( name.startsWith("item_") ) {
                            Matcher m= itemPattern.matcher(name);
                            if ( m.matches() ) {
                                String s= ex.getNodeValue();
                                int ipipe= s.indexOf('|');
                                if ( ipipe>-1 ) {
                                    s= s.substring(0,ipipe);
                                }
                                s= s.trim();
                                items.put( name, s );
                            }
                        }
                    }
                    
                    String selectedItem= tcaItem.get( url.toString() ); 
                    if ( items.size()>0 && isTca ) {
                        DefaultComboBoxModel aModel= new DefaultComboBoxModel();
                        aModel.addElement(""); // all items
                        String e;
                        int index=0;
                        do {
                            e= items.get( String.format("item_%02d", index ) );
                            if ( e!=null ) {
                                if ( index==0 ) {
                                    aModel.addElement("0 ("+e+")");
                                } else {
                                    aModel.addElement(e);
                                }
                            }
                            index++;
                        } while ( e!=null );
                        itemsComboBox.setModel(aModel);
                    } else {
                        DefaultComboBoxModel aModel= new DefaultComboBoxModel();
                        aModel.addElement(""); // all items
                        itemsComboBox.setModel(aModel);
                    }
                    if ( selectedItem!=null ) {
                        itemsComboBox.setSelectedItem(selectedItem);
                    }
                    
                    if ( example!=null && curr.equals(DEFAULT_TIMERANGE) ) { // DANGER: what if they are the same?
                        Das2ServerDataSourceEditorPanel.this.recentComboBox1.setText( example.timeRange );
                    }
                    if ( example!=null ) {
                        try {
                            validTimeRange= DatumRangeUtil.parseTimeRange(example.timeRange);
                        } catch (ParseException ex) {
                            logger.info("default timerange doesn't parse!");
                        }                        
                    }
                    
                    if ( theExamples.size()>0 ) {
                        List<String> keys= new ArrayList<>(theExamples.size());
                        keys.add("LABEL");
                        
                        if ( theExamples.containsKey("example") ) {
                            keys.add( "example" );
                        }
                        for ( String k: theExamples.keySet() ) {
                            if ( !k.equals("example") ) keys.add(k);
                        }
                        
                        if ( theExamples.size()>0 ) {
                            String anExample;
                            anExample= theExamples.entrySet().iterator().next().getValue().timeRange;
                            if ( Das2ServerDataSourceEditorPanel.this.userTimeRange!=null ) {
                                anExample=  Das2ServerDataSourceEditorPanel.this.userTimeRange;
                            }
                            Das2ServerDataSourceEditorPanel.this.recentComboBox1.setText( anExample );
                        }
                                                
                        DefaultComboBoxModel model= new DefaultComboBoxModel( keys.toArray() );
                        Das2ServerDataSourceEditorPanel.this.examplesComboBox.setModel( model );
                        Das2ServerDataSourceEditorPanel.this.examplesComboBox.setEnabled(true);
                    } else {
                        DefaultComboBoxModel model= new DefaultComboBoxModel( new String[] { "NONEFOUND" } );
                        Das2ServerDataSourceEditorPanel.this.examplesComboBox.setModel( model );
                        Das2ServerDataSourceEditorPanel.this.examplesComboBox.setEnabled(false);
                    }
                    Das2ServerDataSourceEditorPanel.this.examplesComboBox.setRenderer( new ListCellRenderer() {
                        @Override
                        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
                            JLabel def= new JLabel();
                            if ( value.equals("LABEL") ) {
                                def.setText(String.format( Locale.US, "<html><i>Example Time Ranges (%d)</i>", theExamples.size() ));
                            } else if ( value.equals("NONEFOUND") ) {
                                def.setText(String.format( Locale.US, "No example time ranges found..." ) );
                            } else {
                                Example e= theExamples.get((String)value);
                                def.setText( "<html>" + e.timeRange + " <i><nbsp>"+ e.label + "</i>" );
                            }
                            return def;
                        }
                    });

                    if ( example==null ) { // legacy
                        Node exampleRange= (Node) xpath.evaluate( "/stream/properties/@x_range", document, XPathConstants.NODE );
                        if ( exampleRange!=null && curr.equals(DEFAULT_TIMERANGE) ) {
                            Das2ServerDataSourceEditorPanel.this.recentComboBox1.setText( exampleRange.getNodeValue() );
                        }
                        if ( exampleRange!=null ) {
                            try {
                               validTimeRange= DatumRangeUtil.parseTimeRange(exampleRange.getNodeValue());
                            } catch (ParseException ex) {
                               logger.info("example timerange doesn't parse!");
                            }
                        }
                    }
                    
                    String rp= readerParams.get( getDataSetId(url) );
                    readerParamsTextArea.setText( rp==null ? "" : rp );
                    
                    Node validRange= (Node)  xpath.evaluate( "/stream/properties/@validRange", document, XPathConstants.NODE );
                    if ( validRange!=null ) {
                        Das2ServerDataSourceEditorPanel.this.validRangeLabel.setText( "valid range: " + validRange.getNodeValue() );
                    } else {
                        Das2ServerDataSourceEditorPanel.this.validRangeLabel.setText("<html><i>no valid range for dataset provided</i></html>");
                    }
                    if ( isTca ) {
                        tcaTextField.setText("60");
                    } else {
                        tcaTextField.setText("");
                    }
                    } catch ( XPathExpressionException ex ) {
                        logger.log(Level.SEVERE, ex.getMessage(), ex);
                    }
                }
            };
            SwingUtilities.invokeLater(run);

        } catch (SAXException ex) {
            JOptionPane.showMessageDialog(examplesComboBox, "Unable to parse dsdf: "+ ex.getMessage() );
            logger.log(Level.SEVERE, ex.getMessage(), ex);
        } catch (ParserConfigurationException | IOException ex) {
            JOptionPane.showMessageDialog(examplesComboBox, "Unable to parse dsdf: "+ ex.getMessage() );
            logger.log(Level.SEVERE, ex.getMessage(), ex);
        } finally {
            try {
                if ( in!=null ) in.close();
            } catch (IOException ex) {
                logger.log(Level.SEVERE, ex.getMessage(), ex);
            }
        }
    }

    /**
     * return the dataset id (Juno/JED/ElectronSpectra) for the tree path.
     * @param p the tree path.
     * @return the dataset id like "http://jupiter.physics.uiowa.edu/das/server?Juno/JED/ElectronSpectra"
     */
    private String getDataSetId( TreePath p ) {
        Object[] oo= p.getPath(); 
        StringBuilder ds= new StringBuilder( String.valueOf( oo[0] ) );
        ds.append("?").append(oo[1]);
        for ( int i=2; i<oo.length; i++ ) {
            ds.append( "/" ).append( oo[i] );
        }
        return ds.toString();
    }
    
    /**
     * return <url>?<id>
     * @param url
     * @return "http://jupiter.physics.uiowa.edu/das/server?Juno/JED/ElectronSpectra"
     */
    private String getDataSetId( URL url ) {
        Map<String,String> p= URISplit.parseParams(url.getQuery());
        return url.getProtocol()+"://"+url.getHost() + url.getPath() + "?" + p.get("dataset");
    }
    
    private void jTree1ValueChanged(javax.swing.event.TreeSelectionEvent evt) {//GEN-FIRST:event_jTree1ValueChanged
        TreePath p= evt.getPath();
        TreePath old= evt.getOldLeadSelectionPath();
        
        TreeModel m= ((JTree)evt.getSource()).getModel();

        if ( old!=null && m.isLeaf(old.getLastPathComponent()) ) {
            String osurl = getDataSetId( old );
            readerParams.put( osurl, readerParamsTextArea.getText() );
        }
        
        this.dsdfContent=null;
        if ( ! m.isLeaf( p.getLastPathComponent() ) ) {
            descriptionLabel.setText( "" );
            this.validRangeLabel.setText("<html><i>no dataset selected</i></html>");
            this.viewDsdfButton.setEnabled(false);
        } else {
            this.viewDsdfButton.setEnabled(true);
            this.validRangeLabel.setText("<html><i>retrieving dataset info...</i></html>");
            String ds= getDataSetId( p );
            int i= ds.indexOf('?');
            String surl = p.getPath()[0] + "?server=dsdf&dataset=" + ds.substring(i+1);
            try {
                final URL url = new URL(surl);
                RequestProcessor.invokeLater( new Runnable() {
                    @Override
                    public void run() {
                        updateDataSetSelected( url );
                    }
                });
            } catch ( MalformedURLException ex ) {
                logger.log(Level.SEVERE, ex.getMessage(), ex);
                JOptionPane.showConfirmDialog( this, "Internal Error: "+ex.toString() ); // give a message
            }
        }
    }//GEN-LAST:event_jTree1ValueChanged

    private void updateDas2Servers() {
        Runnable runOffEvt= new Runnable() {
            public void run() {
                updateDas2ServersImmediately();
            }
        };
        new Thread(runOffEvt).start();
    }


    private void updateDas2ServersImmediately() {
        d2ss= listDas2Servers();
        Runnable run= new Runnable() {
            @Override
            public void run() {
                das2ServerComboBox.setModel( new DefaultComboBoxModel(d2ss.toArray()) );
            
                if ( serverURL.length()==0 ) {
                    serverURL= d2ss.get(0);
                    RequestProcessor.invokeLater( getDataSetsRunnable() );
                }
                das2ServerComboBox.setSelectedItem(serverURL);
                das2ServerComboBox.setRenderer(myListCellRenderer);
            }
        };
        SwingUtilities.invokeLater(run);
    }

    
    private static final Map<String,ImageIcon> icons= Collections.synchronizedMap( new HashMap() );
    
    private static Icon iconFor( Object o, boolean wait ) {
        //return new javax.swing.ImageIcon(Das2ServerDataSourceEditorPanel.class.getResource("/org/autoplot/datasource/fileMag.png"));
        //icons.clear(); // for debugging
        ImageIcon result= icons.get(o.toString());
        if (result==null && wait ) {
            try {
                long t1= System.currentTimeMillis();
                result= new ImageIcon( new URL( "" + o +"?server=logo" ) );
                Image im= result.getImage();
                int h= im.getWidth(null);
                int w= im.getHeight(null);
                int s= 20;
                int h1= Math.min(24,s*w/h);
                BufferedImage bi=  new BufferedImage( s, h1, BufferedImage.TYPE_INT_ARGB);
                Graphics g= bi.createGraphics();
                g.drawImage( im, 0, 0, s, h1, null, null );
                result= new ImageIcon(bi);
                logger.log(Level.FINE, "time to load icon for {0}: {1} ms", new Object[]{ o, System.currentTimeMillis()-t1});
                icons.put( o.toString(), result );
            } catch (MalformedURLException ex) {
                Logger.getLogger(Das2ServerDataSourceEditorPanel.class.getName()).log(Level.SEVERE, null, ex);
            }
        } 
        return result;
    }
    
    private static class IconCellRenderer implements ListCellRenderer {
        DefaultListCellRenderer r= new DefaultListCellRenderer();
        @Override
        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
            Component c= r.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
            Icon icon= iconFor( value, false );
            ((DefaultListCellRenderer)c).setIcon(icon);
            return c;
        }
    }
    
    private transient final ListCellRenderer myListCellRenderer= new IconCellRenderer();
    
    private List<String> listPeers( String suri ) {

        String uri= suri+"?server=peers";

        List<String> result= new ArrayList();

        InputStream in=null;
        
        try {
            in = new URL(uri).openStream();

            DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            InputSource source = new InputSource(in);
            Document initialDocument = builder.parse(source);

            XPathFactory factory = XPathFactory.newInstance();
            XPath xpath = factory.newXPath();

            NodeList urls = (NodeList) xpath.evaluate("//das2server/peers/server/url/text()", initialDocument, XPathConstants.NODESET );

            for ( int i=0; i<urls.getLength(); i++ ) {
                result.add(urls.item(i).getNodeValue());
            }
            
        } catch (MalformedURLException ex) {
            logger.log(Level.SEVERE, "listPeers("+uri+")\n"+ex.getMessage(), ex);
        } catch (IOException | ParserConfigurationException | SAXException | XPathExpressionException ex) {
            logger.log(Level.SEVERE, "listPeers("+uri+")\nresults in:\n"+ex.getMessage(), ex);
        } finally {
            try {
                if ( in!=null ) in.close();
            } catch (IOException ex) {
                Logger.getLogger(Das2ServerDataSourceEditorPanel.class.getName()).log(Level.SEVERE, null, ex);
            }
            return result;
        }
    }

    /**
     * add the default known servers, plus the ones we know about.
     */
    private List<String> listDas2Servers() {
        List<String> d2ss1= new ArrayList( );
        d2ss1.add( "https://planet.physics.uiowa.edu/das/das2Server" );

        if ( serverURL.length()==0 ) {
            d2ss1.addAll( listPeers("https://planet.physics.uiowa.edu/das/das2Server") );
        } else {
            d2ss1.addAll( listPeers(serverURL) );
        }

        File home = new File(AutoplotSettings.settings().resolveProperty(AutoplotSettings.PROP_AUTOPLOTDATA));
        File book = new File(home, "bookmarks");
        File hist = new File(book, "history.txt");
        long t0= System.currentTimeMillis();
        logger.log( Level.FINE, "reading recent datasources from {0}", hist.toString());

        if ( hist.exists() ) {
            BufferedReader r=null;
            try {
                String seek="das2server:";
                int ttaglen= 25;
                r = new BufferedReader(new FileReader(hist));
                String s = r.readLine();
                LinkedHashSet dss = new LinkedHashSet();

                while (s != null) {
                    if ( s.length()>ttaglen+15 && s.substring(ttaglen+4,ttaglen+15).equalsIgnoreCase(seek)) {
                        int i= s.indexOf('?');
                        if ( i==-1 ) i= s.length();
                        String key= s.substring(ttaglen+4+seek.length(),i);
                        if ( dss.contains(key) ) dss.remove( key ); // move to the end
                        dss.add( key );
                    }
                    s = r.readLine();
                }

                d2ss1.removeAll(dss);  // remove whatever we have already
                List<String> d2ssDiscoveryList= new ArrayList(dss);
                Collections.reverse( d2ssDiscoveryList );
                d2ssDiscoveryList.addAll(d2ss1);
                d2ss1= d2ssDiscoveryList; // put the most recently used ones at the front of the list
                
                logger.log( Level.FINE, "read extra das2servers in {0} millis\n", (System.currentTimeMillis()-t0) );
            } catch ( IOException ex ) {
                logger.log( Level.FINE, "IOException when reading in {0}", hist );
                JOptionPane.showConfirmDialog(examplesComboBox,"IOException when reading in "+hist );
            } finally {
                try {
                    if ( r!=null ) r.close();
                } catch (IOException ex) {
                    logger.log( Level.SEVERE, ex.getMessage(), ex );
                }
            }
        } else {
            logger.log( Level.FINE, "no history file found: {0}", hist );
        }
        
        final List<String> fd2ss1= d2ss1;
        Runnable run= new Runnable() {
            @Override
            public void run() {
                for ( String s: fd2ss1 ) {
                    Icon i= iconFor( s, true ); // force load of icon off the event thread.
                    logger.log(Level.FINER, "iconHeight={0}", i.getIconHeight());
                }
            };
        };
        new Thread(run,"loadDas2ServerIcons").start();
        
        return d2ss1;

    }

    private String readDsdf( URL url ) throws IOException {
        try ( InputStream in = openConnection(url) ) {

            StringBuilder sb= new StringBuilder();

            int by= in.read();
            while ( by!=-1 ) {
                sb.append( (char)by );
                by= in.read();
            }

            String s= sb.toString();
            int contentLength= Integer.parseInt( s.substring(4,10) );
            String sxml= s.substring(10,10+contentLength);
            return sxml;
        }
    }
    private void showDsdf( URL url ) {
      
        try {
            
            String sxml= readDsdf( url );

            Reader xin = new BufferedReader(new StringReader(sxml));

            DocumentBuilder builder;
            builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            InputSource source = new InputSource(xin);
            Document document = builder.parse(source);

            XPathFactory factory= XPathFactory.newInstance();

            XPath xpath= (XPath) factory.newXPath();
            NodeList o= (NodeList)xpath.evaluate( "/stream/properties/@*", document, XPathConstants.NODESET );

            StringBuilder result= new StringBuilder("");
            for ( int ii=0; ii<o.getLength(); ii++ ) {
                result.append(  o.item(ii).getNodeName() ).append( "  =  " ) .append(  o.item(ii).getNodeValue() ) .append( "\n" );
            }

            final String fresult= result.toString();

            Runnable run= new Runnable() {
                @Override
                public void run() {
                    JTextArea area= new JTextArea();
                    area.setText(fresult);
                    area.setEditable(false);
                    final JPopupMenu copyMenu= new JPopupMenu();
                    copyMenu.add( new DefaultEditorKit.CopyAction() ).setText("Copy");
                    area.addMouseListener( new MouseAdapter() {
                        @Override
                        public void mousePressed(MouseEvent e) {
                            if ( e.isPopupTrigger() ) copyMenu.show(e.getComponent(), e.getX(), e.getY() );
                        }
                        @Override
                        public void mouseReleased(MouseEvent e) {
                            if ( e.isPopupTrigger() ) copyMenu.show(e.getComponent(), e.getX(), e.getY() );
                        }
                    });
                    JScrollPane sp= new JScrollPane( area,JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED );
                    sp.setPreferredSize(new java.awt.Dimension( 480,480));
                    JOptionPane.showMessageDialog( Das2ServerDataSourceEditorPanel.this, sp );

                }
            };
            SwingUtilities.invokeLater(run);


        } catch (XPathExpressionException | SAXException | ParserConfigurationException | IOException ex) {
            JOptionPane.showMessageDialog(examplesComboBox, "Unable to parse dsdf: "+ ex.getMessage() );
            logger.log(Level.SEVERE, ex.getMessage(), ex);
        } 

    }

    private void viewDsdfButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_viewDsdfButtonActionPerformed
        org.das2.util.LoggerManager.logGuiEvent(evt);
        TreePath p= jTree1.getSelectionPath();
        TreeModel m= jTree1.getModel();
        if ( p==null ) {
            JOptionPane.showConfirmDialog( this, "No dataset selected" );
            return;
        }
        if ( m.isLeaf( p.getLastPathComponent() ) ) {
            {
                try {
                    Object[] oo = p.getPath();
                    StringBuilder ds = new StringBuilder( String.valueOf(oo[1]) );
                    for (int i = 2; i < oo.length; i++) {
                        ds.append( "/" ).append( oo[i] );
                    }
                    String surl = oo[0] + "?server=dsdf&dataset=" + ds.toString();

                    final URL url = new URL(surl);

                    RequestProcessor.invokeLater( new Runnable() {
                        @Override
                        public void run() {
                            showDsdf(url);
                        }
                    } );
                    
                } catch ( MalformedURLException ex ) {
                    logger.log(Level.SEVERE, ex.getMessage(), ex);
                    JOptionPane.showConfirmDialog( this, "Internal Error: "+ex.toString() ); // give a message
                }
            }
        }
    }//GEN-LAST:event_viewDsdfButtonActionPerformed

    private void das2ServerComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_das2ServerComboBoxActionPerformed
        org.das2.util.LoggerManager.logGuiEvent(evt);
        Object o= das2ServerComboBox.getSelectedItem();
        if ( o!=null && String.valueOf(o).length()>0 ) {
            try {
                URL url= new URL(String.valueOf(o));
                setServerURL( url.toString() );
            } catch (MalformedURLException ex) {
                if ( !String.valueOf(o).contains(":") ) {
                    JOptionPane.showMessageDialog(this,"<html>Invalid URL (no http):<br>"+o);
                } else {
                    JOptionPane.showMessageDialog(this,"<html>Invalid URL:<br>"+o);
                }
            }
        }
    }//GEN-LAST:event_das2ServerComboBoxActionPerformed

    private void discoveryCbActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_discoveryCbActionPerformed
        org.das2.util.LoggerManager.logGuiEvent(evt);
        jTree1.setModel( waitTreeModel() );
        RequestProcessor.invokeLater( getDataSetsRunnable() );
    }//GEN-LAST:event_discoveryCbActionPerformed

    private void examplesComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_examplesComboBoxActionPerformed
        org.das2.util.LoggerManager.logGuiEvent(evt);
        String item= (String) examplesComboBox.getSelectedItem();
        if ( item.startsWith("example") ) {
            logger.log(Level.FINE, "example item: {0}", item);
            Example e= theExamples.get(item);
            if ( e==null ) {
                logger.warning("whoops, where is the label");
            } else {
                recentComboBox1.setText( e.timeRange );
                readerParamsTextArea.setText( e.params );
            }
        }
    }//GEN-LAST:event_examplesComboBoxActionPerformed

    private void timeRangeToolActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_timeRangeToolActionPerformed
        org.das2.util.LoggerManager.logGuiEvent(evt);
        TimeRangeTool tt= new TimeRangeTool();
        //JTextField tf= timeRangeTextField;
        RecentComboBox tf= recentComboBox1;
        tt.setSelectedRange(tf.getText());
        int r= JOptionPane.showConfirmDialog( this, tt, "Select Time Range", JOptionPane.OK_CANCEL_OPTION );
        if ( r==JOptionPane.OK_OPTION) {
            tf.setText(tt.getSelectedRange());
        }
    }//GEN-LAST:event_timeRangeToolActionPerformed

   private void intrinsicCbActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_intrinsicCbActionPerformed
      // TODO add your handling code here:
   }//GEN-LAST:event_intrinsicCbActionPerformed

    private void recentComboBox1ItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_recentComboBox1ItemStateChanged
        logger.log(Level.FINEST, "changed {0}", evt.getItem());
    }//GEN-LAST:event_recentComboBox1ItemStateChanged

    private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton1ActionPerformed
        org.das2.util.LoggerManager.logGuiEvent(evt);
        TreePath p= jTree1.getSelectionPath();
        TreeModel m= jTree1.getModel();
        if ( p==null ) {
            JOptionPane.showConfirmDialog( this, "No dataset selected" );
            return;
        }
        if ( m.isLeaf( p.getLastPathComponent() ) ) {
            {
                try {
                    Object[] oo = p.getPath();
                    StringBuilder ds = new StringBuilder( String.valueOf(oo[1]) );
                    for (int i = 2; i < oo.length; i++) {
                        ds.append( "/" ).append( oo[i] );
                    }
                    String surl = oo[0] + "?server=dsdf&dataset=" + ds.toString();

                    final URL url = new URL(surl);

                    RequestProcessor.invokeLater( new Runnable() {
                        @Override
                        public void run() {
                            if ( dsdfContent==null ) {
                                try {
                                    dsdfContent= readDsdf( url );
                                } catch (IOException ex) {
                                    logger.log(Level.SEVERE, null, ex);
                                }
                            }
                            if ( dsdfContent!=null ) {
                                editDsdfContent();
                            }
                            
                        }
                    } );
                    
                } catch ( MalformedURLException ex ) {
                    logger.log(Level.SEVERE, ex.getMessage(), ex);
                    JOptionPane.showConfirmDialog( this, "Internal Error: "+ex.toString() ); // give a message
                }
            }
        }
        
    }//GEN-LAST:event_jButton1ActionPerformed

    public void editDsdfContent() {
        Das2ServerGUI x = new Das2ServerGUI();
        x.setSpecification( dsdfContent );
        x.setParameters( readerParamsTextArea.getText() );
        JScrollPane sp= new JScrollPane(x.getPanel());
        sp.setMaximumSize( new Dimension(500,800) );
        sp.setPreferredSize( new Dimension(300,500) );
        sp.getVerticalScrollBar().setUnitIncrement(sp.getFont().getSize());
        int response= JOptionPane.showConfirmDialog( this, sp, "Edit reader params", JOptionPane.OK_CANCEL_OPTION );
        if ( response==JOptionPane.OK_OPTION ) {
            readerParamsTextArea.setText( x.getParameters() );
        }
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    public javax.swing.JComboBox das2ServerComboBox;
    public javax.swing.JLabel descriptionLabel;
    public javax.swing.JCheckBox discoveryCb;
    public javax.swing.JComboBox examplesComboBox;
    public javax.swing.JCheckBox intrinsicCb;
    public javax.swing.JComboBox<String> itemsComboBox;
    public javax.swing.JButton jButton1;
    public javax.swing.JLabel jLabel1;
    public javax.swing.JLabel jLabel2;
    public javax.swing.JLabel jLabel3;
    public javax.swing.JLabel jLabel4;
    public javax.swing.JLabel jLabel5;
    public javax.swing.JLabel jLabel6;
    public javax.swing.JLabel jLabel7;
    public javax.swing.JScrollPane jScrollPane1;
    public javax.swing.JScrollPane jScrollPane2;
    public javax.swing.JTree jTree1;
    public javax.swing.JTextArea readerParamsTextArea;
    public org.autoplot.datasource.RecentComboBox recentComboBox1;
    public javax.swing.JTextField tcaTextField;
    public javax.swing.JButton timeRangeTool;
    public javax.swing.JLabel validRangeLabel;
    public javax.swing.JButton viewDsdfButton;
    // End of variables declaration//GEN-END:variables


    protected String serverURL = "";
    public static final String PROP_SERVERURL = "serverURL";

    public String getServerURL() {
        return serverURL;
    }

    public void setServerURL(String serverURL) {
        String oldServerURL = this.serverURL;
        this.serverURL = serverURL;
        if ( !this.serverURL.equals(oldServerURL) ) {
            //timeRangeTextField.setText( DEFAULT_TIMERANGE );
            descriptionLabel.setText("");
            jTree1.setModel( waitTreeModel() );
            RequestProcessor.invokeLater( getDataSetsRunnable() );
        }
        firePropertyChange(PROP_SERVERURL, oldServerURL, serverURL);
    }

    protected String dataSetId = null;
    public static final String PROP_DATASETID = "dataSetId";

    public String getDataSetId() {
        return dataSetId;
    }

    public void setDataSetId(String dataSetId) {
        String oldDataSetId = this.dataSetId;
        this.dataSetId = dataSetId;
        firePropertyChange(PROP_DATASETID, oldDataSetId, dataSetId);
    }

    @Override
    public JPanel getPanel() {
        return this;
    }

    @Override
    public boolean reject(String uri) throws Exception {
        URISplit split = URISplit.parse(uri);
        if ( split.file==null || split.file.equals("file:///") ) { // use UIOWA's main one by default.
            split.file= "https://planet.physics.uiowa.edu/das/das2Server";
        }
        String s= split.file;
        if ( s.equals("https://planet.physics.uiowa.edu/das/das2Server") ) {
            return false;
        }
        // there's really no way to tell if it really is a Das2Server on the event thread, so accept all URIs. 
        // and graphically tell them if it is not a valid server.
        return false;
    }


    @Override
    public boolean prepare( String uri, java.awt.Window parent, ProgressMonitor mon) {
        return true;
    }

    @Override
    public void setURI(String uri) {

        URISplit split= URISplit.parse(uri);

        if ( split.resourceUri==null ) {
            serverURL= "";
        } else {
            String uriServerUrl= DataSetURI.fromUri( split.resourceUri );
            if ( uriServerUrl.length()>0 && !uriServerUrl.startsWith("file:/") ) {
                serverURL= uriServerUrl;
            } else {
                serverURL= "";
            }
        }

        Map<String,String> params= URISplit.parseParams(split.params);
        dataSetId= params.remove("dataset");
        if ( dataSetId==null ) {
            dataSetId= params.remove("arg_0");
        }
        if ( dataSetId!=null && dataSetId.startsWith("/") ) {
            dataSetId= dataSetId.substring(1);
        }
        String startTime= params.remove("start_time");
        String endTime= params.remove("end_time");
        String str= params.remove("timerange");
        if ( str!=null ) {
            userTimeRange= str;
            DatumRange tr;
            try {
                tr = DatumRangeUtil.parseTimeRange( str );
                startTime= tr.min().toString();
                endTime= tr.max().toString();
            } catch (ParseException ex) {
                logger.log(Level.SEVERE, ex.getMessage(), ex);
            }
        }
        if ( startTime!=null && endTime!=null ) {
            try {
                Datum t1= TimeUtil.create( startTime );
                Datum t2= TimeUtil.create( endTime );
                DatumRange dr = new DatumRange( t1, t2 );
                if ( userTimeRange==null ) userTimeRange= dr.toString();
                recentComboBox1.setText(dr.toString());
            } catch ( ParseException ex ) {
                recentComboBox1.setText( DEFAULT_TIMERANGE );
            }
        } else {
            recentComboBox1.setText( DEFAULT_TIMERANGE );
        }
		  
        String intrinsic = params.remove("intrinsic");
        intrinsicCb.setSelected("true".equals(intrinsic));
		  
        String interval= params.remove("interval");
        if ( interval!=null ) {
            tcaTextField.setText(interval);
        }
        String item= params.remove("item");
        if ( item!=null ) {
            itemsComboBox.setSelectedItem(item);
            tcaItem.put( "" + serverURL + "?server=dsdf&dataset=" + dataSetId, item );
        }
        
        otherParams.put( "qubeSubset", params.remove("qubeSubset") );
                
        StringBuilder paramsStr= new StringBuilder();
        for ( Entry<String,String> e: params.entrySet() ) {
            if ( e.getKey().startsWith("arg_") ) {
                paramsStr.append(e.getValue()).append("\n");
            } else {
                paramsStr.append(e.getKey()).append("=").append(e.getValue()).append("\n");
            }
        }
        readerParamsTextArea.setText(paramsStr.toString());
        String key= serverURL + "?" + dataSetId;
        readerParams.put( key, paramsStr.toString() );

        das2ServerComboBox.setSelectedItem(serverURL);
        
        updateDas2Servers(); // this will set serverUrl to the last used server if nothing is specified.
        
        if ( serverURL.length()>0 ) {
            RequestProcessor.invokeLater( getDataSetsRunnable() );
        }

    }

    TreeModel waitTreeModel() {
        DefaultMutableTreeNode treeNode1 = new DefaultMutableTreeNode("updating, please wait..." );
        return new javax.swing.tree.DefaultTreeModel(treeNode1);
    }
	 
    /**
     * Class to handle custom rendering of the dataset list.  The DefaultTreeCellRenderer
     * is just a JLable derived object.  So those are the functions being used to get the
     * display properly rendered
     */
    private static class DataSetItemRenderer extends DefaultTreeCellRenderer {

        @Override
        public Component getTreeCellRendererComponent(JTree tree, Object value,
                boolean bSelected, boolean bExpanded, boolean bIsLeaf, int iRow, boolean bHasFocus) {
            super.getTreeCellRendererComponent(tree, value, bSelected, bExpanded, bIsLeaf, iRow,
                    bHasFocus);

            DefaultMutableTreeNode tn = (DefaultMutableTreeNode) value;
            Object obj = tn.getUserObject();

            if (obj instanceof DasServer.DataSrcListItem) {
                DasServer.DataSrcListItem li = (DasServer.DataSrcListItem) obj;


                if (li.description() == null) {
                    setText(String.format("<html><b>%s</b>", li.name()));
                } else {
                    setText(String.format("<html><b>%s</b> &nbsp;<i>%s</i>", li.name(), li.description()));
                }                 
            }

            return this;
        }
    }

    private void updateTree( TreeModel model ) {
        jTree1.setModel(model);
		jTree1.setCellRenderer(new DataSetItemRenderer());
        if ( dataSetId!=null ) selectDataSetId();
    }

    Runnable getDataSetsRunnable() {
        Runnable run= new Runnable() {
            @Override
            public void run() {

                String ss1= serverURL;

                try {
                    DasServer server= DasServer.create( new URL( ss1 ) );
                    final TreeModel model;
                    if ( discoveryCb.isSelected() ) {
                        model= server.getDataSetListWithDiscovery();
                    } else {
                        model= server.getDataSetList();
                    }
                    updateDas2ServersImmediately();
                    SwingUtilities.invokeLater( new Runnable() {
                        @Override
                        public void run() {
                            updateTree(model);
                        }
                    });
                    
                } catch (DasException ex) {

                    logger.log(Level.SEVERE, ex.getMessage(), ex);

                    javax.swing.tree.DefaultMutableTreeNode treeNode1 = new javax.swing.tree.DefaultMutableTreeNode("Error connecting to " + ss1 + ", \n" + ex );
                    jTree1.setModel(new javax.swing.tree.DefaultTreeModel(treeNode1));

                } catch (MalformedURLException ex) {
                    javax.swing.tree.DefaultMutableTreeNode treeNode1 = new javax.swing.tree.DefaultMutableTreeNode("Error connecting to " + ss1 + ", \n" + ex );
                    jTree1.setModel(new javax.swing.tree.DefaultTreeModel(treeNode1));
                    
                }

            }
        };
        return run;
    }

    private void selectDataSetId() {
        String[] ss= dataSetId.split("/");
        TreeNode[] oo= new TreeNode[ss.length+1];
        oo[0]= (TreeNode) jTree1.getModel().getRoot();
        for ( int i=1; i<oo.length; i++ ) {
            for ( int j=0; j<oo[i-1].getChildCount(); j++ ) {
                DefaultMutableTreeNode kid= (DefaultMutableTreeNode) oo[i-1].getChildAt(j);
                if ( ((DasServer.DataSrcListItem)kid.getUserObject()).name().equals( ss[i-1] ) ) {
                    oo[i]= kid;
                    break;
                }
            }
            if ( oo[i]==null ) {
                return;
            }
        }
        final TreePath tp= new TreePath( oo );
        Runnable run= new Runnable() {
            @Override
            public void run() {
            jTree1.setSelectionPath(tp);
            jTree1.scrollPathToVisible(tp);
            }
        };
        SwingUtilities.invokeLater(run);
        
    }

    @Override
    public String getURI() {

        TreePath tp=  jTree1.getSelectionPath();
        if ( tp==null ) {
            return "vap+das2server:"+serverURL + "?" ;
        }

        boolean folder= !jTree1.getModel().isLeaf(tp.getLastPathComponent());
        Object[] tp0= tp.getPath();

        DatumRange timeRange;
        try {
            timeRange = DatumRangeUtil.parseTimeRange(recentComboBox1.getText());
        } catch (ParseException ex) {
            logger.log(Level.SEVERE, ex.getMessage(), ex);
            timeRange= this.validTimeRange;
            if ( timeRange==null ) {
                throw new IllegalArgumentException("No timerange for the URI."); // too bad we can't check reject and reenter the GUI...  Surely it does this...
            }
        }
        
        StringBuilder ldataSetId= new StringBuilder("");
        if (tp0.length > 1) {
            DefaultMutableTreeNode tn = (DefaultMutableTreeNode) tp0[1];
            DasServer.DataSrcListItem li = (DasServer.DataSrcListItem) tn.getUserObject();
            ldataSetId = new StringBuilder(li.name());
            for (int i = 2; i < tp0.length; i++) {
                tn = (DefaultMutableTreeNode) tp0[i];
                li = (DasServer.DataSrcListItem) tn.getUserObject();
                ldataSetId.append("/").append(li.name());
            }
        }
        if ( folder ) ldataSetId.append("/");

        StringBuilder params= new StringBuilder();
        String lreaderParams= readerParamsTextArea.getText();
        String[] ss= lreaderParams.split("\n");
        for ( int i=0; i<ss.length; i++ ) {
            String ss1= ss[i].trim();
            if ( ss1.length()==0 ) continue;
            String[] ss2= ss1.split("\\s+");
            for ( int j=0; j<ss2.length; j++ ) {
                String[] ss3= ss2[j].split("\\s*=\\s*",-2);
                if ( ss3.length==1 ) {
                    params.append( URLEncoder.encode(ss3[0].trim()) );
                } else {
                    params.append( URLEncoder.encode(ss3[0].trim())).append("=").append( URLEncoder.encode(ss3[1].trim() ));
                }
                if ( i<ss.length-1 || j<ss2.length-1 ) {
                    params.append("%20");  //TODO: I don't think this is correct...  See https://sourceforge.net/p/autoplot/bugs/1103/
                }
            }
        }
        
        StringBuilder result= new StringBuilder("vap+das2server:");
        result.append(serverURL).append("?").append("dataset=").append(ldataSetId.toString());
        if ( timeRange!=null ) {
            result.append("&start_time=").append(timeRange.min()).append("&end_time=").append(timeRange.max());
        }
		  
        if(intrinsicCb.isSelected()) result.append("&intrinsic=true");
		  
        String tcaInterval= tcaTextField.getText().trim();
        if ( !tcaInterval.equals("") ) {
            result.append("&interval=").append(tcaInterval);
        }
        String s= itemsComboBox.getSelectedItem().toString().trim();
        if ( !s.equals("") ) {
            if ( s.startsWith("0 (") ) {
                s= "0";
            }
            result.append("&item=").append(s);
        }
        
        if ( params.length()>0 ) result.append("&").append(params.toString());
        
        if ( otherParams.get("qubeSubset")!=null ) {
            result.append("&qubeSubset=").append(otherParams.get("qubeSubset"));
        }
        
        return result.toString();
    }

    @Override
    public void markProblems(List<String> problems) {

    }


    //private boolean expert;
    
    /**
     * call this before prepare.
     * @param expert
     */
    public void setExpertMode( boolean expert ) {
        //this.expert= expert;
        this.discoveryCb.setSelected(!expert);
    }
}