/*
 * Copyright
 *   2007 axYus - www.axyus.com
 *   2007 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.search;

import java.awt.Cursor;
import java.awt.Graphics;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Vector;

import javax.swing.JDesktopPane;
import javax.swing.SwingUtilities;

import org.apache.log4j.Logger;

import fr.gouv.finances.dgfip.utils.Pair;
import fr.gouv.finances.cp.utils.ui.DisplayExceptionDlg;
import fr.gouv.finances.dgfip.xemelios.common.ToolException;
import fr.gouv.finances.dgfip.xemelios.common.config.ChampModel;
import fr.gouv.finances.dgfip.xemelios.common.config.CritereModel;
import fr.gouv.finances.dgfip.xemelios.common.config.ElementModel;
import fr.gouv.finances.dgfip.xemelios.common.config.HiddenModel;
import fr.gouv.finances.dgfip.xemelios.common.config.ListeResultatModel;
import fr.gouv.finances.dgfip.xemelios.common.config.PluginModel;
import fr.gouv.finances.dgfip.xemelios.common.config.WidgetModel;
import fr.gouv.finances.dgfip.xemelios.data.DataResultSet;
import fr.gouv.finances.cp.xemelios.plugins.ExportPluginRunner;
import fr.gouv.finances.cp.xemelios.plugins.SearchPluginRunner;
import fr.gouv.finances.cp.xemelios.ui.CriteriaViewer;
import fr.gouv.finances.cp.xemelios.ui.SearchWindow;
import fr.gouv.finances.cp.xemelios.ui.SearchWindow.DisplayErrorSearchThread;
import fr.gouv.finances.dgfip.xemelios.auth.XemeliosUser;
import fr.gouv.finances.dgfip.xemelios.plugins.ExportPlugin;
import fr.gouv.finances.dgfip.xemelios.plugins.SearchPlugin;
import java.lang.reflect.Constructor;

/**
 * @author chm
 * 
 * TODO To change the template for this generated type comment go to Window -
 * Preferences - Java - Code Style - Code Templates
 */
public class SearchThread extends Thread {

    private static Logger logger = Logger.getLogger(SearchThread.class);
    public Throwable exceptionDuringProcess = null;
    public Object obj = null;
    private boolean background;
    private SearchWindow parentCaller;
    private ElementModel elementModel = null;
    private Pair collectivite = null,  budget = null;
    private PluginModel plugin = null;
    private Vector<CriteriaViewer> criterias = null;

    public SearchThread(SearchWindow parent, ElementModel elementModel, Pair collectivite, Pair budget, Vector<CriteriaViewer> criterias, PluginModel plugin) {
        super("Searcher");
        background = false;
        this.parentCaller = parent;
        this.elementModel = elementModel;
        this.collectivite = collectivite;
        this.budget = budget;
        this.criterias = criterias;
        this.plugin = plugin;
    }

    public SearchThread(SearchWindow parent, ElementModel elementModel, Pair collectivite, Pair budget, Vector<CriteriaViewer> criterias, PluginModel plugin, boolean runInBackground) {
        this(parent, elementModel, collectivite, budget, criterias, plugin);
        background = runInBackground;
    }

    @Override
    public void run() {
        long start = System.currentTimeMillis();
        setPriority(MIN_PRIORITY);
        if (plugin == null) {
            simpleSearch(start);
        } else {
            if (PluginModel.TYPE_SEARCH.equals(plugin.getPluginType())) {
                pluginSearch(start);
            } else if (PluginModel.TYPE_EXPORT.equals(plugin.getPluginType())) {
                pluginExport(start);
            }
        }
        if (exceptionDuringProcess != null) {
            if(!(exceptionDuringProcess instanceof ToolException))
                new DisplayExceptionDlg(parentCaller.getParentFrame(), exceptionDuringProcess);
        }
    }

    protected CriteriaViewer getDoublon (Vector<CriteriaViewer>vcv) {
    	Vector<String> ids = new Vector<String>();
    	for (CriteriaViewer cv:vcv) {
    		// est-ce un CV avec un bouton supprimer ?
    		if (!cv.isHideRemoveIcon()) {
	    		if (ids.contains(cv.getCurrentCritereModel().getId())) return cv;
	    		ids.add(cv.getCurrentCritereModel().getId());
    		}
    	}
    	return null;
    }

