package com.sap.dbmtk.demo;

import java.awt.Dimension;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SortOrder;
import javax.swing.Timer;
import javax.swing.border.Border;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;

import net.miginfocom.swing.MigLayout;

import org.apache.log4j.Logger;
import org.jdesktop.swingx.sort.RowFilters;
import org.jdesktop.swingx.table.TableColumnExt;

import com.sap.dbmtk.demo.SqlStatement.Type;
import com.sap.dbmtk.demo.ui.ClickListener;
import com.sap.dbmtk.demo.ui.ResultSetTableModel;
import com.sap.dbmtk.demo.utils.Configuration;
import com.sap.dbmtk.demo.utils.DbUtils;
import com.sap.dbmtk.demo.utils.StringUtil;
import com.sap.dbmtk.demo.utils.SwingUtils;

public abstract class DataPanelAbstract
extends JPanel
implements ActionListener
{
	private static Logger _logger = Logger.getLogger(DataPanelAbstract.class);
	private static final long serialVersionUID = 1L;

	protected DemoApp         _owner             = null;

	protected String             _panelName         = "";
	protected boolean            _createBorder      = false;

	private   boolean            _firstTimeTabIsSelected = true; // used by onTabSelected() to filter out if it's the first time

	protected JLabel             _filter_lbl        = new JLabel("Filter: ");
	protected JTextField         _filter_txt        = new JTextField();
	protected JLabel             _filter_cnt        = new JLabel();
	protected ResultSetTable     _table             = null;

	protected JButton            _add_but           = new JButton("New");
	protected JButton            _change_but        = new JButton("Change");
	protected JButton            _remove_but        = new JButton("Remove");
	protected JButton            _refresh_but       = new JButton("Refresh");
	protected JCheckBox          _showTableToolTip  = new JCheckBox("Show Table Tooltip", DEFAULT_TABLE_TOOLTIP_IS_SELECTED);

	protected JPanel             _dbSearchPanel     = new JPanel();
	protected int                _dbRowCount        = -1;
//	protected int                _dbShowSearchPanelThreshold = Configuration.getCombinedConfiguration().getIntProperty(PROPKEY_DB_SEARCH_PANEL_THRESHOLD, DEFAULT_DB_SEARCH_PANEL_THRESHOLD);
	protected JLabel             _dbRowCount_lbl    = new JLabel();

//	protected boolean            _onRefreshGetDbCount = Configuration.getCombinedConfiguration().getBooleanProperty(PROPKEY_ON_REFRESH_GET_DB_COUNT, DEFAULT_ON_REFRESH_GET_DB_COUNT);

	public static final String  PROPKEY_ON_REFRESH_GET_DB_COUNT = "on.refresh.get.db.count";
	public static final boolean DEFAULT_ON_REFRESH_GET_DB_COUNT = true;

	public static final String  PROPKEY_DB_SEARCH_PANEL_THRESHOLD = "show.db.search.panel.threshold";
	public static final int     DEFAULT_DB_SEARCH_PANEL_THRESHOLD = 100;

	public static final String  PROPKEY_TABLE_TOOLTIP_IS_SELECTED = ".table.tooltip.isSelected";
	public static final boolean DEFAULT_TABLE_TOOLTIP_IS_SELECTED = true;

	private int getConf_dbShowSearchPanelThreshold()
	{
		return Configuration.getCombinedConfiguration().getIntProperty(PROPKEY_DB_SEARCH_PANEL_THRESHOLD, DEFAULT_DB_SEARCH_PANEL_THRESHOLD);
	}
	private boolean getConf_onRefreshGetDbCount()
	{
		return Configuration.getCombinedConfiguration().getBooleanProperty(PROPKEY_ON_REFRESH_GET_DB_COUNT, DEFAULT_ON_REFRESH_GET_DB_COUNT);
	}
	
	/** parent/child relation */
	private Set<DataPanelAbstract> _childSet = new HashSet<DataPanelAbstract>();

	public DataPanelAbstract(DemoApp demo, String panelName, boolean createBorder)
	{
		_owner        = demo;
		_panelName    = panelName;
		_createBorder = createBorder;
		
		setName(_panelName);
		
		pinit();
	}

	/**
	 * private initialization 
	 */
	private void pinit ()
	{
		if (_createBorder)
		{
			Border border = BorderFactory.createTitledBorder(_panelName);
			setBorder(border);
		}
		
		_dbSearchPanel = createDbSearchPanel();
		_dbSearchPanel.setVisible(false);

		_filter_txt    .setToolTipText("Client filter, that does regular expression on all table cells using this value");
		_filter_cnt    .setToolTipText("Visible rows / actual rows in the GUI Table");
		_dbRowCount_lbl.setToolTipText("Rows braught back from server to client / actual rows in the Database Table");
		_dbSearchPanel .setToolTipText("Restricting records fetched from the database using various where clauses.");

		setLayout(new MigLayout("insets 5 5 0 5", "", ""));   // insets Top Left Bottom Right
		
		add(_filter_lbl, "split");
		add(_filter_txt, "growx, pushx");
		add(_filter_cnt, "wrap");
		
		_table = createTable();
		JScrollPane scroll = new JScrollPane(_table);
		add(scroll, "grow, push, wrap");
		
		add(_dbSearchPanel, "growx, pushx, hidemode 3, wrap");
			
		add(_add_but,          "split, hidemode 2");
		add(_change_but,       "hidemode 2");
		add(_remove_but,       "hidemode 2");
		add(_showTableToolTip, "hidemode 2");
		add(new JLabel(),      "pushx, growx");
		add(_refresh_but,      "wrap");

//		_change_but.setEnabled(false);
//		_remove_but.setEnabled(false);
		_add_but    .setEnabled(false);
		_change_but .setEnabled(false);
		_remove_but .setEnabled(false);
		_refresh_but.setEnabled(false);

		_add_but         .addActionListener(this);
		_change_but      .addActionListener(this);
		_remove_but      .addActionListener(this);
		_refresh_but     .addActionListener(this);
		_showTableToolTip.addActionListener(this);

		_filter_txt.addCaretListener(new CaretListener()
		{
			@Override
			public void caretUpdate(CaretEvent e)
			{
				applyFilter();
			}
		});

		_add_but         .setVisible( showAddButton()     );
		_change_but      .setVisible( showChangeButton()  );
		_remove_but      .setVisible( showRemoveButton()  );
		_refresh_but     .setVisible( showRefreshButton() );
		_showTableToolTip.setVisible( showTableToolTipCheckbox() );
		
		// LOAD some properties
		_showTableToolTip.setSelected(Configuration.getCombinedConfiguration().getBooleanProperty(getName() + PROPKEY_TABLE_TOOLTIP_IS_SELECTED, DEFAULT_TABLE_TOOLTIP_IS_SELECTED));

		loadProps();
		init();
	}

	/**
	 * Override this to do local initializations
	 */
	protected void init()
	{
	}

	/**
	 * Local handler 
	 */
	@Override
	public void actionPerformed(ActionEvent e)
	{
		Object source = e.getSource();

		// setFields() in below calls might throw if the column isn't found...
		try
		{
			// ADD 
			if (_add_but.equals(source))
			{
				internalActionAdd();
			}

			// CHANGE 
			if (_change_but.equals(source))
			{
				internalActionChange();
			}

			// REMOVE 
			if (_remove_but.equals(source))
			{
				internalActionRemove();
			}

			// REFRESH 
			if (_refresh_but.equals(source))
			{
				internalActionRefresh();
			}
		}
		catch (Throwable t)
		{
			String htmlMsg = 
				"<html>"
				+ "Problem loading some field(s) from the '"+getName()+"' panel.<br>"
				+ "<br>"
				+ "<b>Exception</b>: <br>" 
				+ t + "<br>" 
				+ "</html>";
			SwingUtils.showErrorMessage(_owner.getWindow(), "Problem", htmlMsg, t);
		}

		// SHOW TABLE TOOLTIP, check box 
		if (_showTableToolTip.equals(source))
		{
			Configuration conf = Configuration.getInstance(Configuration.USER_TEMP);
			if (conf != null)
			{
				conf.setProperty(getName() + PROPKEY_TABLE_TOOLTIP_IS_SELECTED, _showTableToolTip.isSelected());
				conf.save();
			}
		}

		// ALWAYS DO: to check connection status
//		_owner.refreshStatusBar();
	}

	/** override this to change if the button should be hidden */
	public boolean showAddButton()     { return true; }

	/** override this to change if the button should be hidden */
	public boolean showChangeButton()  { return true; }

	/** override this to change if the button should be hidden */
	public boolean showRemoveButton()  { return true; }

	/** override this to change if the button should be hidden */
	public boolean showRefreshButton() { return true; }

	/** override this to change if the button should be hidden */
	public boolean showTableToolTipCheckbox() { return false; }

	/** is the SHow Table Tooltip enabled */
	public boolean isTableToolTipEnabled() { return _showTableToolTip.isVisible() && _showTableToolTip.isSelected(); }


	public Window getOwner()
	{
		return _owner.getWindow();
	}
	public ResultSetTable getTable()
	{
		return _table;
	}

	public ConnectionProvider getConnectionProvider()
	{
		return _owner;
	}

	public Connection getConnection()
	throws SQLException
	{
		return _owner.getConnection();
	}

	public ResultSetTable createTable()
	{
		final ResultSetTable tab = new ResultSetTable( this );
		
		tab.setName(getName());

		tab.setSortable(true);
		tab.setSortOrderCycle(SortOrder.ASCENDING, SortOrder.DESCENDING, SortOrder.UNSORTED);
		tab.packAll(); // set size so that all content in all cells are visible
		tab.setColumnControlVisible(true);
//		tab.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		tab.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);

		// Add a popup menu
		tab.setComponentPopupMenu( createDataTablePopupMenu(tab) );

//		// Add selection 
//		tab.getSelectionModel().addListSelectionListener(new ListSelectionListener()
//		{
//			@Override
//			public void valueChanged(ListSelectionEvent e)
//			{
//				if (e.getValueIsAdjusting())
//					return;
//
////				System.out.println("Selected author rowid(s): " + StringUtil.toCommaStr(tab.getSelectedRows()) );
//				onTableSelection();
//				
//				boolean enable = tab.getSelectedRowCount() == 1;
//				_change_but.setEnabled(enable);
//				_remove_but.setEnabled(enable);
//			}
//		});
		// Add selection... Also install a Timer that fires AFTER movement (like up/down key)
		int tableSelectionTimerInterval = 350;
		final Timer tableSelectionTimer = new Timer(tableSelectionTimerInterval, new ActionListener()
		{
			@Override
			public void actionPerformed(ActionEvent e)
			{
				_logger.debug("rowSelectionTimer() FIRED");

				// In case we do disconnect, the connection is set to NULL, but this timer is still fired, due to the "deferred" action...
				if ( ! _owner.isConnected() )
					return;

				internalOnTableSelection();
				
				boolean enable = tab.getSelectedRowCount() == 1;
				_change_but.setEnabled(enable);
				_remove_but.setEnabled(enable);
			}
		});
		tableSelectionTimer.setRepeats(false);
		_logger.debug("nodeSelectionTimerInterval="+tableSelectionTimerInterval);
		tab.getSelectionModel().addListSelectionListener(new ListSelectionListener()
		{
			@Override
			public void valueChanged(ListSelectionEvent e)
			{
				if (e.getValueIsAdjusting())
					return;

				_logger.debug("selectedRow(): timer: " + (tableSelectionTimer.isRunning() ? "RESTART": "START"));
				if (tableSelectionTimer.isRunning())
					tableSelectionTimer.restart();
				else
					tableSelectionTimer.start();
			}
		});

		// Add double click listener on the table
		tab.addMouseListener(new ClickListener()
		{
			@Override
			public void doubleClick(MouseEvent e)
			{
				// if we double click, the _change_but might not have been enabled...
				// So lets sheet a bit here
				if (showChangeButton())
					_change_but.setEnabled(true);

				Point p = e.getPoint();
				int vrow = tab.rowAtPoint(p);
				if ( vrow >= 0 )
				{
					tab.getSelectionModel().setSelectionInterval(vrow, vrow);
					_change_but.doClick();
				}
			}
		});

		return tab;
	}

	public JPopupMenu createDataTablePopupMenu(final ResultSetTable tab)
	{
		JPopupMenu pm = new JPopupMenu();
		JMenuItem mi;
		final JMenuItem add_mi     = new JMenuItem("Add...");
		final JMenuItem change_mi  = new JMenuItem("Change...    <double-click>");
		final JMenuItem remove_mi  = new JMenuItem("Remove...");;
		final JMenuItem refresh_mi = new JMenuItem("Refresh");

		// ADD
		if (showAddButton())
		{
			mi = add_mi;
			mi.addActionListener(new ActionListener()
			{
				@Override
				public void actionPerformed(ActionEvent e)
				{
					_add_but.doClick();
				}
			});
			pm.add(mi);
		}

		// CHANGE
		if (showChangeButton())
		{
			mi = change_mi;
			mi.addActionListener(new ActionListener()
			{
				@Override
				public void actionPerformed(ActionEvent e)
				{
					_change_but.doClick();
				}
			});
			pm.add(mi);
		}

		// REMOVE
		if (showRemoveButton())
		{
			mi = remove_mi;
			mi.addActionListener(new ActionListener()
			{
				@Override
				public void actionPerformed(ActionEvent e)
				{
					_remove_but.doClick();
				}
			});
			pm.add(mi);
		}

		// REFRESH
		if (showRefreshButton())
		{
			mi = refresh_mi;
			mi.addActionListener(new ActionListener()
			{
				@Override
				public void actionPerformed(ActionEvent e)
				{
					_refresh_but.doClick();
				}
			});
			pm.add(mi);
		}

		// 
		pm.addPopupMenuListener(new PopupMenuListener()
		{
			@Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {}
			@Override public void popupMenuCanceled(PopupMenuEvent e) {}
			
			@Override
			public void popupMenuWillBecomeVisible(PopupMenuEvent e)
			{
				boolean enable = tab.getSelectedRowCount() == 1;
				if (change_mi != null) change_mi.setEnabled(enable);
				if (remove_mi != null) remove_mi.setEnabled(enable);
			}
		});

		return pm;
	}

	public void applyFilter()
	{
		String searchString = _filter_txt.getText().trim();
		if ( searchString.length() <= 0 ) 
			_table.setRowFilter(null);
		else
		{
			// Create a array with all visible columns... hence: it's only those we want to search
			// Note the indices are MODEL column index
			int[] mcols = new int[_table.getColumnCount()];
			for (int i=0; i<mcols.length; i++)
				mcols[i] = _table.convertColumnIndexToModel(i);
			
			_table.setRowFilter(RowFilters.regexFilter(Pattern.CASE_INSENSITIVE, searchString + ".*", mcols));
		}
		
		String rowc = _table.getRowCount() + "/" + _table.getModel().getRowCount();
		_filter_cnt.setText(rowc);

		rowc = _table.getRowCount() + "/" + _dbRowCount;
		_dbRowCount_lbl.setText(rowc);
	}


	/**
	 * Is called when a row is selected in the table
	 */
	private void internalOnTableSelection()
	{
		onTableSelection();
		fireChildTableSelection();
	}

	/**
	 * Is called when a row is selected in the table
	 */
	public void onTableSelection()
	{
	}

	/**
	 * Called when rows in the "parent" table is Selected
	 */
	public void onParentTableSelection(DataPanelAbstract parent)
	{
	}

	/**
	 * Add a parent/child listener
	 * @param child
	 */
	public void addChildTableSelectionListener(DataPanelAbstract child)
	{
		_childSet.add(child);
	}
	/**
	 * Remove a parent/child listener
	 * @param child
	 */
	public void removeChildTableSelectionListener(DataPanelAbstract child)
	{
		_childSet.remove(child);
	}

	/**
	 * FIRE any listeners: onParentTableSelection(this)
	 */
	private void fireChildTableSelection()
	{
		for (DataPanelAbstract child : _childSet)
		{
			_logger.debug(getName()+": fireChildTableSelection on: "+child.getName());
			child.onParentTableSelection(this);
		}
	}


	/**
	 * If the current TAB is UnDocked.
	 * @return
	 */
	public boolean isUndocked()
	{
		return _owner.isTabUnDocked(getName());
	}

	/**
	 * Check various statuses related to transactions<br>
	 * This should be called AFTER database interactions has been done and the GUI returns to "normal" operation state.<br>
	 * <br>
	 * For the moment this will just check that AutoCommit is in same status as when we committed
	 */
	public void checkTransactionStatus()
	{
		try
		{
			boolean autoCommitAtConnect = getConnectionProvider().isAutoCommitEnabledAtConnect(); 
			boolean autoCommitNow       = DbUtils.getAutoCommit(getConnection(), _owner.getConnectedProductName());

			if (autoCommitNow != autoCommitAtConnect)
			{
				_logger.warn("When checking transaction status: I discovered that AutoCommit is NOW set to '"+autoCommitNow+"' while AutoCommit was to '"+autoCommitAtConnect+"' when the connection was made.");

				String htmlMsg = 
					"<html>"
					+ "When checking transaction status: <br>"
					+ "I discovered that AutoCommit is NOW set to '<b>"+autoCommitNow+"</b>' <br>"
					+ "While AutoCommit was to '<b>"+autoCommitAtConnect+"</b>' when the connection was made. <br>"
					+ "<br>"
					+ "I will set AutoCommit to it's original state: <b>"+autoCommitAtConnect+"</b>"
					+ "<br>"
					+ "<b>NOTE</b>: This is a bug in the application layer, please fix this."
					+ "</html>";
				SwingUtils.showWarnMessage(getOwner(), "Transaction Status", htmlMsg, new Exception("Dummy exception in DataPanel named '"+getName()+"', this was made to get a stacktrace for easier debugging."));
				
				DbUtils.setAutoCommit(getConnection(), _owner.getConnectedProductName(), autoCommitAtConnect, "When checking the Transaction Status after DB Operations");
			}
		}
		catch (SQLException e)
		{
			_logger.warn("Problems when checking internal transaction status after DB Interaction. Caught: "+e);
			SwingUtils.showWarnMessage(getOwner(), "Transaction Status", "Problems when checking internal transaction status after DB Interaction. Caught: "+e, e);
		}
	}

	private void internalActionAdd()
	{
		actionAdd();
		_owner.refreshStatusBar();
		checkTransactionStatus();
	}

	private void internalActionChange()
	{
		actionChange();
		_owner.refreshStatusBar();
		checkTransactionStatus();
	}
	
	private void internalActionRemove()
	{
		actionRemove();
		_owner.refreshStatusBar();
		checkTransactionStatus();
	}
	
	private void internalActionRefresh()
	{
		saveProps();
		if (getConf_onRefreshGetDbCount())
			refreshDbRowCount();

		actionRefresh();
		_owner.refreshStatusBar();
		checkTransactionStatus();
//System.out.println("internalActionRefresh(): _name='"+getName()+"', dbRowCount="+_dbRowCount);
	}

	public void saveProps()
	{
	}
	public void loadProps()
	{
	}

	/**
	 * Is called when the "New" button is pressed
	 */
	public void actionAdd()
	{
	}

	/**
	 * Is called when the "Change" button is pressed
	 */
	public void actionChange()
	{
	}
	
	/**
	 * Is called when the "Remove" button is pressed
	 */
	public void actionRemove()
	{
	}
	
	/**
	 * Is called when the "Refresh" button is pressed
	 */
	public void actionRefresh()
	{
		refreshTableData();
	}

	public void onConnect()
	{
////		refreshTableData();
//		internalActionRefresh();
		
		_add_but    .setEnabled(true);
//		_change_but .setEnabled(true);
//		_remove_but .setEnabled(true);
		_refresh_but.setEnabled(true);

	}


	public void onDisconnect()
	{
		_firstTimeTabIsSelected = true;
		getTable().setModel( new DefaultTableModel() );

		_add_but    .setEnabled(false);
		_change_but .setEnabled(false);
		_remove_but .setEnabled(false);
		_refresh_but.setEnabled(false);
		
		_dbRowCount = -1;
		_dbSearchPanel.setVisible(false);
	}

	public void refreshDbRowCount()
	{
		_dbRowCount = getDbRowCount();
		_dbRowCount_lbl.setText(_dbRowCount+"");
		_dbSearchPanel.setVisible( isDbRowCountAboveThreshold() );
	}

	public void onTabSelected()
	{
		if ( ! getConnectionProvider().isConnected() )
			return;

		if (_firstTimeTabIsSelected)
		{
			// If the below config is ON the refresh is done in internalActionRefresh(), so we don't need to do it here also
			if ( ! getConf_onRefreshGetDbCount() )
				refreshDbRowCount();

			internalActionRefresh();
			_firstTimeTabIsSelected = false;
		}
	}

	/**
	 * how many rows are there in the database table(s)
	 * @return
	 */
	public int getDbRowCount()
	{
		return -1;
	}

	/**
	 * Should we show the DB Search panel or not
	 * @return
	 */
	public boolean isDbRowCountAboveThreshold()
	{
		return _dbRowCount > getConf_dbShowSearchPanelThreshold();
	}

	/**
	 * Should we show the DB Search panel or not
	 * @return
	 */
	public boolean isDbSearchPanelVisible()
	{
		return _dbSearchPanel.isVisible();
	}

	/**
	 * Create the DB Search panel
	 * @return
	 */
	public JPanel createDbSearchPanel()
	{
		return new JPanel();
	}

	/**
	 * called to get a SQL Statement used to populate table  
	 */
	abstract public SqlStatement getTableDataSqlStatement();

	
	/**
	 * called to get and populate the TableModel 
	 */
	public void refreshTableData()
	{
		final SqlStatement sqlStmnt = getTableDataSqlStatement();

		try
		{
			// Executed the SQL
//			sqlStmnt.execute( getConnectionProvider() );
			sqlStmnt.executeWithGuiProgress( getConnectionProvider(), getOwner() );
			
			if (sqlStmnt.getResultSetCount() == 0)
				throw new Exception("No ResultSet was produced by the SQL Statement.");

			// Get the ResultSet and set in in the TABLE
			ResultSetTableModel newRstm = sqlStmnt.getResultSet(0);
			
			boolean applyHideColumns;
			TableModel tm = getTable().getModel();
			if (tm instanceof ResultSetTableModel)
			{
				ResultSetTableModel currentRstm = (ResultSetTableModel) tm;
				currentRstm.setModelData(newRstm, false);
				applyHideColumns = false;
			}
			else
			{
				getTable().setModel(newRstm);
				applyHideColumns = true;
			}
			
			// HIDE some columns if this is desired
			if (applyHideColumns)
			{
				getTable().packAll();

				List<String> hideColumns = getHiddenColumns();
				if (hideColumns != null)
				{
					for (String colName : hideColumns)
					{
						int vpos = getTable().findViewColumn(colName, false);
						if (vpos >= 0)
						{
							TableColumnExt tce = getTable().getColumnExt(vpos);
							if (tce != null)
								tce.setVisible(false);
							
							_logger.info("Hiding column '"+colName+"' in panel '"+_panelName+"'.");
						}
						else
						{
							if (vpos == -1)
								_logger.info("Cant find column '"+colName+"' when trying to hide it in panel '"+_panelName+"'.");
						}
					}
				}
			}

			SqlStatement.showWarningMessages(getConnectionProvider(), getOwner(), "Warning messages from db for: "+getName());
		}
//		catch (SQLException ex)
		catch (Throwable ex)
		{
			SqlStatement.showErrorMessage(ex, sqlStmnt, _owner.getWindow(), "Getting "+getName()+" information <b>failed.</b>");
		}
		applyFilter();

//		// ALWAYS DO: to check connection status
//		_owner.refreshStatusBar();
//
//		// and check transaction status
//		checkTransactionStatus();
	}

	/**
	 * @return List of Strings with column names that you want to hide from the JTables
	 */
	public List<String> getHiddenColumns()
	{
		return null;
	}

	/**
	 * Get tool tip for a specific cell in the table.
	 * 
	 * @param rstm
	 * @param mrow
	 * @param mcol
	 * @return
	 */
	public String getTableToolTipText(ResultSetTableModel rstm, int mrow, int mcol)
	{
		if ( ! isTableToolTipEnabled() )
			return null;

		String htmlTab = rstm.toHtmlTableString(mrow, false);

		StringBuilder sb = new StringBuilder(htmlTab.length() + 20);
		sb.append("<html>").append(htmlTab).append("</html>");
		return sb.toString();
	}

	
	public String getTableToolTipTextWithPicture(ResultSetTableModel rstm, int mrow, int mcol, final String sqlProc, final String keyCol, final ImageIcon noPictureIcon, final Dimension imageMaxBoundary)
	{
		if ( ! isTableToolTipEnabled() )
			return null;

//		final String    SQL_PROC   = "{call get_author_picture(?,?)}";
//		final String    KEY_COL    = "au_id";
//		final Integer   KEY_VAL;
//		final ImageIcon NO_PICTURE = AddOrChangeAuthor.NO_PICTURE;
		final String    SQL_PROC   = sqlProc;
		final String    KEY_COL    = keyCol;
		final Integer   KEY_VAL;
		final ImageIcon NO_PICTURE = noPictureIcon;

		// Get AU_ID
		try
		{
			KEY_VAL = rstm.getValueAsInteger(mrow, KEY_COL, false);
    		if (KEY_VAL == null)
    			return null;
		}
		catch (RuntimeException rte)
		{
			return rte.toString();
		}

		// Get the picture from the database
		byte[] picture = null;
		SqlStatement stmnt = new SqlStatement(Type.CALLABLE, SQL_PROC);
		stmnt.addParam( SqlStatementParam.createInParam(KEY_COL, Types.INTEGER, KEY_VAL) );
		stmnt.addParam( SqlStatementParam.createOracleResultSetParam("a_cursor") );
		
		try
		{
			ResultSet rs = stmnt.executeQuery(getConnectionProvider());
			while (rs.next())
				picture = rs.getBytes(1);

			SqlStatement.putWarningMessages(getConnectionProvider(), null, rs);
			rs.close();
		}
		catch (SQLException e)
		{
			return null;
		}
		// If picture is not in the database, use default picture
		if (picture == null)
		{
			picture = SwingUtils.toBytArray(NO_PICTURE);
			if (picture == null)
				return null;
		}

		// Save the picture to a file, which will be used in the HTML tool tip
		// The temporary file is deleted when the JVM exits
		// This is a workaround since the Java HTML engine doesn't seem to understand: <img src="data:<mime-type>;base64,<data>">
		File tmpFile = null;
		try
		{
			tmpFile = File.createTempFile("dbmtk_demo_image_tooltip_", ".tmp");
			tmpFile.deleteOnExit();
			FileOutputStream fos = new FileOutputStream(tmpFile);
			fos.write(picture);
			fos.close();
		}
		catch (IOException ex)
		{
			return "<html>Sorry problems when creating temporary file '"+tmpFile+"'<br>Caught: "+ex+"</html>";
		}

		// Get width/height and calculate a new size which fits in boundary, but keep image aspect ratio
		ImageIcon tmpImage = new ImageIcon(picture);
		int width  = tmpImage.getIconWidth();
		int height = tmpImage.getIconHeight();

		// calculate a new image size max 500x500, but keep image aspect ratio
		Dimension originSize   = new Dimension(width, height);
		Dimension boundarySize = imageMaxBoundary == null ? new Dimension(170, 220) : imageMaxBoundary;
		Dimension newSize      = SwingUtils.getScaledDimension(originSize, boundarySize);

		// Compose a HTML tooltip and return it...
		StringBuilder sb = new StringBuilder();
		sb.append("<html>");
		if (_logger.isDebugEnabled())
		{
    		sb.append("Picture for '").append(KEY_COL).append("': ").append(KEY_VAL).append("<br>");
    		sb.append("Using temp file: <code>").append(tmpFile).append("</code><br>");
    		sb.append("Width/Height: <code>").append(originSize.width).append(" x ").append(originSize.height).append("</code><br>");
    		sb.append("Size:  <code>").append(StringUtil.bytesToHuman(picture.length, "#.#")).append("</code><br>");
    		sb.append("<hr>");
		}
		sb.append("<table>");
		sb.append("<tr>");
		sb.append("  <td>");
		sb.append("  <img src=\"file:///").append(tmpFile).append("\" width=\"").append(newSize.width).append("\" height=\"").append(newSize.height).append("\">");
		sb.append("</td>");
		sb.append("<td>");
		sb.append(   rstm.toHtmlTableString(mrow, false));
		sb.append("</td>");
		sb.append("</tr>");
		sb.append("</table>");
		sb.append("</html>");

		return sb.toString();
	}
}
