/*
 * Copyright 2002-2010 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jdal.swing;


import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.RowSorter;
import javax.swing.RowSorter.SortKey;
import javax.swing.SortOrder;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.event.RowSorterEvent;
import javax.swing.event.RowSorterListener;
import javax.swing.table.TableCellRenderer;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdal.beans.MessageSourceWrapper;
import org.jdal.dao.Dao;
import org.jdal.dao.Page;
import org.jdal.dao.Page.Order;
import org.jdal.dao.PageChangedEvent;
import org.jdal.dao.PageableDataSource;
import org.jdal.dao.Paginator;
import org.jdal.dao.PaginatorListener;
import org.jdal.model.TableState;
import org.jdal.service.TableService;
import org.jdal.swing.form.FormUtils;
import org.jdal.swing.table.LoadPreferencesAction;
import org.jdal.swing.table.SavePreferencesAction;
import org.jdal.ui.Editor;
import org.jdal.ui.EditorEvent;
import org.jdal.ui.EditorListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;

/**
 * A JPanel with  a JTable and paginator. 
 * 
 * <p>This table view uses a {@link PageableDataSource} to query for data by pages.
 * Has a paginator control to navigate on records and show page info.
 * 
 * <p> You need to configure the PageableDatasource and the ListTableModel before usage. 
 *  
 * @author Jose Luis Martin - ([email protected])
 */
@SuppressWarnings("unchecked")
public class PageableTable<T> extends JPanel implements RowSorterListener, PaginatorListener {
	
	private static final long serialVersionUID = 1L;
	private static final Log log = LogFactory.getLog(PageableTable.class);
	
	/** Border layout */
	private BorderLayout layout = new BorderLayout();
	/** table to show data to user */
	private JTable table;
	/**
	 * @return the table
	 */
	public JTable getTable() {
		return table;
	}

	/**
	 * @param table the table to set
	 */
	public void setTable(JTable table) {
		this.table = table;
	}

	/** the paginator view */
	private PaginatorView paginatorView;
	/** Page used to query PageableDataSource */
	private Page<T> page  = new Page<T>();
	/** pageable datasource to request data page by page */
	private PageableDataSource<T> dataSource;
	/** list table model for the table */
	private ListTableModel tableModel;
	/** scroll pane used as table container */
	private JScrollPane tableScrollPane;
	/** row sorter that query a new page for server side order */
	private ModelRowSorter<ListTableModel> sorter;
	/** visiblity column descriptor in use */
	private List<ColumnDescriptor> columnDescriptors;
	/** visibility box for select visible columns in visibililty menu */
	private VisibilityBox visibilityBox;
	/** Gui Factory to gets editors */
	private GuiFactory guiFactory;
	/** editor name */
	private String editorName;
	/** Open editors Map hold editors that are open for a  model */
	private Map<Object, Window> openDialogs = Collections.synchronizedMap(new HashMap<Object, Window>()); 
	/** TableState service */
	private TableService tableService;
	/** Message Source */
	private MessageSourceWrapper messageSource = new MessageSourceWrapper();
	/** Show right menu when true */
	private boolean showMenu = true;
	/** Show paginator */
	private boolean showPaginator = true;
	/** Change Listeners */
	private ArrayList<ChangeListener> changeListeners = new ArrayList<ChangeListener>();
	/** Editor Listeners */
	private ArrayList<EditorListener> editorListeners = new ArrayList<EditorListener>();
	/** true if table propagate persistent service to editors */
	private boolean configureEditors = true;
	
	// Menus
	JMenuBar rightMenuBar;
	
	// Icons 
	private Icon visibilityMenuIcon;
	private Icon okIcon;
	private Icon cancelIcon;
	private Icon userMenuIcon;
	
