/*
 * Copyright
 *   2007 axYus - www.axyus.com
 *   2005 C.Marchand - christophe.marchand@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 Lesser General Public License as published by
 * the Free Software Foundation; either version 3 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser 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 fr.gouv.finances.cp.xemelios.updater.UpdaterUtil;
import fr.gouv.finances.cp.xemelios.common.config.Loader;
import fr.gouv.finances.cp.xemelios.controls.extend.MainControlWrapper;
import fr.gouv.finances.cp.xemelios.common.components.XemeliosComponentsModel;
import fr.gouv.finances.cp.xemelios.common.components.XemeliosComponentsParser;
import fr.gouv.finances.cp.xemelios.starter.Bootstrap;
import fr.gouv.finances.cp.xemelios.tools.AbstractTool;
import fr.gouv.finances.cp.xemelios.tools.ToolConstants;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.DisplayMode;
import java.awt.Frame;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Box;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JDesktopPane;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.border.EtchedBorder;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileSystemView;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator;
import org.jdesktop.swingworker.SwingWorker;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import fr.gouv.finances.cp.utils.IoUtils;
import fr.gouv.finances.cp.utils.NavigationContext;
import fr.gouv.finances.cp.utils.Pair;
import fr.gouv.finances.cp.utils.PropertiesExpansion;
import fr.gouv.finances.cp.utils.StartUrl;
import fr.gouv.finances.cp.utils.ui.DisplayExceptionDlg;
import fr.gouv.finances.cp.utils.ui.IhmFactory;
import fr.gouv.finances.cp.utils.ui.JWaiter;
import fr.gouv.finances.cp.utils.ui.RamViewer;
import fr.gouv.finances.cp.utils.xml.marshal.XmlOutputter;
import fr.gouv.finances.cp.utils.xml.xpath.FunctionResolver;
import fr.gouv.finances.cp.xemelios.Splash;
import fr.gouv.finances.cp.xemelios.auth.AuthenticationCancelationException;
import fr.gouv.finances.cp.xemelios.auth.AuthenticationConfigurationException;
import fr.gouv.finances.cp.xemelios.auth.AuthenticationFactory;
import fr.gouv.finances.cp.xemelios.auth.AuthenticationManager;
import fr.gouv.finances.cp.xemelios.auth.XemeliosUser;
import fr.gouv.finances.cp.xemelios.common.Constants;
import fr.gouv.finances.cp.xemelios.common.DematTransform;
import fr.gouv.finances.cp.xemelios.common.PJRef;
import fr.gouv.finances.cp.xemelios.common.ToolException;
import fr.gouv.finances.cp.xemelios.common.config.DocumentModel;
import fr.gouv.finances.cp.xemelios.common.config.DocumentsDeriveParser;
import fr.gouv.finances.cp.xemelios.common.config.DocumentsModel;
import fr.gouv.finances.cp.xemelios.common.config.DocumentsParser;
import fr.gouv.finances.cp.xemelios.common.config.ElementModel;
import fr.gouv.finances.cp.xemelios.common.config.EnvironmentDomain;
import fr.gouv.finances.cp.xemelios.common.config.EtatModel;
import fr.gouv.finances.cp.xemelios.common.config.PersonnalConfigParser;
import fr.gouv.finances.cp.xemelios.common.config.PersonnalConfigurationModel;
import fr.gouv.finances.cp.xemelios.common.config.SavedRequestsModel;
import fr.gouv.finances.cp.xemelios.common.listeners.DbEventListener;
import fr.gouv.finances.cp.xemelios.common.listeners.StartListener;
import fr.gouv.finances.cp.xemelios.controls.DlgControl;
import fr.gouv.finances.cp.xemelios.controls.MainControl;
import fr.gouv.finances.cp.xemelios.data.DataAccessException;
import fr.gouv.finances.cp.xemelios.data.DataConfigurationException;
import fr.gouv.finances.cp.xemelios.data.DataImpl;
import fr.gouv.finances.cp.xemelios.data.DataLayerManager;
import fr.gouv.finances.cp.xemelios.importers.EtatImporteur;
import fr.gouv.finances.cp.xemelios.importers.ImportServiceSwingProvider;
import fr.gouv.finances.cp.xemelios.ui.admin.DlgSystemConfiguration;
import fr.gouv.finances.cp.xemelios.ui.export.DlgSearchConfig;
import fr.gouv.finances.cp.xemelios.ui.importDialog.ImportDialog;
import fr.gouv.finances.cp.xemelios.ui.importDialog.ImportFile;
import fr.gouv.finances.cp.xemelios.updater.AbstractConfigurator;
import fr.gouv.finances.cp.xemelios.updater.script.config.ComponentModel;
import fr.gouv.finances.cp.xemelios.updater.script.config.ComponentsParser;
import fr.gouv.finances.cp.xemelios.updater.script.config.XemeliosComponentModel;
import fr.gouv.finances.cp.xemelios.updater.ui.DlgUpdate;

/**
 * Main Xemelios window
 *
 * @author christophe.marchand Date: 15 aot 2004 Time: 11:46:18
 */
public class MainWindow  extends JFrame implements DbEventListener {
    
    private static Logger logger = null;
    /**
     * Comment for <code>serialVersionUID</code>
     */
    private static final long serialVersionUID = 3977304325938558517L;
    
    public static final int DEFAULT_WIDTH = 800;
    public static final int DEFAULT_HEIGHT = 600;
    private Action actExporter, actQuitter;
    private JDesktopPane desk;
    private JTextField statusField;
    private StartListener startListener;
    private DocumentsModel docModels;
    protected File lastInputDirectory = null;
    private JMenu searchMenu;
    private JMenu windowMenu;
    private JMenu browseMenu;
    private JMenu exportMenu;
    private JMenu importerMenu=null;
    private JMenu controlerMenu=null;
    private JMenuItem mnuClean=null;
    private JMenuItem mnuCleanPro=null;
    private JLabel backImage;
    private JWaiter waiter;
    private PersonnalConfigurationModel config;
    private XemeliosUser connectedUser = null;
    // Ne pas renommer, utilise sans lien dans AbstractTool
    public static MainWindow instance  = null;
    public XPathFactory xpf;
    private Properties criteriasInputValues;
    private String componentsModelFileName,updateUrl;
//	int grantAccess=-1;
    /**
     * Separateur par defaut pour le CSV
     */
    public static String fieldSeparator = ";";
    
    public MainWindow(String title, StartListener sl, String[] args) {
        super(title);
        if(instance!=null) throw new RuntimeException("Only one instance of MainWindow can exists");
        instance = this;
        this.startListener = sl;
        startListener.pushProgress();
        try {
            xpf = XPathFactory.newInstance(XPathFactory.DEFAULT_OBJECT_MODEL_URI);
        } catch(Throwable t) {
            xpf = XPathFactory.newInstance();
        }
        xpf.setXPathFunctionResolver(new FunctionResolver());
        try {
            loadConfig();
            checkUpdateScript();
            initComponents();
            try {
                if(!checkPassword()) {
                    startListener.completeProgress();
                    runExit();
                }
            } catch(Throwable t) {
                new DisplayExceptionDlg(this,t);
                runExit();
            }
            setMenusAccess();
            if(!"true".equals(System.getProperty(Constants.SYS_PROP_SKIP_UPDATE_CHECK))) checkUpdateAvailability();
            startListener.completeProgress();
            if(args.length>0) {
                starterImport(args);
            }
        } catch(DataConfigurationException dcEx) {
            Throwable ex = dcEx;
            while(ex.getCause()!=null) ex=ex.getCause();
            ex.printStackTrace();
        }
    }
    /**
     * Cette methode charge la configuration depuis le fichier de conf
     */
    protected void loadConfig() throws DataConfigurationException {
        if(System.getProperty(Constants.SYS_PROP_LOG4J)!=null) {
            String log4jConfFile = System.getProperty(Constants.SYS_PROP_LOG4J);
            DOMConfigurator.configure(log4jConfFile);
            logger = Logger.getLogger(this.getClass());
            logger.info("log4J initialise");
        }
        PropertiesExpansion pe = new PropertiesExpansion();
        for(Object key:System.getProperties().keySet()) {
            pe.setProperty((String)key,System.getProperty((String)key));
        }
        componentsModelFileName=pe.getProperty(Constants.SYS_PROP_COMPONENTS_FILENAME);
        updateUrl=pe.getProperty(Constants.SYSPROP_UPDATE_SOURCE);
        docModels = getDocumentsInfos(pe.getProperty(Constants.SYS_PROP_DOC_DEF_DIR));
        initDataLayer();
        try {
            AuthenticationFactory.getManager(this);
        } catch (AuthenticationConfigurationException acEx) {
            logger.error(acEx.getMessage(), acEx);
            System.exit(2);
        } catch (AuthenticationCancelationException acEx) {
            logger.info(acEx.getMessage());
            System.exit(1);
        }
        loadPersonnalConfig();
    }
    
