/*******************************************************************************
 * Copyright (c) 2004 Actuate Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *  Actuate Corporation  - initial API and implementation
 *******************************************************************************/

package org.eclipse.birt.report.designer.data.ui.dataset;

import java.util.ArrayList;
import java.util.Iterator;

import org.eclipse.birt.report.designer.data.ui.util.Utility;
import org.eclipse.birt.report.designer.internal.ui.util.ExceptionHandler;
import org.eclipse.birt.report.designer.nls.Messages;
import org.eclipse.birt.report.designer.util.DEUtil;
import org.eclipse.birt.report.model.api.ColumnHintHandle;
import org.eclipse.birt.report.model.api.DataSetHandle;
import org.eclipse.birt.report.model.api.PropertyHandle;
import org.eclipse.birt.report.model.api.ResultSetColumnHandle;
import org.eclipse.birt.report.model.api.StructureFactory;
import org.eclipse.birt.report.model.api.activity.SemanticException;
import org.eclipse.birt.report.model.api.elements.DesignChoiceConstants;
import org.eclipse.birt.report.model.api.elements.structures.ColumnHint;

import org.eclipse.birt.report.model.api.elements.structures.ResultSetColumn;
import org.eclipse.birt.report.model.api.metadata.IChoiceSet;
import org.eclipse.birt.report.model.api.util.StringUtil;
import org.eclipse.jface.util.Assert;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ComboBoxCellEditor;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.accessibility.ACC;
import org.eclipse.swt.accessibility.AccessibleAdapter;
import org.eclipse.swt.accessibility.AccessibleControlAdapter;
import org.eclipse.swt.accessibility.AccessibleControlEvent;
import org.eclipse.swt.accessibility.AccessibleEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;

/**
 * 
 */

public class ResultSetColumnPage extends Composite
{

	public interface ITreeRefreshListener
	{

		void treeChanged( );
	}

	private static class ResultSetColumnModel
	{

		private String columnName;
		private String alias;
		private int dataType = 0;
		private String displayName;
		private String helpText;

		public boolean equals( Object obj )
		{
			if ( this == obj )
			{
				return true;
			}
			if ( obj instanceof ResultSetColumnModel )
			{
				ResultSetColumnModel model = (ResultSetColumnModel) obj;
				if ( columnName == null )
				{
					return model.columnName == null;
				}
				return columnName.equals( model.columnName );
			}
			return false;
		}
	}

	private static final ResultSetColumnModel dummyModel = new ResultSetColumnModel( );

	private static final IChoiceSet DATA_TYPE_CHOICE_SET = DEUtil.getMetaDataDictionary( )
			.getStructure( ResultSetColumn.RESULT_SET_COLUMN_STRUCT)
			.getMember( ResultSetColumn.DATA_TYPE_MEMBER )
			.getAllowedChoices( );

	private static final String[] allowedDataTypes = new String[]{
			DesignChoiceConstants.COLUMN_DATA_TYPE_ANY,
			DesignChoiceConstants.COLUMN_DATA_TYPE_DATETIME,
			DesignChoiceConstants.COLUMN_DATA_TYPE_DECIMAL,
			DesignChoiceConstants.COLUMN_DATA_TYPE_FLOAT,
			DesignChoiceConstants.COLUMN_DATA_TYPE_INTEGER,
			DesignChoiceConstants.COLUMN_DATA_TYPE_STRING
	};

	private static String COLUMN_NAME = Messages.getString( "dataset.editor.title.name" ); //$NON-NLS-1$
	private static String COLUMN_DATA_TYPE = Messages.getString( "dataset.editor.title.type" ); //$NON-NLS-1$
	private static String COLUMN_ALIAS = Messages.getString( "dataset.editor.title.alias" ); //$NON-NLS-1$
	private static String COLUMN_DISPLAY_NAME = Messages.getString( "dataset.editor.title.displayName" ); //$NON-NLS-1$

	private static String ERROR_DLG_TITLE = "Invalid input";
	private static String ERROR_MSG_EMPTY_COLUMN_NAME = "The column name cannot be empty";
	private static String ERROR_MSG_DUPLICATED_NAME = "This name has been used by another column.";

	// TODO Add support to helpText after release 1
	// private static String COLUMN_HELP_TEXT = Messages.getString(
	// "dataset.editor.title.helpText" ); //$NON-NLS-1$

	private ArrayList columnList = new ArrayList( );

	private ArrayList renameRecord = new ArrayList( );

