/*
 * XMLFieldMappingEditor.java
 *
 * Created on May 15, 2007, 12:07 AM
 */

package it.businesslogic.ireport.data.xml;

import it.businesslogic.ireport.FieldsProviderEditor;
import it.businesslogic.ireport.IReportConnection;
import it.businesslogic.ireport.JRField;
import it.businesslogic.ireport.connection.JRXMLDataSourceConnection;
import it.businesslogic.ireport.gui.MainFrame;
import it.businesslogic.ireport.gui.ReportQueryDialog;
import it.businesslogic.ireport.util.I18n;
import it.businesslogic.ireport.util.LanguageChangedEvent;
import it.businesslogic.ireport.util.LanguageChangedListener;
import it.businesslogic.ireport.util.Misc;
import java.awt.Point;
import java.util.Enumeration;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.xml.transform.TransformerException;
import net.sf.jasperreports.engine.util.JRXmlUtils;
import org.apache.xpath.CachedXPathAPI;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 *
 * @author  gtoffoli
 */
public class XMLFieldMappingEditor extends javax.swing.JPanel  implements FieldsProviderEditor {
    
    private String xpathExpression = null;
    private Document document = null;
    private CachedXPathAPI xpathAPI = new CachedXPathAPI();
    private ReportQueryDialog reportQueryDialog = null;

    public ReportQueryDialog getReportQueryDialog() {
        return reportQueryDialog;
    }

    public void setReportQueryDialog(ReportQueryDialog reportQueryDialog) {
        this.reportQueryDialog = reportQueryDialog;
    }
    private List recordNodes = new java.util.ArrayList();

    public List getRecordNodes() {
        return recordNodes;
    }

    public void setRecordNodes(List recordNodes) {
        this.recordNodes = recordNodes;
    }

    public CachedXPathAPI getXpathAPI() {
        return xpathAPI;
    }

    public void setXpathAPI(CachedXPathAPI xpathAPI) {
        this.xpathAPI = xpathAPI;
    }

    public Document getDocument() {
        return document;
    }

    public void setDocument(Document document) {
        this.document = document;
    }

    public String getXpathExpression() {
        return xpathExpression;
    }

    public void setXpathExpression(String xpathExpression) {
        this.xpathExpression = xpathExpression;
    }


    
    
    /** Creates new form XMLFieldMappingEditor 
     * @param rqd 
     */
    public XMLFieldMappingEditor(ReportQueryDialog rqd) {
        initComponents();
        this.reportQueryDialog = rqd;
        jTree1.setCellRenderer(new XMLDocumentTreeCellRenderer(this));
        jTree1.setTransferHandler( new XMLTreeTransfertHandler( this ));
        setXpathExpression( rqd.getSQLExpressionArea().getText() );
        updateView();
        
        I18n.addOnLanguageChangedListener(new LanguageChangedListener() {
            public void languageChanged(LanguageChangedEvent evt) {
                applyI18n();
            }
        } );
        
        applyI18n();
    }
    
    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
    private void initComponents() {
        java.awt.GridBagConstraints gridBagConstraints;

        jPopupMenuFields = new javax.swing.JPopupMenu();
        jMenuItemSetRecordNode = new javax.swing.JMenuItem();
        jMenuItemChangeRoot = new javax.swing.JMenuItem();
        jSeparator2 = new javax.swing.JSeparator();
        jMenuItemAddField = new javax.swing.JMenuItem();
        jMenuItemAddAbsoluteField = new javax.swing.JMenuItem();
        jSeparator1 = new javax.swing.JSeparator();
        jMenuItemExpandAll = new javax.swing.JMenuItem();
        jMenuItemCollapseAll = new javax.swing.JMenuItem();
        jMenuItemRefresh = new javax.swing.JMenuItem();
        jScrollPane1 = new javax.swing.JScrollPane();
        jTree1 = new javax.swing.JTree();
        jPanel1 = new javax.swing.JPanel();
        jLabelHelp = new javax.swing.JLabel();

        jPopupMenuFields.addPopupMenuListener(new javax.swing.event.PopupMenuListener() {
            public void popupMenuCanceled(javax.swing.event.PopupMenuEvent evt) {
            }
            public void popupMenuWillBecomeInvisible(javax.swing.event.PopupMenuEvent evt) {
            }
            public void popupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent evt) {
                jPopupMenuFieldsPopupMenuWillBecomeVisible(evt);
            }
        });

