/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package dom.traversal;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Enumeration;
import java.util.Hashtable;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import org.apache.xerces.parsers.DOMParser;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.DocumentTraversal;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.TreeWalker;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import ui.DOMTreeFull;

/** This class shows a DOM Document in a JTree, and presents controls
 *  which allow the user to create and view the progress of a TreeWalker
 *  in the DOM tree.
 */
public class TreeWalkerView 
    extends JFrame 
    implements ActionListener {

    private static final long serialVersionUID = 3257566187583189559L;
    
    Document document;
    TreeNode lastSelected;
    DOMParser parser;
    JTextArea messageText;
    JScrollPane messageScroll;
    DOMTreeFull jtree;
    TreeWalker treeWalker;
    NameNodeFilter nameNodeFilter;
    
    JButton nextButton;
    JButton prevButton;
    JButton removeButton;
    JButton addButton;
    JTextField addText;
    JButton newIterator;
    JList whatToShow;
    JCheckBox match;
    JTextField nameFilter;
   
    // treeWalker specific buttons    
    JButton parentButton;
    JButton nextSiblingButton;
    JButton previousSiblingButton;
    JButton firstChildButton;
    JButton lastChildButton;
    JButton currentButton;
    
    String whatArray[] = new String [] { 
            "ALL",
            "ELEMENT",
            "ATTRIBUTE",
            "TEXT",
            "CDATA_SECTION",
            "ENTITY_REFERENCE",
            "ENTITY",
            "PROCESSING_INSTRUCTION", 
            "COMMENT", 
            "DOCUMENT", 
            "DOCUMENT_TYPE",
            "DOCUMENT_FRAGMENT",
            "NOTATION" 
            };
    JCheckBox expandERs;
    
    
    /** main */
    public static void main (String args[]) {

        if (args.length > 0) {
            String filename = args[0];
            try {
                TreeWalkerView frame = new TreeWalkerView(filename) ;
                frame.addWindowListener(new java.awt.event.WindowAdapter() {
                 public void windowClosing(java.awt.event.WindowEvent e) {
                     System.exit(0);
                 }
                });
                frame.setSize(640, 700);
                frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace(System.err);
                }
        }
    }
    
    Hashtable treeNodeMap = new Hashtable();

    /** Constructor */
    public TreeWalkerView (String filename) {
        super("TreeWalkerView: "+filename);
        try {
            parser = new DOMParser();
            parser.setFeature("http://apache.org/xml/features/dom/defer-node-expansion", true);
            parser.setFeature("http://apache.org/xml/features/continue-after-fatal-error", true);
            Errors errors = new Errors();
            parser.setErrorHandler(errors);
            parser.parse(filename);
            document = parser.getDocument();
            
            if (!document.isSupported("Traversal", "2.0")) {
                // This cannot happen with the DOMParser...
                throw new RuntimeException("This DOM Document does not support Traversal");
            
            }

            // jtree  UI setup
            jtree = new DOMTreeFull((Node)document);
            jtree.getSelectionModel().setSelectionMode
                (TreeSelectionModel.SINGLE_TREE_SELECTION);

            // Listen for when the selection changes, call nodeSelected(node)
            jtree.addTreeSelectionListener(
                new TreeSelectionListener() {
                    public void valueChanged(TreeSelectionEvent e) {
                        TreePath path = (TreePath)e.getPath(); 
                        TreeNode treeNode = (TreeNode)path.getLastPathComponent();
                        if(jtree.getSelectionModel().isPathSelected(path))
                                nodeSelected(treeNode);
                    }
                }
            );
            
            //            
            // controls
            //
            
            BorderLayout borderLayout = new BorderLayout();
            
            //iterate panel
            JPanel iteratePanel = new JPanel();
            iteratePanel.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createTitledBorder("Document Order Traversal"),
                BorderFactory.createEmptyBorder(4, 4, 4, 4)
                ));
            
            prevButton = new JButton("Previous");
            iteratePanel.add(prevButton);
            prevButton.addActionListener(this);
            
            nextButton = new JButton("Next");
            iteratePanel.add(nextButton);
            nextButton.addActionListener(this);
        
            //walkerPanel
            JPanel walkerPanel = new JPanel();
            walkerPanel.setLayout(new BorderLayout());
            walkerPanel.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createTitledBorder("Walk"),
                BorderFactory.createEmptyBorder(4, 4, 4, 4)
                ));
            
            parentButton = new JButton("Parent");
            walkerPanel.add(parentButton, BorderLayout.NORTH);
            parentButton.addActionListener(this);
           
            
            JPanel childPanel = new JPanel();
            firstChildButton = new JButton("First Child");
            childPanel.add(firstChildButton);
            firstChildButton.addActionListener(this);
   
            lastChildButton = new JButton("Last Child");
            childPanel.add(lastChildButton);
            lastChildButton.addActionListener(this);
            walkerPanel.add(childPanel, BorderLayout.SOUTH);
            
            nextSiblingButton = new JButton("Next Sibling");
            walkerPanel.add(nextSiblingButton, BorderLayout.EAST);
            nextSiblingButton.addActionListener(this);
            
            previousSiblingButton = new JButton("Previous Sibling");
            walkerPanel.add(previousSiblingButton, BorderLayout.WEST);
            previousSiblingButton.addActionListener(this);
            
            //DOM tree panel
            JPanel domPanel = new JPanel();
            domPanel.setLayout(new BorderLayout());
            domPanel.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createTitledBorder("Selected Node"),
                BorderFactory.createEmptyBorder(4, 4, 4, 4)
                ));
            
            JPanel buttonPanel = new JPanel();
            currentButton = new JButton("Current");
            buttonPanel.add(currentButton);
            currentButton.addActionListener(this);
   
            removeButton = new JButton("Remove");
            buttonPanel.add(removeButton);
            removeButton.addActionListener(this);
            
            addButton = new JButton("Append Text");
            addText = new JTextField(10);
            buttonPanel.add(addButton);
            domPanel.add(buttonPanel, BorderLayout.NORTH);
            domPanel.add(addText, BorderLayout.CENTER);
            addButton.addActionListener(this);
         
            // treeWalker settings
            JPanel settingsPanel = new JPanel();
            settingsPanel.setLayout(new BorderLayout());
            settingsPanel.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createTitledBorder("Filter Settings"),
                BorderFactory.createEmptyBorder(4, 4, 4, 4)
                ));
            JPanel treeWalkerPanel = new JPanel();
            treeWalkerPanel.setLayout(new BorderLayout());
            
            newIterator = new JButton("createTreeWalker");
            treeWalkerPanel.add(newIterator, BorderLayout.NORTH);
            expandERs = new JCheckBox("expandEntityReferences");
            expandERs.setSelected(true);
            treeWalkerPanel.add(expandERs, BorderLayout.SOUTH);
            settingsPanel.add(treeWalkerPanel, BorderLayout.NORTH);
            newIterator.addActionListener(this);
    
            JPanel whatPanel = new JPanel();
            whatPanel.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createTitledBorder("whatToShow"),
                BorderFactory.createEmptyBorder(0, 0, 0, 0)
                ));
            whatToShow = new JList(whatArray);
            JScrollPane whatScroll = 
            new JScrollPane(whatToShow) {
                    private static final long serialVersionUID = 3545240236637305138L;
                    public Dimension getPreferredSize(){
                        return new Dimension(200, 65 );
                    }
                };
            
            whatPanel.add(whatScroll);
            
            
            JPanel filterPanel = new JPanel();
            filterPanel.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createTitledBorder("NodeNameFilter"),
                BorderFactory.createEmptyBorder(4, 4, 4, 4)
                ));
            filterPanel.setLayout(new BorderLayout());
            match = new JCheckBox("match/ignore node name", true);
            nameFilter = new JTextField(10);
            filterPanel.add(match, BorderLayout.NORTH);
            filterPanel.add(nameFilter, BorderLayout.SOUTH);
            
            settingsPanel.add(treeWalkerPanel, BorderLayout.NORTH);
            settingsPanel.add(whatPanel, BorderLayout.CENTER);
            settingsPanel.add(filterPanel, BorderLayout.SOUTH);
            

            // Listen for when the selection changes, call nodeSelected(node)
            whatToShow.addListSelectionListener(
                new ListSelectionListener() {
                    public void valueChanged(ListSelectionEvent e) {
                        // do nothing on selection...
                    }
                }
            );
            
            
            JPanel controlsPanel = new JPanel(new BorderLayout());
            controlsPanel.setFont(new Font("Dialog", Font.PLAIN, 8));
            JPanel buttonsPanel = new JPanel(new BorderLayout());
            buttonsPanel.add(iteratePanel, BorderLayout.NORTH);
            buttonsPanel.add(walkerPanel, BorderLayout.CENTER);
            buttonsPanel.add(domPanel, BorderLayout.SOUTH);
            controlsPanel.add(buttonsPanel, BorderLayout.NORTH);
            controlsPanel.add(settingsPanel, BorderLayout.CENTER);
            
            controlsPanel.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createTitledBorder("Controls"),
                BorderFactory.createEmptyBorder(4, 4, 4, 4)
                ));
                
                
            // tree panel    
            JPanel treePanel = new JPanel(new BorderLayout());
                
            JScrollPane treeScroll = new JScrollPane(jtree) ;
            treeScroll.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createTitledBorder("Tree View"),
                BorderFactory.createEmptyBorder(4, 4, 4, 4)
                ));

            // message text UI setup
            messageText = new JTextArea(3,5);

            JPanel messagePanel = new JPanel(new BorderLayout());
            messageScroll = new JScrollPane(messageText);
            messagePanel.add(messageScroll);
            messagePanel.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createTitledBorder("Messages"),
                BorderFactory.createEmptyBorder(4, 4, 4, 4)
                ));
            
            JPanel mainPanel = new JPanel();
            mainPanel.setLayout(new BorderLayout());
            mainPanel.add(controlsPanel, BorderLayout.EAST);
            mainPanel.add(treeScroll, BorderLayout.CENTER);
            mainPanel.add(messagePanel, BorderLayout.SOUTH);
            getContentPane().add(mainPanel);
            
            Hashtable errorNodes = errors.getErrorNodes();
            Enumeration elements = errorNodes.elements();
            while (elements.hasMoreElements()) {
                //*** append errors to messageText
                messageText.append( (String)elements.nextElement());
            }    
            
            boolean expand = expandERs.isSelected();
            treeWalker = ((DocumentTraversal)document).
                createTreeWalker(
                    document, 
                    NodeFilter.SHOW_ALL, 
                    new NameNodeFilter(),
                    expand);
            
        } catch (Exception e) {
            e.printStackTrace(System.err);
        }
    }
               
    public void actionPerformed(ActionEvent e) {
        
        if (e.getSource() == newIterator) {
            
            TreeNode treeNode = (TreeNode)jtree.getLastSelectedPathComponent();
            if (treeNode == null) {
                messageText.append("Must select a tree component.");
                return;
            }
            
            Node node = jtree.getNode(treeNode);
            if (node == null) {
                setMessage("No current Node in TreeNode: "+node);
            }
            
            // whatToShow section
            int  [] indices = whatToShow.getSelectedIndices();
            int mask = 0x0;
            for (int i = 0; i < indices.length; i++) {
                if (indices[i] == 0) {
                    mask = 0xFFFF;
                    break;
                } else {
                    mask = mask | 1<<indices[i]-1;
                }
            }
            
            // filter section
            String nameText = nameFilter.getText();
            boolean matched = match.isSelected();
            if (nameNodeFilter==null) {
                nameNodeFilter = new NameNodeFilter();
            }
            if (nameText.equals("")) {
                setMessage("NodeNameFilter name is \"\". Assuming null.");
                nameText = null;
            }
            nameNodeFilter.setName(nameText);
            nameNodeFilter.setMatch(matched);
            
            // expand Entity References?
            boolean expand = expandERs.isSelected();
            
            treeWalker = ((DocumentTraversal)document).
                createTreeWalker(
                    node, 
                    mask, 
                    nameNodeFilter,
                    expand);
            setMessage("createTreeWalker:"+ 
                        " root="+node+
                        ", whatToShow="+mask+
                        ", match="+matched+
                        ", name="+nameText
                        );
            return;
            
        } 
        
        if (e.getSource() == currentButton) {
            
            TreeNode treeNode = (TreeNode)jtree.getLastSelectedPathComponent();
            if (treeNode == null) {
                messageText.append("Must select a tree component.");
                return;
            }
            
            Node node = jtree.getNode(treeNode);
            if (node == null) {
                setMessage("No current Node in TreeNode: "+node);
            }
            treeWalker.setCurrentNode(node);
            return;
            
        }
        if (e.getSource() == addButton) {
            
            String text = addText.getText();
            
            if (text==null) return;
            
            TreeNode treeNode = (TreeNode)jtree.getLastSelectedPathComponent();
            if (treeNode == null) {
                messageText.append("Must select a tree component to add a child to it.");
                return;
            }
            TreePath path = new TreePath(
                    ((DefaultTreeModel)jtree.getModel()).getPathToRoot(treeNode));
            if (path == null) {
                setMessage("Could not create a path.");
                return;
            }
            if(!jtree.getSelectionModel().isPathSelected(path))
                return;
            Node node = jtree.getNode(treeNode);
            Node textNode = document.createTextNode(text);
            try {
                node.appendChild(textNode);
            } catch (DOMException dome) {
                setMessage("DOMException:"+dome.code+", "+dome);
                return;
            }
            ((DOMTreeFull.Model)jtree.getModel()).insertNode(textNode, (MutableTreeNode)treeNode);
            
            return;
        }
        
        if (e.getSource() == removeButton) {

            /** If the node is not selected don't remove. */
            TreeNode treeNode = (TreeNode)jtree.getLastSelectedPathComponent();
            if (treeNode == null) {
                messageText.append("Must select a tree component to remove it.");
                return;
            }
            TreePath path = new TreePath(
                    ((DefaultTreeModel)jtree.getModel()).getPathToRoot(treeNode));
            if (path == null) {
                setMessage("Could not create a path.");
                return;
            }
            if(!jtree.getSelectionModel().isPathSelected(path))
                return;
            Node node = jtree.getNode(treeNode);
            if (node == null) return;
            Node parent = node.getParentNode();
            if (parent == null) return;

            parent.removeChild(node);
    
            ((DefaultTreeModel)jtree.getModel()).removeNodeFromParent((MutableTreeNode)treeNode);
            return;
        } 
        
        if (e.getSource() == previousSiblingButton) {          
            Node node = treeWalker.previousSibling();
            handleButton(node, "previousSibling()");
            return;
        } 
        
        if (e.getSource() == firstChildButton) {          
            Node node = treeWalker.firstChild();
            handleButton(node, "firstChild()");
            return;
        } 
        
        if (e.getSource() == lastChildButton) {          
            Node node = treeWalker.lastChild();
            handleButton(node, "lastChild()");
            return;
        } 
        
        if (e.getSource() == nextSiblingButton) {          
            Node node = treeWalker.nextSibling();
            handleButton(node, "nextSibling()");
            return;
        } 
        
        if (e.getSource() == parentButton) {          
            Node node = treeWalker.parentNode();
            handleButton(node, "parentNode()");
            return;
        } 
        
        if (e.getSource() == nextButton) {          
            Node node = treeWalker.nextNode();
            handleButton(node, "nextNode()");
            return;
        } 
        
        if (e.getSource() == prevButton) {          
            Node node = treeWalker.previousNode();
            handleButton(node, "previousNode()");
            return;
        }
        
    }
    
    /** handle a button press: output messages and select node. */
    void handleButton( Node node, String function) {
        
        setMessage("treeWalker."+function+" == "+node);
   
        if (node==null) return;

        TreeNode treeNode = jtree.getTreeNode(node);
        if (treeNode == null) {
            setMessage("No JTree TreeNode for Node name:" + node.getNodeName());
            return;
        }
            
        TreePath path = new TreePath(
                ((DefaultTreeModel)jtree.getModel()).getPathToRoot(treeNode));
        jtree.requestFocus();
        jtree.setSelectionPath(path);
        jtree.scrollPathToVisible(path);
    }
    
    /** Helper function to set messages */
    void setMessage(String string) {
        messageText.selectAll();
        messageText.cut();
        messageText.append(string);
        messageText.setCaretPosition(0);
    }
        
    /** called when our JTree's nodes are selected.
     */
    void nodeSelected(TreeNode treeNode) {

        lastSelected = treeNode;
        Node node = jtree.getNode(treeNode);
        
        if (node == null) return;
                            
        setMessage(DOMTreeFull.toString(node));
    }
    
    /** Utility function to expand the jtree */
    void expandTree() {
        for (int i = 0; i < jtree.getRowCount(); i++) {
            jtree.expandRow(i);
        }
    }
    
    class Errors implements ErrorHandler {

        Hashtable errorNodes = new Hashtable();

        public void warning(SAXParseException ex) {
            store(ex, "[Warning]");
        }

        public void error(SAXParseException ex) {
            store(ex, "[Error]");
        }

        public void fatalError(SAXParseException ex) throws SAXException {
            store(ex, "[Fatal Error]");
        }

        public Hashtable getErrorNodes() {
            return errorNodes;
        }

        public Object getError(Node node) {
            return errorNodes.get(node);
        }

        public void clearErrors() {
            errorNodes.clear();
        }

        void store(SAXParseException ex, String type) {

            // build error text
            String errorString= type+" at line number, "+ex.getLineNumber()
                +": "+ex.getMessage()+"\n";

            Node currentNode = null;
            try {
                currentNode = (Node)parser.getProperty("http://apache.org/xml/properties/dom-node");
            } catch (SAXException se) {
                System.err.println(se.getMessage());
                return;
            }
            if (currentNode == null) return;

            // accumulate any multiple errors per node in the Hashtable.
            String previous = (String) errorNodes.get(currentNode);
            if (previous != null)
                errorNodes.put(currentNode, previous +errorString);
            else
                errorNodes.put(currentNode, errorString);
        }
    }
    
}
