/*
 * ExecuteSqlScriptPanel.java
 *
 * Copyright (C) 2002-2013 Takis Diakoumis
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 */

package org.executequery.gui;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.io.File;
import java.sql.SQLException;

import javax.swing.BorderFactory;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

import org.apache.commons.lang.StringUtils;
import org.executequery.ActiveComponent;
import org.executequery.EventMediator;
import org.executequery.GUIUtilities;
import org.executequery.base.DefaultTabViewActionPanel;
import org.executequery.components.FileChooserDialog;
import org.executequery.components.MinimumWidthActionButton;
import org.executequery.components.TableSelectionCombosGroup;
import org.executequery.event.ApplicationEvent;
import org.executequery.event.ConnectionEvent;
import org.executequery.event.ConnectionListener;
import org.executequery.event.DefaultKeywordEvent;
import org.executequery.sql.ActionOnError;
import org.executequery.sql.ExecutionController;
import org.executequery.sql.SqlScriptRunner;
import org.executequery.sql.SqlStatementResult;
import org.executequery.util.ThreadUtils;
import org.executequery.util.ThreadWorker;
import org.underworldlabs.swing.AbstractStatusBarPanel;
import org.underworldlabs.swing.GUIUtils;
import org.underworldlabs.swing.ProgressBar;
import org.underworldlabs.swing.ProgressBarFactory;
import org.underworldlabs.swing.util.SwingWorker;
import org.underworldlabs.util.MiscUtils;

/** 
 *
 * @author   Takis Diakoumis
 * @version  $Revision: 1383 $
 * @date     $Date: 2014-06-15 22:12:02 +1000 (Sun, 15 Jun 2014) $
 */