	private ArrayList listenerList = new ArrayList( );

	private IStructuredContentProvider contentProvider = new IStructuredContentProvider( ) {

		public void dispose( )
		{
		}

		public void inputChanged( Viewer viewer, Object oldInput,
				Object newInput )
		{
		}

		public Object[] getElements( Object inputElement )
		{
			ArrayList list = ( (ArrayList) inputElement );
			ArrayList elementsList = new ArrayList( list );
			elementsList.add( dummyModel );
			return elementsList.toArray( );
		}
	};

	private ITableLabelProvider labelProvider = new ITableLabelProvider( ) {

		public Image getColumnImage( Object element, int columnIndex )
		{
			return null;
		}

		public String getColumnText( Object element, int columnIndex )
		{
			ResultSetColumnModel model = (ResultSetColumnModel) element;
			String text = null;
			if ( model != dummyModel )
			{
				switch ( columnIndex )
				{
					case 1 :
						text = model.columnName;
						break;
					case 2 :
						text = DATA_TYPE_CHOICE_SET.findChoice( allowedDataTypes[model.dataType] )
								.getDisplayName( );
						break;
					case 3 :
						text = model.alias;
						break;
					case 4 :
						text = model.displayName;
						break;
				}
			}
			else
			{
				if ( columnIndex == 1 )
				{
					text = "<New column here...>";
				}
			}
			return Utility.convertToGUIString( text );
		}

		public void addListener( ILabelProviderListener listener )
		{
		}

		public void dispose( )
		{
		}

		public boolean isLabelProperty( Object element, String property )
		{
			return false;
		}

		public void removeListener( ILabelProviderListener listener )
		{
		}

	};

	private ICellModifier cellModifier = new ICellModifier( ) {

		public boolean canModify( Object element, String property )
		{
			if ( property == null )
			{
				return false;
			}
			if ( element == dummyModel && !property.equals( COLUMN_NAME ) )
			{
				return false;
			}
			return true;
		}

		public Object getValue( Object element, String property )
		{
			ResultSetColumnModel model = (ResultSetColumnModel) element;
			if ( COLUMN_DATA_TYPE.equals( property ) )
			{
				return new Integer( model.dataType );
			}
			String value = null;
			if ( COLUMN_NAME.equals( property ) )
			{
				if ( model != dummyModel )
				{
					// String prefix = value = "NewColumn";
					// if ( containColumn( null, value, true ) )
					// {
					// for ( int i = 1; i < columnList.size( ); i++ )
					// {
					//
					// value = prefix + i;
					// if ( !containColumn( null, value, true ) )
					// {
					// break;
					// }
					// }
					// }
					// }
					// else
					// {
					value = model.columnName;
				}
			}
			else if ( COLUMN_ALIAS.equals( property ) )
			{
				value = model.alias;
			}
			else if ( COLUMN_DISPLAY_NAME.equals( property ) )
			{
				value = model.displayName;
			}
			return Utility.convertToGUIString( value );
		}

		public void modify( Object element, String property, Object newValue )
		{
			if ( element instanceof Item )
			{
				element = ( (Item) element ).getData( );
			}
			ResultSetColumnModel model = (ResultSetColumnModel) element;
			if ( COLUMN_DATA_TYPE.equals( property ) )
			{
				model.dataType = ( (Integer) newValue ).intValue( );
			}
			else
			{
				String value = (String) newValue;
				if ( COLUMN_NAME.equals( property )
						&& StringUtil.isBlank( value ) )
				{
					ExceptionHandler.openMessageBox( ERROR_DLG_TITLE,
							ERROR_MSG_EMPTY_COLUMN_NAME,
							SWT.ICON_INFORMATION );
					return;
				}
				if ( COLUMN_NAME.equals( property )
						|| COLUMN_ALIAS.equals( property ) )
				{
					value = Utility.convertToModelString( value, true );
					if ( containColumn( model, value, true ) )
					{
						ExceptionHandler.openMessageBox( ERROR_DLG_TITLE,
								ERROR_MSG_DUPLICATED_NAME,
								SWT.ICON_INFORMATION );
						return;
					}
				}
				if ( model == dummyModel )
				{
					model = new ResultSetColumnModel( );
					columnList.add( model );
				}
				if ( COLUMN_NAME.equals( property ) )
				{
					addRenameRecord( model.columnName, value );
					model.columnName = value;
				}
				else if ( COLUMN_ALIAS.equals( property ) )
				{
					model.alias = value;
				}
				else if ( COLUMN_DISPLAY_NAME.equals( property ) )
				{
					model.displayName = value;
				}
			}
			updateTable( );
			updateButtons( );
		}

	};

