/*
 * SoilTreeModel.java
 *
 * Created on April 19, 2007, 12:27 PM
 *
 */
package usda.weru.soil;

import java.awt.EventQueue;

import de.schlichtherle.truezip.file.TFile;

import de.schlichtherle.truezip.file.TFileReader;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import org.apache.log4j.Logger;
import usda.weru.util.ConfigData;
import usda.weru.util.FileTreeNode;

/**
 *
 * @author Joseph Levin
 */


public class SoilTreeModel extends DefaultTreeModel {

    private static final long serialVersionUID = 1L;

    private static final Logger LOGGER = Logger.getLogger(SoilTreeModel.class);
    /** Creates a new instance of SoilTreeModel */
    private List<SoilDatabase> c_databases;
    private boolean c_includeProject;
    private TemplatesNode c_templatesNode;
    private ProjectNode c_projectNode;
    private DefaultMutableTreeNode c_rootNode;

    /**
     *
     * @param includeProject
     */
    public SoilTreeModel(boolean includeProject) {
        super(null);
        c_includeProject = includeProject;
        build();

    }

    /**
     *
     */
    public void build() {
        c_databases = new LinkedList<SoilDatabase>();

        c_templatesNode = new TemplatesNode();
        ConfigData.getDefault().addPropertyChangeListener(ConfigData.SoilDB, c_templatesNode);

        if (c_includeProject) {
            c_rootNode = new DefaultMutableTreeNode();

            c_projectNode = new ProjectNode();
            ConfigData.getDefault().addPropertyChangeListener(ConfigData.CurrentProj, c_projectNode);

            c_rootNode.add(c_projectNode);
            c_rootNode.add(c_templatesNode);

            setRoot(c_rootNode);
        } else {
            setRoot(c_templatesNode);
        }

        if (c_projectNode != null) {
            ConfigData.getDefault().fireAll(c_templatesNode, c_projectNode);
        } else {
            ConfigData.getDefault().fireAll(c_templatesNode);
        }
    }

    /**
     *
     * @param node
     */
    @Override
    public void nodeStructureChanged(final TreeNode node) {
        try {
            super.nodeStructureChanged(node);
        } catch (Exception e) {
            EventQueue.invokeLater(new Runnable() {

                @Override
                public void run() {
                    //use the super so if the second time there is still an exception then the user will be alerted.
                    SoilTreeModel.super.nodeStructureChanged(node);
                }
            });
        }
    }

    /**
     *
     * @return
     */
    public List<SoilDatabase> getDatabases() {
        return c_databases;
    }

    /**
     *
     * @return
     */
    public TemplatesNode getTemplatesNode() {
        return c_templatesNode;
    }

    /**
     *
     * @return
     */
    public TreePath getPathToTemplates() {
        if (c_templatesNode != null) {
            return new TreePath(getPathToRoot(c_templatesNode));
        } else {
            return null;
        }
    }

    /**
     *
     * @return
     */
    public ProjectNode getProjectNode() {
        return c_projectNode;
    }

    /**
     *
     * @return
     */
    public TreePath getPathToProject() {
        if (c_projectNode != null) {
            return new TreePath(getPathToRoot(c_projectNode));
        } else {
            return null;
        }
    }

    /**
     *
     */
    public class ProjectNode extends FileTreeNode implements PropertyChangeListener {

        private static final long serialVersionUID = 1L;

        /**
         *
         */
        public ProjectNode() {
            super(null, IFC_FILTER);
            setDisplayName("Current Project");
        }

        /**
         *
         */
        public void refresh() {

            String path = ConfigData.getDefault().getDataParsed(ConfigData.CurrentProj);
            if (path != null) {
                TFile file = new TFile(path);
                setFile(file);
            } else {
                setFile(null);
            }

            nodeStructureChanged(this);
        }

