/**
 * Redistribution and use of this software and associated documentation
 * ("Software"), with or without modification, are permitted provided
 * that the following conditions are met:
 *
 * 1. Redistributions of source code must retain copyright
 *    statements and notices.  Redistributions must also contain a
 *    copy of this document.
 *
 * 2. Redistributions in binary form must reproduce the
 *    above copyright notice, this list of conditions and the
 *    following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *
 * 3. The name "Exolab" must not be used to endorse or promote
 *    products derived from this Software without prior written
 *    permission of Intalio, Inc.  For written permission,
 *    please contact info@exolab.org.
 *
 * 4. Products derived from this Software may not be called "Exolab"
 *    nor may "Exolab" appear in their names without prior written
 *    permission of Intalio, Inc. Exolab is a registered
 *    trademark of Intalio, Inc.
 *
 * 5. Due credit should be given to the Exolab Project
 *    (http://www.exolab.org/).
 *
 * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
 * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Copyright 2001 (C) Intalio, Inc. All Rights Reserved.
 *
 * $Id: QueryAnalyser.java 8104 2009-02-25 23:32:15Z rjoachim $
 */


package org.exolab.castor.gui;

import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.WindowEvent;
import java.io.FileReader;
import java.io.FileWriter;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Iterator;
import java.util.Vector;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextPane;
import javax.swing.JToolBar;
import javax.swing.UIManager;
import javax.swing.table.DefaultTableModel;

import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.JDOManager;
import org.exolab.castor.jdo.OQLQuery;
import org.exolab.castor.jdo.QueryResults;
import org.exolab.castor.jdo.engine.OQLQueryImpl;
import org.exolab.castor.mapping.Mapping;
import org.exolab.castor.mapping.MappingException;
import org.exolab.castor.xml.Marshaller;
import org.exolab.castor.xml.Unmarshaller;


/**
 * A simple Query tool. With this tool you can perform interactive
 * OQL Queries against a Castor Database.
 *
 * @author <a href="mauch@imkenberg.de">Thorsten Mauch</a>
 * @version $Revision: 8104 $ $Date: 2005-12-13 14:58:48 -0700 (Tue, 13 Dec 2005) $
 */
public final class QueryAnalyser {
    private boolean _packFrame = false;

    /**
     * Construct the application.
     */
    private QueryAnalyser(final String databasename, final String dbconfig) {
        MainFrame frame = new MainFrame(databasename, dbconfig);
        //Validate frames that have preset sizes
        //Pack frames that have useful preferred size info, e.g. from their layout
        if (_packFrame) {
            frame.pack();
        } else {
            frame.validate();
        }
        //Center the window
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        Dimension frameSize = frame.getSize();
        if (frameSize.height > screenSize.height) {
            frameSize.height = screenSize.height;
        }
        if (frameSize.width > screenSize.width) {
            frameSize.width = screenSize.width;
        }
        frame.setLocation((screenSize.width - frameSize.width) / 2,
                (screenSize.height - frameSize.height) / 2);
        frame.setVisible(true);
    }