	private TableViewer columnTable;

	private Button up, down, delete;

	/**
	 * @param parent
	 * @param style
	 */
	public ResultSetColumnPage( Composite parent, int style )
	{
		super( parent, style );
		setLayout( new GridLayout( 2, false ) );
		GridData gd = new GridData( GridData.FILL_BOTH );
		gd.widthHint = 650;
		setLayoutData( gd );
		createTableArea( );
		createButtonArea( );
		initAccessible( );
	}

	private void createTableArea( )
	{
		Table table = new Table( this, SWT.SINGLE
				| SWT.FULL_SELECTION
				| SWT.BORDER );
		table.setLayoutData( new GridData( GridData.FILL_BOTH ) );
		table.setLinesVisible( true );
		table.setHeaderVisible( true );
		table.addKeyListener( new KeyAdapter( ) {

			/**
			 * @see org.eclipse.swt.events.KeyAdapter#keyReleased(org.eclipse.swt.events.KeyEvent)
			 */
			public void keyReleased( KeyEvent e )
			{
				// If Delete pressed, delete the selected row
				if ( e.keyCode == SWT.DEL )
				{
					buttonPressed( SWT.DEL );
				}
			}

		} );

		String[] columns = new String[]{
				null,
				COLUMN_NAME,
				COLUMN_DATA_TYPE,
				COLUMN_ALIAS,
				COLUMN_DISPLAY_NAME
		};
		int[] columnWidth = new int[]{
				20, 100, 100, 100, 100
		};
		String[] displayDataTypes = new String[allowedDataTypes.length];
		for ( int i = 0; i < displayDataTypes.length; i++ )
		{
			displayDataTypes[i] = DATA_TYPE_CHOICE_SET.findChoice( allowedDataTypes[i] )
					.getDisplayName( );
		}

		ComboBoxCellEditor comboCell = new ComboBoxCellEditor( table,
				displayDataTypes,
				SWT.READ_ONLY );
		CellEditor[] cellEditors = new CellEditor[]{
				null,
				new TextCellEditor( table ),
				comboCell,
				new TextCellEditor( table ),
				new TextCellEditor( table )
		};

		for ( int i = 0; i < columns.length; i++ )
		{
			TableColumn column = new TableColumn( table, SWT.LEFT );
			column.setResizable( columns[i] != null );
			if ( columns[i] != null )
			{
				column.setText( columns[i] );
			}
			column.setWidth( columnWidth[i] );
		}
		columnTable = new TableViewer( table );
		columnTable.setCellEditors( cellEditors );
		columnTable.setColumnProperties( columns );
		columnTable.setContentProvider( contentProvider );
		columnTable.setLabelProvider( labelProvider );
		columnTable.setCellModifier( cellModifier );
		columnTable.setInput( columnList );
		columnTable.addSelectionChangedListener( new ISelectionChangedListener( ) {

			public void selectionChanged( SelectionChangedEvent event )
			{
				updateButtons( );
			}

		} );

	}

	private void createButtonArea( )
	{
		Composite composite = new Composite( this, SWT.NONE );
		composite.setLayoutData( new GridData( GridData.VERTICAL_ALIGN_CENTER ) );
		composite.setLayout( Utility.createGridLayoutWithoutMargin( ) );

		up = new Button( composite, SWT.ARROW | SWT.UP );
		GridData gd = new GridData( GridData.VERTICAL_ALIGN_CENTER );
		gd.widthHint = gd.heightHint = 20;
		up.setLayoutData( gd );
		up.addSelectionListener( new SelectionAdapter( ) {

			public void widgetSelected( SelectionEvent e )
			{
				buttonPressed( SWT.UP );
			}

		} );

		delete = new Button( composite, SWT.PUSH );
		gd = new GridData( GridData.VERTICAL_ALIGN_CENTER );
		gd.widthHint = gd.heightHint = 20;
		delete.setLayoutData( gd );
		delete.setImage( PlatformUI.getWorkbench( )
				.getSharedImages( )
				.getImage( ISharedImages.IMG_TOOL_DELETE ) );
		delete.addSelectionListener( new SelectionAdapter( ) {

			public void widgetSelected( SelectionEvent e )
			{
				buttonPressed( SWT.DEL );
			}

		} );

		down = new Button( composite, SWT.ARROW | SWT.DOWN );
		gd = new GridData( GridData.VERTICAL_ALIGN_CENTER );
		gd.widthHint = gd.heightHint = 20;
		down.setLayoutData( gd );
		down.addSelectionListener( new SelectionAdapter( ) {

			public void widgetSelected( SelectionEvent e )
			{
				buttonPressed( SWT.DOWN );
			}

		} );

	}