        /**
         *
         * @param evt
         */
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            /**
             * Note:  we do not enable assertions in our code.  As far as I can tell,
             * we have never enabled assertions in our code.  Thus this block is useless.
             * Please never use it.  Note that this WILL BREAK the code every time if
             * uncommented and assertions are enabled.  Please just let it be.
             * 
             * Sincerely,
             * Jonathan Hornbaker
             */
//            assert ConfigData.CurrentProj.equals(evt.getPropertyName()) : "PropertyChangeListener is being too greedy!";
            refresh();
        }
    }

    /**
     *
     */
    public class TemplatesNode extends DefaultMutableTreeNode implements PropertyChangeListener {

        private static final long serialVersionUID = 1L;

        private TFile c_file;

        /**
         *
         */
        public TemplatesNode() {
            setUserObject("Soil Templates");
        }

        /**
         *
         * @param file
         */
        public TemplatesNode(TFile file) {
            this();
            c_file = file;

        }

        /**
         *
         * @param evt
         */
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            /**
             * Note:  we do not enable assertions in our code.  As far as I can tell,
             * we have never enabled assertions in our code.  Thus this block is useless.
             * Please never use it.  Note that this WILL BREAK the code every time if
             * uncommented and assertions are enabled.  Please just let it be.
             * 
             * Sincerely,
             * Jonathan Hornbaker
             */
//            assert ConfigData.SoilDB.equals(evt.getPropertyName()) : "PropertyChangeListener is being too greedy!";
            String path = ConfigData.getDefault().getDataParsed(ConfigData.SoilDB);
            if (path != null) {
                TFile file = new TFile(path);
                if (!file.equals(c_file)) {
                    c_file = file;
                    refresh();
                }
            } else {
                if (c_file != null) {
                    c_file = null;
                    refresh();
                }
            }

        }

        /**
         *
         */
        public void refresh() {
            try {
                if (c_databases != null) {
                    for (SoilDatabase database : c_databases) {
                        database.dispose();
                    }
                }
            } catch (NullPointerException npe) {
                LOGGER.warn("Unexpected null value while disposing soil database.");
            }

            removeAllChildren();

            MutableTreeNode[] children = createChildren();
            for (int i = 0; i < children.length; i++) {
                insert(children[i], i);
            }
            nodeStructureChanged(this);
        }

        /**
         *
         * @return
         */
        @Override
        public boolean isLeaf() {
            return super.isLeaf();
        }

        /**
         *
         * @return
         */
        public MutableTreeNode[] createChildren() {
            List<MutableTreeNode> nodes = new LinkedList<MutableTreeNode>();
            findDatabases(nodes, c_file);
            Collections.sort(nodes, new Comparator<MutableTreeNode>() {

                @Override
                public int compare(MutableTreeNode o1, MutableTreeNode o2) {
                    if (o1.isLeaf() == o2.isLeaf()) {
                        return o1.toString().compareTo(o2.toString());
                    } else {
                        if (o1.isLeaf()) {
                            return 1;
                        } else {
                            return -1;
                        }
                    }
                }
            });
            return nodes.toArray(new MutableTreeNode[nodes.size()]);
        }

        private void findDatabases(List<MutableTreeNode> nodes, TFile file) {
            SoilDatabase db = createDatabase(file);
            if (db != null) {
                //File is a database
                c_databases.add(db);
                nodes.addAll(Arrays.asList(db.createNodes()));
            }
            TFile[] children = file.listFiles();
            if (children != null) {
                for (TFile child : children) {
                    String fileName = child.getName();
                    if (fileName.startsWith(".svn") || fileName.startsWith("CVS")) {
                        continue;
                    }
                    findDatabases(nodes, child);
                }
            }
        }

        private SoilDatabase createDatabase(TFile file) {
            if (file == null) {
                return null;
            }
            String name = file.getName().toLowerCase();
            if (name.endsWith(".mdb")) {
                String dbName = getFriendlyName(c_file, file);
                if (dbName != null) {
                    return new MdbSoilDatabase(file, dbName);
                } else {
                    return new MdbSoilDatabase(file);
                }
            } else {
                if (name.endsWith(".ifc") && file.getParentFile().equals(c_file)) {
                    return new IfcSoilDatabase(file);
                } else {
                    if (file.isDirectory() && !file.equals(c_file)) {
                        java.io.File[] l_children = file.listFiles(IFC_FILTER);
                        if (l_children != null && l_children.length > 0) {
                            String dbName = getFriendlyName(c_file, file);
                            if (dbName != null) {
                                return new IfcSoilDatabase(file, dbName);
                            } else {
                                return new IfcSoilDatabase(file);
                            }
                        }
                    } else {
                        if (name.endsWith(".db")) {
                            //generic db file
                            Properties props = new Properties();
                            Reader reader = null;
                            try {
                                reader = new TFileReader(file);
                                props.load(reader);
                                String type = props.getProperty("type");
                                if (ArsSoapSoilDatabase.TYPE.equals(type)) {
                                    return new ArsSoapSoilDatabase(props);
                                } else {
                                }
                            } catch (FileNotFoundException fnfe) {
                                //Ignore, shouldn't be here
                                return null;
                            } catch (IOException ioe) {
                                //TODO: add log message indicating there was an error reading the database file
                                return null;
                            } finally {
                                try {
                                    reader.close();
                                } catch (IOException e) {
                                    //TODO: add log message indicating an error closing the stream
                                }
                            }
                        }
                    }
                }
            }

            return null;
        }
    }

    private static String getFriendlyName(TFile rootFile, TFile file) {

        if (rootFile == null) {
            return null;
        }
        if (rootFile.equals(file)) {
            return null;
        }
        //Find a nicer name
        String root = rootFile.getAbsolutePath().toLowerCase();
        String path = file.getAbsolutePath().toLowerCase();
        if (path.startsWith(root)) {
            String name = file.getAbsolutePath().substring(root.length() + 1);
            return name;
        }
        return null;
    }

    private static class IFCFileFilter implements FileFilter {

        @Override
        public boolean accept(java.io.File file) {
            if (file.getName().startsWith(".svn")) {
                return false;
            } else if (file.getName().startsWith("CVS")) {
                return false;
            } else if (file.isDirectory() || file.getName().toLowerCase().trim().endsWith(".ifc")) {
                return true;
            } else {
                return false;
            }
        }
    }
    private final IFCFileFilter IFC_FILTER = new IFCFileFilter();
}
