/*
 * iReport - Visual Designer for JasperReports.
 * Copyright (C) 2002 - 2009 Jaspersoft Corporation. All rights reserved.
 * http://www.jaspersoft.com
 *
 * Unless you have purchased a commercial license agreement from Jaspersoft,
 * the following license terms apply:
 *
 * This program is part of iReport.
 *
 * iReport is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * iReport 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with iReport. If not, see <http://www.gnu.org/licenses/>.
 */
package com.jaspersoft.ireport.designer.data.fieldsproviders;

import com.jaspersoft.ireport.locale.I18n;
import com.jaspersoft.ireport.designer.data.*;
import com.jaspersoft.ireport.designer.FieldsProviderEditor;
import com.jaspersoft.ireport.designer.IReportManager;
import com.jaspersoft.ireport.designer.data.fieldsproviders.hibernate.HQLFieldsReader;
import com.jaspersoft.ireport.designer.utils.Misc;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import net.sf.jasperreports.engine.design.JRDesignField;
import org.apache.commons.beanutils.MethodUtils;

/**
 *
 * @author  gtoffoli
 */
public class BeanInspectorPanel extends javax.swing.JPanel implements FieldsProviderEditor {
    
    private boolean pathOnDescription = false;
    private JTable jTableFields = null; 
    private boolean comboVisible = true;
    private ReportQueryDialog reportQueryDialog = null;
    
    
    /** Creates new form BeanInspectorPanel */
    public BeanInspectorPanel() {
        initComponents();
        
        DefaultTreeModel dttm = (DefaultTreeModel)jTree1.getModel();
        DefaultMutableTreeNode root = new DefaultMutableTreeNode();
        jTree1.setModel(new DefaultTreeModel( root ));
        
        jTree1.setCellRenderer( new JBTreeCellRenderer());
        
        //applyI18n();
    }
    
    
    public void setClassNames(List classNames)
    {
        if (classNames == null) return;
        
        jComboBox1.removeAllItems();
        for (int i=0; i<classNames.size(); ++i)
        {
            jComboBox1.addItem( classNames.get(i));
        }
        
        jComboBox1ActionPerformed(null);
    }
    
    /**
     * Must be used when the combobox is not visible...
     */
    public void setClassName(String className)
    {
        DefaultMutableTreeNode root = (DefaultMutableTreeNode)jTree1.getModel().getRoot();
        root.removeAllChildren();
        jTree1.updateUI();
        if (className != null && className.trim().length() > 0)
        {
            exploreBean( root,className, "");
        }
    }
    