	private void buttonPressed( int buttonId )
	{
		ResultSetColumnModel model = getSelectedColumn( );
		int index = columnList.indexOf( model );
		columnList.remove( index );
		switch ( buttonId )
		{
			case SWT.UP :
				columnList.add( index - 1, model );
				break;
			case SWT.DOWN :
				columnList.add( index + 1, model );
				break;
		}
		updateTable( );
		updateButtons( );
	}

	public void setInput( DataSetHandle handle )
	{
		if ( handle != null )
		{
			for ( Iterator iter = handle.getPropertyHandle( DataSetHandle.RESULT_SET_HINTS_PROP )
					.iterator( ); iter.hasNext( ); )
			{
				ResultSetColumnHandle columnHandle = (ResultSetColumnHandle) iter.next( );
				ResultSetColumnModel model = new ResultSetColumnModel( );
				model.columnName = columnHandle.getColumnName( );
				for ( int i = 0; i < allowedDataTypes.length; i++ )
				{
					if ( allowedDataTypes[i].equals( columnHandle.getDataType( ) ) )
					{
						model.dataType = i;
						break;
					}
				}
			}
			for ( Iterator iter = handle.getPropertyHandle( DataSetHandle.COLUMN_HINTS_PROP )
					.iterator( ); iter.hasNext( ); )
			{
				ColumnHintHandle hintHandle = (ColumnHintHandle) iter.next( );
				int index = columnList.indexOf( hintHandle.getColumnName( ) );
				if ( index != -1 )
				{
					ResultSetColumnModel model = (ResultSetColumnModel) columnList.get( index );
					model.alias = hintHandle.getAlias( );
					model.displayName = hintHandle.getDisplayName( );
					model.helpText = hintHandle.getHelpText( );
				}
			}
			updateTable( );
		}
	}

	/**
	 * 
	 * @param handle
	 */
	public void saveResult( DataSetHandle handle )
	{
		Assert.isNotNull( handle );
		PropertyHandle resultSetPropertyHandle = handle.getPropertyHandle( DataSetHandle.RESULT_SET_HINTS_PROP );
		PropertyHandle columnHintPropertyHandle = handle.getPropertyHandle( DataSetHandle.COLUMN_HINTS_PROP );
		try
		{
			// Remove all defined columns
			resultSetPropertyHandle.setStringValue( null );
			ArrayList removeList = new ArrayList( );
			// Apply new name
			for ( Iterator iter = columnHintPropertyHandle.iterator( ); iter.hasNext( ); )
			{
				ColumnHintHandle columnHintHandle = (ColumnHintHandle) iter.next( );
				String newName = getNewName( columnHintHandle.getColumnName( ) );
				if ( newName != null )
				{
					columnHintHandle.setColumnName( newName );
				}
				if ( !containColumn( null,
						columnHintHandle.getColumnName( ),
						false ) )
				{
					removeList.add( columnHintHandle );
				}
			}
			// Remove all deleted column hint
			columnHintPropertyHandle.removeItems( removeList );

			Iterator iter = columnList.iterator( );
			for ( int i = 0; iter.hasNext( ); i++ )
			{
				ResultSetColumnModel model = (ResultSetColumnModel) iter.next( );
				ResultSetColumn column = StructureFactory.createResultSetColumn( );
				column.setColumnName( model.columnName );
				column.setDataType( allowedDataTypes[model.dataType] );
				column.setPosition( new Integer( i ) );
				resultSetPropertyHandle.addItem( column );
				ColumnHintHandle columnHintHandle = null;
				for ( Iterator hintIter = columnHintPropertyHandle.iterator( ); hintIter.hasNext( ); )
				{
					columnHintHandle = (ColumnHintHandle) hintIter.next( );
					if ( columnHintHandle.getColumnName( )
							.equals( model.columnName ) )
					{
						break;
					}
					columnHintHandle = null;
				}
				if ( columnHintHandle == null )
				{
					ColumnHint columnHint = StructureFactory.createColumnHint( );
					columnHint.setProperty( ColumnHint.COLUMN_NAME_MEMBER,
							model.columnName );
					columnHintHandle = (ColumnHintHandle) columnHintPropertyHandle.addItem( columnHint );
				}
				columnHintHandle.setAlias( model.alias );
				columnHintHandle.setDisplayName( model.displayName );
				columnHintHandle.setHelpText( model.helpText );
			}

		}
		catch ( SemanticException e )
		{
			ExceptionHandler.handle( e );
		}
	}