public class ExecuteSqlScriptPanel extends DefaultTabViewActionPanel
                                  implements NamedView,
                                             FocusComponentPanel,
                                             ActiveComponent,
                                             ExecutionController,
                                             ConnectionListener {
    
    public static final String TITLE = "Execute SQL Script ";
    public static final String FRAME_ICON = "ExecuteSqlScript16.png";
    
    private JComboBox connectionsCombo; 

    private JTextField fileNameField;

    private TableSelectionCombosGroup combosGroup;

    private LoggingOutputPanel outputPanel;
    
    private SqlTextPaneStatusBar statusBar;

    private JComboBox actionOnErrorCombo;
    
    private JCheckBox logOutputCheckBox;
    
    private JButton startButton;

    private JButton rollbackButton;
    
    private JButton commitButton;
    
    private JButton stopButton;

    private SqlScriptRunner sqlScriptRunner;
    
    public ExecuteSqlScriptPanel() {

        super(new BorderLayout());

        try  {

            init();

        } catch (Exception e) {
          
            e.printStackTrace();
        }

    }
    
    private void init() throws Exception {
        
        fileNameField = WidgetFactory.createTextField();
        connectionsCombo = WidgetFactory.createComboBox();
        combosGroup = new TableSelectionCombosGroup(connectionsCombo);

        actionOnErrorCombo = WidgetFactory.createComboBox();

        ActionOnError[] actionsOnError = {
                ActionOnError.HALT,
                ActionOnError.CONTINUE
        };        
        
        actionOnErrorCombo.setModel(new DefaultComboBoxModel(actionsOnError));
        
        outputPanel = new LoggingOutputPanel();
        statusBar = new SqlTextPaneStatusBar();
        statusBar.setBorder(BorderFactory.createEmptyBorder(2, 0, 0, 1));

        JButton button = WidgetFactory.createInlineFieldButton("Browse");
        button.setActionCommand("browse");
        button.addActionListener(this);
        button.setMnemonic('r');

        logOutputCheckBox = new JCheckBox("<html>&nbsp;&nbsp;<i>Note:</i> This will slow down the process significantly </html>");
        
        JPanel mainPanel = new JPanel(new GridBagLayout());
        
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridy = 0;
        gbc.gridx = 0;
        gbc.gridheight = 1;
        gbc.insets.top = 7;
        gbc.insets.bottom = 5;
        gbc.insets.right = 5;
        gbc.insets.left = 5;
        gbc.anchor = GridBagConstraints.NORTHWEST;
        mainPanel.add(new JLabel("Connection:"), gbc);
        gbc.gridx = 1;
        gbc.weightx = 1.0;
        gbc.insets.top = 5;
        gbc.gridwidth = GridBagConstraints.REMAINDER;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        mainPanel.add(connectionsCombo, gbc);
        gbc.gridy++;
        gbc.gridx = 0;
        gbc.weightx = 0;
        gbc.gridwidth = 1;
        gbc.insets.top = 5;
        mainPanel.add(new JLabel("Action on Error:"), gbc);
        gbc.gridx = 1;
        gbc.weightx = 1.0;
        gbc.gridwidth = GridBagConstraints.REMAINDER;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.insets.top = 0;
        mainPanel.add(actionOnErrorCombo, gbc);
        gbc.gridy++;
        gbc.gridx = 0;
        gbc.weightx = 0;
        gbc.gridwidth = 1;
        gbc.insets.top = 5;
        mainPanel.add(new JLabel("Input File:"), gbc);
        gbc.gridx = 1;
        gbc.weightx = 1.0;
        gbc.insets.top = 0;
        mainPanel.add(fileNameField, gbc);
        gbc.gridx = 2;
        gbc.weightx = 0;
        gbc.insets.left = 0;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        mainPanel.add(button, gbc);
        gbc.gridy++;
        gbc.gridx = 0;
        gbc.insets.top = 5;
        gbc.insets.left = 5;
        mainPanel.add(new JLabel("Log output:"), gbc);
        gbc.gridx = 1;
        gbc.insets.top = 2;
        gbc.insets.left = 0;
        mainPanel.add(logOutputCheckBox, gbc);        
        gbc.gridy++;
        gbc.gridx = 0;
        gbc.weighty = 1.0;
        gbc.weightx = 1.0;
        gbc.insets.top = 5;
        gbc.insets.left = 5;
        gbc.insets.bottom = 0;
        gbc.gridwidth = GridBagConstraints.REMAINDER;
        gbc.fill = GridBagConstraints.BOTH;
        mainPanel.add(outputPanel, gbc);
        gbc.gridy++;
        gbc.gridx = 0;
        gbc.weighty = 0;
        gbc.insets.bottom = 5;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        mainPanel.add(statusBar, gbc);
        
        mainPanel.setBorder(BorderFactory.createEtchedBorder());

        int minimumButtonWidth = 85;
        startButton = new MinimumWidthActionButton(minimumButtonWidth, this, "Start", "start");
        rollbackButton = new MinimumWidthActionButton(minimumButtonWidth, this, "Commit", "commit");
        commitButton = new MinimumWidthActionButton(minimumButtonWidth, this, "Rollback", "rollback");
        stopButton = new MinimumWidthActionButton(minimumButtonWidth, this, "Stop", "stop");

        rollbackButton.setEnabled(false);
        commitButton.setEnabled(false);
        stopButton.setEnabled(false);
        
        JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 2, 5));
        buttonPanel.add(startButton);
        buttonPanel.add(rollbackButton);
        buttonPanel.add(commitButton);
        buttonPanel.add(stopButton);

        add(mainPanel, BorderLayout.CENTER);
        add(buttonPanel, BorderLayout.SOUTH);        
        setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));

        EventMediator.registerListener(this);        
    }

    public boolean logOutput() {

        return logOutputCheckBox.isSelected();
    }
    
    public void browse() {

        FileChooserDialog fileChooser = new FileChooserDialog();
        fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
        fileChooser.setMultiSelectionEnabled(false);

        fileChooser.setDialogTitle("Select Export File Path");
        fileChooser.setDialogType(JFileChooser.OPEN_DIALOG);

        int result = fileChooser.showDialog(GUIUtilities.getInFocusDialogOrWindow(), "Select");
        if (result == JFileChooser.CANCEL_OPTION) {

            return;
        }

        File file = fileChooser.getSelectedFile();
        fileNameField.setText(file.getAbsolutePath());
    }

    private boolean fieldsValid() {
        
        String fileName = fileNameField.getText();
        if (StringUtils.isBlank(fileName)) {

            GUIUtilities.displayErrorMessage("Please select an input file");
            return false;
        
        } else {

            File file = new File(fileName); 
            
            if (!file.exists()) {
                
                GUIUtilities.displayErrorMessage("The selected file does not exists in the file system");
                return false;
            }
            
        }
        
        return true;
    }
    
    public boolean tabViewClosing() {

        cleanup();
        return true;
    }

    public void cleanup() {

        combosGroup.close();

        if (statusBar != null) {
         
            statusBar.cleanup();
        }
        
        closeConnection();
        EventMediator.deregisterListener(this);
    }

    private void closeConnection() {

        if (sqlScriptRunner != null) {

            try {
        
                sqlScriptRunner.close();

            } catch (SQLException e) {

                e.printStackTrace();
            }
        
        }
    }

    public boolean canHandleEvent(ApplicationEvent event) {

        return (event instanceof DefaultKeywordEvent) || (event instanceof ConnectionEvent);
    }

    public Component getDefaultFocusComponent() {

        return fileNameField;
    }

    private void enableButtons(final boolean enableStart, final boolean enableStop,
            final boolean enableCommit, final boolean enableRollback) {

        GUIUtils.invokeLater(new Runnable() {
           public void run() {
               startButton.setEnabled(enableStart);
               stopButton.setEnabled(enableStop);                
               commitButton.setEnabled(enableCommit);
               rollbackButton.setEnabled(enableRollback);                
            } 
            
        });        
    }

    private SwingWorker swingWorker;
    private boolean executing;
    
    public void start() {

        if (!executing) {

            if (fieldsValid()) {
    
                enableButtons(false, true, false, false);
                
                swingWorker = new ThreadWorker() {
                    public Object construct() {

                        executing = true;
                        return execute();
                    }
                    public void finished() {

                        SqlStatementResult sqlStatementResult = (SqlStatementResult) get();
                        
                        try {

                            outputPanel.append("Statements executed: " + sqlStatementResult.getStatementCount());
                            outputPanel.append("Total records affected: " + sqlStatementResult.getUpdateCount());
    
                        } finally {

                            executing = false;
                            enableButtons(false, false, true, true);
                        }
                    }
                };
                swingWorker.start();
            }
            
        }
    }
    
    public void stop() {
        
        ThreadUtils.startWorker(new Runnable() {
        
            public void run() {
            
                if (executing) {
                    try {
        
                        if (swingWorker != null) {
                            
                            swingWorker.interrupt();
                        }
                        sqlScriptRunner.stop();
        
                    } finally {
                        
                        executing = false;
                    }
                }
            }
        });
    }
    
    public void commit() {
        
        if (sqlScriptRunner != null) {
            
            try {

                sqlScriptRunner.commit();
                outputPanel.append("Commit complete");
                
            } catch (SQLException e) {

                outputPanel.appendError("Error during commit:\n" + e.getMessage());

            } finally {

                closeConnection();
                enableButtons(true, false, false, false);
            }
        }
        
    }
    
    public void rollback() {
        
        if (sqlScriptRunner != null) {
            
            try {

                sqlScriptRunner.rollback();
                outputPanel.append("Rollback complete");

            } catch (SQLException e) {

                outputPanel.appendError("Error during rollback:\n" + e.getMessage());
                
            } finally {
                
                closeConnection();
                enableButtons(true, false, false, false);
            }
        }
        
    }
    
    private SqlStatementResult execute() {
 
        if (sqlScriptRunner == null) {
            
            sqlScriptRunner = new SqlScriptRunner(this);
        }

        outputPanel.clear();
        long startTime = System.currentTimeMillis();
        SqlStatementResult sqlStatementResult = null;

        try {

            statusBar.setStatusText("Executing...");
            statusBar.startProgressBar();

            sqlStatementResult = sqlScriptRunner.execute(
                        combosGroup.getSelectedHost().getDatabaseConnection(),
                        fileNameField.getText(),
                        (ActionOnError) actionOnErrorCombo.getSelectedItem());

        } finally {

            if (sqlStatementResult != null && sqlStatementResult.isException()) {
                
                if (sqlStatementResult.isInterrupted()) {
                
                    outputPanel.appendWarning("Operation cancelled by user action");
                    
                } else {
                    
                    outputPanel.appendError("Execution error:\n" + sqlStatementResult.getErrorMessage());                    
                }
                
            }
            
            long endTime = System.currentTimeMillis();

            statusBar.setStatusText("Done");
            statusBar.stopProgressBar();

            outputPanel.append("Total duration: " + MiscUtils.formatDuration(endTime - startTime));
            GUIUtilities.scheduleGC();
        }

        return sqlStatementResult;
    }
    
    private static int instanceCount = 1;
    public String getDisplayName() {

        return TITLE + (instanceCount++);
    }

    public String toString() {

        return getDisplayName();
    }

    // ---------------------------------------------
    // ConnectionListener implementation
    // ---------------------------------------------
    
    /**
     * Indicates a connection has been established.
     * 
     * @param the encapsulating event
     */
    public void connected(ConnectionEvent connectionEvent) {

        combosGroup.connectionOpened(connectionEvent.getDatabaseConnection());
    }

    /**
     * Indicates a connection has been closed.
     * 
     * @param the encapsulating event
     */
    public void disconnected(ConnectionEvent connectionEvent) {

        combosGroup.connectionClosed(connectionEvent.getDatabaseConnection());
    }

    private static final int STATUS_BAR_HEIGHT = 21;

    private ProgressBar progressBar;
    
    class SqlTextPaneStatusBar extends AbstractStatusBarPanel {

        protected SqlTextPaneStatusBar() {
            
            super(STATUS_BAR_HEIGHT);

            addLabel(0, 200, true);
            progressBar = ProgressBarFactory.create(false);
            addComponent(((JComponent) progressBar), 1, 120, false);
        }
        
        public void setStatusText(String text) {
            
            setLabelText(0, text);
        }
        
        public void cleanup() {
         
            progressBar.cleanup();
            progressBar = null;
        }
        
        public void startProgressBar() {

            GUIUtils.invokeLater(new Runnable() {
               public void run() {

                   progressBar.start();
                } 
            });
        }

        public void stopProgressBar() {
            
            GUIUtils.invokeLater(new Runnable() {
                public void run() {

                    progressBar.stop();
                 } 
             });
        }

    } // SqlTextPaneStatusBar
    
    public void message(final String message) {
        ThreadUtils.invokeAndWait(new Runnable() {
            public void run() {
                outputPanel.append(message);
            }
        });
    }

    public void actionMessage(final String message) {
        ThreadUtils.invokeAndWait(new Runnable() {
            public void run() {
                outputPanel.appendAction(message);
            }
        });
    }

    public void errorMessage(final String message) {
        ThreadUtils.invokeAndWait(new Runnable() {
            public void run() {
                outputPanel.appendError(message);
            }
        });
    }

    public void queryMessage(final String message) {
        ThreadUtils.invokeAndWait(new Runnable() {
            public void run() {
                outputPanel.appendActionFixedWidth(message);
            }
        });
    }

    public void warningMessage(final String message) {
        ThreadUtils.invokeAndWait(new Runnable() {
            public void run() {
                outputPanel.appendWarning(message);
            }
        });
    }

}