    protected void initComponents() {
        criteriasInputValues = loadCriteriasInputValues();
        IhmFactory iF = IhmFactory.newInstance();
        initActions();
        startListener.pushProgress();
        desk = new JDesktopPane();
        desk.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
        ImageIcon image = iF.getIconFromResource(ImageResources.SPLASH);
        backImage = new JLabel(image);
        backImage.setBounds(0, 0, image.getIconWidth(), image.getIconHeight());
        desk.add(backImage, JLayeredPane.FRAME_CONTENT_LAYER);
        initMenus();
        startListener.pushProgress();
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosed(WindowEvent e) {
            }
            
            @Override
            public void windowClosing(WindowEvent e) {
                runExit();
                logger.debug("closing");
            }
        });
        desk.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent evt) {
                Dimension dim = evt.getComponent().getSize();
                Dimension imgSize = backImage.getSize();
                backImage.setLocation((int) (dim.getWidth() - imgSize
                        .getWidth()) / 2, (int) (dim.getHeight() - imgSize
                        .getHeight()) / 2);
            }
        });
        startListener.pushProgress();
        JPanel inner = new JPanel(new BorderLayout());
        JPanel status = new JPanel(new BorderLayout());
        startListener.pushProgress();
        status.add(statusField = new JTextField(30), BorderLayout.WEST);
        startListener.pushProgress();
        statusField.setEditable(false);
        statusField.setBorder(new EmptyBorder(0, 0, 0, 0));
        startListener.pushProgress();
        status.add(waiter = new JWaiter(), BorderLayout.EAST);
        if("true".equals(System.getProperty(Constants.SYS_PROP_ADMIN_MENU_GC))) {
            status.add(new RamViewer(),BorderLayout.CENTER);
        }
        status.setBorder(new EtchedBorder(EtchedBorder.LOWERED));
        inner.add(status, BorderLayout.SOUTH);
        startListener.pushProgress();
        inner.add(desk, BorderLayout.CENTER);
        setContentPane(inner);
        startListener.pushProgress();
        
        // Maximisation de la fenetre
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        GraphicsConfiguration gc = getGraphicsConfiguration();
        GraphicsDevice gd = gc.getDevice();
        DisplayMode dm = gd.getDisplayMode();
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        Rectangle rect = ge.getMaximumWindowBounds();
        
        if(rect!=null && (rect.getWidth()!=dm.getWidth() || rect.getHeight()!=dm.getHeight())) {
            setBounds(rect);
        } else {
            setLocation((int) ((screenSize.getWidth() - DEFAULT_WIDTH) / 2),
                    (int) ((screenSize.getHeight() - DEFAULT_HEIGHT) / 2));
            pack();
            setSize(new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT));
            setExtendedState(MAXIMIZED_BOTH);
        }
        
        startListener.pushProgress();
        setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        startListener.pushProgress();
        setVisible(true);
        startListener.pushProgress();
//        checkUpdateAvailability();
    }
    
    protected void initActions() {
        startListener.pushProgress();
        actExporter = new AbstractAction("Exporter...") {
            /**
             * Comment for <code>serialVersionUID</code>
             */
            private static final long serialVersionUID = 1L;
            
            public void actionPerformed(ActionEvent e) {
                exporter();
            }
        };
        actExporter.setEnabled(false);
        startListener.pushProgress();
        actQuitter = new AbstractAction("Quitter") {
            /**
             * Comment for <code>serialVersionUID</code>
             */
            private static final long serialVersionUID = 1L;
            
            public void actionPerformed(ActionEvent e) {
                runExit();
            }
        };
        startListener.pushProgress();
    }
    protected void initMenus() {
        JMenuBar mBar = new JMenuBar();
        JMenu fileMenu = new JMenu("Fichier");
        
        importerMenu = new JMenu("Importer");
        controlerMenu = new JMenu("Analyser");
        fileMenu.add(importerMenu);
        fileMenu.add(controlerMenu);
        fileMenu.addSeparator();
        fileMenu.add(new JMenuItem(actExporter));
        fileMenu.addSeparator();
        if ("true".equals(System.getProperty(Constants.SYS_PROP_RESET_CONFIG))) {
            JMenuItem mnuReset = new JMenuItem("Reset config ...");
            fileMenu.add(mnuReset);
            mnuReset.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent evt) {
                    reloadConfiguration();
                }
            });
        }
        if("true".equals(System.getProperty(Constants.SYS_PROP_ADMIN_MENU_GC))) {
            mnuClean = new JMenuItem("Garbage collector");
            fileMenu.add(mnuClean);
            mnuClean.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent evt) {
                    System.gc();
                }
            });
            mnuClean.setEnabled(false);
        }
        mnuCleanPro = new JMenuItem("Supprimer...");
        fileMenu.add(mnuCleanPro);
        mnuCleanPro.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                advancedCleanUp();
            }
        });
        fileMenu.addSeparator();
        JMenuItem mnuEnv;
        fileMenu.add(mnuEnv = new JMenuItem("Environnement..."));
        mnuEnv.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                DlgEditEnvironment dee = new DlgEditEnvironment(MainWindow.this,docModels,EnvironmentDomain.DOMAIN_DOCUMENTS);
                dee.setVisible(true);
            }
        });
        fileMenu.addSeparator();
        JMenuItem mnuUpdate = new JMenuItem("Mises � jour...");
        mnuUpdate.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                new DlgUpdate(MainWindow.this);
            }
        });
        fileMenu.add(mnuUpdate);
        fileMenu.addSeparator();
        
        JMenuItem mnuConfig = new JMenuItem("Configuration...");
        mnuConfig.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                new DlgSystemConfiguration(MainWindow.this,true).setVisible(true);
            }
        });
        fileMenu.add(mnuConfig);
        fileMenu.addSeparator();
        
        fileMenu.add(new JMenuItem(actQuitter));
        mBar.add(fileMenu);
        
        searchMenu = new JMenu("Recherche");
        browseMenu = new JMenu("Navigation");
        // Ajout des items du menu importer
        for(DocumentModel dm:docModels.getDocuments()) {
            if(dm.getDisplayInMenuIf()!=null && !"true".equals(System.getProperty(dm.getDisplayInMenuIf()))) continue;
            ActionImport ai = new ActionImport(dm.getTitre(),dm.getId());
            if(dm.isControlable()){
                ActionControl ac = new ActionControl(dm.getTitre(),dm.getId());
                controlerMenu.add(new JMenuItem(ac));
            }
            importerMenu.add(new JMenuItem(ai));
            JMenu searchEntry = new JMenu(dm.getTitre());
            JMenu browseEntry = null;
            if(dm.isBrowsable()) {
                browseEntry = new BrowseMenu(dm);
            }
            int searchCount=0;
            for(Enumeration<EtatModel> enumer2=dm.getEtats().elements();enumer2.hasMoreElements();) {
                EtatModel em = enumer2.nextElement();
                if(em.getDisplayInMenuIf()!=null && !"true".equals(System.getProperty(em.getDisplayInMenuIf()))) continue;
                JMenu tmpMnu = null;
                if(!dm.isGroupMenuItem()) {
                    tmpMnu = new JMenu(em.getTitre());
                } else {
                    tmpMnu = searchEntry;
                }
                for(ElementModel elm:em.getSearchableElements()) {
                    if(!elm.isDisplay()) continue;
                    ActionSearch as = new ActionSearch(elm.getTitre(),dm.getId(),em.getId(),elm.getId());
                    tmpMnu.add(new JMenuItem(as));
                    searchCount++;
                }
                if(tmpMnu!=searchEntry) {
                    if(tmpMnu.getMenuComponentCount()>0) searchEntry.add(tmpMnu);
                }
            }
            if(searchCount>0) {
                searchMenu.add(searchEntry);
            }
            if(browseEntry!=null) {
                browseMenu.add(browseEntry);
            }
        }
        
        searchMenu.setEnabled(false);
        browseMenu.setEnabled(false);
        mBar.add(searchMenu);
        mBar.add(browseMenu);
        
