/*
 * Copyright
 *   2005 axYus - www.axyus.com
 *   2005 C.Marchand - christophe.marchand@axyus.com
 *   2008 JP.Tessier - jean-philippe.tessier@axyus.com
 *
 * This file is part of XEMELIOS.
 *
 * XEMELIOS is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * XEMELIOS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with XEMELIOS; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package fr.gouv.finances.cp.xemelios.ui;

import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
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.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.TreeSet;
import java.util.Vector;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Box;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;

import org.apache.log4j.Logger;
import org.xml.sax.SAXException;

import com.jgoodies.forms.builder.PanelBuilder;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;

import fr.gouv.finances.cp.utils.Pair;
import fr.gouv.finances.cp.utils.ui.DisplayExceptionDlg;
import fr.gouv.finances.cp.utils.ui.DlgSelectFromList;
import fr.gouv.finances.cp.utils.ui.IhmFactory;
import fr.gouv.finances.cp.utils.xml.marshal.XmlMarshallable;
import fr.gouv.finances.cp.xemelios.common.Constants;
import fr.gouv.finances.cp.xemelios.common.ToolException;
import fr.gouv.finances.cp.xemelios.common.config.CritereModel;
import fr.gouv.finances.cp.xemelios.common.config.DocumentModel;
import fr.gouv.finances.cp.xemelios.common.config.ElementModel;
import fr.gouv.finances.cp.xemelios.common.config.InputModel;
import fr.gouv.finances.cp.xemelios.common.config.OptionModel;
import fr.gouv.finances.cp.xemelios.common.config.PropertyModel;
import fr.gouv.finances.cp.xemelios.common.config.RecherchePaireModel;
import fr.gouv.finances.cp.xemelios.common.config.SelectModel;
import fr.gouv.finances.cp.xemelios.common.config.SpecialKeyEntryModel;
import fr.gouv.finances.cp.xemelios.data.DataAccessException;
import fr.gouv.finances.cp.xemelios.data.DataConfigurationException;
import fr.gouv.finances.cp.xemelios.data.DataLayerManager;

/**
 * Affiche un critere de recherche
 *
 * @author chm
 */
public class CriteriaViewer extends JPanel implements ActionListener {
    public static final transient String OR_ASSOCIATOR = "||";
    
    public static final transient String AND_ASSOCIATOR = "&&";
    
    public static final String DATE_FORMAT = System.getProperty(Constants.SYS_PROP_DATE_FORMAT,"yyyy-MM-dd");
    
    public static final SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
    
    private static Logger logger = Logger.getLogger(CriteriaViewer.class);
    
    static IhmFactory iF = IhmFactory.newInstance();
    
    static ImageIcon deleteImg = IhmFactory.getIconFromResource(ImageResources.DELETE_ROW);
    
    static ImageIcon blankImg = null;
    
    /**
     * Comment for <code>serialVersionUID</code>
     */
    private static final long serialVersionUID = 3258412820227045174L;
    
    private ElementModel em;
    private DocumentModel dm;
    private Pair collectivite,budget;
    private Hashtable<String, InputData> inputs;
    private ArrayList<InputData> orderedInputs;
//    private ChampModel optionnalField = null;
    private boolean hideRemoveIcon, hideChoice;
    
    private JComboBox cbxAndOr, cbxTypeCritere;
    private Container replaceMe;
    private SearchWindow parent;
    private JComponent componentToFocus = null;
    private JCheckBox chkDisplayOptionnal = null;
    private CritereModel currentCritereModel = null;
    private JLabel defaultLabel = null;
    private JLabel deleteMe = null;
    
    public CriteriaViewer(SearchWindow parent, ElementModel em, Pair collectivite, Pair budget,boolean hideRemoveIcon, Vector<CritereModel>criteresOp) {
        super();
        this.parent = parent;
        this.hideRemoveIcon = hideRemoveIcon;
        this.em = em;
//        logger.debug(em.getId()+": "+em.getAvailableCriteres().size()+" criterias");
        this.dm = em.getParent().getParent();
        this.collectivite=collectivite;
        this.budget=budget;
        this.hideChoice = false;
        if (blankImg == null) {
            Image img = new BufferedImage(deleteImg.getIconWidth(), deleteImg.getIconHeight(), BufferedImage.TYPE_INT_RGB);
            Graphics2D gr = (Graphics2D)img.getGraphics();
            gr.setBackground(parent.getBackground());
            gr.clearRect(0, 0, deleteImg.getIconWidth(), deleteImg.getIconWidth());
            blankImg = new ImageIcon(img);
        }
        initialize(hideRemoveIcon,false, criteresOp);
    }
    public CriteriaViewer(SearchWindow parent, ElementModel em, Pair collectivite, Pair budget,boolean hideRemoveIcon) {
        this(parent,em,collectivite,budget,hideRemoveIcon,false);
    }
    public CriteriaViewer(SearchWindow parent, ElementModel em, Pair collectivite, Pair budget, boolean hideRemoveIcon,boolean hideChoice) {
        super();
        this.parent = parent;
        this.hideRemoveIcon = hideRemoveIcon;
        this.em = em;
//        logger.debug(em.getId()+": "+em.getAvailableCriteres().size()+" criterias");
        this.dm = em.getParent().getParent();
        this.collectivite=collectivite;
        this.budget=budget;
        this.hideChoice = hideChoice;
        if (blankImg == null) {
            Image img = new BufferedImage(deleteImg.getIconWidth(), deleteImg.getIconHeight(), BufferedImage.TYPE_INT_RGB);
            Graphics2D gr = (Graphics2D)img.getGraphics();
            gr.setBackground(parent.getBackground());
            gr.clearRect(0, 0, deleteImg.getIconWidth(), deleteImg.getIconWidth());
            blankImg = new ImageIcon(img);
        }
        initialize(hideRemoveIcon,hideChoice);
    }
    