	// Attributes needed to work with Page objects in Reports
	private String sortPropertyName;
	private Page.Order order;
	
	
	/**
	 * Initalize component after properties set. Normally called by context vía init-method
	 */
	public void init() {
		okIcon = FormUtils.getIcon(okIcon, "/images/16x16/dialog-ok.png");
		cancelIcon = FormUtils.getIcon(cancelIcon, "/images/16x16/dialog-cancel.png");
		visibilityMenuIcon = FormUtils.getIcon(visibilityMenuIcon, "/images/16x16/view-choose.png");
		userMenuIcon = FormUtils.getIcon(userMenuIcon, "/images/table/16x16/users.png");
		
		if (tableModel == null) {
			tableModel = new ListTableModel();
		}	
		
		setLayout(layout);
		
		// Server side sorter
		sorter = new ModelRowSorter<ListTableModel>(tableModel);
		sorter.addRowSorterListener(this);
		// configure paginator
		if (showPaginator) {
			if (paginatorView == null) {
				paginatorView = new PaginatorView();
				paginatorView.init();
			}
		
			paginatorView.setPaginator(page);
			page.addPaginatorListener(this);
			add(paginatorView.getPanel(), BorderLayout.SOUTH);
		}
		else {
			page.setPageSize(Integer.MAX_VALUE);
		}
		
		createColumnDescriptos();
		table = new JTable(tableModel, tableModel.getTableColumnModel());
		table.setAutoCreateRowSorter(false);
		table.setRowSorter(sorter);
		table.setRowHeight(22);
		table.addMouseListener(new TableListener());
		tableScrollPane = new JScrollPane(table);
	
		this.setBackground(Color.WHITE);
		add(tableScrollPane, BorderLayout.CENTER);
	
		
		if (showMenu)
			createMenu();
		
		page.setPageableDataSource(dataSource);
		// goto first page
		page.firstPage();
		// restore table state
		restoreState();
	}
	
	/**
	 * Create the right menu bar
	 */
	private void createMenu() {
		rightMenuBar = new JMenuBar();
		rightMenuBar.setLayout(new BoxLayout(rightMenuBar, BoxLayout.PAGE_AXIS));
		rightMenuBar.setMargin(new Insets(0, 0, 0, 0));
		// Add Visibility menu
		JMenu menu = new JMenu();
		menu.setMargin(new Insets(0,0,0,0));
		menu.setIcon(visibilityMenuIcon);
		menu.setMaximumSize(new Dimension(50,50));
		visibilityBox = new VisibilityBox(columnDescriptors);
		menu.add(visibilityBox);
		menu.getPopupMenu().addPopupMenuListener(new VisibilityPopupListener());
		JMenuItem okMenuItem = new JMenuItem(new OkVisibilityAction());
		JMenuItem cancelMenuItem = new JMenuItem(new CancelVisibilityAction());
		menu.addSeparator();
		menu.add(okMenuItem);
		menu.add(cancelMenuItem);
		rightMenuBar.add(menu);
		JMenu prefsMenu = new JMenu();
		prefsMenu.setMargin(new Insets(0, 0, 0, 0));
		prefsMenu.setIcon(userMenuIcon);
		prefsMenu.setMaximumSize(new Dimension(50,50));
		prefsMenu.add(new JMenuItem(new LoadPreferencesAction(this, 
					messageSource.getMessage("PageableTable.loadPreferences", null, "Load Preferences", Locale.getDefault()))));
		prefsMenu.add(new JMenuItem(new SavePreferencesAction(this, 
					messageSource.getMessage("PageableTable.savePreferences", null, "Save Preferences", Locale.getDefault()))));
		rightMenuBar.add(prefsMenu);
		rightMenuBar.add(Box.createVerticalGlue());
		// Add menu bar to right
		add (rightMenuBar, BorderLayout.EAST);
	}

	/**
	 * Create columns desciptors list for visibility menu
	 */
	private void createColumnDescriptos() {
		// get info about columns on table model
		columnDescriptors = new ArrayList<ColumnDescriptor>(tableModel.getPropertyCount());
		for (int i = 0; i < tableModel.getPropertyCount(); i++) {
			columnDescriptors.add(new ColumnDescriptor(tableModel.getColumnNames().get(i), 
					tableModel.getDisplayNames().get(i), true));
		}
	}
	