    /** 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;

        jPanel1 = new javax.swing.JPanel();
        jLabel1 = new javax.swing.JLabel();
        jComboBox1 = new javax.swing.JComboBox();
        jScrollPane1 = new javax.swing.JScrollPane();
        jTree1 = new javax.swing.JTree();
        jPanel2 = new javax.swing.JPanel();
        jButton2 = new javax.swing.JButton();
        jButton3 = new javax.swing.JButton();

        setLayout(new java.awt.GridBagLayout());

        jPanel1.setLayout(new java.awt.GridBagLayout());

        jLabel1.setText(I18n.getString("BeanInspectorPanel.Label.Bean")); // NOI18N
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
        jPanel1.add(jLabel1, gridBagConstraints);

        jComboBox1.setMinimumSize(new java.awt.Dimension(51, 22));
        jComboBox1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jComboBox1ActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(4, 0, 4, 0);
        jPanel1.add(jComboBox1, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.weightx = 1.0;
        add(jPanel1, gridBagConstraints);

        jTree1.setRootVisible(false);
        jTree1.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                jTree1MouseClicked1(evt);
            }
        });
        jScrollPane1.setViewportView(jTree1);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridwidth = 3;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(0, 4, 0, 0);
        add(jScrollPane1, gridBagConstraints);

        jPanel2.setLayout(new java.awt.GridBagLayout());

        jButton2.setText(I18n.getString("BeanInspectorPanel.Button.AddFields")); // NOI18N
        jButton2.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton2ActionPerformed(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(2, 0, 2, 4);
        jPanel2.add(jButton2, gridBagConstraints);

        jButton3.setText(I18n.getString("BeanInspectorPanel.Button.ClearFields")); // NOI18N
        jButton3.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton2ActionPerformed1(evt);
            }
        });
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST;
        gridBagConstraints.insets = new java.awt.Insets(2, 0, 2, 0);
        jPanel2.add(jButton3, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(0, 4, 0, 0);
        add(jPanel2, gridBagConstraints);
    }// </editor-fold>//GEN-END:initComponents

    private void jButton2ActionPerformed1(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton2ActionPerformed1

        DefaultTableModel dtm = (DefaultTableModel)getJTableFields().getModel();
        dtm.setRowCount(0);
        
    }//GEN-LAST:event_jButton2ActionPerformed1

    @SuppressWarnings("unchecked")
    private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButton2ActionPerformed

        DefaultTableModel dtm = (DefaultTableModel)getJTableFields().getModel();
         
        TreePath[] paths = jTree1.getSelectionPaths();
        if (paths == null) return;
        for (int i=0; i<paths.length; ++i)
        {
            boolean valid = true;
            TreePath tp = paths[i];

            TreeJRField tjrf =  (TreeJRField)((DefaultMutableTreeNode)tp.getLastPathComponent()).getUserObject();
            String returnType =  Misc.getJRFieldType( tjrf.getObj().getName() );
            
            JRDesignField field = new JRDesignField();
            field.setName(tjrf.getField().getName());
            field.setValueClassName(returnType);
            
            field.setDescription(tjrf.getField().getDescription() );
            Vector row = new Vector();
            row.addElement(field);
            row.addElement(field.getValueClassName());
            row.addElement(field.getDescription());
            
            if (isComboVisible() && jComboBox1.getSelectedItem() instanceof FieldClassWrapper)
            {
                FieldClassWrapper cw = (FieldClassWrapper)jComboBox1.getSelectedItem();
                field.setName( cw.getFieldName() + "." + field.getDescription()   );
                field.setDescription( field.getName() );
            }

            // Check for duplicates fields...
            boolean found = fieldAlreadyExists(field);
            String baseName = field.getName();
            for (int j=1; isPathOnDescription() && found; ++j)
            {
                    field.setName(  baseName + "_" + j);
                    found = fieldAlreadyExists(field);
            }
            
            if (!found)
            {
                dtm.addRow(row);
                getJTableFields().getSelectionModel().addSelectionInterval( getJTableFields().getRowCount()-1, getJTableFields().getRowCount()-1 );
            }
        }
    }//GEN-LAST:event_jButton2ActionPerformed

    private void jComboBox1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jComboBox1ActionPerformed

        Object obj = jComboBox1.getSelectedItem();
        if (obj == null) 
        {
            setClassName(null);
            return;
        }
        if (obj instanceof String) setClassName( (String)obj );
        if (obj instanceof FieldClassWrapper) setClassName( ((FieldClassWrapper)obj).getClassType());
            
    }//GEN-LAST:event_jComboBox1ActionPerformed

    
    private boolean fieldAlreadyExists(JRDesignField field)
    {
        boolean found = false;
            for (int j=0; j<getJTableFields().getRowCount(); ++j)
            {
               Object ff = getJTableFields().getValueAt(j, 0);
               if ( ff instanceof JRDesignField )
               {
                   if ( ((JRDesignField)ff).getName().equals(field.getName()))
                   {
                       found = true;
                       break;
                   }
               }
            }
        
        return found;
    }
    
    private void jTree1MouseClicked1(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_jTree1MouseClicked1

        
         if (evt.getClickCount() == 2 && evt.getButton() == MouseEvent.BUTTON1)
        {
            DefaultMutableTreeNode tn = (DefaultMutableTreeNode)jTree1.getSelectionPath().getLastPathComponent();
            
            if (tn.getChildCount()>0) return;
            
                /*if (!jTree1.isCollapsed( jTree1.getSelectionPath() ))
            {
                jTree1.collapsePath( jTree1.getSelectionPath() );
                return;
            }
                 *
                 */
            if (tn.getUserObject() instanceof TreeJRField)
            {
                TreeJRField jrf = (TreeJRField)tn.getUserObject();
                if (!jrf.getObj().isPrimitive() && !jrf.getObj().getName().startsWith("java.lang."))
                {
                   exploreBean(tn, jrf.getObj().getName(), 
                           isPathOnDescription() ?
                            Misc.nvl(  jrf.getField().getDescription() , "") :
                            Misc.nvl(  jrf.getField().getName() , "")   );
                }
            }
        }       
         
    }//GEN-LAST:event_jTree1MouseClicked1
    
    
    public void exploreBean(DefaultMutableTreeNode root, String classname, String parentPath)
    {
        try {
            
            root.removeAllChildren();
            if (parentPath.length() > 0) parentPath += ".";
            
            Class clazz = Class.forName(classname,true,IReportManager.getInstance().getReportClassLoader() );
            
            java.beans.PropertyDescriptor[] pd = org.apache.commons.beanutils.PropertyUtils.getPropertyDescriptors(clazz);
            for (int nd =0; nd < pd.length; ++nd)
            {
                   String fieldName = pd[nd].getName();
                   if (pd[nd].getPropertyType() != null && pd[nd].getReadMethod() != null)
                   {
                        Class clazzRT = pd[nd].getPropertyType();

                        if ( clazzRT.isPrimitive() ) {
                          clazzRT = MethodUtils.getPrimitiveWrapper(clazzRT);
                        }

                        String returnType = clazzRT.getName();


                       JRDesignField field = new JRDesignField();
                       field.setName(fieldName);
                       field.setValueClassName(returnType);

                       if (isPathOnDescription())
                       {
                            field.setDescription(parentPath + fieldName);
                       }
                       else
                       {
                           field.setName(parentPath + fieldName);
                       }
                       
                       TreeJRField jtf = new TreeJRField();
                   
                       jtf.setField( field );
                       jtf.setObj( pd[nd].getPropertyType() );
                   
                       boolean bChildrens = true;
                       if (pd[nd].getPropertyType().isPrimitive() || pd[nd].getPropertyType().getName().startsWith("java.lang."))
                       {
                           bChildrens = false;
                       }
                       root.add(new DefaultMutableTreeNode(jtf, bChildrens));
                   }
            }
            
            jTree1.expandPath( new TreePath(root.getPath()) );
            jTree1.updateUI();
            
        } catch (ClassNotFoundException cnf)
        {
            javax.swing.JOptionPane.showMessageDialog(this, 
                    Misc.formatString( //"messages.BeanInspectorPanel.classNotFoundError",
                                I18n.getString("BeanInspectorPanel.Message.Error"), new Object[]{cnf.getMessage()}),
                                I18n.getString("BeanInspectorPanel.Message.Error2"),javax.swing.JOptionPane.ERROR_MESSAGE);
            return;
        } catch (Exception ex)
        {
            ex.printStackTrace();
            javax.swing.JOptionPane.showMessageDialog(this, ex.getMessage() ,
                         I18n.getString("BeanInspectorPanel.Message.Error2"),javax.swing.JOptionPane.ERROR_MESSAGE);
            return;
        }
    }

    public JTable getJTableFields() {
        return jTableFields;
    }

    public void setJTableFields(JTable jTableFields) {
        this.jTableFields = jTableFields;
    }

    public boolean isComboVisible() {
        return comboVisible;
    }

    public void setComboVisible(boolean comboVisible) {
        this.comboVisible = comboVisible;
        jPanel1.setVisible( comboVisible );
    }

    public boolean isPathOnDescription() {
        return pathOnDescription;
    }

    public void setPathOnDescription(boolean pathOnDescription) {
        this.pathOnDescription = pathOnDescription;
    }
    
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton jButton2;
    private javax.swing.JButton jButton3;
    private javax.swing.JComboBox jComboBox1;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JPanel jPanel2;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JTree jTree1;
    // End of variables declaration//GEN-END:variables
    
    
    
    /*
    public void applyI18n(){
                // Start autogenerated code ----------------------
                jButton2.setText(I18n.getString("beanInspectorPanel.button2","Add selected field(s)"));
                jButton3.setText(I18n.getString("beanInspectorPanel.button3","Clear fields list"));
                jLabel1.setText(I18n.getString("beanInspectorPanel.label1","Bean"));
                // End autogenerated code ----------------------
    }
    */

    int lastExecution = 0;
    
    @SuppressWarnings("unchecked")
    public void queryChanged(String newQuery) {
    
        lastExecution++;
        int thisExecution = lastExecution;
        // Execute a thread to perform the query change...
        
        String error_msg = "";
        lastExecution++;
            
        int in = lastExecution;
        
        if (getReportQueryDialog() == null) return;
            
        getReportQueryDialog().getJLabelStatusSQL().setText(I18n.getString("BeanInspectorPanel.Label.StatusSQL"));
        /////////////////////////////
            
        try {
        Thread.currentThread().setContextClassLoader( IReportManager.getInstance().getReportClassLoader());
        } catch (Exception ex)
        {
            ex.printStackTrace();
        }
            
        if (in < lastExecution) return; //Abort, new execution requested
        
        HQLFieldsReader hqlFR = new HQLFieldsReader(newQuery, getReportQueryDialog().getDataset().getParametersList());
            
        if (in < lastExecution) return; //Abort, new execution requested
        
        try {
            Vector fields = hqlFR.readFields();

            List columns = new ArrayList();
            for (int i=0; i<fields.size(); ++i)
            {
                JRDesignField field = (JRDesignField)fields.elementAt(i);
                columns.add( new Object[]{field, field.getValueClassName(), field.getDescription()} );
            }

            Vector v = hqlFR.getNotScalars();

            if (v.size() == 0) v = null;
            
            if (in < lastExecution) return; //Abort, new execution requested
            setBeanExplorerFromWorker(v,true,true);

            if (in < lastExecution) return; //Abort, new execution requested
            setColumnsFromWorker(columns);

        } catch (Exception ex)
        {
            ex.printStackTrace();
            setBeanExplorerFromWorker(null,true,true);
            setColumnErrorFromWork( I18n.getString("BeanInspectorPanel.Message.Error3") +  ex.getMessage() );
        }
        
        getReportQueryDialog().getJLabelStatusSQL().setText(I18n.getString("BeanInspectorPanel.Message.StatusSQL2"));
    }
    
    /**
     * Set the columns error message in the ReportQueryDialog....
     * This is called from a none swing thread, hence all the invoke and
     * wait magic.
     * The message is only set if the query string matches the one the 
     * error message is for.
     *
     * @param columns The list of columns to set.
     */
    protected void setColumnErrorFromWork( final String error_msg) {
        try {

            Runnable r = new Runnable() {
                public void run() {
                    getReportQueryDialog().setColumnsError( error_msg );
                    getReportQueryDialog().getQueryEditorPane().requestFocusInWindow();
                }
            };

            if (SwingUtilities.isEventDispatchThread()) { r.run(); }
            else { SwingUtilities.invokeAndWait( r ); }
        
        } catch(Exception e) {
            // oh well we got interrupted.
        }
    }
    
    /**
     * Set the columns in the ReportQueryDialog....
     * This is called from a none swing thread, hence all the invoke and
     * wait magic.
     * The message is only set if the query string matches the one the 
     * error message is for.
     *
     * @param columns The list of columns to set.
     */
    protected void setColumnsFromWorker( final List columns) {
        try {

            Runnable r = new Runnable() {
                public void run() {
                    getReportQueryDialog().setColumns( columns );
                    getReportQueryDialog().getQueryEditorPane().requestFocusInWindow();
                }
            };

            if (SwingUtilities.isEventDispatchThread()) { r.run(); }
            else { SwingUtilities.invokeAndWait( r ); }
        
        } catch(Exception e) {
            // oh well we got interrupted.
        }
    }
    
    /**
     * Set the bean explorer
     *
     * @param columns The list of columns to set.
     */
    protected void setBeanExplorerFromWorker( final Vector v,final  boolean pathOnDescription, final boolean useCombo){
        try {
            SwingUtilities.invokeAndWait( new Runnable() {
                public void run() {
                    Vector v1 = v;
                    if (v1 == null) v1 = new Vector();
                    
                    setComboVisible(useCombo);
                    setPathOnDescription(pathOnDescription);
                    setClassNames(v1);
            
                    getReportQueryDialog().getQueryEditorPane().requestFocusInWindow();
                    getReportQueryDialog().getQueryEditorPane().requestFocus();
                }
            }  );
        } catch(Exception e) {
            // oh well we got interrupted.
            e.printStackTrace();
        }
    }

    public ReportQueryDialog getReportQueryDialog() {
        return reportQueryDialog;
    }

    public void setReportQueryDialog(ReportQueryDialog reportQueryDialog) {
        this.reportQueryDialog = reportQueryDialog;
    }
    
    
}