	public boolean isEmpty( )
	{
		return columnList.isEmpty( );
	}

	private void addRenameRecord( String oldName, String newName )
	{
		if ( oldName == null )
		{
			return;
		}
		for ( Iterator iter = renameRecord.iterator( ); iter.hasNext( ); )
		{
			String[] record = (String[]) iter.next( );
			if ( record[1].equals( oldName ) )
			{
				record[1] = newName;
				return;
			}
		}
		renameRecord.add( new String[]{
				oldName, newName
		} );
	}

	private String getNewName( String oldName )
	{
		for ( Iterator iter = renameRecord.iterator( ); iter.hasNext( ); )
		{
			String[] record = (String[]) iter.next( );
			if ( record[0].equals( oldName ) )
			{
				return record[1];
			}
		}
		return null;
	}

	private boolean containColumn( ResultSetColumnModel currentModel,
			String newName, boolean includingAlias )
	{
		if ( newName == null )
		{
			return false;
		}
		for ( Iterator iter = columnList.iterator( ); iter.hasNext( ); )
		{
			ResultSetColumnModel model = (ResultSetColumnModel) iter.next( );
			if ( model == currentModel )
			{
				continue;
			}
			if ( newName.equals( model.columnName )
					|| ( includingAlias && newName.equals( model.alias ) ) )
			{
				return true;
			}
		}
		return false;
	}

	private ResultSetColumnModel getSelectedColumn( )
	{
		return (ResultSetColumnModel) ( (IStructuredSelection) columnTable.getSelection( ) ).getFirstElement( );
	}

	public void addTreeRefreshListener( ITreeRefreshListener listener )
	{
		if ( !listenerList.contains( listener ) )
		{
			listenerList.add( listener );
		}
	}

	public void updateTable( )
	{
		columnTable.refresh( );
		for ( Iterator iter = listenerList.iterator( ); iter.hasNext( ); )
		{
			( (ITreeRefreshListener) iter.next( ) ).treeChanged( );
		}
	}

	private void updateButtons( )
	{
		boolean upEnabled, downEnabled, deleteEnabled;
		upEnabled = downEnabled = deleteEnabled = false;
		if ( !columnTable.getSelection( ).isEmpty( ) )
		{
			ResultSetColumnModel model = getSelectedColumn( );
			int selectedIndex = columnList.indexOf( model );
			if ( selectedIndex != -1 )
			{
				upEnabled = ( selectedIndex != 0 );
				downEnabled = ( selectedIndex != columnList.size( ) - 1 );
				deleteEnabled = true;
			}
		}
		up.setEnabled( upEnabled );
		down.setEnabled( downEnabled );
		delete.setEnabled( deleteEnabled );
	}

	/**
	 * make custom control accessible
	 * 
	 */
	void initAccessible( )
	{
		getAccessible( ).addAccessibleListener( new AccessibleAdapter( ) {

			public void getHelp( AccessibleEvent e )
			{
				e.result = getToolTipText( );
			}
		} );

		getAccessible( ).addAccessibleControlListener( new AccessibleControlAdapter( ) {

			public void getChildAtPoint( AccessibleControlEvent e )
			{
				Point testPoint = toControl( new Point( e.x, e.y ) );
				if ( getBounds( ).contains( testPoint ) )
				{
					e.childID = ACC.CHILDID_SELF;
				}
			}

			public void getLocation( AccessibleControlEvent e )
			{
				Rectangle location = getBounds( );
				Point pt = toDisplay( new Point( location.x, location.y ) );
				e.x = pt.x;
				e.y = pt.y;
				e.width = location.width;
				e.height = location.height;
			}

			public void getChildCount( AccessibleControlEvent e )
			{
				e.detail = 0;
			}

			public void getRole( AccessibleControlEvent e )
			{
				e.detail = ACC.ROLE_COMBOBOX;
			}

			public void getState( AccessibleControlEvent e )
			{
				e.detail = ACC.STATE_NORMAL;
			}
		} );
	}
}