	/** 
	 * Handle sort changes in model sorter. 
	 * Query PageableDataSource for new page with the sort changes
	 * @see javax.swing.event.RowSorterListener#sorterChanged(javax.swing.event.RowSorterEvent)
	 */
	public void sorterChanged(RowSorterEvent e) {
		if (sorter.getSortKeys().size() > 0 && 
				tableModel.isPropertyColumn(sorter.getSortKeys().get(0).getColumn())) {
			// set first page
			configurePage();
			page.firstPage();
		}
	}

	/**
	 * Convert the Order from SortKey to Page.Order
	 * @param key the SortKey
	 * @return  the Page order
	 */
	private Page.Order converSortOrder(RowSorter.SortKey key) {
		Page.Order order = Order.ASC;
		if (key.getSortOrder() == SortOrder.DESCENDING) {
			order = Order.DESC;
		}
		return order;
	}
	
	/**
	 * Configure sort and order in page from sorter
	 */
	private void configurePage() {
		Page.Order order = Page.Order.ASC;
		String sortPropertyName = null;
		List<? extends SortKey> keys = sorter.getSortKeys();
		// If sorting, get values to set in page
		if (keys.size() > 0) {
			RowSorter.SortKey key = sorter.getSortKeys().get(0);
			if (tableModel.isPropertyColumn(key.getColumn())) {
				sortPropertyName = tableModel.getSortPropertyName(key.getColumn());
				order = converSortOrder(key);
			}
			
		}
		page.setSortName(sortPropertyName);
		page.setOrder(order);
	}
	
	/**
	 * Handle paginators changes.
	 * @see org.jdal.dao.PaginatorListener#pageChanged(org.jdal.dao.PageChangedEvent)
	 */
	public void pageChanged(PageChangedEvent event) {
		tableModel.setList(page.getData());
		fireChangeEvent();
	}
	
	/**
	 * Get a dialog for editing a row
	 */
	
	public Window getEditor() {
		if (editorName == null)
			return null;
		
		Window owner = SwingUtilities.getWindowAncestor(this);
		Window window;
		if (owner instanceof Frame) {
			window = (Window) guiFactory.getObject(editorName, new Object[] {owner});
		}
		else { 
			window = (Window) guiFactory.getObject(editorName);
		}
		
		if (window instanceof Editor) {
			Editor<T> editor = (Editor<T>) window;
			if (dataSource instanceof Dao && configureEditors) {
				editor.setPersistentService((Dao<T,?extends Serializable>) dataSource);
			}
			// add editor listeners
			for (EditorListener listener : editorListeners) {
				editor.addEditorListener(listener);
			}
		}
		
		return window;
	}
	
	/**
	 * @param toEdit model to edit
	 * @return model editor.
	 */
	public Window getEditor(Object toEdit) {
		Window dlg = openDialogs.get(toEdit);
		if (dlg == null) {
			dlg = getEditor();
			
			if (dlg == null)
				return null;
				
			openDialogs.put(toEdit, dlg);
			((View<Object>) dlg).setModel(toEdit);
			((View<Object>) dlg).refresh();
			dlg.addWindowListener(new DialogWindowListener());
			if (dlg instanceof Editor) {
				Editor<T> editor = (Editor<T>) dlg;
				editor.addEditorListener(new EditorListener() {
					
					public void modelChanged(EditorEvent e) {
						refresh();
					}
				});
			}
		}
		((View<T>) dlg).refresh();
		
		return dlg;
	}
	
	/**
	 * Restore TableState
	 */
	
	public void restoreState() {
		if (tableService != null) {
			TableState state = tableService.getState(getName());
			if (state != null)
				restoreState(state);
		}
	}
	