    protected void pluginSearch(long start) {
        Hashtable<String, String> parameters = new Hashtable<String, String>();
        boolean shouldContinue = true;
        for (CriteriaViewer cv : criterias) {
            try {
                parameters.putAll(cv.getParameters());
            } catch (ToolException tEx) {
                // un champ a mal ete saisi
                shouldContinue = false;
                exceptionDuringProcess = tEx;
                obj = cv;
            }
        }
    	/**
    	 * On teste si on est en mode recherche par plugin
    	 * et si dans la liste des critères optionnels utilisés
    	 * on n'a pas de doublon
    	 */
    	CriteriaViewer doublon = null;
    	if ((doublon = getDoublon (criterias))!=null) {
    		obj = doublon;//.focusOnDelete();
    		doublon.focusOnDelete();
    		exceptionDuringProcess = new ToolException("Il n'est pas possible d'utiliser plusieurs fois le même critère optionnel dans une recherche prédéfinie. Veuillez supprimer les doublons.", ToolException.ERROR_MISFILLED_CRITERIA);
    		shouldContinue = false;
    	}        
        if (shouldContinue) {
            try {
                Class clazz = Class.forName(plugin.getPluginClass());
                Constructor cc = clazz.getConstructor(XemeliosUser.class);
                SearchPlugin xp = (SearchPlugin) cc.newInstance(parentCaller.getUser());
                xp.setPluginModel(plugin);
                xp.setCollectivite(collectivite);
                xp.setBudget(budget);

                SearchPluginRunner runner = new SearchPluginRunner(parentCaller.getUser());
                runner.setPlugin(xp);
                runner.setParameters(parameters);
                runner.setOwner(parentCaller.getParentFrame());
                runner.setSearchWindow(parentCaller);
                runner.setElementModel(elementModel);
                runner.setListeResultat(getListeResultat());
                runner.setStart(start);
                runner.startSearch();
            } catch (ClassNotFoundException cnfEx) {
                exceptionDuringProcess = cnfEx;
            } catch (InstantiationException iEx) {
                exceptionDuringProcess = iEx;
            } catch (IllegalAccessException iaEx) {
                exceptionDuringProcess = iaEx;
            } catch (Exception ex) {
                exceptionDuringProcess = ex;
            }
        } else {
            // erreur de saisie des criteres
            parentCaller.getParentFrame().stopWaiter();
            parentCaller.setCursor(Cursor.getDefaultCursor());
            Runnable code = new DisplayErrorSearchThread(parentCaller, (ToolException) exceptionDuringProcess, obj);
            if (SwingUtilities.isEventDispatchThread()) {
                code.run();
            } else {
                SwingUtilities.invokeLater(code);
            }
        }
    }

    protected void pluginExport(long start) {
//        throw new RuntimeException("not yet implemented");
        logger.debug("entering pluginxport");
        Hashtable<String, String> parameters = new Hashtable<String, String>();
        boolean shouldContinue = true;
        for (CriteriaViewer cv : criterias) {
            try {
                parameters.putAll(cv.getParameters());
            } catch (ToolException tEx) {
                // un champ a mal ete saisi
                shouldContinue = false;
                exceptionDuringProcess = tEx;
                obj = cv;
            }
        }
        if (shouldContinue) {
            try {
                Class clazz = Class.forName(plugin.getPluginClass());
                Constructor cc = clazz.getConstructor(XemeliosUser.class);
                ExportPlugin xp = (ExportPlugin) cc.newInstance(parentCaller.getUser());
                xp.setPluginModel(plugin);
                xp.setCollectivite(collectivite);
                xp.setBudget(budget);
                xp.setElementModel(elementModel);
                xp.setParameters(parameters);

                ExportPluginRunner runner = new ExportPluginRunner(parentCaller.getUser());
                runner.setPlugin(xp);
                runner.setOwner(parentCaller.getParentFrame());
                runner.setStart(start);
                runner.setSearchWindow(parentCaller);
                runner.startExport();
            } catch (ClassNotFoundException cnfEx) {
                exceptionDuringProcess = cnfEx;
            } catch (InstantiationException iEx) {
                exceptionDuringProcess = iEx;
            } catch (IllegalAccessException iaEx) {
                exceptionDuringProcess = iaEx;
            } catch (Exception ex) {
                exceptionDuringProcess = ex;
            }
        } else {
            // erreur de saisie des criteres
            parentCaller.getParentFrame().stopWaiter();
            parentCaller.setCursor(Cursor.getDefaultCursor());
            Runnable code = new DisplayErrorSearchThread(parentCaller, (ToolException) exceptionDuringProcess, obj);
            if (SwingUtilities.isEventDispatchThread()) {
                code.run();
            } else {
                SwingUtilities.invokeLater(code);
            }
        }
    }

