package org.autoplot.jythonsupport.ui;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import org.das2.datum.DatumRange;
import org.das2.util.LoggerManager;
import org.jdesktop.beansbinding.AutoBinding;
import org.jdesktop.beansbinding.AutoBinding.UpdateStrategy;
import org.jdesktop.beansbinding.BeanProperty;
import org.jdesktop.beansbinding.Bindings;
import org.autoplot.datasource.DataSetSelector;
import org.autoplot.datasource.DataSetURI;
import org.autoplot.datasource.DataSourceEditorPanel;
import org.autoplot.datasource.DataSourceEditorPanelUtil;
import org.autoplot.datasource.DataSourceUtil;
import org.autoplot.datasource.URISplit;
import org.autoplot.datasource.WindowManager;
import org.das2.qds.filters.FiltersChainPanel;

/**
 * GUI for creating a list of URIs and variables associated with them.
 * @author jbf
 */
public class NamedURIListTool extends JPanel {
    
    private static final Logger logger= LoggerManager.getLogger("jython.mashup");
    private static final String CLASS_NAME = NamedURIListTool.class.getName();    
    
    protected static final String PROP_URIS= "uris";
    
    /**
     * see code use, use is not typical.
     */
    protected static final String PROP_ID="id";
        
    JScrollPane scrollPane;
    
    /**
     * list of URIs.
     */
    List<String> uris=null;
    
    /**
     * list of Java identifiers, one for each URI.
     */
    List<String> ids=null;
    
    List<Boolean> isAuto= null;

    DataMashUp dataMashUp;
    