	/**
	 * Restore the column visibility from TableState
	 * @param state the table state
	 */
	public void restoreState(TableState state) {
		for (ColumnDescriptor cd : columnDescriptors) {
			cd.setVisible(state.getVisibleColumns().contains(cd.getPropertyName()));
		}
		updateVisibleColumns();
		
		if (paginatorView != null) {
			getPaginator().setPageSize(state.getPageSize());
		}
	}
	
	
	/**
	 * Update TableModel column model from columDescriptors 
	 */
	private void updateVisibleColumns() {
		List<String> displayNames = new ArrayList<String>(columnDescriptors.size());
		List<String> propertyNames = new ArrayList<String>(columnDescriptors.size());
		
		for (ColumnDescriptor cd : columnDescriptors) {
			if (cd.isVisible()) {
				displayNames.add(cd.getDisplayName());
				propertyNames.add(cd.getPropertyName());
			}
		}
		tableModel.setDisplayNames(displayNames);
		tableModel.setColumnNames(propertyNames);
		tableModel.init();
		table.setColumnModel(tableModel.getTableColumnModel());
		tableModel.fireTableChanged();
	}
	
	public void saveState() {
		if (tableService ==  null)
			return;  // nothing to do
		
		TableState state = new TableState();
		List<String> visible = new ArrayList<String>();
		for (ColumnDescriptor cd : columnDescriptors) {
			if (cd.isVisible())
				visible.add(cd.getPropertyName());
		}
		state.setName(getName());
		state.setVisibleColumns(visible);
		state.setPageSize(paginatorView.getPaginator().getPageSize());
		tableService.saveState(state);
	}

	public void addChangeListener(ChangeListener l) {
		if (!changeListeners.contains(l))
			changeListeners.add(l);
	}
	
	public void removeChangeListener(ChangeListener l) {
		changeListeners.remove(l);
	}
	
	/**
	 * Notify ChangeListeners that state change
	 */
	private void fireChangeEvent()  {
		ChangeEvent e = new ChangeEvent(this);
		
		for (ChangeListener l : changeListeners)
			l.stateChanged(e);
	}
	/**
	 * @return the paginatorView
	 */
	public PaginatorView getPaginatorView() {
		return paginatorView;
	}

	/**
	 * @param paginatorView the paginatorView to set
	 */
	public void setPaginatorView(PaginatorView paginatorView) {
		this.paginatorView = paginatorView;
	}

	/**
	 * @return the dataSource
	 */
	public PageableDataSource<T> getDataSource() {
		return dataSource;
	}

	/**
	 * @param dataSource the dataSource to set
	 */
	public void setDataSource(PageableDataSource<T> dataSource) {
		this.dataSource = dataSource;
		// review datasource duplication
		page.setPageableDataSource(dataSource);
	}
	
	public Paginator getPaginator() {
		return paginatorView.getPaginator();
	}

	/**
	 * @return the tableModel
	 */
	public ListTableModel getTableModel() {
		return tableModel;
	}

	/**
	 * @param tableModel the tableModel to set
	 */
	public void setTableModel(ListTableModel tableModel) {
		this.tableModel = tableModel;
	}
	
	
	public Icon getVisibilityMenuIcon() {
		return visibilityMenuIcon;
	}

	public void setVisibilityMenuIcon(Icon visibilityMenuIcon) {
		this.visibilityMenuIcon = visibilityMenuIcon;
	}

	public Icon getOkIcon() {
		return okIcon;
	}

	public void setOkIcon(Icon okIcon) {
		this.okIcon = okIcon;
	}

	public Icon getCancelIcon() {
		return cancelIcon;
	}

	public void setCancelIcon(Icon cancelIcon) {
		this.cancelIcon = cancelIcon;
	}
	
	/**
	 * Listener to watch visibility popup menu and sync visibility state
	 * whene popoup is cancelled externally.
	 * @author Jose Luis Martin - ([email protected])
	 */
	class VisibilityPopupListener implements PopupMenuListener {

		/** 
		 * {@inheritDoc}
		 * @see javax.swing.event.PopupMenuListener#popupMenuCanceled(javax.swing.event.PopupMenuEvent)
		 */
		public void popupMenuCanceled(PopupMenuEvent e) {
			visibilityBox.setColumnDescriptors(columnDescriptors);
			
		}