    protected void initialize(boolean hideRemoveIcon, boolean hideChoice) {
        FormLayout fl = new FormLayout("3dlu,pref,3dlu,25dlu,3dlu,110dlu,3dlu,pref:grow(1.0),3dlu", "0dlu,pref");
        PanelBuilder pb = new PanelBuilder(fl, this);
        CellConstraints cc = new CellConstraints();
        if (!hideRemoveIcon) {
            final AbstractAction act = new AbstractAction() {

                public void actionPerformed(ActionEvent e) {
                    parent.removeLine(CriteriaViewer.this);
                }
                
            };
            deleteMe = new JFocusableLabel(deleteImg,act);
            deleteMe.addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent evt) {
                    act.actionPerformed(null);
                }
            });
            deleteMe.setToolTipText("Supprimer ce critère");
            pb.add(deleteMe, cc.xy(2, 2));
            Pair[] data = new Pair[2];
            data[0] = new Pair(AND_ASSOCIATOR, "ET");
            data[1] = new Pair(OR_ASSOCIATOR, "OU");
            pb.add(cbxAndOr = new JComboBox(data), cc.xy(4, 2));
        } else {
            JLabel blank = new JLabel(blankImg);
            blank.setBorder(new EmptyBorder(2,2,2,2));
            pb.add(blank, cc.xy(2, 2));
        }
        if(!hideChoice) {
            pb.add(cbxTypeCritere = new JComboBox(em.getAvailableCriteres()), cc.xy(6, 2));
            cbxTypeCritere.addActionListener(this);
        } else {
            pb.add(defaultLabel = new JLabel("XXXX"), cc.xy(6, 2));
        }
        pb.add(replaceMe = new JPanel(new FlowLayout()), cc.xy(8, 2));
        if(!hideChoice && cbxTypeCritere.getItemCount()>0) {
            cbxTypeCritere.setSelectedIndex(0);
        } else {
            inputs = new Hashtable<String, InputData>();
            orderedInputs = new ArrayList<InputData>();
        }
    }
    
    protected void initialize(boolean hideRemoveIcon, boolean hideChoice, Vector<CritereModel> criteresOp) {
        FormLayout fl = new FormLayout("3dlu,pref,3dlu,25dlu,3dlu,110dlu,3dlu,pref:grow(1.0),3dlu", "0dlu,pref");
        PanelBuilder pb = new PanelBuilder(fl, this);
        CellConstraints cc = new CellConstraints();
        if (!hideRemoveIcon) {
            final AbstractAction act = new AbstractAction() {

                public void actionPerformed(ActionEvent e) {
                    parent.removeLine(CriteriaViewer.this);
                }
                
            };
            deleteMe = new JFocusableLabel(deleteImg,act);
            deleteMe.addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent evt) {
                    act.actionPerformed(null);
                }
            });
            deleteMe.setToolTipText("Supprimer ce critère");
            pb.add(deleteMe, cc.xy(2, 2));
            Pair[] data = new Pair[2];
            data[0] = new Pair(AND_ASSOCIATOR, "ET");
            data[1] = new Pair(OR_ASSOCIATOR, "OU");
            pb.add(cbxAndOr = new JComboBox(data), cc.xy(4, 2));
        } else {
            JLabel blank = new JLabel(blankImg);
            blank.setBorder(new EmptyBorder(2,2,2,2));
            pb.add(blank, cc.xy(2, 2));
        }
        if(!hideChoice) {
            pb.add(cbxTypeCritere = new JComboBox(criteresOp), cc.xy(6, 2));
            cbxTypeCritere.addActionListener(this);
        } else {
            pb.add(defaultLabel = new JLabel("XXXX"), cc.xy(6, 2));
        }
        pb.add(replaceMe = new JPanel(new FlowLayout()), cc.xy(8, 2));
        if(!hideChoice && cbxTypeCritere.getItemCount()>0) {
            cbxTypeCritere.setSelectedIndex(0);
        } else {
            inputs = new Hashtable<String, InputData>();
            orderedInputs = new ArrayList<InputData>();
        }
    }
    
    public void focusOnDelete() {
    	deleteMe.grabFocus();
    }
    
    public void actionPerformed(ActionEvent evt) {
        if (evt.getSource() == cbxTypeCritere) {
            inputs = new Hashtable<String, InputData>();
            orderedInputs = new ArrayList<InputData>();
            CritereModel cm = (CritereModel)cbxTypeCritere.getSelectedItem();
            changeCritereModel(cm.clone());
        }
    }
    public void changeCritereModel(final CritereModel newCm) {
        CritereModel cm = null;
        if(!hideChoice) {
            for(int i=0;i<cbxTypeCritere.getModel().getSize();i++) {
                Object other = cbxTypeCritere.getModel().getElementAt(i);
                if(newCm.equals(other)) {
                    cm = (CritereModel)other;
                    break;
                }
            }
        } else {
            cm=newCm.clone();
        }
        if(cm==null) return;
        if(!hideChoice && !cbxTypeCritere.getSelectedItem().equals(cm)) {
            cbxTypeCritere.removeActionListener(this);
            cbxTypeCritere.setSelectedItem(cm);
            cbxTypeCritere.addActionListener(this);
        }
        currentCritereModel = cm.clone();
        replaceMe.removeAll();
        if(hideChoice) {
            defaultLabel.setText(cm.getLibelle());
        }
        if(currentCritereModel.hasBlank())
            replaceMe.add(Box.createHorizontalGlue());
        for (XmlMarshallable xm : cm.getInputs()) {
            if (xm instanceof InputModel) {
                final ArrayList<Component> al = new ArrayList<Component>();
                final InputModel im = (InputModel)xm;
                if (im.getLibelle() != null)
                    al.add(getLabel(im.getLibelle()));
                String datatype = im.getDatatype();
                Pair[] pairs = null;
                if (datatype!=null) {
                    try { pairs = DataLayerManager.getImplementation().getOperators(datatype); } catch(Throwable t) {}
                    if(pairs.length>0) {
                        final MComboBox opers = new MComboBox(pairs);
                        opers.setName("OPERATEUR:" + im.getId());
                        al.add(opers);
                        opers.addActionListener(new ActionListener() {
                            public void actionPerformed(ActionEvent e) {
                                Pair item = (Pair)opers.getSelectedItem();
                                InputData id = inputs.get("VALEUR:"+im.getId());
                                JButton pbHelp = null;
                                if(id!=null) {
                                    // on cherche si il y a un helpButton
                                    for(Component comp:al) {
                                        if(comp instanceof JHelpButton) {
                                            pbHelp = (JButton)comp;
                                            break;
                                        }
                                    }
                                }
                                if(item.key.equals("xem:StringIsNull") || item.key.equals("xem:StringIsNotNull")) {
                                    if(id!=null) {
                                        if(id instanceof MTextField) {
                                            MTextField mtf = (MTextField)id;
                                            mtf.setText("--");
                                            mtf.setEnabled(false);
                                            if(pbHelp!=null) pbHelp.setEnabled(false);
                                        }
                                    }
                                } else {
                                    if(id instanceof MTextField) {
                                        MTextField mtf = (MTextField)id;
                                        mtf.setText("");
                                        mtf.setEnabled(true);
                                        if(pbHelp!=null) pbHelp.setEnabled(true);
                                    }
                                }
                            }
                        });
                        String opValue = MainWindow.getInstance().getCriteriaInputValue(im.getKeyName()+".OPERATEUR");
                        if(opValue!=null) {
//                            logger.debug("opValue="+opValue);
                            int opIndex = -1;
                            try { opIndex = Integer.parseInt(opValue); } catch(Throwable t) {}
                            if(opIndex!=-1 && opIndex<opers.getItemCount()) opers.setSelectedIndex(opIndex);
                        }
                    }
                }
                MTextField jf = new MTextField(im);
                jf.setName("VALEUR:" + im.getId());
                jf.setColumns(10);
                al.add(jf);
                String helpPath = im.getHelpPath();
                if(helpPath!=null) {
                    JHelpButton pbHelp = new JHelpButton(jf,em,collectivite,budget, helpPath);
                    al.add(pbHelp);
                }
                // on regarde si il a une valeur par défaut
                if(im.getValue()!=null) jf.setText(im.getValue());
                else if(MainWindow.getInstance().getCriteriaInputValue(im.getKeyName())!=null) {
                    jf.setText(MainWindow.getInstance().getCriteriaInputValue(im.getKeyName()));
                }
                for (Component c : al) {
                    replaceMe.add(c);
                    if (c.getName() != null) {
                        inputs.put(c.getName(), (InputData)c);
                        orderedInputs.add((InputData)c);
                    }
                }
            } else if (xm instanceof SelectModel) {
                SelectModel sm = (SelectModel)xm;
                Collection<Pair> data = null;
                ArrayList<FocusListener> focusListeners = new ArrayList<FocusListener>();
                data = sm.getNewRecipient();
                // 1 : est-ce que la liste dépend de la nomenclature
                // si oui, regrarder si il faut mettre une liste comportant la liste des nomenclatures
                if(sm.isDependantRepository()){
                	try{
                		Vector<Pair> vRepository = DataLayerManager.getImplementation().getListeCodeLibelleRepository(dm,collectivite);
                		if(vRepository.size()>1){
                			MComboBox mcb = new MComboBox(vRepository);
                			mcb.setName("ListRepository_"+sm.getId());
                			replaceMe.add(mcb);
                			// TODO : Foculistener des éléments de la combobox
                			MComboBox mcbListeCorresLibelle = remplirListe(sm,data,focusListeners);
                			mcb.addItemListener(new MComboBoxItemListener(this,sm,collectivite,mcb,mcbListeCorresLibelle));
                			
                		}else{
                			remplirListe(sm,data,focusListeners);
                		}
                	} catch (Exception dbEx) {
                		Component comp2 = this;
                		while (comp2 != null && !(comp2 instanceof JFrame))
                			comp2 = comp2.getParent();
                		if (comp2 != null)
                			new DisplayExceptionDlg((JFrame)comp2, dbEx);
                		else dbEx.printStackTrace();
                	}               
                }else{
                	// 2 : mettre la liste
// debut de recherche des valeurs a mettre dans la liste
                	if (sm.getOptions() != null) {
                		for (OptionModel om : sm.getOptions()) {
                			data.add(new Pair(om.getValue(), om.getLibelle()));
                		}
                	}
                	for (RecherchePaireModel rpm : sm.getRecherches()) {
                		if (rpm.getPath() != null) {
                			// on fait une requete dans le referentiel pour
                			// remplir la liste
                			try {
                				Collection<Pair> localData = null;
                				localData = rpm.getNewRecipient();
                				localData.addAll(DataLayerManager.getImplementation().queryRepository(dm,collectivite,rpm));
                				data.addAll(localData);
                			} catch (Exception dbEx) {
                				Component comp2 = this;
                				while (comp2 != null && !(comp2 instanceof JFrame))
                					comp2 = comp2.getParent();
                				if (comp2 != null)
                					new DisplayExceptionDlg((JFrame)comp2, dbEx);
                				else dbEx.printStackTrace();
                			}
                		}
                	}
                	for(SpecialKeyEntryModel skem:sm.getSpecialKeys()) {
                		if(!skem.isDepends()) {
                			// si pas de dependance, on peut remplir maintenant
                			try {
                				data.addAll(DataLayerManager.getImplementation().getSpecialKeys1(dm,collectivite,budget,skem.isDistinct()));
                			} catch(DataAccessException daEx) {
                				new DisplayExceptionDlg(parent.parentFrame,daEx);
                			} catch(DataConfigurationException dcEx) {
                				new DisplayExceptionDlg(parent.parentFrame,dcEx);
                			}
                		} else {
                			// il faut creer un FocusListener pour reinitialiser la liste a chaque fois
                			SpecialKeyFocusAdapter fl = new SpecialKeyFocusAdapter(this,skem,sm,collectivite,budget);
                			focusListeners.add(fl);
                		}
                	}
                	Vector<Pair> vData = new Vector<Pair>();
                	for (Pair p : data)
                		vData.add(p);
// fin de recherche des valeurs a mettre dans la liste
                	MComboBox cbxData = new MComboBox(vData,sm);
                	cbxData.setName("VALEUR:" + sm.getId());
                	if(!focusListeners.isEmpty()) {
                		for(FocusListener fl:focusListeners) cbxData.addFocusListener(fl);
                	}
                	if (sm.getLibelle() != null)
                		replaceMe.add(getLabel(sm.getLibelle()));
                	replaceMe.add(cbxData);
                	if(sm.getValue()!=null) {
                		Pair p = new Pair(sm.getValue(),"");
                		// les paires se compares uniquement par leurs clefs
                		cbxData.setSelectedItem(p);
                	}
                	inputs.put(cbxData.getName(), cbxData);
                	orderedInputs.add(cbxData);
                }
            } else {
                logger.warn("classe non traitee: " + xm.getClass().getName());
            }
        }
        // on regarde si il faut afficher le check box pour le champ optionnel
        String idAffichage = cm.getIdAffichage();
        if(idAffichage!=null && idAffichage.length()>0) {
            chkDisplayOptionnal = new JCheckBox("Afficher le champ");
            // if(option.isChecked()) chkDisplayOptionnal.setSelected(true);
            if(cm.isDisplayOptional()) {
                chkDisplayOptionnal.setSelected(true);
            }
            replaceMe.add(chkDisplayOptionnal);
        } else {
            chkDisplayOptionnal = null;
        }
        
        parent.repaintCriterias();
        // on regarde si le critere a des valeurs
        if(newCm.getValeurs()!=null) {
            for(PropertyModel pm:newCm.getValeurs()) {
                String key = pm.getName();
                InputData id = inputs.get(key);
                if(id==null) {
                    if(key.equals("AND_OR") && cbxAndOr!=null) {
                        Pair p = new Pair(pm.getValue(),"||".equals(pm.getValue())?"OU":"ET");
                        cbxAndOr.setSelectedItem(p);
                    } else {
                        logger.debug("id not found: "+key);
                    }
//                    id.setInputValue(pm.getValue());
                } else {
                    if(id instanceof MComboBox) {
                        JComboBox cbx = (JComboBox)id;
                        if(cbx.isShowing()) {
                            cbx.dispatchEvent(new FocusEvent(cbx,FocusEvent.FOCUS_GAINED));
                        }
                    }
                    id.setInputValue(pm.getValue());
                }
            }
        }
        if(replaceMe.getComponentCount()==0) {
            // c'est un titre, il faut le faire grandir au max
            removeAll();
            setLayout(new FlowLayout());
            add(new JLabel(cm.getLibelle()));
        }
    }
    
    private MComboBox remplirListe(SelectModel sm,Collection<Pair> data,ArrayList<FocusListener> focusListeners){
    	if (sm.getOptions() != null) {
    		for (OptionModel om : sm.getOptions()) {
    			data.add(new Pair(om.getValue(), om.getLibelle()));
    		}
    	}
    	for (RecherchePaireModel rpm : sm.getRecherches()) {
    		if (rpm.getPath() != null) {
    			// on fait une requete dans le referentiel pour
    			// remplir la liste
    			try {
    				Collection<Pair> localData = null;
    				localData = rpm.getNewRecipient();
    				localData.addAll(DataLayerManager.getImplementation().queryRepository(dm,collectivite,rpm));
    				data.addAll(localData);
    			} catch (Exception dbEx) {
    				Component comp2 = this;
    				while (comp2 != null && !(comp2 instanceof JFrame))
    					comp2 = comp2.getParent();
    				if (comp2 != null)
    					new DisplayExceptionDlg((JFrame)comp2, dbEx);
    				else dbEx.printStackTrace();
    			}
    		}
    	}
    	for(SpecialKeyEntryModel skem:sm.getSpecialKeys()) {
    		if (!skem.isDepends()) {
				// si pas de dependance, on peut remplir maintenant
				try {
					data.addAll(DataLayerManager.getImplementation().getSpecialKeys1(dm, collectivite, budget,skem.isDistinct()));
				} catch (DataAccessException daEx) {
					new DisplayExceptionDlg(parent.parentFrame, daEx);
				} catch (DataConfigurationException dcEx) {
					new DisplayExceptionDlg(parent.parentFrame, dcEx);
				}
			} else {
				// il faut creer un FocusListener pour reinitialiser la liste a
				// chaque fois
				SpecialKeyFocusAdapter fl = new SpecialKeyFocusAdapter(this,
						skem, sm, collectivite, budget);
				focusListeners.add(fl);
			}
		}
		Vector<Pair> vData = new Vector<Pair>();
		for (Pair p : data)
			vData.add(p);
		// fin de recherche des valeurs a mettre dans la liste
		MComboBox cbxData = new MComboBox(vData, sm);
		cbxData.setName("VALEUR:" + sm.getId());
		if (!focusListeners.isEmpty()) {
			for (FocusListener fl : focusListeners)
				cbxData.addFocusListener(fl);
		}
		if (sm.getLibelle() != null)
			replaceMe.add(getLabel(sm.getLibelle()));
			replaceMe.add(cbxData);
		if (sm.getValue() != null) {
			Pair p = new Pair(sm.getValue(), "");
			// les paires se compares uniquement par leurs clefs
			cbxData.setSelectedItem(p);
		}
		inputs.put(cbxData.getName(), cbxData);
		orderedInputs.add(cbxData);
		return cbxData;
    }
    
    private MComboBox refreshListe(SelectModel sm,Collection<Pair> data,ArrayList<FocusListener> focusListeners,Pair repository){
    	inputs.clear();
    	orderedInputs.clear();
    	for (RecherchePaireModel rpm : sm.getRecherches()) {
    		if (rpm.getPath() != null) {
    			// on fait une requete dans le referentiel pour
    			// remplir la liste
    			try {
    				Collection<Pair> localData = null;
    				localData = rpm.getNewRecipient();
    				localData.addAll(DataLayerManager.getImplementation().queryRepository(dm,collectivite,rpm,repository));
    				data.addAll(localData);
    			} catch (Exception dbEx) {
    				Component comp2 = this;
    				while (comp2 != null && !(comp2 instanceof JFrame))
    					comp2 = comp2.getParent();
    				if (comp2 != null)
    					new DisplayExceptionDlg((JFrame)comp2, dbEx);
    				else dbEx.printStackTrace();
    			}
    		}
    	}
		Vector<Pair> vData = new Vector<Pair>();
		for (Pair p : data)
			vData.add(p);
		// fin de recherche des valeurs a mettre dans la liste
		MComboBox cbxData = new MComboBox(vData, sm);
		cbxData.setName("VALEUR:" + sm.getId());
		if (!focusListeners.isEmpty()) {
			for (FocusListener fl : focusListeners)
				cbxData.addFocusListener(fl);
		}
		if (sm.getLibelle() != null)
			replaceMe.add(getLabel(sm.getLibelle()));
			replaceMe.add(cbxData);
		if (sm.getValue() != null) {
			Pair p = new Pair(sm.getValue(), "");
			// les paires se compares uniquement par leurs clefs
			cbxData.setSelectedItem(p);
		}
		inputs.put(cbxData.getName(), cbxData);
		orderedInputs.add(cbxData);
		return cbxData;
    }
      
    
    public static Component getLabel(String lbl) {
        return iF.newLabel(lbl, "normal");
    }
    
    public boolean isOptionalDisplayChecked() {
        boolean result = false;
        if(chkDisplayOptionnal!=null) {
            result = chkDisplayOptionnal.isSelected();
        }
        return result;
    }
    
    public String getXPathExpr() throws ToolException {
        String expr = ((CritereModel)cbxTypeCritere.getSelectedItem()).getPath().getPath();
        int ix = expr.indexOf("##");
        while (ix >= 0) {
            String sTmp = expr.substring(ix + 2);
            int pos = sTmp.indexOf("##");
            if (pos < 0)
                throw new ToolException("Expression mal formee: " + expr, ToolException.ERROR_MALFORMED_XPATH_EXPR);
            String middle = sTmp.substring(0, pos);
            InputData id = inputs.get(middle);
            if (id.getInputValue() == null || id.getInputValue().length() == 0) {
                componentToFocus = (JComponent)id;
                throw new ToolException("Le champ désigné par le curseur doit être rempli.", ToolException.ERROR_MISFILLED_CRITERIA);
            }
            if (id.getInputValue() != null && id.getInputValue().indexOf('"') >= 0) {
                componentToFocus = (JComponent)id;
                throw new ToolException("Le champ désigné par le curseur ne doit pas contenir de guillemets.", ToolException.ERROR_MISFILLED_CRITERIA);
            }
            if(id instanceof MTextField) {
                MTextField df = (MTextField)id;
                if("numeric".equals(df.getDatatype())) {
                    try { Float.parseFloat(df.getText()); } catch(NumberFormatException mfEx) {
                        throw new ToolException("Le champ désigné par le curseur doit contenir une valeur numérique.",ToolException.ERROR_MISFILLED_CRITERIA);
                    }
                } else if("date".equals(df.getDatatype())) {
                    // verification de la valeur saisie : non vide, bien formattée, etc...
                    expr = expr.replace("##" + middle + "##", id.getXmlFormattedDate());
                }
                if(df.getMarshallModel() instanceof InputModel) {
                    InputModel im = (InputModel)df.getMarshallModel();
                    MainWindow.getInstance().setCriteriaInputValue(im.getKeyName(),df.isEditable()?id.getInputValue():null);
                    if(im.getDatatype()!=null) {
                        InputData op = inputs.get("OPERATEUR:"+im.getId());
                        if(op instanceof MComboBox) {
                            MComboBox cbxOp = (MComboBox)op;
                            int selectedIx = cbxOp.getSelectedIndex();
                            MainWindow.getInstance().setCriteriaInputValue(im.getKeyName()+".OPERATEUR",Integer.toString(selectedIx));
                        }
                    }
                }
            }
            expr = expr.replace("##" + middle + "##", id.getInputValue());	// .replaceAll("'","''")
            ix = expr.indexOf("##");
        }
        expr = expr.replaceAll("\\{", "[").replaceAll("\\}", "]");
        return expr;
    }
    public Hashtable<String,String> getParameters() throws ToolException {
        Hashtable<String,String> ret = new Hashtable<String,String>();
        for(Enumeration<InputData> enumer=inputs.elements();enumer.hasMoreElements();) {
            InputData id = enumer.nextElement();
            if (id.getInputValue() == null || id.getInputValue().length() == 0) {
                componentToFocus = (JComponent)id;
                throw new ToolException("Le champ désigné par le curseur doit être rempli.", ToolException.ERROR_MISFILLED_CRITERIA);
            }
            if (id.getInputValue() != null && id.getInputValue().indexOf('"') >= 0) {
                componentToFocus = (JComponent)id;
                throw new ToolException("Le champ désigné par le curseur ne doit pas contenir de guillemets.", ToolException.ERROR_MISFILLED_CRITERIA);
            }
            if(id instanceof MTextField) {
                MTextField df = (MTextField)id;
                if("numeric".equals(df.getDatatype())) {
                    try { Float.parseFloat(df.getText()); } catch(NumberFormatException mfEx) {
                        componentToFocus = df;
                        throw new ToolException("Le champ désigné par le curseur doit contenir une valeur numérique.",ToolException.ERROR_MISFILLED_CRITERIA);
                    }
                } else if("date".equals(df.getDatatype())) {
                    try {
                        sdf.parse(df.getText());
                    } catch(ParseException pEx) {
                        componentToFocus=df;
                        throw new ToolException("Le champ désigné par le curseur doit contenir une date au format "+DATE_FORMAT,ToolException.ERROR_MISFILLED_CRITERIA);
                    }
                }
            }
            String name = id.getName();
//            name = name.substring(name.indexOf(':')+1);
//logger.debug(id.getName()+"->"+name);
            ret.put(name,id.getInputValue());
        }
        return ret;
    }
    
    public String getAndOrEr() {
        if (hideRemoveIcon)
            return "";
        return ((Pair)(cbxAndOr.getSelectedItem())).key;
    }
    
    public void doFocusOnError() {
        if (componentToFocus != null)
            componentToFocus.grabFocus();
    }
    public XmlMarshallable getXmlMarshallable() {
        currentCritereModel.getValeurs().removeAllElements();
        currentCritereModel.removeAllProperties();
        currentCritereModel.setDisplayOptional(isOptionalDisplayChecked());
        if(cbxAndOr!=null && cbxAndOr.getSelectedItem()!=null) {
            try {
                currentCritereModel.addChild(new PropertyModel("AND_OR",((Pair)cbxAndOr.getSelectedItem()).key),PropertyModel.QN);
            } catch(SAXException saxEx) { }
        }
        for(InputData id:orderedInputs) {
            try {
                currentCritereModel.addChild(new PropertyModel(id.getName(),id.getInputValue()),PropertyModel.QN);
            } catch(SAXException saxEx) { }
        }
        return currentCritereModel;
    }
    public CritereModel getModel() {
        return (CritereModel)getXmlMarshallable();
    }
    interface InputData {
        public String getInputValue();
        public void setInputValue(String data);
        public XmlMarshallable getMarshallModel();
        public String getName();
        public String getXmlFormattedDate();
    }
    
    class MComboBox extends JComboBox implements InputData {
        /**
         * Comment for <code>serialVersionUID</code>
         */
        private static final long serialVersionUID = 1L;
        private SelectModel sm;
        private Hashtable<String,Pair> innerData;
        
        public MComboBox(Vector<Pair> data) { this(data,null); }
        public MComboBox(Vector<Pair> data,SelectModel sm) {
            super(data);
            setEditable(false);
            this.sm=sm;
            if(sm!=null) addListener();
            innerData = new Hashtable<String,Pair>();
            for(Pair p:data) innerData.put(p.key,p);
        }
        
        public MComboBox(Pair[] data) { this(data,null); }
        public MComboBox(Pair[] data,SelectModel sm) {
            super(data);
            setEditable(false);
            this.sm=sm;
            if(sm!=null) addListener();
            innerData = new Hashtable<String,Pair>();
            for(int i=0;i<data.length;i++) {
                innerData.put(data[i].key,data[i]);
            }
        }
        private void addListener() {
            addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent evt) {
                    if(getSelectedItem()!=null) {
                        MComboBox.this.sm.setValue(((Pair)getSelectedItem()).key);
                    }
                }
            });
        }
        public String getInputValue() {
            Object item = getSelectedItem();
            if (item == null)
                return null;
            if (item instanceof Pair) {
                return ((Pair)item).key;
            }
            return item.toString();
        }
        public void setInputValue(String data) {
            if(getItemCount()==0) grabFocus();
            Pair p = innerData.get(data);
            if(p==null) {
                p = new Pair(data,data);
                addItem(p);
            }
            if(p!=null) setSelectedItem(p);
        }
        public XmlMarshallable getMarshallModel() {
            if(sm==null) return null;
            sm.setValue(getInputValue());
            return sm;
        }
        public String getXmlFormattedDate(){
            return "";
        }
        public void setData(Vector<Pair> vpair){
        	removeAllItems();
        	for(int i=0;i<vpair.size();i++) {
        		addItem(vpair.get(i));
                innerData.put(vpair.get(i).key,vpair.get(i));
            }
        }
    }
    
    class MTextField extends JTextField implements InputData {
        /**
         * Comment for <code>serialVersionUID</code>
         */
        private static final long serialVersionUID = 1L;
        private InputModel im;
        
        public MTextField(InputModel im) {
            super();
            this.im=im;
            if(im.isUppercase()) setDocument(new UppercaseFilteredField());
            else if("date".equals(im.getDatatype())) {
                setDocument(new DateFilteredField());
                addFocusListener(new FocusListener() {
                    public void focusGained(FocusEvent e) {
                    }
                    public void focusLost(FocusEvent e) {
                        try {
                            Date d = sdf.parse(getText());
                            setText(sdf.format(d));
                        } catch(Throwable t) { }
                    }
                    
                });
            }
            if(im!=null) {
                addCaretListener(new CaretListener() {
                    public void caretUpdate(CaretEvent e) {
                        MTextField.this.im.setValue(getText());
                    }
                });
            }
        }
        public String getXmlFormattedDate(){
            SimpleDateFormat sdfxml = new SimpleDateFormat(im.getXmlDateFormat());
            try {
                Date d = sdf.parse(getText());
                return sdfxml.format(d);
            } catch (Throwable t) {
                
            }
            return null;
        }
        public String getDatatype() { return im.getDatatype(); }
        
        public String getInputValue() {
            return getText();
        }
        public void setInputValue(String data){ 
//            logger.debug("setValue("+data+")");
            setText(data);
//            MTextField.this.im.setValue(data);
        }
//        @Override
//        public void setText(String txt) {
//            logger.debug(im.getId()+".setText("+txt+")");
//            if(im.getId().equals("MtLig") && (txt==null || txt.length()==0)) {
//                Exception ex = new Exception();
//                for(StackTraceElement ste:ex.getStackTrace()) System.out.println(ste.toString());
//            }
//            super.setText(txt);
//        }
        public XmlMarshallable getMarshallModel() {
            im.setValue(getInputValue());
            return im;
        }
        public Date getDate() {
            if(getDatatype().equals("date"))
                try {
                    return sdf.parse(getText());
                } catch(Throwable t) {};
                return null;
        }
    }
    
    private class UppercaseFilteredField extends PlainDocument {
        /**
         * Comment for <code>serialVersionUID</code>
         */
        private static final long serialVersionUID = 1L;
        
        @Override
        public void insertString(int offset, String str, AttributeSet attr) throws BadLocationException {
            if (str == null)
                return;
            
            str = str.toUpperCase();
            
            super.insertString(offset, str, attr);
        }
        
    }
    private class DateFilteredField extends PlainDocument {
        private static final long serialVersionUID = 2758758288192923608L;
        private String format = null;
        public DateFilteredField(String format) {
            super();
            this.format=format;
        }
        public DateFilteredField() {
            this(DATE_FORMAT);
        }
        public String getFormat() { return format; }
        @Override
        public void insertString(int offset, String str, AttributeSet attr) throws BadLocationException {
            if (str == null)
                return;
            if (getFormat() == null || getFormat().length() == 0) {
                return;
            }
            if (offset >= getFormat().length())
                return;
            char aChar = str.charAt(0);
            char fChar = getFormat().charAt(offset);
            if (Character.isDigit(aChar) == Character.isLetter(fChar)) {
                if (!Character.isLetterOrDigit(fChar)) {
                    // if char is not a number
                    
                } else {
                    super.insertString(offset, str, attr);
                    offset++;
                    if (offset < getFormat().length()) {
                        char bChar = getFormat().charAt(offset);
                        String sChar = Character.toString(bChar);
                        // if the next char in the format is not a digit append the sign.
                        if (!Character.isLetterOrDigit(bChar) && !sChar.equals(getText(offset, 1))) {
                            super.insertString(offset, sChar, attr);
                        }
                    }
                }
            }
        }
        
        
        /**
         * This method was created in VisualAge.
         * @param offset int
         * @param len int
         */
        @Override
        public void remove(int offset, int len) throws BadLocationException {
            if (getFormat() == null || getFormat().length() == 0) {
                return;
            }
            //
            //
            super.remove(offset, len);
            char bChar = getFormat().charAt(offset);
            // if the next char in the format is not a digit append the sign.
            if (!Character.isLetterOrDigit(bChar)) {
                super.remove(--offset, 1);
            }
        }
    }
    private class JHelpButton extends JButton {
        /**
         * Comment for <code>serialVersionUID</code>
         */
        private static final long serialVersionUID = 1L;
        private JTextField targetField;
        private String path;
        private Pair collectivite,budget;
        private ElementModel em;
        
        JHelpButton(JTextField targetField, ElementModel elementModel, Pair collectivite, Pair budget, String path) {
            super("...");
            this.targetField = targetField;
            this.path = path;
            this.em=elementModel;
//            this.dm=elementModel.getParent().getParent();
            this.collectivite=collectivite;
            this.budget=budget;
            Dimension fieldSize = targetField.getPreferredSize();
            setPreferredSize(new Dimension((int)fieldSize.getHeight(),(int)fieldSize.getHeight()));
            setToolTipText("Liste des éléments");
            addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent evt) {
                    JHelpButton.this.displayHelpBox();
                }
            });
        }
        
        protected void displayHelpBox() {
            TreeSet<String> values = new TreeSet<String>();
            try {
                setCursor(new Cursor(Cursor.WAIT_CURSOR));
                values.addAll(DataLayerManager.getImplementation().getDistinctValues(em,collectivite,budget,path));
                Container c = CriteriaViewer.this.parent;
                while(c!=null && !(c instanceof JFrame)) {
                    c = c.getParent();
                }
                JFrame parent = (JFrame)c;
                DlgSelectFromList dsfl = new DlgSelectFromList(parent,values);
                if(dsfl.run()) {
                    targetField.setText(dsfl.getSelectedValue());
                }
            } catch(Throwable t) {
                Container c = CriteriaViewer.this.parent;
                while(c!=null && !(c instanceof JFrame)) {
                    c = c.getParent();
                }
                new DisplayExceptionDlg((JFrame)c,t);
            } finally {
                setCursor(Cursor.getDefaultCursor());
            }
        }
    }
    private class SpecialKeyFocusAdapter extends FocusAdapter {
        private CriteriaViewer cv;
        private SelectModel sm;
        private SpecialKeyEntryModel skem;
        private DocumentModel dm;
        private Pair collectivite,budget;
        public SpecialKeyFocusAdapter(CriteriaViewer cv, SpecialKeyEntryModel skem, SelectModel sm,Pair collectivite, Pair budget) {
            super();
            this.cv=cv;
            this.skem=skem;
            this.sm = sm;
            this.collectivite=collectivite;
            this.budget=budget;
            dm = cv.dm;
        }
        @Override
        public void focusGained(FocusEvent evt) {
            JComboBox cbx = (JComboBox)evt.getSource();
            cbx.removeFocusListener(this);
            cbx.removeAllItems();
            Vector<Pair> data=new Vector<Pair>();
            try {
                if (sm.getOptions() != null) {
                    for (OptionModel om : sm.getOptions()) {
                        data.add(new Pair(om.getValue(), om.getLibelle()));
                    }
                }
                for (RecherchePaireModel rpm : sm.getRecherches()) {
                    if (rpm.getPath() != null) {
                        // on fait une requete dans le referentiel pour
                        // remplir la liste
                        try {
                            data.addAll(DataLayerManager.getImplementation().queryRepository(dm,collectivite,rpm));
                        } catch (Exception dbEx) {
                            Component comp2 = cbx;
                            while (comp2 != null && !(comp2 instanceof JFrame))
                                comp2 = comp2.getParent();
                            if (comp2 != null)
                                new DisplayExceptionDlg((JFrame)comp2, dbEx);
                            else dbEx.printStackTrace();
                        }
                    }
                }
                // il y a des dependances, on recherche les ##VALEUR:id##
                String key1=null, key2=null;
                String path = skem.getKey1().getData();
                String itemName = path.substring(2,path.length()-2);
                InputData id = cv.inputs.get(itemName);
                if (id.getInputValue() != null && id.getInputValue().length() > 0) {
                    key1 = id.getInputValue();
                }
                Collection<Pair> tmp = sm.getNewRecipient();
                if(skem.getPos()==2) {
                    // we can search here
                    tmp.addAll(DataLayerManager.getImplementation().getSpecialKeys2(dm,collectivite,budget,new Pair(key1,key1),skem.isDistinct()));
                } else {
                    path = skem.getKey1().getData();
                    itemName = path.substring(2,path.length()-2);
                    id = cv.inputs.get(itemName);
                    if (id.getInputValue() != null && id.getInputValue().length() > 0) {
                        key2 = id.getInputValue();
                    }
                    tmp.addAll(DataLayerManager.getImplementation().getSpecialKeys3(dm,collectivite,budget,new Pair(key1,key1),new Pair(key2,key2),skem.isDistinct()));
                }
                data.addAll(tmp);
                for(Pair p:data) cbx.addItem(p);
                cbx.setPopupVisible(true);
                cbx.grabFocus();
                cbx.addFocusListener(this);
            } catch(DataConfigurationException dcEx) {
                // TODO : attention au probleme de focus en boucle
                logger.error("dans focusGained",dcEx);
            } catch(DataAccessException daEx) {
                // TODO : attention au probleme de focus en boucle
                logger.error("dans focusGained",daEx);
            }
        }
    }
        
    private class MComboBoxItemListener implements ItemListener {
    	private SelectModel sm;
        private DocumentModel dm;
        private Pair collectivite;
        private MComboBox mcb,mcbListeCorresLib;
        
        public MComboBoxItemListener(CriteriaViewer cv,SelectModel sm,Pair collectivite,MComboBox mcbox,MComboBox mcboxListeCorrsLib) {
            super();
            this.sm = sm;
            this.collectivite=collectivite;
            dm = cv.dm;
            this.mcb=mcbox;
            this.mcbListeCorresLib=mcboxListeCorrsLib;
        }
    	
    	
    	public void itemStateChanged(ItemEvent evt){
    		try {
    			Pair repository = (Pair)mcb.getSelectedItem();
    			Vector<Pair> vRepository = (Vector<Pair>)DataLayerManager.getImplementation().queryRepository(dm,collectivite,sm.getRecherches().get(0),repository);
    			mcbListeCorresLib.setData(vRepository);
			} catch (DataConfigurationException e) {
				e.printStackTrace();
			} catch (DataAccessException e) {
				e.printStackTrace();
			}			
    	}
    }
    
    private class JFocusableLabel extends JLabel {

        public JFocusableLabel(Icon icon, Action act) {
            super(icon);
            setFocusable(true);
            setBorder(new EmptyBorder(2,2,2,2));
            getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE,0), "CLICK");
            getActionMap().put("CLICK", act);
            addFocusListener(new FocusAdapter() {

                @Override
                public void focusGained(FocusEvent e) {
                    ((JFocusableLabel)e.getComponent()).paintFocus();
                }

                @Override
                public void focusLost(FocusEvent e) {
                    ((JFocusableLabel)e.getComponent()).paintFocus();
                }
                
            });
            addMouseListener(new MouseAdapter() {

                @Override
                public void mousePressed(MouseEvent e) {
                    requestFocus();
                }
                
            });
        }
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
//            if(isFocusOwner()) {
//                g.setColor(new Color(255,0,0));
//                g.drawRect(0, 0, getWidth(), getHeight());
//            }
            paintFocus(g);
        }
        protected void paintFocus(Graphics g) {
            if(g==null) return;
            if(isFocusOwner()) {
                g.setColor(UIManager.getColor("Button.focus"));
            } else {
                g.setColor(UIManager.getColor("Label.background"));
            }
            g.drawRect(0, 0, getWidth()-1, getHeight()-1);
        }
        protected void paintFocus() { paintFocus(getGraphics()); }


        @Override
        protected void processFocusEvent(FocusEvent e) {
            super.processFocusEvent(e);
            invalidate();
        }

    }
    public void setInputValue(String inputId,String value) {
        InputData id = inputs.get(inputId);
        if(id!=null) id.setInputValue(value);
    }
	public CritereModel getCurrentCritereModel() {
		return currentCritereModel;
	}
	public boolean isHideRemoveIcon() {
		return hideRemoveIcon;
	}
}