    protected void simpleSearch(long start) {
        StringBuffer buff = new StringBuffer();
        buff.append(elementModel.getPath().getPath());
        boolean shouldContinue = true;
        HashMap<String, ChampModel> additionnalFields = new HashMap<String, ChampModel>();
        Vector<CritereModel> models = new Vector<CritereModel>();
        for (CriteriaViewer cv : criterias) {
            CritereModel cm = cv.getModel();
            models.add(cm);
            try {
                if (cv.getAndOrEr().equals(CriteriaViewer.OR_ASSOCIATOR)) {
                    buff.append("|").append(elementModel.getPath().getPath());
                }
                buff.append(cv.getXPathExpr());

                if (cv.isOptionalDisplayChecked()) {
                    // to add the optional columns to display
                    String idAffichage = cm.getIdAffichage();
                    if (idAffichage != null && idAffichage.length() > 0) {
                        // Look for the "champ" matching this ID in the result list...
                        ChampModel champOptionnel = elementModel.getListeResultat().getChamps().get(idAffichage).clone();
                        // ... and add it to the optional field list
                        if (champOptionnel != null) {
                            additionnalFields.put(champOptionnel.getId(), champOptionnel);
                        }
                    }
                }
            } catch (ToolException tEx) {
                shouldContinue = false;
                parentCaller.parentFrame.stopWaiter();
                setPriority(NORM_PRIORITY);
                if (tEx.getErrorCode() != ToolException.ERROR_MISFILLED_CRITERIA) {
                    logger.error(tEx.getMessage(), tEx);
                } else {
                    parentCaller.setCursor(Cursor.getDefaultCursor());
                    exceptionDuringProcess = tEx;
                    obj = cv;
                }
                Runnable code = new DisplayErrorSearchThread(parentCaller, tEx, obj);
                if (SwingUtilities.isEventDispatchThread()) {
                    code.run();
                } else {
                    SwingUtilities.invokeLater(code);
                }
                exceptionDuringProcess = null;
                break;
            } catch (Exception ex) {
                shouldContinue = false;
                exceptionDuringProcess = ex;
                parentCaller.parentFrame.stopWaiter();
            }
        }
        if (!shouldContinue) {
            parentCaller.getParentFrame().stopWaiter();
            parentCaller.setCursor(Cursor.getDefaultCursor());
            return;
        }
        if (background) {
            Runnable code = new Runnable() {

                public void run() {
                    logger.debug("iconify");
                    parentCaller.setTitle("Recherche en cours...");
                    JDesktopPane dp = parentCaller.getDesktopPane();
                    dp.getDesktopManager().iconifyFrame(parentCaller);
                    Graphics g = dp.getGraphics();
                    if (g != null) {
                        dp.update(g);
                    } else {
                        logger.fatal("impossible de recuperer G");
                    }
                }
            };
            if (SwingUtilities.isEventDispatchThread()) {
                code.run();
            } else {
                try {
                    SwingUtilities.invokeAndWait(code);
                } catch (Throwable t) {
                }
            }
            Thread.yield();
            setPriority(Thread.MIN_PRIORITY);
        }
        ListeResultatModel lrm = new ListeResultatModel(ListeResultatModel.QN);
        lrm.setParent(elementModel);

        for (ChampModel cm : additionnalFields.values()) {
            try {
                lrm.addChild(cm, ChampModel.QN);
            } catch (Throwable t) {
            } // c'est nous qu'on maitrise, on fait pas de
        // conneries
        }
        ListeResultatModel lrm2 = elementModel.getListeResultat().clone();
        for (ChampModel cm : lrm2.getListeChamps()) {
            try {
                if (cm.isAfficheDefaut() && cm.isAffichable()) {
                    lrm.addChild(cm, ChampModel.QN);
                }
            } catch (Throwable t) {
            } // c'est nous qu'on maitrise toujours
        }
        lrm.setTriDefaut(lrm2.getTriDefaut());
        lrm.setOrdre(lrm2.getOrdre());
        for (HiddenModel hm : lrm2.getHiddens()) {
            try {
                lrm.addChild(hm, HiddenModel.QN);
            } catch (Throwable t) {
            } //qu'on matrise toujours, ouyouyouille !
        }
        for (WidgetModel wm : lrm2.getWidgets()) {
            try {
                lrm.addChild(wm, WidgetModel.QN);
            } catch (Throwable t) {
            }
        }
        try {
            XPathExecuter xe = new XPathExecuter(elementModel, buff.toString(), models, collectivite, budget, lrm, start, parentCaller);
            if (background) {
                xe.addPropertyChangeListener(new PropertyChangeListener() {

                    public void propertyChange(PropertyChangeEvent evt) {
                        if ("COMPLETED".equals(evt.getPropertyName()) && Boolean.TRUE.equals(evt.getNewValue())) {
                            Runnable code = new Runnable() {

                                public void run() {
                                    logger.debug("uniconify");
                                    parentCaller.redisplayTitle();
                                    parentCaller.getDesktopPane().getDesktopManager().deiconifyFrame(parentCaller);
                                }
                            };
                            if (SwingUtilities.isEventDispatchThread()) {
                                code.run();
                            } else {
                                SwingUtilities.invokeLater(code);
                            }
                        }
                    }
                });
            }
            xe.execute();
            try {
                xe.get();
            } catch (Throwable t) { /* only to wait for completion */
            }
        } catch (Throwable th) {
            th.printStackTrace();
            if (th instanceof OutOfMemoryError) {
                exceptionDuringProcess = new ToolException("Mémoire insuffisante pour cette requête. \nEssayez d'augmenter les paramètres -Xmx et -Xss de la JVM ou affinez votre requête.", ToolException.ERROR_DB_ENGINE);
            } else if (th instanceof StackOverflowError) {
                exceptionDuringProcess = new ToolException("Mémoire insuffisante pour cette requête. \nEssayez d'augmenter les paramètres -Xmx et -Xss de la JVM ou affinez votre requête.", ToolException.ERROR_DB_ENGINE);
            } else {
                exceptionDuringProcess = th;
            }
        }
    }