		/** 
		 * {@inheritDoc}
		 * @see javax.swing.event.PopupMenuListener#popupMenuWillBecomeInvisible(javax.swing.event.PopupMenuEvent)
		 */
		public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
		
		}

		/** 
		 * {@inheritDoc}
		 * @see javax.swing.event.PopupMenuListener#popupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent)
		 */
		public void popupMenuWillBecomeVisible(PopupMenuEvent e) {		
			visibilityBox.setColumnDescriptors(columnDescriptors);
		}
	}
	
	class OkVisibilityAction extends AbstractAction {

		private static final long serialVersionUID = 1L;

		public OkVisibilityAction() {
			super(messageSource.getMessage("Accept"), okIcon);
		}
		
		/**
		 * Copy visibility descriptors to table and change de ListableTableModel
		 * with new properties
		 * @param e ActionEvent the JButton ActionEvent
		 */
		public void actionPerformed(ActionEvent e) {
		
			for (int i = 0; i < columnDescriptors.size(); i++) {
				ColumnDescriptor cd = columnDescriptors.get(i);
				cd.setVisible(visibilityBox.getColumnDescriptors().get(i).isVisible());
			}
			updateVisibleColumns();
		}
	}
	
	class CancelVisibilityAction extends AbstractAction {
		
		private static final long serialVersionUID = 1L;

		public CancelVisibilityAction() {
			super(messageSource.getMessage("Cancel"), cancelIcon);
		}

		/**
		 * When cancel, set visibility descriptors from table 
		 * and discard selection changes.
		 * @param e ActionEvent from JButton
		 */
		public void actionPerformed(ActionEvent e) {
			visibilityBox.setColumnDescriptors(columnDescriptors);
			
		}
		
	}
	
	private class TableListener extends MouseAdapter {

		@Override
		public void mouseClicked(MouseEvent e) {
			Point point = e.getPoint();
			int row = table.rowAtPoint(point);
			int col = table.columnAtPoint(point);
			// check Actions
			if (col != -1 && row != -1 && tableModel.isActionColumn(col)) {
				TableRowAction action = (TableRowAction) tableModel.getValueAt(row, col);
				action.setTable(PageableTable.this);
				action.setRow(tableModel.getList().get(row));
				action.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "clicked"));
				
			}
			// check double click on rows
			if (row != -1 && e.getClickCount() == 2) {
				Object toEdit = tableModel.getList().get(row);
				Window dlg = getEditor(toEdit);
				if (dlg != null) {
					if (dlg instanceof Frame) {
						((Frame) dlg).setState(Frame.NORMAL);
						((Frame) dlg).requestFocus();
					}
					dlg.setLocationRelativeTo(null);
					dlg.setVisible(true);
				}
			}
		}
	}
	
	/**
	 * Remove dialogs from openDialog Map when closed. Will fail if the model 
	 * hashcode change after editing it.
	 *
	 * @author Jose Luis Martin - ([email protected])
	 */
	private class DialogWindowListener extends WindowAdapter {
		@Override
		public void windowClosed(WindowEvent e) {
			if (openDialogs.remove(((View<Object>) e.getWindow()).getModel()) == null)
				log.warn("Tray to remove a non existant Dialog, ¿may be model hashcode changed?");
		}
	}
	

	public GuiFactory getGuiFactory() {
		return guiFactory;
	}

	public void setGuiFactory(GuiFactory guiFactory) {
		this.guiFactory = guiFactory;
	}

	public String getEditorName() {
		return editorName;
	}

	public void setEditorName(String editorName) {
		this.editorName = editorName;
	}

	public void refresh() {
		page.setPage(page.getPage());
	}
	
	/**
	 * @return the filter
	 */
	public Object getFilter() {
		return page.getFilter();
	}

	/**
	 * @param filter the filter to set
	 */
	public void setFilter(Object filter) {
		page.setFilter(filter);
	}

	/**
	 * @param columnClass
	 * @param renderer
	 * @see javax.swing.JTable#setDefaultRenderer(java.lang.Class, javax.swing.table.TableCellRenderer)
	 */
	public void setDefaultRenderer(Class<?> columnClass, TableCellRenderer renderer) {
		table.setDefaultRenderer(columnClass, renderer);
	}
	
	public String getSortPropertyName() {
		return sortPropertyName;
	}

	public void setSortPropertyName(String sortPropertyName) {
		this.sortPropertyName = sortPropertyName;
	}

	public Page.Order getOrder() {
		return order;
	}

	public void setOrder(Page.Order order) {
		this.order = order;
	}

	/**
	 * @return the tableService
	 */
	public TableService getTableService() {
		return tableService;
	}

	/**
	 * @param tableService the tableService to set
	 */
	public void setTableService(TableService tableService) {
		this.tableService = tableService;
	}

	/**
	 * @return the userMenuIcon
	 */
	public Icon getUserMenuIcon() {
		return userMenuIcon;
	}

	/**
	 * @param userMenuIcon the userMenuIcon to set
	 */
	public void setUserMenuIcon(Icon userMenuIcon) {
		this.userMenuIcon = userMenuIcon;
	}

	/**
	 * @return the messageSource
	 */
	public MessageSource getMessageSource() {
		return messageSource.getMessageSource();
	}

	/**
	 * @param messageSource the messageSource to set
	 */
	@Autowired
	public void setMessageSource(MessageSource messageSource) {
		this.messageSource.setMessageSource(messageSource);
	}

	/**
	 * @return the showMenu
	 */
	public boolean isShowMenu() {
		return showMenu;
	}

	/**
	 * @param showMenu the showMenu to set
	 */
	public void setShowMenu(boolean showMenu) {
		this.showMenu = showMenu;
	}

	/**
	 * @return List of checked keys
	 */
	public List<Serializable> getChecked() {
		return tableModel.getChecked();
	}

	/**
	 * @return model selected and visible in current page
	 */
	public List<T> getVisibleSelected() {
		return tableModel.getVisibleChecked();
	}

	/**
	 * Select all posible filtered results.
	 */
	public void selectAll() {
		Page<T> page = new Page<T>(Integer.MAX_VALUE);
		page.setFilter(this.page.getFilter());
		tableModel.check(dataSource.getKeys(page));
	}

	/**
	 *  Un select all selected 
	 */
	public void unSelectAll() {
		tableModel.uncheckAll();
	}

	/**
	 * @return the configureEditors
	 */
	public boolean isConfigureEditors() {
		return configureEditors;
	}

	/**
	 * @param configureEditors the configureEditors to set
	 */
	public void setConfigureEditors(boolean configureEditors) {
		this.configureEditors = configureEditors;
	}

	public void addEditorListener(EditorListener listener) {
		if (!editorListeners.contains(listener))
			editorListeners.add(listener);
	}
	
	public void removeEditorListener(EditorListener listener) {
		editorListeners.remove(listener);
	}

	/**
	 * @return the showPaginator
	 */
	public boolean isShowPaginator() {
		return showPaginator;
	}

	/**
	 * @param showPaginator the showPaginator to set
	 */
	public void setShowPaginator(boolean showPaginator) {
		this.showPaginator = showPaginator;
	}

	/**
	 * @return the page size
	 * @see org.jdal.dao.Page#getPageSize()
	 */
	public int getPageSize() {
		return page.getPageSize();
	}

	/**
	 * @param pageSize
	 * @see org.jdal.dao.Page#setPageSize(int)
	 */
	public void setPageSize(int pageSize) {
		page.setPageSize(pageSize);
	}
}