    /**
     * Main method.
     */
    public static void main(final String[] args) {
        if (args.length != 2) {
            System.out.println(
                    "Usage: org.exolab.castor.tools.QueryAnalyser <Databasename> <Databaseconfig>");
            System.out.println(
                    "Example: org.exolab.castor.tools.QueryAnalyser testdb database.xml");
            System.exit(1);
        }
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e) {
            e.printStackTrace();
        }
        new QueryAnalyser(args[0], args[1]);
    }

    private class MainFrame extends JFrame {
        /** SerialVersionUID. */
        private static final long serialVersionUID = 3524368558606642742L;

        private QueryHistory _qhistory = new QueryHistory();
        private Mapping _mapping = new Mapping();

        private JPanel _contentPane;
        private BorderLayout _borderLayout1 = new BorderLayout();
        private DefaultTableModel _model;

        private JDOManager _jdo;
        private String _dbName;
        private String _dbConf;
        private JTabbedPane _tabbedPane = new JTabbedPane();
        private JToolBar _toolbar = new JToolBar();
        private JButton _btnNext = new JButton();
        private JButton _btnExit = new JButton();
        private JPanel _sqlresult = new JPanel();
        private JTextPane _sqlPane = new JTextPane();
        private BorderLayout _borderLayout3 = new BorderLayout();
        private JTextPane _oqlquery = new JTextPane();
        private JPanel _queryPanel = new JPanel();
        private BorderLayout _borderLayout2 = new BorderLayout();
        private JScrollPane _resultScrollpane = new JScrollPane();
        private JTable _resultTable = new JTable();
        private JButton _execute = new JButton();
        private JScrollPane _errorScrollPane = new JScrollPane();
        private JPanel _errorPanel = new JPanel();
        private JTextPane _oqlerror = new JTextPane();
        private BorderLayout _borderLayout4 = new BorderLayout();
        private JButton _btnPrevious = new JButton();
        private JLabel _statusBar = new JLabel();

        /**
         * Construct the frame.
         */
        public MainFrame(final String dbName, final String dbConf) {
            _dbName = dbName;
            _dbConf = dbConf;

            enableEvents(AWTEvent.WINDOW_EVENT_MASK);
            try {
                jbInit();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        
        /**
         * Component initialization.
         */
        private void jbInit() throws Exception    {
            ClassLoader cl = ClassLoader.getSystemClassLoader();
            //setIconImage(Toolkit.getDefaultToolkit().createImage(
            //    testframe.class.getResource("[Your Icon]")));
            _contentPane = (JPanel) this.getContentPane();
            _contentPane.setLayout(_borderLayout1);
            this.setSize(new Dimension(600, 400));
            this.setTitle("Castor OQL-Ouery Analyser");
            //ResultScrollpane.setHorizontalScrollBarPolicy(
            //    JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
            //ResultScrollpane.setVerticalScrollBarPolicy(
            //    JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);

            _sqlresult.setLayout(_borderLayout3);
            _oqlquery.setFont(new java.awt.Font("Dialog", 0, 12));
            _oqlquery.setToolTipText("create Query here");
            _queryPanel.setLayout(_borderLayout2);
            _resultScrollpane.setToolTipText("");
            _resultTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
            _btnExit.setMaximumSize(new Dimension(50, 39));
            _btnExit.setMinimumSize(new Dimension(50, 39));
            _btnExit.setActionCommand("");
            _btnExit.setIcon(new ImageIcon(cl.getResource(
                    "org/exolab/castor/gui/images/exit.gif")));
            _btnExit.setMnemonic('0');

            _btnExit.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(final ActionEvent e) {
                        exit();
                }
            });

            _execute.setMaximumSize(new Dimension(50, 39));
            _execute.setMinimumSize(new Dimension(50, 39));
            _execute.setActionCommand("");
            _execute.setIcon(new ImageIcon(cl.getResource(
                    "org/exolab/castor/gui/images/fire.gif")));
            _execute.setMnemonic('0');
            _execute.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(final ActionEvent e) {
                    executeActionPerformed(e);
                }
            });
            _errorPanel.setLayout(_borderLayout4);
            _btnPrevious.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(final ActionEvent e) {
                    btnPreviousActionPerformed(e);
                }
            });
            _btnNext.setMaximumSize(new Dimension(50, 39));
            _btnNext.setMinimumSize(new Dimension(50, 39));
            _btnNext.setActionCommand("");
            _btnNext.setIcon(new ImageIcon(cl.getResource(
                    "org/exolab/castor/gui/images/arrw04e.gif")));
            _btnNext.setMnemonic('0');
            _btnNext.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(final ActionEvent e) {
                    btnNextActionPerformed(e);
                }
            });
            _btnPrevious.setMaximumSize(new Dimension(50, 39));
            _btnPrevious.setMinimumSize(new Dimension(50, 39));
            _btnPrevious.setToolTipText("Goto previous Query");
            _btnPrevious.setIcon(new ImageIcon(cl.getResource(
                    "org/exolab/castor/gui/images/arrw04d.gif")));
            _btnPrevious.setMnemonic('0');
            _contentPane.add(_tabbedPane, BorderLayout.CENTER);
            _contentPane.add(_toolbar, BorderLayout.NORTH);

            _toolbar.add(_btnExit, null);
            _toolbar.add(_execute, null);
            _toolbar.add(_btnPrevious, null);
            _toolbar.add(_btnNext, null);
            _contentPane.add(_statusBar, BorderLayout.SOUTH);
            _tabbedPane.add(_queryPanel, "OQL Query");
            _queryPanel.add(_oqlquery, BorderLayout.CENTER);
            _tabbedPane.add(_resultScrollpane, "Resultset");
            _tabbedPane.add(_errorScrollPane, "Stacktrace");
            _errorScrollPane.getViewport().add(_errorPanel, null);
            _errorPanel.add(_oqlerror, BorderLayout.CENTER);
            _tabbedPane.add(_sqlresult, "SQL");
            _sqlresult.add(_sqlPane, BorderLayout.CENTER);



            _resultScrollpane.getViewport().add(_resultTable, null);
            // Open the Database
            openDB();
            _statusBar.setText("Database " + _jdo.getDatabaseName() + " waiting for Queries");
            // Load the query history
            loadHistory();
            _oqlquery.setText(_qhistory.getCurrentQuery());
        }
        
        /**
         * Overridden so we can exit when window is closed.
         */
        protected void processWindowEvent(final WindowEvent e) {
            super.processWindowEvent(e);
            if (e.getID() == WindowEvent.WINDOW_CLOSING) {
                exit();

            }
        }

        void executeActionPerformed(final ActionEvent e) {
            performQuery();
        }

        public void performQuery() {

            OQLQuery oql;
            QueryResults r;

            boolean firstObject = true;
            Object o;
            Vector<Method> properties = null;
            _model = new DefaultTableModel();

            try {
                _statusBar.setText("performing Query");
                // clear results
                clearTabs();

                //ResultScrollpane.getViewport().remove(
                //    ResultScrollpane.getViewport().getComponent(0));
                // create a new conec
                Database db = _jdo.getDatabase();
                db.begin();

                 /**
                    * add query to the history,
                    * maybe it's important lo loglso querys that won't work
                    * for this reason it's logged before creation
                    */
                _qhistory.addQuery(_oqlquery.getText());


                oql = db.getOQLQuery(_oqlquery.getText());


                // and execute it
                Date starttime = new Date();
                r = oql.execute(Database.READONLY);
                Date endtime = new Date();
                // write the status bar
                _statusBar.setText("Query successful, Time: "
                        + (endtime.getTime() - starttime.getTime()) + " ms");

                // get SQL statement via backdoor
                _sqlPane.setText(((OQLQueryImpl) oql).getSQL());


                while (r.hasMore()) {
                    o = r.next();
                    if (firstObject) {
                        properties = getProperties(o);
                        fillTableHeader(properties, _model);
                        firstObject = false;
                    }
                    _model.addRow(fillRow(properties, o));

                }
                db.commit();

                _resultTable.setModel(_model);
                _resultTable.repaint();
                _tabbedPane.setSelectedComponent(_resultScrollpane);

            } catch (Exception e) {
                // Print error into pane and status bar
                java.io.StringWriter sw = new    java.io.StringWriter();
                e.printStackTrace(new java.io.PrintWriter(sw));
                _oqlerror.setText(sw.getBuffer().toString());
                // focus to errortab
                _statusBar.setText(e.getMessage());
                //TabbedPane.setSelectedComponent(ErrorScrollPane);

            }

        }

        private Vector<Method> getProperties(final Object o) {
            Vector<Method> properties = new Vector<Method>();
            Method[] ms = o.getClass().getMethods();
            Method m;

            for (int i = 0; i < ms.length; i++) {
                m = ms[i];
                // if it begins with m and have no argument it is
                // a property
                if (m.getName().startsWith("get") && m.getParameterTypes().length == 0) {
                        properties.add(m);
                }
            }
            return properties;
        }

        private void fillTableHeader(
                final Vector<Method> properties,
                final DefaultTableModel model) {
            Iterator<Method> i = properties.iterator();
            Method m;
            while (i.hasNext()) {
                m = i.next();
                model.addColumn(m.getName().substring(3));
            }
        }

        private Vector<Object> fillRow(final Vector<Method> properties, final Object o) {
            Method m;
            Object temp;
            Vector<Object> results = new Vector<Object>();
            Iterator<Method> i = properties.iterator();
            while (i.hasNext()) {
                    temp = null;
                    m = i.next();
                    try {
                        temp = m.invoke(o, (Object[]) null);
                    } catch (Exception ie) {
                        temp = null;
                    }
                    results.add(temp);
            }
            return results;
        }

        private void openDB() {
            try {
                JDOManager.loadConfiguration (_dbConf, ClassLoader.getSystemClassLoader());
                _jdo = JDOManager.createInstance(_dbName);
                //only to try a connection
                _jdo.getDatabase();
            } catch (MappingException pe) {
                pe.printStackTrace();
                System.exit(1);
            } catch (org.exolab.castor.jdo.PersistenceException pe) {
                pe.printStackTrace();
                System.exit(1);
            }
        }
        
        /**
         * Delete all content in the tabbed pane.
         */
        private void clearTabs() {
            _oqlerror.setText("");
            _sqlPane.setText("");
            _resultTable.setModel(new DefaultTableModel());
        }

        void btnPreviousActionPerformed(final ActionEvent e) {
            clearTabs();
            _oqlquery.setText(_qhistory.getPreviousQuery());
            _tabbedPane.setSelectedComponent(_queryPanel);
        }

        void btnNextActionPerformed(final ActionEvent e) {
            clearTabs();
            _oqlquery.setText(_qhistory.getNextQuery());
            _tabbedPane.setSelectedComponent(_queryPanel);
        }

        public void saveHistory() {
            // write back the history to file
            try {
                FileWriter writer = new FileWriter("queryhistory.xml");
                Marshaller marshaller = new Marshaller(writer);
                marshaller.setMapping(_mapping);
                marshaller.marshal(_qhistory);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        private void loadHistory() {
            try {
                Unmarshaller unmarshaller = new Unmarshaller(
                        Class.forName("org.exolab.castor.gui.QueryHistory"));
                ClassLoader cl = ClassLoader.getSystemClassLoader();
                _mapping.loadMapping(cl.getResource("org/exolab/castor/gui/Queryanlyser.xml"));
                unmarshaller.setMapping(_mapping);

                FileReader reader = new FileReader("queryhistory.xml");
                _qhistory = (QueryHistory) unmarshaller.unmarshal(reader);
            } catch (Exception e) {
                e.printStackTrace();
                // if there is no file, it's ok also
                // then we have an empty History
            }
        }

        void exit() {
            saveHistory();
            System.exit(0);
        }
    }
}