/*
 * MassSoilTreeModel.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.JOptionPane;
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.soil.JdbcSoilDatabase.ComponentNode;
import usda.weru.soil.JdbcSoilDatabase.MapUnitNode;
import usda.weru.soil.JdbcSoilDatabase.LegendNode;

/**
 *
 * @author Joseph Levin
 */


public class MassSoilTreeModel extends DefaultTreeModel {

    private static final long serialVersionUID = 1L;

    private static final Logger LOGGER = Logger.getLogger(MassSoilTreeModel.class);
    /** Creates a new instance of MassSoilTreeModel */
    private List<SoilDatabase> c_databases;
    private TemplatesNode c_templatesNode;
    private DefaultMutableTreeNode c_rootNode;
    
    List<DefaultMutableTreeNode> selected;
    private int legend = 0;
    private int mapUnit = 0;
    private int component = 0;
    
    private int legendLimit = 1;
    private int mapUnitLimit = 10;
    private int componentLimit = 50;

    /**
     *
     * @param includeProject
     */
    public MassSoilTreeModel() {
        super(null);
        build();
        selected = new LinkedList<DefaultMutableTreeNode>();
    }

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

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

        
        setRoot(c_templatesNode);
        
        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.
                    MassSoilTreeModel.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;
        }
    }
    
    public int getNumLegend() { return legend; }
    
    public int getNumMapUnit() { return mapUnit; }
    
    public int getNumComponent() { return component; }

    /**
     *
     */
    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(".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;
        }
    }

    public boolean pathSelected(TreePath path)
    {
        Object temp = path.getLastPathComponent();
        DefaultMutableTreeNode res;
        try { res = (DefaultMutableTreeNode) temp; }
        catch(ClassCastException cce) { return false; }
        return selected.contains(res);
    }
    
    public void selectPath(TreePath path)
    {
        Object temp = path.getLastPathComponent();
        DefaultMutableTreeNode res;
        try { res = (DefaultMutableTreeNode) temp; }
        catch(ClassCastException cce) { return; }
        if(res instanceof LegendNode)
        {
            if(selected.contains(res))
            {
                selected.remove(res);
                legend --;
            }
            else
            {
                if(legend == legendLimit)
                {
                    JOptionPane.showMessageDialog(null, "Only one legend node is "
                            + "allowed to be selected at a time.\n"
                            + "Please Download the currently selected node first "
                            + "or unselect the current node.", "Large Selection Size", JOptionPane.WARNING_MESSAGE);
                }
                else
                {
                    selected.add(res);
                    legend ++;
                }
            }
        }
        else if(res instanceof MapUnitNode)
        {
            if(selected.contains(res))
            {
                selected.remove(res);
                mapUnit --;
            }
            else
            {
                if(mapUnit == mapUnitLimit)
                {
                    JOptionPane.showMessageDialog(null, "Only ten map unit nodes are "
                            + "allowed to be selected at a time.\n"
                            + "Please Download the currently selected nodes first "
                            + "or unselect a node.", "Large Selection Size", JOptionPane.WARNING_MESSAGE);
                }
                else
                {
                    selected.add(res);
                    mapUnit ++;
                }
            }
        }
        else if(res instanceof ComponentNode)
        {
            if(selected.contains(res))
            {
                selected.remove(res);
                component --;
            }
            else
            {
                if(component == componentLimit)
                {
                    JOptionPane.showMessageDialog(null, "Only fifty component nodes are "
                            + "allowed to be selected at a time.\n"
                            + "Please Download the currently selected nodes first "
                            + "or unselect a node.", "Large Selection Size", JOptionPane.WARNING_MESSAGE);
                }
                else
                {
                    selected.add(res);
                    component ++;
                }
            }
        }
    }
    
    public boolean downloadReady() { return !selected.isEmpty(); }

    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();
}