/**
 * Simple data model for description of table column in visibility menu
 * 
 * @author Jose Luis Martin - ([email protected])
 */
class ColumnDescriptor  implements Cloneable {
	private String propertyName;
	private String displayName;
	private boolean visible;

	
	
	/**
	 * Create a new ColumnDescriptor
	 * @param propertyName
	 * @param displayName
	 * @param visible
	 */
	public ColumnDescriptor(String propertyName, String displayName,
			boolean visible) {
		this.propertyName = propertyName;
		this.displayName = displayName;
		this.visible = visible;
	}


	public String getPropertyName() {
		return propertyName;
	}


	public void setPropertyName(String propertyName) {
		this.propertyName = propertyName;
	}


	public String getDisplayName() {
		return displayName;
	}


	public void setDisplayName(String displayName) {
		this.displayName = displayName;
	}


	public boolean isVisible() {
		return visible;
	}


	public void setVisible(boolean visible) {
		this.visible = visible;
	}


	/**
	 * Clone this object
	 */
	@Override
	public Object clone()  {
		try {
			return super.clone();
		} catch (CloneNotSupportedException e) {
			return null;
		}
	}
}

/**
 * Simple Box Component that hold a ColumnDescriptor and a checkbox
 * to show visibility state of a table column
 * 
 * @author Jose Luis Martin - ([email protected])
 */