    protected ListeResultatModel getListeResultat() {
        HashMap<String, ChampModel> additionnalFields = new HashMap<String, ChampModel>();
        for (CriteriaViewer cv : criterias) {
            CritereModel cm = cv.getModel();
            try {

                if (cv.isOptionalDisplayChecked()) {
                    // to add the optional columns to display
                    String idAffichage = cm.getIdAffichage();
                    if (idAffichage != null && idAffichage.length() > 0) {
                        // Look for the "champ" matching this ID in the result list...
                        ChampModel champOptionnel = elementModel.getListeResultat().getChamps().get(idAffichage).clone();
                        // ... and add it to the optional field list
                        if (champOptionnel != null) {
                            additionnalFields.put(champOptionnel.getId(), champOptionnel);
                        }
                    }
                }
            } catch (Exception ex) {
            }
        }
        ListeResultatModel lrm = new ListeResultatModel(ListeResultatModel.QN);
        lrm.setParent(elementModel);

        for (ChampModel cm : additionnalFields.values()) {
            try {
                lrm.addChild(cm, ChampModel.QN);
            } catch (Throwable t) { }
        }
        ListeResultatModel lrm2 = elementModel.getListeResultat().clone();
        for (ChampModel cm : lrm2.getListeChamps()) {
            try {
                if (cm.isAfficheDefaut() && cm.isAffichable()) {
                    lrm.addChild(cm, ChampModel.QN);
                }
            } catch (Throwable t) { }
        }
        lrm.setTriDefaut(lrm2.getTriDefaut());
        lrm.setOrdre(lrm2.getOrdre());
        for (HiddenModel hm : lrm2.getHiddens()) {
            try {
                lrm.addChild(hm, HiddenModel.QN);
            } catch (Throwable t) { }
        }
        for (WidgetModel wm : lrm2.getWidgets()) {
            try {
                lrm.addChild(wm, WidgetModel.QN);
            } catch (Throwable t) {
            }
        }
        return lrm;
    }

    /**
     * display the result
     * 
     * @param rs
     * @param lrm
     * @param start
     * @deprecated. Instead, call this method in the done() method of a
     *              SwingWorker
     */
    protected void displayResult(DataResultSet rs, ListeResultatModel lrm, long start) {
        Runnable code = new ResultDisplayerThread(rs, lrm, start, elementModel, parentCaller);
        if (SwingUtilities.isEventDispatchThread()) {
            code.run();
        } else {
            try {
                SwingUtilities.invokeAndWait(code);
            } catch (Throwable t) {
            }
        }
    }
}