    public NamedURIListTool( ) {
        scrollPane = new javax.swing.JScrollPane();
        
        ids= Collections.emptyList();
        uris= Collections.emptyList();
        isAuto= Collections.emptyList();
        
        setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.LINE_AXIS));
        add(scrollPane);
        refresh();
    }
    
    /**
     * rebuild the GUI based on the uris.
     */
    final public void refresh() {
        JPanel content= new JPanel();
        BoxLayout lo= new BoxLayout( content, BoxLayout.Y_AXIS );
        content.setLayout( lo );

        for ( int i=0; i<uris.size(); i++ ) {
            content.add( onePanel(i) );
        }
        content.add( onePanel(-1) );
        scrollPane.setViewportView(content);
        
    }
    
    /**
     * return the uris.
     * @return the uris.
     */
    public String[] getUris() {
        assert ids.size()==uris.size();
        return uris.toArray( new String[uris.size()] );
    }
    
    /**
     * return the id for each URI.
     * @return the ids
     */
    public String[] getIds() {
        assert ids.size()==uris.size();
        return ids.toArray( new String[ids.size()] );
    }
    
    /**
     * return the URI for the name, something that when resolved will result in
     * the dataset.
     * @param name
     * @return the URI or null if the name is not found.
     */
    public String getUriForId( String name ) {
        String suri=null;
        for ( int i=0; i<ids.size(); i++ ) {
            if ( ids.get(i).equals(name) ) {
                suri= uris.get(i);
            }
        }
        if ( suri!=null ) {
            if ( this.timeRange!=null ) {
                String stimeRange= this.timeRange.toString().replaceAll("\\ ","+");
                suri= "vap+inline:getDataSet(\'"+suri+"\',\'"+stimeRange+"\')";
            }
            return suri;
        } else {
            return null;
        }
    }
    
    /**
     * set the DataMashUp tool so we can handle variable rename.
     * @param dmu 
     */
    public void setDataMashUp( DataMashUp dmu ) {
        this.dataMashUp= dmu;
    }
    
    /** make up a name that does not exist in the list of names.
     * 
     * @param names names that must be unique with the new name
     * @return the new name
     */
    public String makeupName( List<String> names ) {
        int max= 0;
        for (String n : names) {
            if ( n.startsWith("ds" ) ) {
                try {
                    int j= Integer.parseInt(n.substring(2) );
                    max= Math.max(max,j);
                } catch ( NumberFormatException ex ) {
                }
            }
        }
        return "ds"+(max+1);
    }
    
    private void bindTimeRange( DataSetSelector dss ) {
        AutoBinding binding;
        binding = Bindings.createAutoBinding( UpdateStrategy.READ_WRITE, this, BeanProperty.create("timeRange"), dss, BeanProperty.create("timeRange"));
        binding.bind();
    }
    
    private DatumRange timeRange;

    public static final String PROP_TIMERANGE = "timeRange";

    public DatumRange getTimeRange() {
        return timeRange;
    }

    public void setTimeRange(DatumRange timeRange) {
        DatumRange oldTimeRange = this.timeRange;
        this.timeRange = timeRange;
        firePropertyChange(PROP_TIMERANGE, oldTimeRange, timeRange);
    }
    
    private boolean showIds = true;

    public static final String PROP_SHOWIDS = "showIds";

    public boolean isShowIds() {
        return showIds;
    }

    public void setShowIds(boolean showIds) {
        boolean oldShowIds = this.showIds;
        this.showIds = showIds;
        firePropertyChange(PROP_SHOWIDS, oldShowIds, showIds);
    }
    

    /**
     * return the panel with the add and remove icons.
     * @param fi the position 
     * @return one panel  ( +  panel GUI  - )
     */
    private JPanel onePanel( final int fi ) {
        logger.entering( CLASS_NAME, "onePanel", fi );
        final JPanel sub= new JPanel( new BorderLayout() );
        sub.setName("sub"+fi);
        
        Dimension limit= new Dimension(100,24);
        if ( !showIds ) {
            limit= new Dimension(24,24);
        }
        Dimension dim= new Dimension(24,24);
        
        if ( fi>=0 ) {
            if ( showIds ) {
                JButton name= new JButton( ids.get(fi) + "=" );
                name.setMaximumSize( limit );
                name.setPreferredSize( limit );
                name.setToolTipText( "press to rename " );
                name.addActionListener(new ActionListener() {
                 @Override
                    public void actionPerformed(ActionEvent e) {
                        org.das2.util.LoggerManager.logGuiEvent(e);                    
                        String oldName= ids.get(fi);
                        renameAndEdit(fi);
                        String newName= ids.get(fi);
                        firePropertyChange( PROP_ID + "Name_"+fi, oldName, newName );
                    }
                } );
                sub.add( name, BorderLayout.WEST );
            } else {
                JButton subAdd= new JButton( new ImageIcon( FiltersChainPanel.class.getResource("/resources/add.png") ) );
                subAdd.setMaximumSize( limit );
                subAdd.setPreferredSize( limit );            
                subAdd.setToolTipText( "add new URI" );        
                subAdd.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        org.das2.util.LoggerManager.logGuiEvent(e);
                        List<String> ids= new ArrayList<>(NamedURIListTool.this.ids);
                        List<String> uris= new ArrayList<>(NamedURIListTool.this.uris);
                        String newName= makeupName( ids );
                        ids.add(fi,newName);
                        uris.add(fi,"");
                        setIds(ids);
                        setUris(uris);
                    }
                } );
           
                sub.add( subAdd, BorderLayout.WEST );
                
            }
        } else {
        
           JButton subAdd= new JButton( new ImageIcon( FiltersChainPanel.class.getResource("/resources/add.png") ) );
           subAdd.setMaximumSize( limit );
           subAdd.setPreferredSize( limit );            
           subAdd.setToolTipText( "add new URI" );        
           subAdd.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    org.das2.util.LoggerManager.logGuiEvent(e);
                    List<String> ids= new ArrayList<>(NamedURIListTool.this.ids);
                    List<String> uris= new ArrayList<>(NamedURIListTool.this.uris);
                    List<Boolean> isAuto= new ArrayList<>(NamedURIListTool.this.isAuto);
                    String newName= makeupName( ids );
                    ids.add(newName);
                    uris.add("");
                    isAuto.add(true);
                    setIds(ids);
                    setUris(uris);
                    setIsAuto(isAuto);
                }
            } );
           
            sub.add( subAdd, BorderLayout.WEST );
        }

        if ( fi>=0 ) {
            JButton subDelete= new JButton( new ImageIcon( FiltersChainPanel.class.getResource("/resources/subtract.png") ) );
            subDelete.setMaximumSize( limit );
            subDelete.setPreferredSize( dim );
            
            subDelete.setToolTipText( "remove uri " );
            final int ffi= fi;
            subDelete.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    //TODO: delete URI
                    Container parent= sub.getParent();
                    parent.remove(sub);
                    parent.validate();
                    uris.remove(ffi);
                    ids.remove(ffi);
                    isAuto.remove(ffi);
                    refresh();
                }
            } );
            JPanel p= new JPanel();
            p.setLayout( new BoxLayout( p, BoxLayout.X_AXIS ) );
            p.add( Box.createHorizontalStrut(11) );
            p.add( subDelete );
            sub.add( p, BorderLayout.EAST );
        }

        if ( fi>=0 ) {
            final DataSetSelector dss= new DataSetSelector();
            dss.setPlotItButtonVisible(false);
            dss.setPlayButton(false);
            dss.setValue( uris.get(fi) );
            bindTimeRange(dss);
            
            try{
                List<String> recent= DataSetSelector.getDefaultRecent();
                List<String> recentSansInline= new ArrayList<>();
                for ( String s: recent ) {
                    if ( s.startsWith("vap+inline:") ) {
                        if ( s.contains("getDataSet") ) {
                            logger.log(Level.FINEST, "skipping {0}", s);
                            continue;  // don't include mash-ups in the list of things to mash-up.
                        }
                    }
                    URISplit split= URISplit.parse(s);
                    if ( ".jy".equals(split.ext) ) {
                        logger.log(Level.FINEST, "skipping {0}", s);
                        continue;
                    }
                    if ( !".vap".equals(split.ext) ) {
                        recentSansInline.add(s);
                    }
                }
                recentSansInline.addAll( uris );
                LinkedHashSet<String> nuris= new LinkedHashSet<>();
                nuris.addAll( recentSansInline );
                
                dss.setRecent( new ArrayList<String>(nuris) ); //TODO: This was breaking mashup tool, clearing all entries.
            } catch ( IllegalArgumentException ex ) {
                
            }
            dss.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    String newName= null;
                    String currentName= null;
                    if ( isAuto.get(fi) || uris.get(fi).trim().length()==0 ) {
                        List<String> nids= new ArrayList<>(ids);
                        List<String> nuris= new ArrayList<>(uris);
                        nids.remove(fi);
                        nuris.remove(fi);
                        newName= DataSourceUtil.guessNameFor( dss.getValue(), nuris, nids );
                        if ( isValidIdentifier(newName) ) { 
                            currentName= ids.get( fi );
                        }
                    }
                    String uri= dss.getValue();
                    String uri2= DataSetURI.blurTsbUri(uri);
                    uris.set( fi,uri2 );
                    if ( !uri.equals(uri2) ) {
                        dss.setValue(uri2);
                    }
                    if (dataMashUp!=null ) dataMashUp.refresh();
                    if ( currentName!=null && newName!=null ) {
                        doVariableRename( fi, currentName, newName );
                    }
                }
            });
            dss.getEditor().addFocusListener(new FocusAdapter() {
                @Override
                public void focusLost(FocusEvent e) {
                    uris.set( fi,dss.getValue());
                    if (dataMashUp!=null ) dataMashUp.refresh();
                }
            }) ;
            sub.add( dss, BorderLayout.CENTER );

        } else {
            final JLabel tf= new JLabel();
            tf.setText("<html><i>&nbsp;(click to add)</i></html>");
            sub.add( tf, BorderLayout.CENTER );

        }

        Dimension maximumSize = sub.getPreferredSize();
        maximumSize.width = Integer.MAX_VALUE;
        sub.setMaximumSize(maximumSize);

        return sub;
    }
    
    private void doVariableRename( int fi, String oldName, String newName ) {
        ids.set( fi, newName );
        refresh();
        if ( dataMashUp!=null ) dataMashUp.rename( oldName, newName );
    }
    
    /**
     * returns true if the string is a valid Java (and Python) identifier.
     * @param n
     * @return 
     */
    private boolean isValidIdentifier( String n ) {
        boolean s= n.length()>0 && Character.isJavaIdentifierStart(n.charAt(0) );
        for ( int i=1; s && i<n.length(); i++ ) {
            s= s && Character.isJavaIdentifierPart(n.charAt(i) );
        }
        return s;
    }
    
    /**
     * bring up the editor for the URI in this position with GUI
     * controls to manually name the parameter.
     * @param fi 
     */
    private void renameAndEdit( int fi ) {
        String currentName= ids.get(fi);
        boolean autoName= isAuto.get(fi);
        
        JPanel p= new JPanel();
        p.setLayout( new BoxLayout( p, BoxLayout.Y_AXIS ) );
        //JLabel c= new JLabel( "Parameter name (a name with no spaces, made of letters, numbers and underscores):" );
        
        //c.setAlignmentX( Component.LEFT_ALIGNMENT );
        //p.add( c );

        final JCheckBox cb= new JCheckBox("Manually set parameter name (a name with no spaces, made of letters, numbers and underscores):");
        cb.setToolTipText("checked indicates variable name will be picked automatically");
        cb.setSelected(!autoName);
        p.add( cb );
        
        int em=  p.getFont().getSize();
        JPanel p1= new JPanel();
        p1.setLayout( new BoxLayout( p1, BoxLayout.X_AXIS ) );
        final JTextField tf= new JTextField(currentName);
        tf.setMaximumSize( new Dimension( em*50, em*2 ) );
        tf.setPreferredSize( new Dimension( em*50, em*2 ) );
        tf.setEnabled( cb.isSelected() );
        cb.addActionListener( new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                tf.setEnabled( cb.isSelected() ); 
            }
        });
        //p1.add( Box.createHorizontalStrut( 3*em ) );
        p1.add( tf );
        p1.add( Box.createGlue() );
        p1.setAlignmentX( Component.LEFT_ALIGNMENT );
        
        p.add( p1 );
        p.add( Box.createVerticalStrut( em ) );
        p.add( Box.createGlue() );
        
        DataSourceEditorPanel edit=null;
        try {
            String uri= uris.get(fi);
            edit = DataSourceEditorPanelUtil.getDataSourceEditorPanel( p, uri );
        } catch ( IllegalArgumentException ex ) {
            logger.log(Level.SEVERE, "can't get editor for #{0}", fi);
        }
        String title= edit!=null ? "Rename parameter and dataset editor" : "Rename parameter"; // this is so the position and size are remembered separately.
        while ( JOptionPane.OK_OPTION==WindowManager.showConfirmDialog( scrollPane, p, title, JOptionPane.OK_CANCEL_OPTION ) ) {
            String newName= tf.getText();
            if ( !cb.isSelected() && edit!=null ) {
                List<String> nids= new ArrayList(ids);
                List<String> nuris= new ArrayList(uris);
                nids.remove(fi);
                nuris.remove(fi);
                newName= DataSourceUtil.guessNameFor(edit.getURI(),nuris,nids);
            }
            if ( isValidIdentifier(newName) ) {
                if ( !currentName.equals(newName) ) {
                    doVariableRename( fi, currentName, newName );
                }
                isAuto.set( fi, !cb.isSelected() );
                if ( edit!=null ) {
                    String uri= edit.getURI();
                    uri= DataSetURI.blurTsbUri(uri);
                    uris.set( fi, uri );
                }
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        refresh();
                    }
                });
                break;
            }
        }
    }

    /**
     * set the ids, where these should be one ID for each URI.  When there
     * are the same number of URIs and IDs, refresh is called.
     * @param ids 
     */
    public void setIds( List<String> ids ) {
        this.ids= new ArrayList<>(ids);
        if ( uris.size()==ids.size() ) refresh();
    }
    
    /**
     * set the URIs, where there should be one for each ID.  When there
     * are the same number of URIs and IDs, refresh is called.
     * @param uris 
     */
    public void setUris( List<String> uris ) {
        this.uris= new ArrayList<>(uris);
        if ( uris.size()==ids.size() ) refresh();
    }
    
    /**
     * set the automatic renaming flag for each ID.   When there
     * are the same number as URIs and IDs, refresh is called.
     * @param isAuto 
     */
    public void setIsAuto( List<Boolean> isAuto ) {
        this.isAuto= new ArrayList<>(isAuto);
        if ( isAuto.size()==ids.size() ) refresh();
    }
    
    /**
     * return the Jython code that gets these.
     * @return the Jython code that gets these.
     */
    protected String getAsJython() {
        StringBuilder b= new StringBuilder();
        for ( int i=0; i<this.uris.size(); i++ ) {
            b.append( this.ids.get(i) ).append( "=" ).append( "getDataSet('").append( this.uris.get(i) ).append("')\n");
        }
        return b.toString();
    }

    /**
     * return the Jython code that gets these, to prefix vap+inline:...
     * Note, IDs with empty URIs are ignored.
     * @return jython code for loading each URI into a variable.
     */
    protected String getAsJythonInline() {
        StringBuilder b= new StringBuilder();
        for ( int i=0; i<this.uris.size(); i++ ) {
            String uri= this.uris.get(i);
            if ( uri.trim().length()>0 ) {
                String s= this.uris.get(i);
                if ( s.contains("'") ) {
                    logger.info("removing single quotes from URI, hope that doesn't break anything.");
                    b.append( this.ids.get(i) ).append( "=" ).append( "getDataSet('").append( s.replaceAll("'","") ).append("\')&");
                } else {
                    b.append( this.ids.get(i) ).append( "=" ).append( "getDataSet('").append( s ).append("')&");
                }
            }
        }
        return b.toString();
    }
    
    /**
     * return null if nothing is selected, the URI otherwise.
     * @param id the current selection, which can be an identifier, QDataSet.UNITS, or 10.0.
     * @return null if nothing is selected, the URI otherwise.
     */
    public String selectDataId( String id ) {
        JPanel dsSelector1= new JPanel();
        JPanel dsSelector= new JPanel();
        dsSelector1.add( new JScrollPane( dsSelector, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS ) );
        dsSelector1.setPreferredSize( new Dimension(600,500 ) );
        dsSelector1.setMaximumSize( new Dimension(600,500 ) );
        
        dsSelector.setLayout( new BoxLayout(dsSelector,BoxLayout.Y_AXIS ) );
        ButtonGroup bg= new ButtonGroup();
        JCheckBox[] butts= new JCheckBox[this.uris.size()+2];
        GridBagLayout layout= new GridBagLayout();
        dsSelector.setLayout(layout);
        GridBagConstraints c= new GridBagConstraints();
        c.anchor= GridBagConstraints.WEST;
        c.weighty= 0.0;
        int i;
        for ( i=0; i<this.uris.size(); i++ ) {
            final JCheckBox cb= new JCheckBox( this.ids.get(i) );
            if ( this.ids.get(i).equals(id) ) cb.setSelected(true);
            butts[i]= cb;
            c.gridy= i;
            c.gridx= 1;
            c.weightx= 0.0;
            dsSelector.add( cb, c );
            
            JLabel label=  new JLabel( this.uris.get(i) );
            label.addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    cb.setSelected(true);
                }
            });
            c.gridx= 2;            
            c.weightx= 1.0;
            dsSelector.add( label, c );
            bg.add(cb);
        }
        
        // ------------------------------------------------
        
        final JCheckBox cb= new JCheckBox( "Literal: " );
        final int ilit= i++;
        butts[ilit]= cb;
        cb.setToolTipText("enter a literal like 0.0");
        c.gridy= this.uris.size();
        c.gridx= 1;
        c.weightx= 0.0;
        dsSelector.add( cb, c );
        bg.add(cb);
        
        final JTextField literalTF= new JTextField("0.0");
        literalTF.setMinimumSize( new Dimension(120,literalTF.getFont().getSize()*2) );
        literalTF.setPreferredSize( new Dimension(120,literalTF.getFont().getSize()*2) );
        
        c.gridx= 2;
        c.weightx= 1.0;
        literalTF.addFocusListener(new FocusListener() {
            String orig=null;
            @Override
            public void focusGained(FocusEvent e) {
                orig= literalTF.getText();
            }
            @Override
            public void focusLost(FocusEvent e) {
                if ( !literalTF.getText().equals(orig) ) {
                    cb.setSelected(true);
                }
            }
        } );
        literalTF.addKeyListener( new KeyAdapter() {
            @Override
            public void keyTyped(KeyEvent e) {
                cb.setSelected(true);
            }
        });
        dsSelector.add( literalTF, c );
        
        // ------------------------------------------------
        
        final JCheckBox cb2= new JCheckBox( "Expression: " );
        final int iexpr= i++;
        butts[iexpr]= cb2;
        cb2.setToolTipText("enter an expression");
        c.gridy++;
        c.gridx= 1;
        c.weightx= 0.0;
        dsSelector.add( cb2, c );
        bg.add(cb2);
        
        final JTextField exprTF= new JTextField(expression);
        exprTF.setMinimumSize( new Dimension(120,exprTF.getFont().getSize()*2) );
        exprTF.setMaximumSize( new Dimension(600,exprTF.getFont().getSize()*2) );
        exprTF.setPreferredSize( new Dimension(600,exprTF.getFont().getSize()*2) );
        
        c.gridx= 2;
        c.weightx= 1.0;
        exprTF.addFocusListener(new FocusListener() {
            String orig=null;
            @Override
            public void focusGained(FocusEvent e) {
                orig= exprTF.getText();
            }
            @Override
            public void focusLost(FocusEvent e) {
                if ( !exprTF.getText().equals(orig) ) {
                    cb2.setSelected(true);
                }
            }
        } );
        
        if ( !isValidIdentifier(id) ) {
            if ( id.startsWith("'") || id.startsWith("\"") ) { // string literals
                literalTF.setText( id );
                butts[ilit].setSelected(true);
            } else {
                try {
                    Double.parseDouble(id);
                    if ( id.length()<20 ) {
                        id= String.format( "%s", id );
                    }
                    literalTF.setText( id );
                    butts[ilit].setSelected(true);
                } catch ( NumberFormatException ex ) {
                    exprTF.setText( id );
                    butts[iexpr].setSelected(true);
                }
            }
        } else if ( id.equals("None") ) {
            literalTF.setText( id );
            butts[ilit].setSelected(true);
        }
        dsSelector.add( exprTF, c );
        
        // -------------------------------------------------------
        
        JPanel p= new JPanel();
        c.gridy++;
        c.weighty= 1.0;
        dsSelector.add( p, c );
        
        bg.add(cb);
        if ( JOptionPane.OK_OPTION == WindowManager.showConfirmDialog( this, dsSelector, "Select Variable", JOptionPane.OK_CANCEL_OPTION )  ) {
            for ( i=0; i<this.uris.size(); i++ ) {
                if ( butts[i].isSelected() ) {
                    return this.ids.get(i);
                }
            }
            if ( butts[this.uris.size()].isSelected() ) {
                return literalTF.getText().trim();
            } else if ( butts[this.uris.size()+1].isSelected() ) {
                return exprTF.getText().trim();
            } else {
                return null;
            }
        } else {
            return null;
        }
        
    }

    private String expression="";
    
    /** 
     * set the expression which will appear in the list of names, constants and expressions.
     * @param expr 
     */
    void setExpression(String expr) {
        this.expression= expr;
    }
}