class VisibilityItem extends JComponent implements ChangeListener {
	
	private static final long serialVersionUID = 1L;
	private ColumnDescriptor cd;
	private JCheckBox check;
	
	/**
	 * @param cd
	 */
	public VisibilityItem(ColumnDescriptor cd) {
		this.cd = cd;
		BoxLayout layout = new BoxLayout(this, BoxLayout.LINE_AXIS);
		setLayout(layout);
		this.setAlignmentX(0f);
		check = new JCheckBox(cd.getDisplayName(), cd.isVisible());
		check.setAlignmentX(0f);
		add(check);
		check.addChangeListener(this);
		refresh();
	}
	
	public void refresh() {
		check.setSelected(cd.isVisible());
	}
	
	/**
	 * Update the columnDescriptor when check change
	 * @param e the ChangeEvent
	 */
	public void stateChanged(ChangeEvent e) {
		cd.setVisible(check.isSelected());
		
	}

	/**
	 * @param columnDescriptor
	 */
	public void setColumnDescriptor(ColumnDescriptor columnDescriptor) {
		this.cd = columnDescriptor;
		refresh();
		
	}
}

/**
 * Box to show visibility column state in a JMenu popup window.
 *  
 * @author Jose Luis Martin - ([email protected])
 */
class VisibilityBox extends JComponent {
	
	private static final long serialVersionUID = 1L;
	private final static Log log = LogFactory.getLog(VisibilityBox.class);
	/** column descriptors copy */
	List<ColumnDescriptor> columnDescriptors;

	/**
	 * Create a new VisiblityBox initialized with column descriptors
	 * @param columnDescriptors column descriptors
	 */
	public VisibilityBox(List<ColumnDescriptor> columnDescriptors) {
		setColumnDescriptors(columnDescriptors);
		setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
		for (ColumnDescriptor cd : this.columnDescriptors) {
			add(new VisibilityItem(cd));
		}
	}

	/**
	 * refresh the state of visibility item when column descriptor list changes
	 */
	public void refresh() {
		for (int i = 0; i < getComponents().length; i++) {
			Component c = getComponent(i);
			if (c instanceof VisibilityItem) {
				if (log.isDebugEnabled())
					log.debug("refresh: " + columnDescriptors.get(i).isVisible());
				
				((VisibilityItem) c).setColumnDescriptor(columnDescriptors.get(i));
			}
		}
	}

	/**
	 * get the column descriptors
	 * @return the column descriptor list
	 */
	public List<ColumnDescriptor> getColumnDescriptors() {
		return columnDescriptors;
	}

	/**
	 * Sets the column descriptor list. 
	 * We clone all column descriptor list to easy discard changes when the 
	 * user cancel changes to visibility column state. 
	 * @param columnDescriptors
	 */
	public void setColumnDescriptors(List<ColumnDescriptor> columnDescriptors) {
		this.columnDescriptors = new ArrayList<ColumnDescriptor>(columnDescriptors.size());
		for (ColumnDescriptor cd : columnDescriptors) {
			this.columnDescriptors.add((ColumnDescriptor) cd.clone());
		}
		refresh();
	}
}