        jMenuItemSetRecordNode.setText("Set record node (generate xPath)");
        jMenuItemSetRecordNode.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuItemSetRecordNodeActionPerformed(evt);
            }
        });
        jPopupMenuFields.add(jMenuItemSetRecordNode);

        jMenuItemChangeRoot.setText("Set document root");
        jMenuItemChangeRoot.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuItemChangeRootActionPerformed(evt);
            }
        });
        jPopupMenuFields.add(jMenuItemChangeRoot);
        jPopupMenuFields.add(jSeparator2);

        jMenuItemAddField.setText("Add node as field");
        jMenuItemAddField.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuItemAddFieldActionPerformed(evt);
            }
        });
        jPopupMenuFields.add(jMenuItemAddField);

        jMenuItemAddAbsoluteField.setText("Add node as field (using absolute path)");
        jMenuItemAddAbsoluteField.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuItemAddAbsoluteFieldActionPerformed(evt);
            }
        });
        jPopupMenuFields.add(jMenuItemAddAbsoluteField);
        jPopupMenuFields.add(jSeparator1);

        jMenuItemExpandAll.setText("Expand all");
        jMenuItemExpandAll.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuItemExpandAllActionPerformed(evt);
            }
        });
        jPopupMenuFields.add(jMenuItemExpandAll);

        jMenuItemCollapseAll.setText("Collapse all");
        jMenuItemCollapseAll.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuItemCollapseAllActionPerformed(evt);
            }
        });
        jPopupMenuFields.add(jMenuItemCollapseAll);

        jMenuItemRefresh.setText("Reset / Refresh document");
        jMenuItemRefresh.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuItemRefreshActionPerformed(evt);
            }
        });
        jPopupMenuFields.add(jMenuItemRefresh);

        setMinimumSize(new java.awt.Dimension(250, 23));
        setPreferredSize(new java.awt.Dimension(280, 322));
        setLayout(new java.awt.BorderLayout());

        jScrollPane1.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                jScrollPane1MouseClicked(evt);
            }
        });

        jTree1.setDragEnabled(true);
        jTree1.setMinimumSize(new java.awt.Dimension(73, 64));
        jTree1.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                jTree1MouseClicked(evt);
            }
            public void mousePressed(java.awt.event.MouseEvent evt) {
                jTree1MousePressed(evt);
            }
        });
        jScrollPane1.setViewportView(jTree1);

        add(jScrollPane1, java.awt.BorderLayout.CENTER);

        jPanel1.setBackground(new java.awt.Color(255, 255, 204));
        jPanel1.setLayout(new java.awt.GridBagLayout());

        jLabelHelp.setBackground(new java.awt.Color(255, 255, 204));
        jLabelHelp.setIcon(new javax.swing.ImageIcon(getClass().getResource("/it/businesslogic/ireport/icons/log/question.png"))); // NOI18N
        jLabelHelp.setText("<html>Drag a node into the fields table to map a new field");
        jLabelHelp.setVerticalTextPosition(javax.swing.SwingConstants.TOP);
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
        jPanel1.add(jLabelHelp, gridBagConstraints);

        add(jPanel1, java.awt.BorderLayout.NORTH);
    }// </editor-fold>//GEN-END:initComponents

    private void jMenuItemCollapseAllActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemCollapseAllActionPerformed
    expandAll(jTree1, false);
}//GEN-LAST:event_jMenuItemCollapseAllActionPerformed

private void jMenuItemExpandAllActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemExpandAllActionPerformed
    expandAll(jTree1, true);
}//GEN-LAST:event_jMenuItemExpandAllActionPerformed

public void expandAll(JTree tree, boolean expand) {
        TreeNode root = (TreeNode)tree.getModel().getRoot();
        // Traverse tree from root
        expandAll(tree, new TreePath(root), expand);
    }

private void expandAll(JTree tree, TreePath parent, boolean expand) {
    // Traverse children
    TreeNode node = (TreeNode)parent.getLastPathComponent();
    if (node.getChildCount() >= 0) {
        for (Enumeration e=node.children(); e.hasMoreElements(); ) {
            TreeNode n = (TreeNode)e.nextElement();
            TreePath path = parent.pathByAddingChild(n);
            expandAll(tree, path, expand);
        }
    }
    // Expansion or collapse must be done bottom-up
    if (expand) {
        tree.expandPath(parent);
    } else {
        tree.collapsePath(parent);
    }
}



private void jMenuItemAddAbsoluteFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemAddAbsoluteFieldActionPerformed
    TreePath tp = jTree1.getSelectionPath();
    
    if (tp == null) return;
    
    JRField field = createField(tp, false);
    if (field != null)
    {
        getReportQueryDialog().addField(field);
    }
}//GEN-LAST:event_jMenuItemAddAbsoluteFieldActionPerformed

private void jMenuItemAddFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemAddFieldActionPerformed
    TreePath tp = jTree1.getSelectionPath();
    
    if (tp == null) return;
    
    JRField field = createField(tp, true);
    if (field != null)
    {
        getReportQueryDialog().addField(field);
    }
}//GEN-LAST:event_jMenuItemAddFieldActionPerformed

private void jMenuItemChangeRootActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemChangeRootActionPerformed
    
    TreePath tp = jTree1.getSelectionPath();
    
    if (tp == null) return;
    
    Node newRoot = (Node)((DefaultMutableTreeNode)tp.getLastPathComponent()).getUserObject();
    
    jTree1.setModel(new DefaultTreeModel( this.addTreeDocument(newRoot)  ));
    
}//GEN-LAST:event_jMenuItemChangeRootActionPerformed

private void jTree1MouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_jTree1MouseClicked
    if (SwingUtilities.isRightMouseButton(evt) && evt.getClickCount() == 1)
    {
        Misc.ensurePathIsSelected(jTree1.getPathForLocation(evt.getX(), evt.getY() ), jTree1);
        jPopupMenuFields.show(jTree1, evt.getX(), evt.getY());
     
    }
}//GEN-LAST:event_jTree1MouseClicked

private void jScrollPane1MouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_jScrollPane1MouseClicked
    /*
    evt.setSource( jTree1 );
    Point p2 = SwingUtilities.convertPoint(jScrollPane1, evt.getPoint(), jTree1);
    evt.translatePoint(p2.x - evt.getX() , p2.y - evt.getY());
    jTree1MouseClicked(evt);
    */
}//GEN-LAST:event_jScrollPane1MouseClicked

private void jMenuItemSetRecordNodeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemSetRecordNodeActionPerformed
    
    TreePath tp = jTree1.getSelectionPath();
    
    if (tp == null) return;
    
    String expression = generateXpathExpression(tp, false);
    
    if (reportQueryDialog != null)
    {
        reportQueryDialog.getSQLExpressionArea().setText(expression);
    }
    
}//GEN-LAST:event_jMenuItemSetRecordNodeActionPerformed

private void jTree1MousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_jTree1MousePressed
    
}//GEN-LAST:event_jTree1MousePressed

private void jMenuItemRefreshActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItemRefreshActionPerformed
    updateView();
}//GEN-LAST:event_jMenuItemRefreshActionPerformed

private void jPopupMenuFieldsPopupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent evt) {//GEN-FIRST:event_jPopupMenuFieldsPopupMenuWillBecomeVisible
   
    TreePath tp = jTree1.getSelectionPath();
    
    jMenuItemSetRecordNode.setEnabled(false);
    jMenuItemChangeRoot.setEnabled(false);
    jMenuItemAddField.setEnabled(false);
    jMenuItemAddAbsoluteField.setEnabled(false);
    
    if (tp == null) 
    {   
        return;
    }
    
    if (((((DefaultMutableTreeNode)tp.getLastPathComponent()).getUserObject()) instanceof Node) )
    {
        Node node = (Node)((DefaultMutableTreeNode)tp.getLastPathComponent()).getUserObject();
        if (node.getNodeType() == Node.ELEMENT_NODE)
        {
            jMenuItemSetRecordNode.setEnabled(true);
            jMenuItemChangeRoot.setEnabled(true);
        } 
        
        jMenuItemAddField.setEnabled(true);
        jMenuItemAddAbsoluteField.setEnabled(true);
    }
    
}//GEN-LAST:event_jPopupMenuFieldsPopupMenuWillBecomeVisible
    
    
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JLabel jLabelHelp;
    private javax.swing.JMenuItem jMenuItemAddAbsoluteField;
    private javax.swing.JMenuItem jMenuItemAddField;
    private javax.swing.JMenuItem jMenuItemChangeRoot;
    private javax.swing.JMenuItem jMenuItemCollapseAll;
    private javax.swing.JMenuItem jMenuItemExpandAll;
    private javax.swing.JMenuItem jMenuItemRefresh;
    private javax.swing.JMenuItem jMenuItemSetRecordNode;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JPopupMenu jPopupMenuFields;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JSeparator jSeparator1;
    private javax.swing.JSeparator jSeparator2;
    private javax.swing.JTree jTree1;
    // End of variables declaration//GEN-END:variables
    
    
    public void updateView()
    {
        IReportConnection conn = (IReportConnection)MainFrame.getMainInstance().getProperties().get("DefaultConnection");
        
        String file = "";
        if (conn instanceof JRXMLDataSourceConnection)
        {
            file = ((JRXMLDataSourceConnection)conn).getFilename();
            
            try {
                document = JRXmlUtils.parse(file );
            } catch (Exception ex)
            {
                ex.printStackTrace();
            }
            
            DefaultMutableTreeNode root = addTreeDocument(document.getDocumentElement());
            jTree1.setModel(new DefaultTreeModel(root));
            
        }
        else
        {
            jTree1.setModel(new DefaultTreeModel(new DefaultMutableTreeNode("No file found")));
        }
        
        updateXpathView();
        jTree1.updateUI();
    }
    
    public DefaultMutableTreeNode addTreeDocument(Node node)
    {
        
        if (node.getNodeType() != Node.ELEMENT_NODE &&
            node.getNodeType() != Node.ATTRIBUTE_NODE) return null;
        DefaultMutableTreeNode tnode = new DefaultMutableTreeNode(node);
        
        NamedNodeMap nm = node.getAttributes();
        for (int i=0; nm != null && i<nm.getLength(); ++i)
        {
            Node childNode = nm.item(i);
            DefaultMutableTreeNode cnode = addTreeDocument(childNode);
            if (cnode != null)
            {
                tnode.add(cnode);
            }
        }
        
        NodeList nl = node.getChildNodes();
        
        for (int i=0; nl != null && i<nl.getLength(); ++i)
        {
            Node childNode = nl.item(i);
            DefaultMutableTreeNode cnode = addTreeDocument(childNode);
            if (cnode != null)
            {
                tnode.add(cnode);
            }
        }
        
        return tnode;
    }
    
    /**
     * Ad hoc queryChanged method for EJBQL queries....
     * @param newQuery 
     */
    public void queryChanged(String newQuery) {
        
        setXpathExpression(newQuery);
        
         try {
                
                Runnable r = new Runnable() {
                        public void run() {
                            updateXpathView();
                        }
                    };
                    
                if (!SwingUtilities.isEventDispatchThread())
                {
                    SwingUtilities.invokeAndWait( r );
                }
                else
                {
                        r.run();
                }
                    
            } catch(Exception e) {
                // oh well we got interrupted.
            }
        
    }
    
    public void updateXpathView()
    {
        if (document == null || getXpathExpression() == null || getXpathExpression().trim().length() == 0) return;
        try {
            
            NodeList nodeList = xpathAPI.selectNodeList(document, getXpathExpression());
            
            getRecordNodes().clear();
            for (int i=0; nodeList != null && i<nodeList.getLength(); ++i)
            {
                getRecordNodes().add( nodeList.item(i));
            }
            
            if (reportQueryDialog != null)
            {
                reportQueryDialog.getJLabelStatusSQL().setText("Selected nodes: " + nodeList.getLength());
            }
            
            jTree1.updateUI();
            
        } catch (Exception ex)
        {
            if (reportQueryDialog != null)
            {
                reportQueryDialog.getJLabelStatusSQL().setText("Invalid XPath expression: " + ex.getMessage() + " (" + getXpathExpression() + ")");
                ex.printStackTrace();
            }
        }
    }
    
    /**
     *  Generates an xpath expression to arrive to that node from the root.
     *  The root considered is the tree root, that can be different from the XML Document root.
     * 
     *  @param tp The TreePath
     *  @param relative if the root is on the path, the xpath expression stops here....
     * @return the xpath representation of the path (like /root/node1/node2)
     */
    public String generateXpathExpression(TreePath tp, boolean relative)
    {
        String xpath = "";
        boolean rootFound = false;
        Object[] nodes = (Object[])((DefaultMutableTreeNode)tp.getLastPathComponent()).getUserObjectPath();
        
        if (relative)
        {
            // Look for a record root on the path to the root...
            for (int i=nodes.length-1; i>=0; --i)
            {
                Node node = (Node)nodes[i];
                if (getRecordNodes().contains(node))
                {
                    rootFound = true;
                    break;
                }
            }
            if (!rootFound)
            {
                String postfix = "";
                Node node = (Node)nodes[nodes.length-1];
                if (node.getNodeType() == Node.ATTRIBUTE_NODE)
                {
                    postfix = "/@" + node.getNodeName();
                    node = (Node)nodes[nodes.length-2];
                }
                
                // look if there is 
                String exp = "ancestor::" + node.getNodeName();
                for (int i=0; i<getRecordNodes().size(); ++i)
                {
                    try {
                        org.w3c.dom.Node currentNode = (org.w3c.dom.Node) getRecordNodes().get(i);
                        org.w3c.dom.Node foundNode = xpathAPI.selectSingleNode(currentNode, exp);
                        if (foundNode == node) {
                            return exp + postfix;
                        }
                    } catch (TransformerException ex) {
                        //ex.printStackTrace();
                    }
                }
            }
        }
        
        
        rootFound = false;
        for (int i=nodes.length-1; i>=0; --i)
        {
            Node node = (Node)nodes[i];
            if (relative)
            {
                if (getRecordNodes().contains(node))
                {
                    if (xpath.length() == 0)
                    {
                        xpath = "child::text()";
                    }
                    rootFound = true;
                    break;
                }
            }
            
            if (xpath.length() > 0) xpath = "/" + xpath;
                    
            if (node.getNodeType() == Node.ELEMENT_NODE)
            {
                xpath = node.getNodeName() + xpath;
            }
            else
            {
                xpath = "@" + node.getNodeName();
            }
        }
        
        if ((!rootFound || !relative) && !xpath.startsWith("/"))
        {
            xpath = "/" + xpath;
        }
        
        return xpath;
    }
    
    
    public JRField createField(TreePath path, boolean mode)
    {
        JRField jrField = null;
	DefaultMutableTreeNode dmtn = (DefaultMutableTreeNode)path.getLastPathComponent();
        if (dmtn.getUserObject() != null && dmtn.getUserObject() instanceof Node)
        {
            Node node = (Node)dmtn.getUserObject();
            jrField = new JRField( node.getNodeName(), "java.lang.String" );
            jrField.setDescription(  generateXpathExpression( path, mode ));
            boolean found = true;
            String name = node.getNodeName();
            
            for (int i=2; found; ++i)
            {
                found = false;
                DefaultTableModel dtm = (DefaultTableModel)getReportQueryDialog().getFieldsTable().getModel();
                for (int k=0; k<dtm.getRowCount(); ++k)
                {
                    JRField tmpField = (JRField)dtm.getValueAt(k, 0);
                    if (tmpField.getName().equals(name))
                    {
                        found = true;
                        break;
                    }
                }
                
                if (found)
                {
                    name = node.getNodeName() + i;
                }
            }
            
            jrField.setName(name);
        }
        
        return jrField;
    }
    
    public void applyI18n()
    {
        jMenuItemSetRecordNode.setText(I18n.getString("XMLFieldMappingEditor.menuItemSetRecordNode","Set record node (generate xPath)"));
        jMenuItemChangeRoot.setText(I18n.getString("XMLFieldMappingEditor.menuItemChangeRoot","Set document root"));
        jMenuItemAddField.setText(I18n.getString("XMLFieldMappingEditor.menuItemAddField","Add node as field"));
        jMenuItemAddAbsoluteField.setText(I18n.getString("XMLFieldMappingEditor.menuItemAddAbsoluteField","Add node as field (using absolute path)"));
        jMenuItemRefresh.setText(I18n.getString("XMLFieldMappingEditor.menuItemRefresh","Reset / Refresh document"));
        jLabelHelp.setText("<html>" + I18n.getString("XMLFieldMappingEditor.labelHelp","Drag a node into the fields table to map a new field."));
        
        jMenuItemExpandAll.setText(I18n.getString("XMLFieldMappingEditor.menuItemExpandAll","Expand All"));
        jMenuItemCollapseAll.setText(I18n.getString("XMLFieldMappingEditor.menuItemCollapseAll","Collapse All"));
        
    }
}
