/*
 * Copyright
 *   2007 axYus - www.axyus.com
 *   2005 Y.Laurelut - yann.laurelut@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.importDialog;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JToolBar;
import javax.swing.ScrollPaneConstants;
import javax.swing.table.TableColumn;
import javax.swing.tree.DefaultMutableTreeNode;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.jdesktop.swingx.JXTreeTable;

import fr.gouv.finances.cp.utils.ui.IhmFactory;
import fr.gouv.finances.cp.xemelios.ui.ImageResources;


/**
 * Provides the mechanism to call a modal (blocking)
 * dialog window, that will show the user all the files found in directories,
 * and let him/her choose which files to really import.
 * The "Ok" button at the bottom lets the user close the window, and
 * returns the information in a <code>List<ImportFile></code>.
 * <br>Note: this class is a wrapper, the actual work is done in InnerImportDialog.
 *
 * @author Yann Laurelut
 */
public class ImportDialog {
    private final static Logger logger = Logger.getLogger(ImportDialog.class);
    
    private JFrame parent = null;
    private String title = "";
    
    /** The list of results passed back to the caller of ImportDialog */
    protected List<ImportFile> resultList = new ArrayList<ImportFile>();
    
    /**
     * Constructor.
     * @param parent The parent frame for this dialog
     * @param title Title of the dialog window
     */
    public ImportDialog(JFrame parent, String title) {
        this.parent = parent;
        this.title = title;
    }
    
    /**
     * Displays the modal dialog box.
     * @return the list of files to be imported
     */
    public List<ImportFile> startDialog(File rootDir, String[] extensions) {
        ImportFileTreeTableModel model = createImportFileModel(rootDir, extensions);
        InnerImportDialog d = new InnerImportDialog(model, parent, title, true);
        d.setVisible(true);
        
        return resultList;
    }
    
    /**
     * Provides a modal dialog window, presenting the user with
     * all the files found suitable for import, starting from
     * a given path and drilling down the directories.
     *
     * @author Yann Laurelut
     */
    class InnerImportDialog extends JDialog implements ActionListener {
        JButton boutonExpandAll, boutonCollapseAll, boutonSelectAll, boutonSelectNone, boutonOk, boutonAnnuler;
        // Constants
        static final long serialVersionUID = 345134599;
        
        private JXTreeTable treeTable;
        private ImportFileTreeTableModel importFileTreeTableModel = null;
        