//        if ("true".equals(System.getProperty(Constants.SYS_PROP_EXPORT_EXCEL))) {
        // Export XLS
        exportMenu = new JMenu("Exports");
        JMenuItem exportMenuConfig = new JMenuItem("Configurer");
        exportMenuConfig.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                configExport();
            }
        });
        exportMenu.add(exportMenuConfig);
        JMenuItem exportMenuLancer = new JMenuItem("Ex�cuter");
        exportMenuLancer.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                launchExport();
            }
        });
        exportMenu.add(exportMenuLancer);
        mBar.add(exportMenu);
//        }
        
        JMenu toolMenu = loadToolMenu();
        if(toolMenu!=null)
            mBar.add(toolMenu);
        
        windowMenu = new WindowMenu("Fen�tres",desk);
        
        mBar.add(windowMenu);
        
        mBar.add(Box.createHorizontalGlue());
        
        JMenu helper = new JMenu("?");
        Action aboutAction = new AbstractAction("A Propos") {
            private static final long serialVersionUID = 1L;
            
            public void actionPerformed(ActionEvent evt) {
                new AboutBox(MainWindow.this);
            }
        };
        helper.add(new JMenuItem(aboutAction));
        Action helpAction = new AbstractAction("Aide en ligne") {
            /**
             * Comment for <code>serialVersionUID</code>
             */
            private static final long serialVersionUID = 1L;
            
            public void actionPerformed(ActionEvent evt) {
                try {
                    StartUrl.startUrl(System.getProperty(Constants.SYS_PROP_HELP_URL));
                } catch(Throwable t) {}
            }
        };
        helper.add(helpAction);
        mBar.add(helper);
        
        setJMenuBar(mBar);
    }
    
    protected void importer(String docId) {
        DocumentModel dm = docModels.getDocumentById(docId);
        try {
            Class clazz = Class.forName(dm.getImportClass());
            Object obj = clazz.newInstance();
            if(!(obj instanceof EtatImporteur)) {
                JOptionPane.showMessageDialog(this,"Cette classe n'est pas un importeur.\nLe fichier de configuration qui vous a �t� livr� est certainement invalide.\nVeuillez contacter votre fournisseur.\n","Erreur",JOptionPane.ERROR_MESSAGE);
                return;
            }
            EtatImporteur ei = (EtatImporteur)obj;
            ImportContent fToImport = getFilesToImport(dm.getExtension(),dm.getLibelleExtension());
            doImport(ei,fToImport,dm, false);
        } catch(Exception ex) {
            logger.error("importer",ex);
            new DisplayExceptionDlg(this,ex);
        } finally {
            //waiter.stopProgress();
        }
    }
    public void doImport(EtatImporteur ei,ImportContent fToImport,DocumentModel dm, boolean synchronizeMe){
        ImportServiceSwingProvider issp = new ImportServiceSwingProvider(this,ei,fToImport.filesToImport.size()+fToImport.pjs.size());
        if(synchronizeMe) issp.setDisplayFeedback(false);
        ei.setImpSvcProvider(issp);
        ei.setDocument(dm);
        waiter.startProgress();
        try {
            ei.setFilesToImport(fToImport.filesToImport);
            ei.setPjs(fToImport.pjs);
            if(synchronizeMe) {
                ei.run();
            } else {
                ei.execute();
            }
        } catch (Exception ex) {
            logger.error("doImport",ex);
            new DisplayExceptionDlg(this,ex);
        } finally {
            waiter.stopProgress();
        }
    }
    public void doImport(EtatImporteur ei,ImportContent fToImport,DocumentModel dm, DlgControl parentController, MainControl.Triplet triplet,boolean synchronizeMe) {
        ImportServiceSwingProvider issp = new ImportServiceSwingProvider(this,ei,fToImport.filesToImport.size()+fToImport.pjs.size());
        if(synchronizeMe) issp.setDisplayFeedback(false);
        ei.setImpSvcProvider(issp);
        ei.setDocument(dm);
        waiter.startProgress();
        try {
            ei.setFilesToImport(fToImport.filesToImport);
            ei.setPjs(fToImport.pjs);
            if(synchronizeMe) {
                ei.run();
            } else {
                ei.execute();
            }
            
        } catch (Exception ex) {
            logger.error("doImport",ex);
            new DisplayExceptionDlg(this,ex);
        } finally {
            waiter.stopProgress();
        }
        try {
            ei.get();
        } catch(Throwable t) {
            t.printStackTrace();
            parentController.notifyImportFailed();
        } finally {
            parentController.notifyImportFinished(triplet);
        }
    }
    protected void controller(String docId){
        MainControlWrapper mc = new MainControlWrapper(docModels.getDocumentById(docId),docModels);
        DlgControl dc = new DlgControl(this,docModels.getDocumentById(docId),mc);
        dc.setVisible(true);
    }
    protected void exporter() {
        JInternalFrame f = desk.getSelectedFrame();
        if(f!=null) {
            SearchWindow s = (SearchWindow)f;
            if(s.canExport()) {
//                if ("true".equals(System.getProperty(Constants.SYS_PROP_EXPORT_EXCEL)))
                s.exportXLS();
//                else
//                    s.exportCSV();
            }
        }
    }
    protected void runExit() {
        this.setCursor(new Cursor(Cursor.WAIT_CURSOR));
        JInternalFrame[] childs = desk.getAllFrames();
        for(int i=0;i<childs.length;i++) {
            desk.remove(childs[i]);
            childs[i].dispose();
        }
        try {
            File tmpDir = new File(System.getProperty("java.io.tmpdir"));
            tmpDir = new File(tmpDir,Constants.NOM_APP);
            File[] files = tmpDir.listFiles();
            for(File f:files) f.delete();
            tmpDir.delete();
        } catch(Throwable t) {}
        Thread[] thList = new Thread[100];
        Thread.currentThread().getThreadGroup().enumerate(thList,true);
        for(int i=0;i<thList.length;i++) {
            if(thList[i]==null) break;
            if("Automatic indexer".equals(thList[i].getName())) {
                DBWait dbWait = new DBWait(null);
                dbWait.setVisible(true);
                try {
                    thList[i].join();
                } catch(InterruptedException iEx) {}
                dbWait.setVisible(false);
            }
        }
        savePersonnalConfig();
        saveCriteriasInputValues();
        System.exit(0);
    }
    public SearchWindow displaySearchWindow(ElementModel element) {
        ElementModel elm = element.clone();
        EtatModel em = element.getParent();
        DocumentModel dm = em.getParent();
        elm.setParent(em);
        try {
            if(DataLayerManager.getImplementation().canSearch(dm,em)) {
                if(elm==null) {
                    JOptionPane.showMessageDialog(this,"L'�tat "+em.getTitre()+" n'a pas d'�l�ment de recherche d�finit.","Erreur de configuration",JOptionPane.ERROR_MESSAGE);
                    return null;
                }
                SearchWindow sw = new SearchWindow(this,dm,elm);
                desk.add(sw);
                desk.getDesktopManager().maximizeFrame(sw);
                return sw;
            } else {
                throw new ToolException(ToolException.ERROR_NO_DATA);
            }
        } catch(ToolException tEx) {
            if(tEx.getErrorCode()==ToolException.ERROR_NO_DATA) {
                JOptionPane.showMessageDialog(this,"Aucun "+em.getTitre()+" n'a �t� import�.\nVous devez en importer au moins un avant d'effectuer une recherche.","Erreur",JOptionPane.WARNING_MESSAGE);
            } else {
                new DisplayExceptionDlg(this,tEx);
            }
        } catch(DataAccessException daEx) {
            new DisplayExceptionDlg(this,daEx);
        } catch(DataConfigurationException dcEx) {
            new DisplayExceptionDlg(this,dcEx);
        }
        return null;
    }
    protected void search(String docId,String etatId,String elementId) {
        DocumentModel dm = docModels.getDocumentById(docId).clone();
        EtatModel em = dm.getEtatById(etatId);
        try {
            if(DataLayerManager.getImplementation().canSearch(dm,em)) {
                ElementModel elm = em.getElementById(elementId);
                if(elm==null) {
                    JOptionPane.showMessageDialog(this,"L'�tat "+em.getTitre()+" n'a pas d'�l�ment de recherche d�finit.","Erreur de configuration",JOptionPane.ERROR_MESSAGE);
                    return;
                }
                SearchWindow sw = new SearchWindow(this,dm,elm);
                desk.add(sw);
                desk.getDesktopManager().maximizeFrame(sw);
                sw.show();
            } else {
                throw new ToolException(ToolException.ERROR_NO_DATA);
            }
        } catch(ToolException tEx) {
            if(tEx.getErrorCode()==ToolException.ERROR_NO_DATA) {
                JOptionPane.showMessageDialog(this,"Aucun "+em.getTitre()+" n'a �t� import�.\nVous devez en importer au moins un avant d'effectuer une recherche.","Erreur",JOptionPane.WARNING_MESSAGE);
            } else {
                new DisplayExceptionDlg(this,tEx);
            }
        } catch(DataAccessException daEx) {
            new DisplayExceptionDlg(this,daEx);
        } catch(DataConfigurationException dcEx) {
            new DisplayExceptionDlg(this,dcEx);
        }
    }
    
    protected boolean checkPassword() throws AuthenticationConfigurationException, AuthenticationCancelationException, DataAccessException {
        AuthenticationManager manager = AuthenticationFactory.getManager(this);
        XemeliosUser user = null;
        
        int retries = 3;
        boolean bLogged = false;
        if(startListener instanceof Splash) {
            Splash splash = (Splash)startListener;
            splash.setAlwaysOnTop(false);
            splash.setVisible(false);
        }
        while (retries > 0 && !bLogged) {
            user = manager.getProvider().getUser();
            if (user == null)
                retries = 0;
            else {
                bLogged = manager.getChecker().authenticate(user);
                if (!bLogged) {
                    JOptionPane.showMessageDialog(this,
                            "Mot de passe incorrect.", "Erreur",
                            JOptionPane.ERROR_MESSAGE);
                }
            }
        }
        if (startListener instanceof Splash) {
            Splash splash = (Splash) startListener;
            splash.setVisible(true);
            splash.setAlwaysOnTop(true);
        }
        if (bLogged)
            connectedUser = user;
        return bLogged;
    }
    
    protected DocumentsModel getDocumentsInfos(String repertoires) {
        DocumentsModel ret = null;
        try {
            ret = Loader.getDocumentsInfos(repertoires);
        } catch (Exception ex) {
            logger.error( ex);
            startListener.completeProgress();
            if (startListener instanceof Splash) {
                Splash sp = (Splash) startListener;
                sp.setAlwaysOnTop(false);
                sp.setVisible(false);
            }
            new DisplayExceptionDlg((JFrame) null, ex);
        }
        return ret;
    }
    
    protected ImportContent getFilesToImport(final String extension, final String titreEtat) {
        ImportContent ic = new ImportContent();
        FileSystemView fsv = FileSystemView.getFileSystemView();
        if (lastInputDirectory == null)
            lastInputDirectory = fsv.getHomeDirectory();
        JFileChooser fc = new JFileChooser(lastInputDirectory, fsv);
        FileFilter defaultFileFilter = null;
        fc.addChoosableFileFilter(defaultFileFilter = new FileFilter() {
            @Override
            public boolean accept(File arg0) {
                return arg0.isDirectory()
                || arg0.getName().toUpperCase().endsWith(
                        extension.toUpperCase());
            }
            
            @Override
            public String getDescription() {
                return "Fichiers " + titreEtat;
            }
        });
        fc.addChoosableFileFilter(new FileFilter() {
            @Override
            public boolean accept(File arg0) {
                return arg0.isDirectory()
                || arg0.getName().toUpperCase().endsWith(".XML");
            }
            
            @Override
            public String getDescription() {
                return "Fichiers XML";
            }
        });
        fc.addChoosableFileFilter(new FileFilter() {
            @Override
            public boolean accept(File arg0) {
                return arg0.isDirectory()
                || arg0.getName().toUpperCase().endsWith(".ZIP");
            }
            
            @Override
            public String getDescription() {
                return "Archives ZIP";
            }
        });
        fc.setFileFilter(defaultFileFilter);
        fc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
        fc.setMultiSelectionEnabled(true);
        fc.setDialogTitle("Choisissez un fichier ou r�pertoire � importer");
        fc.showOpenDialog(this);
        File[] ret = null;
        if(fc.getSelectedFile()!=null && fc.getSelectedFile().isFile()) {
            // si l'utilisateur a choisi un fichier
//			ret = new File[1];
//			ret[0] = fc.getSelectedFile();
            ret = fc.getSelectedFiles();
            lastInputDirectory = ret[0].getParentFile();
        } else if(fc.getSelectedFile()!=null && fc.getSelectedFile().isDirectory()) {
            // si l'utilisateur a choisi un r?ertoire
            logger.debug("path="+fc.getSelectedFile().getAbsolutePath());
            // on cr? le mod?e de donn?s pour l'affichage dans la boite de dialogue d'import
            String[] extensions = {"xml", "zip", extension};
            
            // on affiche les fichiers trouv? dans les r?ertoires ?l'utilisateur, pour
            // qu'il d?ide ceux qu'il veut exclure de l'import
            ImportDialog test = new ImportDialog(this, "S�lectionnez les fichiers � importer");
            List<ImportFile> resultats = test.startDialog(fc.getSelectedFile(), extensions);
            ret = new File[resultats.size()];
            int counter = 0;
            for(Iterator iter = resultats.iterator(); iter.hasNext(); ) {
                ret[counter++] = ((ImportFile)iter.next()).getFile();
            }
            lastInputDirectory = fc.getSelectedFile();
        } else {
            // l'utilisateur n'a rien choisi, appuy?sur "Annuler"
            ret = new File[0];
        }
        
        // on regarde si l'un des fichiers a importer est un zip
        //ArrayList<File> files = new ArrayList<File>();
        File tmpDir = new File(System.getProperty("java.io.tmpdir"));
        for (int i = 0; i < ret.length; i++) {
            if (ret[i].getName().toLowerCase().endsWith(".zip")) {
                if (ret[i].exists()) {
                    ZipFile zf = null;
                    try {
                        zf = new ZipFile(ret[i]);
                        for (Enumeration<? extends ZipEntry> enumer = zf
                                .entries(); enumer.hasMoreElements();) {
                            ZipEntry ze = enumer.nextElement();
                            if (!ze.isDirectory()) {
                                String fileName = ze.getName();
                                String entryName = fileName.toLowerCase();
                                fileName = fileName.replace(
                                        File.pathSeparatorChar, '_').replace(
                                        File.separatorChar, '_').replace(':',
                                        '|').replace('\'', '_').replace('/',
                                        '_');
                                logger.debug(entryName);
                                if(PJRef.isPJ(ze)) {
                                    PJRef pj = new PJRef(ze);
                                    pj.writeTmpFile(tmpDir,zf);
                                    ic.pjs.add(pj);
                                } else if ((entryName.endsWith(extension.toLowerCase()) || entryName.endsWith(".xml")) && !fileName.startsWith("_")) {
                                    // on decompresse le fichier dans le
                                    // repertoire temporaire, comme ca il sera
                                    // supprime en quittant
                                    InputStream is = zf.getInputStream(ze);
                                    BufferedInputStream bis = new BufferedInputStream(is);
                                    File output = new File(tmpDir, fileName);
                                    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(output));
                                    byte[] buffer = new byte[1024];
                                    int read = bis.read(buffer);
                                    while (read > 0) {
                                        bos.write(buffer, 0, read);
                                        read = bis.read(buffer);
                                    }
                                    bos.flush();
                                    bos.close();
                                    bis.close();
                                    ic.filesToImport.add(output);
                                }
                            }
                        }
                        zf.close();
                    } catch (ZipException zEx) {
                        JOptionPane.showMessageDialog(this, "Le fichier "
                                + ret[i].getName()
                                + " n'est pas une archive ZIP valide",
                                "Erreur", JOptionPane.ERROR_MESSAGE);
                    } catch (IOException ioEx) {
                        JOptionPane
                                .showMessageDialog(
                                this,
                                "Le fichier "
                                + ret[i].getName()
                                + " est illisible. V�rifiez que les noms des fichiers\ncontenus dans l'archive ne comportent pas de caract�res sp�ciaux ou accentu�s.",
                                "Erreur", JOptionPane.ERROR_MESSAGE);
                        ioEx.printStackTrace();
                    } finally {
                        if(zf!=null) {
                            try { zf.close(); } catch(Throwable t) {}
                        }
                    }
                }
            } else {
                ic.filesToImport.add(ret[i]);
            }
        }
        return ic;
    }
    
    protected void checkUpdateAvailability() {
        UpdateChecker uc = new UpdateChecker();
        uc.execute();
    }
    
    public void innerWindowChanged() {
        JInternalFrame inner = desk.getSelectedFrame();
        if (inner == null) {
            logger.debug("pas de fenetre ouverte");
            actExporter.setEnabled(false);
        } else {
            if (inner instanceof SearchWindow) {
                SearchWindow sw = (SearchWindow) inner;
                boolean canExport = true;
                canExport &= sw.canExport();
                canExport &= getConnectedUser().isAllowedTo(sw.getCurrentCollectivite().key, XemeliosUser.ROLE_EXPORT);
                canExport &= sw.isEtatExporttable();
                actExporter.setEnabled(canExport);
            }
        }
    }
    
    public void msg(String message) {
        statusField.setText(message);
    }
    
    public static final void main(String[] args) {
        new MainWindow(Constants.NOM_APP, new EmptyStartListener(), args);
    }
    
    public void startWaiter() {
        waiter.startProgress();
    }
    
    public void stopWaiter() {
        waiter.stopProgress();
    }
    
    private void advancedCleanUp() {
        new DlgCleanDatabase(this, true, docModels);
//        new DlgCleanDatabaseOld(this, docModels);
    }
    
    private void starterImport(String[] args) {
        if (startListener instanceof Splash) {
            Splash splash = (Splash) startListener;
            splash.setAlwaysOnTop(false);
            splash.setVisible(false);
        }
        if(connectedUser.isAllowedTo(null,XemeliosUser.ROLE_IMPORT)) {
            for (String fName : args) {
                File f = null;
                try {
                    f = new File(fName);
                } catch (Throwable t) {
                }
                if (f != null && f.exists()) {
                    // le fichier existe, il faut choisir le type de document
                    AskDocumentType adt = new AskDocumentType(this, fName,
                            docModels);
                    adt.setVisible(true);
                    DocumentModel dm = adt.getDocumentType();
                    if (dm != null) {
                        try {
                            Class clazz = Class.forName(dm.getImportClass());
                            Object obj = clazz.newInstance();
                            if (!(obj instanceof EtatImporteur)) {
                                JOptionPane.showMessageDialog(this,
                                        "Cette classe n'est pas un importeur.\n" +
                                        "Le fichier de configuration qui vous a �t� livr� est certainement invalide.\n" +
                                        "Veuillez contacter votre fournisseur.","Erreur", JOptionPane.ERROR_MESSAGE);
                                return;
                            }
                            EtatImporteur ei = (EtatImporteur) obj;
                            File[] fToImport = new File[] { f };
                            ImportServiceSwingProvider issp = new ImportServiceSwingProvider(this,ei,fToImport.length);
                            ei.setImpSvcProvider(issp);
                            ei.setDocument(dm);
                            //
                            waiter.startProgress();
                            ei.setFilesToImport(fToImport);
                            ei.setPjs(new ArrayList<PJRef>());
                            ei.execute();
                            waiter.stopProgress();
                        } catch (Exception ex) {
                            logger.error("starterImport", ex);
                            /*DisplayExceptionDlg dlg = */
                            new DisplayExceptionDlg(
                                    this, ex);
                        } finally {
                            waiter.stopProgress();
                        }
                    }
                }
            }
        } else {
            JOptionPane.showMessageDialog(this,"Vous n'avez pas les droits suffisants pour importer ce fichier.","Erreur",JOptionPane.ERROR_MESSAGE);
        }
        if (startListener instanceof Splash) {
            Splash splash = (Splash) startListener;
            splash.setAlwaysOnTop(false);
            splash.setVisible(false);
        }
    }
    
    public SavedRequestsModel getSavedRequest(ElementModel el) {
        return config.getSavedRequests(el);
    }
    
    protected void savePersonnalConfig() {
        File f = new File(System.getProperty("user.home"));
        File fProp = new File(f, Constants.PERSONNAL_CONFIG_FILENAME);
        FileOutputStream fw = null;
        try {
            XmlOutputter xo = new XmlOutputter(fw = new FileOutputStream(fProp), "ISO-8859-1");
            config.marshall(xo);
        } catch (Throwable t) {
            logger.error("savePersonnalConfig", t);
        } finally {
            if (fw != null) {
                try {
                    fw.flush();
                } catch (Throwable t) {
                }
            }
        }
    }
    
    protected void loadPersonnalConfig() {
        File f = new File(System.getProperty("user.home"));
        File fProp = new File(f, Constants.PERSONNAL_CONFIG_FILENAME);
        config = new PersonnalConfigurationModel(
                PersonnalConfigurationModel.QN);
        if (fProp.exists()) {
            try {
                PersonnalConfigParser parser = new PersonnalConfigParser();
                parser.parse(fProp);
                config = (PersonnalConfigurationModel) parser.getMarshallable();
            } catch (Throwable t) {
                logger.error("loadPersonnalConfig", t);
            }
        }
    }
    protected Properties loadCriteriasInputValues() {
        Properties props = new Properties();
        File f = new File(System.getProperty("user.home"));
        File fProp = new File(f, Constants.CRITERIAS_INPUTS_FILENAME);
        if(fProp.exists()) {
            try {
                FileInputStream fis = new FileInputStream(fProp);
                props.load(fis);
                fis.close();
            } catch(IOException ioEx) {}
        }
        return props;
    }
    protected void saveCriteriasInputValues() {
        File f = new File(System.getProperty("user.home"));
        File fProp = new File(f, Constants.CRITERIAS_INPUTS_FILENAME);
        try {
            FileOutputStream fos = new FileOutputStream(fProp);
            criteriasInputValues.store(fos,null);
            fos.close();
        } catch(IOException ioEx) {}
    }
    
    protected void browse(DocumentModel dm, EtatModel emParam,String collectivite, String budget, String specialKey1,String specialKey2) {
        EtatModel em = emParam == null ? dm.getDefaultBrowsableEtat() : emParam;
        NavigationContext nc = new NavigationContext(dm.getId(),em.getId(),em.getBrowsableElement().getId(),collectivite,budget,specialKey1,specialKey2,null,null);
        try {
            if(em==null) throw new DataConfigurationException("No default browsable etat for "+dm.getId());
            DataImpl impl = DataLayerManager.getImplementation();
            ArrayList<String> documents = impl.getDocumentListFromSpecialKeys(em.getBrowsableElement(),collectivite,budget,specialKey1,specialKey2,null);
            if(documents.size()==0) {
                JOptionPane.showMessageDialog(this,"Le document est introuvable","",JOptionPane.WARNING_MESSAGE);
            } else if(documents.size()>1) {
                JOptionPane.showMessageDialog(this,"Plusieurs documents r�pondant � ces crit�res ont �t� trouv�s.","Erreur",JOptionPane.ERROR_MESSAGE);
            } else {
                String docName = documents.get(0);
                String content = impl.getDocumentAsString(em,new Pair(collectivite,null),new Pair(budget,null),docName);
                logger.debug(content);
                String encoding = IoUtils.getEncoding(content);
                InputStream is = IoUtils.getInputStream(em.getXslt());
                Hashtable<String,Object> parameters = new Hashtable<String,Object>();
                parameters.put(HtmlViewer.PARAM_DESTINATION,em.useExternalBrowser()?HtmlViewer.VALUE_DESTINATION_EXTERNAL:HtmlViewer.VALUE_DESTINATION_INTERNAL);
                InputStream htmlStream = DematTransform.transform(new ByteArrayInputStream(content.getBytes(encoding)),is,encoding,parameters);
                displayHtmlStream(htmlStream,em,desk,nc);
            }
        } catch (Throwable t) {
            JOptionPane.showMessageDialog(this, t.getMessage(), "Erreur",
                    JOptionPane.ERROR_MESSAGE);
            t.printStackTrace();
        }
    }
    
    public void displayHtmlStream(InputStream is, EtatModel em, JDesktopPane desk) throws IOException {
        displayHtmlStream(is, em, desk, null);
    }
    
    public void displayHtmlStream(InputStream is, EtatModel em, JDesktopPane desk, NavigationContext nc) throws IOException {
        File tmpDir = new File(System.getProperty("java.io.tmpdir"));
        tmpDir = new File(tmpDir, Constants.NOM_APP);
        tmpDir.mkdirs();
        File tmp = File.createTempFile("xemelios-data-", ".html", tmpDir);
        BufferedInputStream bis = new BufferedInputStream(is);
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(tmp));
        byte[] buff = new byte[1024];
        int lengthRead = bis.read(buff);
        while (lengthRead > 0) {
            bos.write(buff, 0, lengthRead);
            lengthRead = bis.read(buff);
        }
        bos.flush();
        bos.close();
        try {
            String prefix = System.getProperty(Constants.SYS_PROP_NAVIGATOR_PREFIX);
            if (prefix == null)
                prefix = "";
            String url = tmp.getCanonicalPath();
            if (em.useExternalBrowser()) {
                StartUrl.startUrl(url);
            } else {
                HtmlViewer viewer = new HtmlViewer(tmp.toURL(), "D�tail "
                        + em.getTitre(), nc, this);
                desk.add(viewer);
                desk.getDesktopManager().maximizeFrame(viewer);
                viewer.show();
            }
        } catch (Exception toEx) {
            Component comp2 = desk;
            while (comp2 != null && !(comp2 instanceof JFrame))
                comp2 = comp2.getParent();
            if (comp2 != null)
                new DisplayExceptionDlg((JFrame) comp2, toEx);
            else
                toEx.printStackTrace();
        }
    }
    
    protected void initDataLayer() throws DataConfigurationException {
        logger.debug("entering initDataLayer");
        String availableLayers = System.getProperty(Constants.SYS_PROP_AVAILABLE_LAYERS);
        StringTokenizer tokenizer = new StringTokenizer(availableLayers, ",");
        while(tokenizer.hasMoreTokens()) {
            String className = tokenizer.nextToken();
            logger.debug("instanciating "+className);
            try {
                Class.forName(className);
            } catch (ClassNotFoundException cnfEx) {
                logger.error("error while initializing data layer class ("+className+") :", cnfEx);
            }
        }
        logger.debug("setting dataImpl to "+System.getProperty(Constants.SYS_PROP_DATA_IMPL));
        DataLayerManager.setDataImpl(System.getProperty(Constants.SYS_PROP_DATA_IMPL));
        logger.debug("exiting successfully initDataLayer");
    }
    
    private void reloadConfiguration(){
        PropertiesExpansion pe = new PropertiesExpansion();
        for(Object key:System.getProperties().keySet()) {
            pe.setProperty((String)key,System.getProperty((String)key));
        }
        docModels = getDocumentsInfos(pe.getProperty(Constants.SYS_PROP_DOC_DEF_DIR));
        try {
            DataLayerManager.getImplementation().reset();
        } catch (DataConfigurationException e) {
            e.printStackTrace();
            new DisplayExceptionDlg(this,e);
        }
    }
    
    public XemeliosUser getConnectedUser() { return connectedUser; }
    /**
     * Reads and execute update script if it is present
     *
     */
    protected void checkUpdateScript() {
        File f = new File(System.getProperty(Constants.SYS_PROP_XEMELIOS_PRG),"update-script.xml");
        if(f.exists()) {
            try {
                DocumentBuilder domBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
                XPath xpath = xpf.newXPath();
                Document dom = domBuilder.parse(f);
                NodeList nl = (NodeList)xpath.evaluate("/update-script/*",dom,XPathConstants.NODESET);
                for(int i=0;i<nl.getLength();i++) {
                    Node node = nl.item(i);
                    if(node.getNodeType()==Node.ELEMENT_NODE) {
                        String nodeName = node.getNodeName();
                        if("component".equals(nodeName)) {
                            String id=xpath.evaluate("./@id",node);
                            String version=xpath.evaluate("./@version",node);
                            String description=xpath.evaluate("./@description",node);
                            installComponent(id,version,description);
                        } else if("configurator".equals(nodeName)) {
                            String className = xpath.evaluate("./@class",node);
                            PropertiesExpansion props = new PropertiesExpansion(System.getProperties());
                            NodeList params = (NodeList)xpath.evaluate("./param",node,XPathConstants.NODESET);
                            for(int paramCount=0;paramCount<params.getLength();paramCount++) {
                                Node p = params.item(paramCount);
                                String paramName = xpath.evaluate("./@name",p);
                                String paramValue = xpath.evaluate("./text()",p);
                                props.put(paramName, paramValue);
                            }
                            executeConfigurator(className,props);
                        } else if("remove-component".equals(nodeName)){
                            // TODO: implement this
                        } else {
                            logger.warn("update-script: unexpected element <"+nodeName+"/>");
                        }
                    }
                }
            } catch(Throwable t) {
                new DisplayExceptionDlg((Frame)null,t);
            }
            f.delete();
        }
    }
    /**
     * This methods registers or updates a new component in components-file.
     * @param id
     * @param version
     * @param description
     * @throws ParserConfigurationException
     * @throws SAXException
     * @throws IOException
     * @throws XPathExpressionException
     */
    protected void installComponent(String id,String version,String description) throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
        // TODO: rewrite this correctly
        logger.info("installing component "+id);
        XemeliosComponentModel installedComponents = null;
        File f = new File(System.getProperty("xemelios.components.filename"));
        if(f.exists()) {
            ComponentsParser parser = new ComponentsParser();
            parser.parse(f);
            installedComponents = (XemeliosComponentModel)parser.getMarshallable();
        } else {
            installedComponents = new XemeliosComponentModel(XemeliosComponentModel.QN);
        }
        ComponentModel cm = new ComponentModel(id,version,description);
        String cVersion = installedComponents.getComponentVersion(cm.getId());
        if(cVersion==null) {
            installedComponents.addChild(cm,ComponentModel.QN);
        } else {
            installedComponents.remove(cm.getId());
            installedComponents.addChild(cm,ComponentModel.QN);
        }
        FileOutputStream fos = new FileOutputStream(f);
        XmlOutputter output = new XmlOutputter(fos,"ISO-8859-1");
        installedComponents.marshall(output);
        fos.flush(); fos.close();
    }
    protected void executeConfigurator(String className, PropertiesExpansion props) throws Exception {
        logger.info("executing configurator "+className);
        Class clazz = Class.forName(className);
        Constructor cons = clazz.getConstructor(new Class[0]);
        Object o = cons.newInstance(new Object[0]);
        if(o instanceof AbstractConfigurator) {
            AbstractConfigurator ac = (AbstractConfigurator)o;
            ac.setProperties(props);
            ac.setMainWindow(this);
            ac.doConfigure();
        } else {
            throw new Exception(className+" does not extends "+AbstractConfigurator.class.getName());
        }
    }
    public String getCriteriaInputValue(String criteriaId) {
        if(criteriaId==null) return null;
        return (String)criteriasInputValues.get(criteriaId);
    }
    public synchronized void setCriteriaInputValue(String criteriaId,String value) {
        if(criteriaId==null) return;
        if(value!=null && value.length()>0) criteriasInputValues.put(criteriaId,value);
        else criteriasInputValues.remove(criteriaId);
    }
    private static class EmptyStartListener implements StartListener {
        public void notifyMessage(String msg) {
        }
        
        public void notifyProgress(int progress) {
        }
        
        public void pushProgress() {
        }
        
        public void completeProgress() {
        }
        
        public void setVisible(boolean visible) {
        }
    }
    
    private final class ActionImport extends AbstractAction {
        /**
         * Comment for <code>serialVersionUID</code>
         */
        private static final long serialVersionUID = 3833182540793198128L;
        
        String docId, etatId;
        
        public ActionImport(String titre, String docId) {
            super(titre);
            this.docId = docId;
        }
        
        public void actionPerformed(ActionEvent evt) {
            importer(docId);
        }
    }
    
    private final class ActionControl extends AbstractAction {
        /**
         * Comment for <code>serialVersionUID</code>
         */
        private static final long serialVersionUID = 3833182540793198128L;
        
        String docId, etatId;
        
        public ActionControl(String titre, String docId) {
            super(titre);
            this.docId = docId;
        }
        
        public void actionPerformed(ActionEvent evt) {
            controller(docId);
        }
    }
    
    private final class ActionSearch extends AbstractAction {
        /**
         * Comment for <code>serialVersionUID</code>
         */
        private static final long serialVersionUID = 3617860763465560632L;
        
        String docId, etatId, elementId;
        
        public ActionSearch(String titre, String docId, String etatId, String elementId) {
            super(titre);
            this.docId = docId;
            this.etatId = etatId;
            this.elementId = elementId;
        }
        
        public void actionPerformed(ActionEvent evt) {
            search(docId, etatId, elementId);
        }
    }
    
    private final class ActionBrowse extends AbstractAction {
        /**
         * Comment for <code>serialVersionUID</code>
         */
        private static final long serialVersionUID = 3617860763465560632L;
        
        DocumentModel dm;
        
        EtatModel em;
        
        String collectivite, budget;
        
        String specialKey1, specialKey2;
        
        public ActionBrowse(String titre, DocumentModel dm, EtatModel em,
                String collectivite, String budget, String spe1, String spe2) {
            super(titre);
            this.dm = dm;
            this.em = em;
            this.collectivite = collectivite;
            this.budget = budget;
            this.specialKey1 = spe1;
            this.specialKey2 = spe2;
        }
        
        public ActionBrowse(String titre, DocumentModel dm,
                String collectivite, String budget) {
            this(titre, dm, dm.getDefaultBrowsableEtat(), collectivite, budget,
                    null, null);
        }
        
        public void actionPerformed(ActionEvent evt) {
            MainWindow.this.browse(dm, em, collectivite, budget, specialKey1,
                    specialKey2);
        }
    }
    
    private class DBWait extends JDialog {
        /**
         * Comment for <code>serialVersionUID</code>
         */
        private static final long serialVersionUID = 1L;
        
        public DBWait(JFrame owner) {
            super(owner, false);
            initComponents();
        }
        
        protected void initComponents() {
            JPanel pane = new JPanel(new BorderLayout());
            pane.add(new JLabel(
                    "Construction des index en cours, veuillez patienter..."),
                    BorderLayout.CENTER);
            setContentPane(pane);
            pack();
        }
    }
    
    private class BrowseMenu extends JMenu {
        /**
         * Comment for <code>serialVersionUID</code>
         */
        private static final long serialVersionUID = 1L;
        
        private DocumentModel dm;
        
        public BrowseMenu(DocumentModel dm) {
            super(dm.getTitre());
            this.dm = dm;
        }
        
        @Override
        public void menuSelectionChanged(boolean isSelected) {
            if (isSelected) {
                showMenu();
            }
            super.menuSelectionChanged(isSelected);
        }
        
        private void showMenu() {
            removeAll();
            try {
                Vector<Pair> collectivites = DataLayerManager.getImplementation().getCollectivites(dm);
                for (Pair p : collectivites) {
                    if (!getConnectedUser().isAllowedTo(p.key,
                            XemeliosUser.ROLE_BROWSE))
                        continue;
                    p.libelle = p.libelle.trim();
                    Vector<Pair> budgets = DataLayerManager.getImplementation().getBudgets(dm, p);
                    JMenu mnuColl = new JMenu(p.libelle);
                    for (Pair budget : budgets) {
                        if (dm.getSpecialKeys().size() > 0) {
                            mnuColl.add(new BudgetKeyMenu(dm, p, budget));
                        } else {
                            JMenuItem mnuBudget = new JMenuItem(
                                    new ActionBrowse(budget.libelle, dm, p.key,
                                    budget.key));
                            mnuColl.add(mnuBudget);
                        }
                    }
                    add(mnuColl);
                }
            } catch (DataAccessException daEx) {
                JMenuItem it = new JMenuItem("pas de document import�");
                it.setEnabled(false);
                add(it);
            } catch (DataConfigurationException daEx) {
                JMenuItem it = new JMenuItem("Erreur de configuration");
                it.setEnabled(false);
                add(it);
            }
        }
    }
    
    private class BudgetKeyMenu extends JMenu {
        /**
         * Comment for <code>serialVersionUID</code>
         */
        private static final long serialVersionUID = 1L;
        
        private DocumentModel dm;
        
        private Pair collectivite, budget;
        
        BudgetKeyMenu(DocumentModel dm, Pair collectivite, Pair budget) {
            super(budget.libelle);
            this.dm = dm;
            this.collectivite = collectivite;
            this.budget = budget;
        }
        
        @Override
        public void menuSelectionChanged(boolean isSelected) {
            if (isSelected) {
                showMenu();
            }
            super.menuSelectionChanged(isSelected);
        }
        
        private void showMenu() {
            removeAll();
            try {
                Vector<Pair> specialKeys1 = null;
                if (dm.getSpecialKeys().size() > 0) {
                    specialKeys1 = DataLayerManager.getImplementation().getSpecialKeys1(dm, collectivite, budget, true);
                }
                if (specialKeys1 != null && specialKeys1.isEmpty()) {
                    // ca devrait pas arriver
                    add(new JMenuItem(new ActionBrowse("Ouvrir", dm,
                            collectivite.key, budget.key)));
                } else {
                    for (Pair p : specialKeys1) {
                        if (dm.getSpecialKeys().size() > 1) {
                            Vector<Pair> specialKeys2 = DataLayerManager.getImplementation().getSpecialKeys2(dm,collectivite, budget, p, true);
                            if (specialKeys2.size() > 0) {
                                JMenu mnu = new JMenu(p.libelle);
                                for (Pair k2 : specialKeys2) {
                                    mnu.add(new JMenuItem(new ActionBrowse(
                                            k2.libelle, dm, dm
                                            .getDefaultBrowsableEtat(),
                                            collectivite.key, budget.key,
                                            p.key, k2.key)));
                                }
                                add(mnu);
                            } else {
                                JMenuItem mnuK1 = new JMenuItem(
                                        new ActionBrowse(p.libelle, dm, dm
                                        .getDefaultBrowsableEtat(),
                                        collectivite.key, budget.key,
                                        p.key, null));
                                add(mnuK1);
                            }
                        } else {
                            JMenuItem mnuK1 = new JMenuItem(new ActionBrowse(
                                    p.libelle, dm,
                                    dm.getDefaultBrowsableEtat(),
                                    collectivite.key, budget.key, p.key, null));
                            add(mnuK1);
                        }
                    }
                }
            } catch (DataAccessException daEx) {
                JMenuItem it = new JMenuItem("pas de document import�");
                it.setEnabled(false);
                add(it);
            } catch (DataConfigurationException dcEx) {
                JMenuItem it = new JMenuItem("Erreur de configuration");
                it.setEnabled(false);
                add(it);
            }
        }
    }
    
    public DocumentsModel getDocModels() {
        return docModels;
    }
    
    protected void setMenusAccess() {
        final Runnable updater = new Runnable() {
            public void run() {
                searchMenu.setEnabled(getConnectedUser().isAllowedTo(null,XemeliosUser.ROLE_SEARCH));
                browseMenu.setEnabled(getConnectedUser().isAllowedTo(null,XemeliosUser.ROLE_BROWSE));
                if (importerMenu != null) {
                    importerMenu.setEnabled(getConnectedUser().isAllowedTo(null,XemeliosUser.ROLE_IMPORT));
                }
                if (controlerMenu != null) {
                    controlerMenu.setEnabled(getConnectedUser().isAllowedTo(null,XemeliosUser.ROLE_IMPORT));
                }
                if (mnuClean != null)
                    mnuClean.setEnabled(getConnectedUser().isAllowedTo(null,XemeliosUser.ROLE_CLEAN));
                if (mnuCleanPro != null)
                    mnuCleanPro.setEnabled(getConnectedUser().isAllowedTo(null,XemeliosUser.ROLE_CLEAN));
                actExporter.setEnabled(getConnectedUser().isAllowedTo(null,XemeliosUser.ROLE_EXPORT));
                if (exportMenu != null)
                    exportMenu.setEnabled(getConnectedUser().isAllowedTo(null, XemeliosUser.ROLE_EXPORT));
                
            }
        };
        SwingUtilities.invokeLater(updater);
    }
    
    public StartListener getStartListener() {
        return startListener;
    }
    
    public static MainWindow getInstance() {
        return instance;
    }
    
    @Override
    protected void finalize() {
        instance = null;
    }
    
    public static class ImportContent {
        ArrayList<File> filesToImport;
        ArrayList<PJRef> pjs;
        public ImportContent() {
            super();
            filesToImport = new ArrayList<File>();
            pjs = new ArrayList<PJRef>();
        }
        public void setFilesToImport(ArrayList<File> array){
            this.filesToImport=array;
        }
    }
    public JDesktopPane getDesk() { return desk; }
    
    private void configExport() {
        DlgSearchConfig sc = new DlgSearchConfig(this,docModels, DlgSearchConfig.EXEC_FROM_CONF);
        sc.setVisible(true);
    }
    
    private void launchExport() {
        DlgExportAll dga = new DlgExportAll(this, true, docModels);
        dga.setVisible(true);
    }
    
    public class UpdateChecker extends SwingWorker<Boolean,Object> {
        protected Boolean doInBackground() {
            UpdaterUtil uu = new UpdaterUtil(MainWindow.instance.updateUrl,MainWindow.instance.componentsModelFileName);
            return new Boolean(uu.areThereUpdatesAvailable());
        }
        protected void done() {
            try {
                if(get().booleanValue()) {
                    String message = "<html>Des mises � jour sont disponibles.<br><br>Pour les installer, allez dans<br>le menu Fichier / Mises � jour,<br>puis cliquez sur Rechercher.</html>";
                    JOptionPane.showMessageDialog(MainWindow.instance,message,"Information",JOptionPane.INFORMATION_MESSAGE);
                }
            } catch(Throwable t) {}
        }
    }
    
    protected JMenu loadToolMenu() {
        JMenu ret = new JMenu("Outils");
        PropertiesExpansion pe = new PropertiesExpansion();
        pe.putAll(System.getProperties());
        // load components list
        XemeliosComponentsModel components = null;
        try {
            XemeliosComponentsParser parser = new XemeliosComponentsParser();
            parser.parse(new File(pe.getProperty(Constants.SYS_PROP_COMPONENTS_FILENAME)));
            components = (XemeliosComponentsModel)parser.getMarshallable();
        } catch(Throwable t) {}
        if(components==null) components = new XemeliosComponentsModel(XemeliosComponentsModel.QN);
        String sDir = pe.getProperty(Constants.SYS_PROP_XEMELIOS_TOOLS_DIR);
        if(sDir!=null) {
            String[] directories = sDir.split(",");
            for(String currentDirectory:directories) {
                File dir = new File(currentDirectory);
                if(dir.exists() && dir.isDirectory()) {
                    File[] jars = dir.listFiles(new java.io.FileFilter() {
                        public boolean accept(File pathname) {
                            return (pathname.getName().toUpperCase().endsWith(".JAR"));
                        }
                    });
                    for(File jar:jars) {
                        try {
                            JarFile jf = new JarFile(jar);
                            Manifest mn = jf.getManifest();
                            String toolClassName = mn.getMainAttributes().getValue(ToolConstants.TOOL_CLASS);
                            String toolRequires = mn.getMainAttributes().getValue(ToolConstants.TOOL_REQUIRES);
                            String toolDescription = mn.getMainAttributes().getValue(ToolConstants.TOOL_DESC);
                            String toolIcon = mn.getMainAttributes().getValue(ToolConstants.TOOL_ICON);
                            jf.close();
                            if(toolClassName!=null && toolClassName.length()>0 && toolDescription!=null && toolDescription.length()>0) {
                                try {
                                    if(toolRequires!=null && toolRequires.length()>3) {
                                        String[] requiredComps = toolRequires.split(";");
                                        for(String comp:requiredComps) {
                                            String[] compDetails = comp.split(",");
                                            if(compDetails.length==2) {
                                                fr.gouv.finances.cp.xemelios.common.components.ComponentModel installedComp = components.getComponent(compDetails[0]);
                                                if(installedComp==null) throw new Exception("Component "+compDetails[0]+" ("+compDetails[1]+") required");
                                                if(!installedComp.isNewerOrEqualThan(compDetails[1])) throw new Exception(installedComp.getId()+" "+installedComp.getVersion()+") to old: "+compDetails[1]+" required");
                                            }
                                        }
                                    }
                                    // tout est OK, on ajoute le jar au classpath
                                    if(!"true".equals(pe.getProperty(Constants.SYS_PROP_XEMELIOS_AVOID_TOOL_JAR_LOADING))) {
                                        Bootstrap.addFile(jar);
                                    }
                                    Action a = null;
                                    if(toolIcon!=null) {
                                        ImageIcon img = IhmFactory.getIconFromResource(toolIcon);
                                        if(img!=null) a = new ActionPlugin(toolClassName, toolDescription,img);
                                        else a = new ActionPlugin(toolClassName, toolDescription);
                                    } else {
                                        a = new ActionPlugin(toolClassName, toolDescription);
                                    }
                                    JMenuItem mnuItem = new JMenuItem(a);
                                    ret.add(mnuItem);
                                } catch(Exception ex) {
                                    logger.info("can not install plugin "+toolClassName+": "+ex.getMessage());
                                    continue;
                                }
                            }
                        } catch(Throwable t) {}
                    }
                }
            }
            if(ret.getItemCount()>0)
                return ret;
        }
        return null;
    }
    
    private class ActionPlugin extends AbstractAction {
        private String className = null;
        public ActionPlugin(String className, String desc) {
            super(desc);
            this.className = className;
        }
        public ActionPlugin(String className, String desc, Icon icon) {
            super(desc,icon);
            this.className=className;
        }
        
        public void actionPerformed(ActionEvent e) {
            try {
                Class clazz = Class.forName(className);
                Object o = clazz.newInstance();
                if(o instanceof AbstractTool) {
                    AbstractTool at = (AbstractTool)o;
                    at.run();
                } else {
                    throw new Exception(className+" does not extends "+AbstractTool.class.getName());
                }
            } catch(Exception ex) {
                new DisplayExceptionDlg(MainWindow.instance,ex);
            }
        }
        
    }

}