        /**
         * Default constructor
         * @param model The data model for this TreeTable
         * @param parent The parent of this dialog
         * @param title The title of the dialog
         * @param modal To indicate whether this dialog is modal or not
         */
        @SuppressWarnings("serial")
        public InnerImportDialog(ImportFileTreeTableModel model, JFrame parent, String title, boolean modal) {
            super(parent, title, modal);
            importFileTreeTableModel = model;
            treeTable = new JXTreeTable(importFileTreeTableModel);
            JScrollPane jScrollPane1 = new javax.swing.JScrollPane();
            
            setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
            jScrollPane1.setViewportView(treeTable);
            jScrollPane1.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
            
            boutonOk = new JButton("Ok");
            boutonOk.addActionListener(this);
            boutonAnnuler = new JButton("Annuler");
            boutonAnnuler.addActionListener(this);
            
            JToolBar toolbar = new JToolBar(JToolBar.HORIZONTAL);
            toolbar.setFloatable(false);
            toolbar.setRollover(true);
//            IhmFactory ihmFactory = IhmFactory.newInstance();
            boutonExpandAll = new JButton(IhmFactory.getIconFromResource(ImageResources.EXPAND_ALL));
            boutonExpandAll.setToolTipText("Déplier l'arborescence");
            boutonExpandAll.addActionListener(this);
            toolbar.add(boutonExpandAll);
            boutonCollapseAll = new JButton(IhmFactory.getIconFromResource(ImageResources.COLLAPSE_ALL));
            boutonCollapseAll.setToolTipText("Replier l'arborescence");
            boutonCollapseAll.addActionListener(this);
            toolbar.add(boutonCollapseAll);
            boutonSelectAll = new JButton(IhmFactory.getIconFromResource(ImageResources.SELECT_ALL));
            boutonSelectAll.setToolTipText("Tout sélectionner");
            boutonSelectAll.addActionListener(this);
            toolbar.add(boutonSelectAll);
            boutonSelectNone = new JButton(IhmFactory.getIconFromResource(ImageResources.SELECT_NONE));
            boutonSelectNone.setToolTipText("Tout désélectionner");
            boutonSelectNone.addActionListener(this);
            toolbar.add(boutonSelectNone);
            
            
            org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(getContentPane());
            getContentPane().setLayout(layout);
            layout.setHorizontalGroup(
                    layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                    .add(layout.createSequentialGroup()
                    .addContainerGap()
                    .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING)
                    .add(org.jdesktop.layout.GroupLayout.LEADING, jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 434, Short.MAX_VALUE)
                    .add(layout.createSequentialGroup()
                    .add(boutonAnnuler)
                    .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                    .add(boutonOk)))
                    .addContainerGap())
                    .add(toolbar, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 458, Short.MAX_VALUE)
                    );
            layout.setVerticalGroup(
                    layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                    .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup()
                    .add(toolbar, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, 25, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                    .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                    .add(jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 365, Short.MAX_VALUE)
                    .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                    .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                    .add(boutonOk)
                    .add(boutonAnnuler))
                    .addContainerGap())
                    );
            pack();
            
            // init for the toolbar
            setSize(500, 400);
            
            setLocationRelativeTo(parent);
            
            // To beautify things, we specify column widths
            TableColumn col = treeTable.getColumnModel().getColumn(1);
            col.setPreferredWidth(75);
            col.setMinWidth(60);
            col = treeTable.getColumnModel().getColumn(0);
            col.setMinWidth(200);
            col.setPreferredWidth(380);
        }
        
        /**
         * Called when a button is clicked
         */
        public void actionPerformed(ActionEvent ae) {
            Object cmd = ae.getSource();
            if(cmd.equals(boutonExpandAll)) {
                treeTable.expandAll();
            } else if(cmd.equals(boutonCollapseAll)) {
                treeTable.collapseAll();
            } else if(cmd.equals(boutonSelectAll)) {
                importFileTreeTableModel.selectAll();
            } else if(cmd.equals(boutonSelectNone)) {
                importFileTreeTableModel.selectNone();
            } else if(cmd.equals(boutonOk)) {
                resultList = importFileTreeTableModel.getFilesToImportList();
                setVisible(false);
            } else if(cmd.equals(boutonAnnuler)) {
                setVisible(false);
            }
        }
        
    }
    // end of class InnerImportDialog
    /**
     * Drill through the directory tree, starting from the given folder,
     * and creates a Model of ImportFile to be used for display in the
     * ImportDialog.
     * @param rootDir The directory to start searching from
     * @param extensions The extensions of files we are looking for (eg "xml", "xhl"...)
     * @return A Model of ImportFile
     */
    public ImportFileTreeTableModel createImportFileModel(File rootDir, String[] extensions) {
        ImportFileTreeTableModel model = null;
        
        if(!rootDir.isDirectory()) {
            // jeter une exception
            return model;
        }
        ImportFile racineDir = new ImportFile(rootDir);
        MyTreeNode racine = splitPath(racineDir, racineDir);
        
        Iterator iter = FileUtils.iterateFiles(rootDir, extensions, true);
        ImportFile fichierImport = null;
        // Pour chaque fichier trouvé, on crée un noeud dans l'arborescence
        while(iter.hasNext()) {
            File fichier = (File)iter.next();
            fichierImport = new ImportFile(fichier);
            addSorted(racine, splitPath(fichierImport,racineDir));
        }
        model = new ImportFileTreeTableModel(racine);
        
        return model;
    }
    
    /**
     * Creates a tree structure of DefaultMutableTreeNodes to match the structure
     * of directories in the given ImportFile, up to directory <tt>upToDir</tt>. <br>
     * For example, if the path of the given ImportFile is "/", only
     * one TreeNode will be returned.<br>
     * If the path is "/foo/bar/file1.xml", there will be 3 imbricated
     * TreeNodes, one for directory "foo", one for directory "bar", and
     * a final one for file "file1.xml".
     * @param fichierImport The ImportFile with full path
     * @return a TreeNode being the root of the directory structure for
     * this ImportFile, with children TreeNodes accordingly.
     */
    private MyTreeNode splitPath(ImportFile fichierImport, ImportFile upToDir) {
        MyTreeNode currentNode = new MyTreeNode(fichierImport);
        if(upToDir.getFile().equals(fichierImport.getFile())) return currentNode;
        File upDir = upToDir.getFile();
        File parentFile;
        while( (parentFile=((ImportFile)currentNode.getUserObject()).getFile().getParentFile())!=null) {
            ImportFile parentImportFile = new ImportFile(parentFile);
            MyTreeNode parentNode = new MyTreeNode(parentImportFile);
            parentNode.add(currentNode);
            currentNode = parentNode;
            if(parentFile.equals(upDir)) break;
        }
        
        return currentNode;
    }
    
    /**
     * Adds a new node to the tree structure under the given root node.
     * It does not add the node directly under the root, but goes through
     * the hierarchy of nodes under the root, to insert the new one without
     * duplicating any directory.
     * @param racine root node to add the other node to
     * @param newNode The new leaf node, with complete tree structure to the root, to be added
     */
    private void addSorted(MyTreeNode racine, MyTreeNode newNode) {
        MyTreeNode newNodeBrowser = newNode;
        MyTreeNode rootBrowser = racine;
        boolean continuer = true;
        
        while(continuer) {
            ImportFile newNodeBrowserFile = (ImportFile)newNodeBrowser.getUserObject();
            ImportFile rootBrowserFile = (ImportFile)rootBrowser.getUserObject();
            
            if(newNodeBrowserFile.equals(rootBrowserFile)) {
                newNodeBrowser = (MyTreeNode)newNodeBrowser.getFirstChild();
                MyTreeNode rootSon = matchingChild(newNodeBrowser, rootBrowser);
                if(rootSon!=null) {
                    // on continue avec ce descendant de root
                    rootBrowser = rootSon;
                } else {
                    continuer = false;
                }
            } else {
                continuer = false;
            }
        }
        // on ajoute notre noeud à l'emplacement actuel
        rootBrowser.add(newNodeBrowser);
    }
    
    /**
     * Returns a child of the given rootBrowser if it is equal to the given newNodeBrowser.
     * The equality is defined as: if there exists a child of rootBrowser, such as:<br>
     * <code>((ImportFile)child.getUserObject()).equals((ImportFile)newNodeBrowser.getUserObject())</code>
     * @param newNodeBrowser a DefaultMutableTreeNode that we want to insert in the tree
     * @param rootBrowser the current node we want to plug the above node to
     * @return the child of rootBrowser equals to newNodeBrowser if one is found, null otherwise.
     */
    private MyTreeNode matchingChild(DefaultMutableTreeNode newNodeBrowser, DefaultMutableTreeNode rootBrowser) {
        MyTreeNode result = null;
        ImportFile newNodeBrowserFile = (ImportFile)newNodeBrowser.getUserObject();
        
        for(Enumeration enumerator = rootBrowser.children(); enumerator.hasMoreElements() && result==null; ) {
            MyTreeNode child = (MyTreeNode)enumerator.nextElement();
            ImportFile childFile = (ImportFile)child.getUserObject();
            if(childFile.equals(newNodeBrowserFile)) {
                result = child;
            }
        }
        return result;
    }
    public class MyTreeNode extends DefaultMutableTreeNode {
        private ImportFile importFile;
        public MyTreeNode(ImportFile iFile) {
            super(iFile);
            this.importFile=iFile;
        }
        public boolean isLeaf() {
            boolean ret = !(importFile.getFile().isDirectory());
//            logger.debug("isLeaf("+importFile.getFile().getName()+")->"+ret);
//            if(ret) logger.debug("\thas "+getChildCount()+" childs");
            return ret;
        }
    }
}
