/*******************************************************************************
 * 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.internal.ui.views.attributes.page;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.eclipse.birt.report.designer.core.model.SessionHandleAdapter;
import org.eclipse.birt.report.designer.internal.ui.views.attributes.widget.ExpressionComposite;
import org.eclipse.birt.report.designer.nls.Messages;
import org.eclipse.birt.report.designer.ui.dialogs.ExpressionProvider;
import org.eclipse.birt.report.engine.api.EngineConfig;
import org.eclipse.birt.report.engine.api.ReportEngine;
import org.eclipse.birt.report.model.api.ColumnHandle;
import org.eclipse.birt.report.model.api.CommandStack;
import org.eclipse.birt.report.model.api.DesignElementHandle;
import org.eclipse.birt.report.model.api.GroupElementFactory;
import org.eclipse.birt.report.model.api.HideRuleHandle;
import org.eclipse.birt.report.model.api.PropertyHandle;
import org.eclipse.birt.report.model.api.ReportItemHandle;
import org.eclipse.birt.report.model.api.RowHandle;
import org.eclipse.birt.report.model.api.StructureFactory;
import org.eclipse.birt.report.model.api.activity.NotificationEvent;
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.HideRule;
import org.eclipse.birt.report.model.api.metadata.PropertyValueException;
import org.eclipse.birt.report.model.elements.interfaces.IReportItemModel;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
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.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;

/**
 * The visibility attribute page of DE element. Note: Visibility Not support
 * multi-selection.
 */
public class VisibilityPage extends AttributePage
{

	/**
	 * The display title for all-outputs selection
	 */
	private static final String All_Title = Messages.getString( "VisibilityPage.Label.DetailAll" ); //$NON-NLS-1$

	/**
	 * The display title for specific-outputs selection
	 */
	private static final String Spec_Title = Messages.getString( "VisibilityPage.Label.DetailSpecific" ); //$NON-NLS-1$

	/**
	 * The CheckBox for "Hide Element"
	 */
	Button hideCheckbox;

	/**
	 * The Radio Button for "For all outputs" and "For Specific outputs...".
	 */
	Button allRadio, specRadio;

	/**
	 * The Group contains output expressions.
	 */
	Group group;

	/**
	 * The container for "For all outputs"/"For Specific outputs..." controls
	 */
	Composite allContainer, specContainer;

	/**
	 * ExpressionBuilder for "For all outputs","HTML","PDF".
	 */
	ExpressionComposite allExpression;
	HashMap specExpressions;

	/**
	 * CheckBoxes for HTML and PDF output type.
	 */
	HashMap specCheckButtons;

	/**
	 * The SelectionListener.
	 */
	SelectionListener listener;

	/**
	 * The supported output formats
	 */
	String[] typeInfo;

	/**
	 * Constructor
	 * 
	 * @param parent
	 *            A widget which will be the parent of the new instance (cannot
	 *            be null)
	 * @param style
	 *            The style of widget to construct
	 */
	public VisibilityPage( Composite parent, int style )
	{
		super( parent, style );
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.birt.report.designer.internal.ui.views.attributes.page.AttributePage#buildUI()
	 */
	protected void buildUI( )
	{
		listener = new SelectionAdapter( ) {

			public void widgetSelected( SelectionEvent e )
			{
				setOutputEnable( hideCheckbox.getSelection( ) );
				if ( hideCheckbox.getSelection( ) )
				{
					saveHideInfo( );
				}
				else
				{
					for ( Iterator iter = input.iterator( ); iter.hasNext( ); )
					{
						DesignElementHandle handle = (DesignElementHandle) iter.next( );
						clearProperty( handle );
					}
				}
			}
		};

		setLayout( WidgetUtil.createGridLayout( 4 ) );

		// creates "Hide Element" CheckBox
		hideCheckbox = new Button( this, SWT.CHECK );
		hideCheckbox.setText( Messages.getString( "VisibilityPage.Check.HideElement" ) ); //$NON-NLS-1$
		GridData data = new GridData( );
		// data.verticalSpan = 4;
		data.verticalAlignment = GridData.BEGINNING;
		hideCheckbox.setLayoutData( data );
		hideCheckbox.addSelectionListener( listener );
		WidgetUtil.createGridPlaceholder( this, 3, true );
		WidgetUtil.createHorizontalLine( this, 4 );

		// // /creates separator label
		// Label separator = new Label( this, SWT.SEPARATOR | SWT.VERTICAL );
		// data = new GridData( );
		// data.verticalSpan = 4;
		// data.verticalAlignment = GridData.FILL;
		// separator.setLayoutData( data );

		// creates "For all outputs" Radio button
		allRadio = new Button( this, SWT.RADIO );
		allRadio.setText( Messages.getString( "VisibilityPage.Radio.AllOutputs" ) ); //$NON-NLS-1$
		allRadio.addSelectionListener( listener );
		WidgetUtil.createGridPlaceholder( this, 3, true );

		// creates "For Specific outputs..." Radio button
		specRadio = new Button( this, SWT.RADIO );
		specRadio.setText( Messages.getString( "VisibilityPage.Radio.SpecificOutputs" ) ); //$NON-NLS-1$
		WidgetUtil.createGridPlaceholder( this, 3, true );

		// creates the group container
		group = new Group( this, SWT.NONE );
		group.setLayout( new StackLayout( ) );
		data = new GridData( );
		data.verticalSpan = 4;
		data.verticalAlignment = GridData.FILL;
		data.grabExcessHorizontalSpace = true;
		data.horizontalAlignment = GridData.FILL;
		group.setLayoutData( data );

		allContainer = buildUIForAlloutput( group );
		specContainer = buildUIForSpecific( group );

		// sets default output type
		allRadio.setSelection( true );
		allContainer.setVisible( false );
		specContainer.setVisible( false );
		setOutputInfo( );
	}

	/**
	 * Builds UI control for "For all outputs" type
	 * 
	 * @param parent
	 *            A widget which will be the parent of the new instance (cannot
	 *            be null)
	 * @return The container contains "For all outputs"-relative controls.
	 */
	private Composite buildUIForAlloutput( Composite parent )
	{
		Composite container = new Composite( parent, SWT.NONE );
		GridLayout layout = new GridLayout( 2, false );
		container.setLayout( layout );
		Label label = new Label( container, SWT.NONE );
		label.setText( Messages.getString( "VisibilityPage.Label.Expression" ) ); //$NON-NLS-1$
		allExpression = new ExpressionComposite( container );
		allExpression.addListener( SWT.Modify, new Listener( ) {

			public void handleEvent( Event event )
			{
				saveHideInfo( );
			}
		} );
		GridData data = new GridData( );
		data.grabExcessHorizontalSpace = true;
		data.horizontalAlignment = GridData.FILL;
		allExpression.setLayoutData( data );
		return container;
	}

	/**
	 * Builds UI control for "For Specific outputs..." type
	 * 
	 * @param parent
	 *            A widget which will be the parent of the new instance (cannot
	 *            be null)
	 * @return The container contains "For Specific outputs..."-relative
	 *         controls.
	 */
	private Composite buildUIForSpecific( Composite parent )
	{
		Composite container = new Composite( parent, SWT.NONE );
		GridLayout layout = new GridLayout( 3, false );
		container.setLayout( layout );

		ReportEngine engine = new ReportEngine( new EngineConfig( ) );
		typeInfo = engine.getSupportedFormats( );
		specCheckButtons = new HashMap( );
		specExpressions = new HashMap( );

		for ( int i = 0; i < typeInfo.length; i++ )
		{
			Button btn = new Button( container, SWT.CHECK );
			btn.setText( typeInfo[i] );
			btn.setSelection( false );

			Label label = new Label( container, SWT.NONE );
			label.setText( Messages.getString( "VisibilityPage.Label.Expression" ) ); //$NON-NLS-1$

			ExpressionComposite expression = new ExpressionComposite( container );
			GridData data = new GridData( );
			data.grabExcessHorizontalSpace = true;
			data.horizontalAlignment = GridData.FILL;
			expression.setLayoutData( data );

			specCheckButtons.put( typeInfo[i], btn );
			specExpressions.put( typeInfo[i], expression );

			btn.addSelectionListener( listener );
			expression.addListener( SWT.Modify, new Listener( ) {

				public void handleEvent( Event event )
				{
					saveHideInfo( );
				}
			} );
		}

		return container;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.birt.report.designer.internal.ui.views.attributes.page.TabPage#setInput(java.util.List)
	 */
	public void setInput( List elements )
	{
		resetUI( );
		super.setInput( elements );

	}

	private void setExpressionProvider( DesignElementHandle handle )
	{
		ExpressionProvider provider = new ExpressionProvider( handle );
		allExpression.setExpressionProvider( provider );
		for ( int i = 0; i < typeInfo.length; i++ )
		{
			( (ExpressionComposite) specExpressions.get( typeInfo[i] ) ).setExpressionProvider( provider );
		}
	}

	public void elementChanged( DesignElementHandle focus, NotificationEvent ev )
	{
		refreshValues( null );
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.birt.report.designer.internal.ui.views.attributes.page.AttributePage#refreshValues(java.util.Set)
	 */
	protected void refreshValues( Set propertiesSet )
	{
		if ( shareSameVisibility( input ) )
		{
			doLoad( getFirstElementHandle( ) );
		}
		else
		{
			doLoad( null );
		}
	}

	private void doLoad( DesignElementHandle itemHandle )
	{
		setExpressionProvider( itemHandle );
		hideCheckbox.setEnabled( true );

		if ( itemHandle == null )
		{
			hideCheckbox.setSelection( false );
			setOutputEnable( false );
			return;
		}

		Iterator visibilities = visibilityRulesIterator( itemHandle );

		hideCheckbox.setSelection( ( visibilities != null )
				&& visibilities.hasNext( ) );
		setOutputEnable( hideCheckbox.getSelection( ) );

		if ( visibilities == null )
		{
			return;
		}

		while ( visibilities.hasNext( ) )
		{
			HideRuleHandle ruleHandle = (HideRuleHandle) visibilities.next( );
			String format = ruleHandle.getFormat( );
			String expression = ruleHandle.getExpression( );
			if ( expression == null )
				expression = ""; //$NON-NLS-1$
			if ( DesignChoiceConstants.FORMAT_TYPE_ALL.equalsIgnoreCase( format ) )
			{
				allRadio.setSelection( true );
				allExpression.setText( expression );
				specRadio.setSelection( false );
				break;
			}
			allRadio.setSelection( false );
			specRadio.setSelection( true );

			if(specCheckButtons.containsKey( format ))( (Button) specCheckButtons.get( format ) ).setSelection( true );
			if(specExpressions.containsKey( format ))( (ExpressionComposite) specExpressions.get( format ) ).setText( expression );
		}

		for ( int i = 0; i < typeInfo.length; i++ )
		{
			ExpressionComposite expr = (ExpressionComposite) specExpressions.get( typeInfo[i] );
			Button check = (Button) specCheckButtons.get( typeInfo[i] );
			expr.setEnabled( check.getEnabled( ) && check.getSelection( ) );
		}
		setOutputInfo( );
	}

	// below two commented methods need replace by model api on
	// GroupElementHandle
	private boolean shareSameVisibility( List input )
	{
		return GroupElementFactory.newGroupElement( SessionHandleAdapter.getInstance( )
				.getReportDesignHandle( ),
				input )
				.shareSameValue( IReportItemModel.VISIBILITY_PROP );
	}

	private void resetUI( )
	{
		allRadio.setSelection( true );
		specRadio.setSelection( false );
		allContainer.setVisible( false );
		specContainer.setVisible( false );
		setOutputInfo( );

		for ( int i = 0; i < typeInfo.length; i++ )
		{
			( (Button) specCheckButtons.get( typeInfo[i] ) ).setSelection( false );
			( (ExpressionComposite) specExpressions.get( typeInfo[i] ) ).setText( "true" );
		}
		allExpression.setText( "true" );//$NON-NLS-1$
	}

	/**
	 * Sets the enable status of output type.
	 * 
	 * @param enable
	 *            The enable status.
	 */
	private void setOutputEnable( boolean enable )
	{
		// if ( allRadio.getEnabled( ) == enable )
		// {
		// return;
		// }
		allRadio.setEnabled( enable );
		specRadio.setEnabled( enable );
		group.setEnabled( enable );
		Composite container = allContainer.isVisible( ) ? allContainer
				: specContainer;
		Control[] children = container.getChildren( );
		for ( int i = 0; i < children.length; i++ )
		{
			children[i].setEnabled( enable );
		}

		for ( int i = 0; i < typeInfo.length; i++ )
		{
			( (ExpressionComposite) specExpressions.get( typeInfo[i] ) ).setEnabled( ( (Button) specCheckButtons.get( typeInfo[i] ) ).getSelection( ) );
		}

	}

	/**
	 * Sets output information
	 */
	private void setOutputInfo( )
	{
		if ( allRadio.getSelection( ) && !allContainer.isVisible( ) )
		{
			group.setText( All_Title );
			( (StackLayout) group.getLayout( ) ).topControl = allContainer;
			group.layout( );
		}
		else if ( specRadio.getSelection( ) && !specContainer.isVisible( ) )
		{
			group.setText( Spec_Title );
			( (StackLayout) group.getLayout( ) ).topControl = specContainer;
			group.layout( );
		}
	}

	/**
	 * Saves the current hide information
	 */
	protected void saveHideInfo( )
	{
		// if the user selects specRadio, the UI must refresh to show the
		// specific outputs.
		setOutputInfo( );

		CommandStack stack = getActionStack( );
		stack.startTrans( Messages.getString( "VisibilityPage.menu.SaveHides" ) ); //$NON-NLS-1$

		if ( allRadio.getSelection( ) )
		{
			for ( Iterator iter = input.iterator( ); iter.hasNext( ); )
			{
				DesignElementHandle element = (DesignElementHandle) iter.next( );

				Iterator visibilities = visibilityRulesIterator( element );
				if ( visibilities != null && visibilities.hasNext( ) )
				{
					if ( getHideRuleHandle( element,
							DesignChoiceConstants.FORMAT_TYPE_ALL ) == null )
					{
						if ( !clearProperty( element ) )
							stack.rollback( );
					}
				}
				updateHideRule( element,
						DesignChoiceConstants.FORMAT_TYPE_ALL,
						true,
						allExpression.getText( ) );
			}
		}
		else
		{
			for ( Iterator iter = input.iterator( ); iter.hasNext( ); )
			{
				DesignElementHandle element = (DesignElementHandle) iter.next( );

				boolean hideForAll = false;
				for ( int i = 0; i < typeInfo.length; i++ )
				{
					boolean selection = ( (Button) specCheckButtons.get( typeInfo[i] ) ).getSelection( );
					String expression = ( (ExpressionComposite) specExpressions.get( typeInfo[i] ) ).getText( );
					if ( selection )
					{
						hideForAll = true;
					}
					if ( !updateHideRule( element,
							typeInfo[i],
							selection,
							expression ) )
					{
						stack.rollback( );
					}
				}

				if ( hideForAll )
				{
					if ( !updateHideRule( element,
							DesignChoiceConstants.FORMAT_TYPE_ALL,
							false,
							null ) )
					{
						stack.rollback( );
					}
				}

				hideCheckbox.setSelection( true );
				setOutputEnable( true );
			}
		}
		stack.commit( );
	}

	/**
	 * Update a given hide-output information.
	 * 
	 * @param format
	 *            The hide-output type.
	 * @param checked
	 *            Whether to remove the hide information.
	 * @param expression
	 *            The new expression.
	 */
	private boolean updateHideRule( DesignElementHandle element, String format,
			boolean checked, String expression )
	{
		// save the output type
		if ( checked )
		{
			HideRuleHandle hideHandle = getHideRuleHandle( element, format );
			if ( hideHandle == null )
			{
				try
				{
					hideHandle = createHideRuleHandle( element,
							format,
							expression );
				}
				catch ( SemanticException e )
				{
					WidgetUtil.processError( this.getShell( ), e );
					return false;
				}
			}
			else
			{
				if ( !expression.equals( hideHandle.getExpression( ) )
						&& ( !expression.equals( "" ) || hideHandle.getExpression( ) != null ) ) //$NON-NLS-1$
				{
					hideHandle.setExpression( expression );
				}
			}
		}
		else
		{
			// remove the given output format
			Iterator visibilities = visibilityRulesIterator( element );
			if ( visibilities == null )
			{
				return true;
			}
			while ( visibilities.hasNext( ) )
			{
				HideRuleHandle handle = (HideRuleHandle) visibilities.next( );
				if ( format.equalsIgnoreCase( handle.getFormat( ) ) )
				{
					try
					{
						getVisibilityPropertyHandle( element ).removeItem( handle.getStructure( ) );
					}
					catch ( PropertyValueException e )
					{
						WidgetUtil.processError( this.getShell( ), e );
						return false;
					}
					return true;
				}
			}
		}
		return true;
	}

	/**
	 * Gets a given hide-output Handle.
	 * 
	 * @param handle
	 *            The ReportItemHandle.
	 * @param format
	 *            hide-output format.
	 * @return hide-output Handle.
	 */
	private HideRuleHandle getHideRuleHandle( DesignElementHandle handle,
			String format )
	{
		Iterator visibilities = visibilityRulesIterator( handle );
		if ( visibilities == null )
		{
			return null;
		}

		while ( visibilities.hasNext( ) )
		{
			HideRuleHandle hideHandle = (HideRuleHandle) visibilities.next( );
			if ( format.equalsIgnoreCase( hideHandle.getFormat( ) ) )
			{
				return hideHandle;
			}
		}
		return null;
	}

	/**
	 * Creates a new hide-output Handle.
	 * 
	 * @param format
	 *            hide-output format.
	 * @return hide-output Handle.
	 * @throws SemanticException
	 */
	private HideRuleHandle createHideRuleHandle( DesignElementHandle element,
			String format, String expression ) throws SemanticException
	{
		PropertyHandle propertyHandle = getVisibilityPropertyHandle( element );
		HideRule hide = StructureFactory.createHideRule( );

		hide.setFormat( format );
		hide.setExpression( expression );

		propertyHandle.addItem( hide );

		return (HideRuleHandle) hide.getHandle( propertyHandle );

	}

	/**
	 * Clears the VISIBILITY_PROP property value.
	 * 
	 * @return True if operation successes, false if fails.
	 */
	private boolean clearProperty( DesignElementHandle handle )
	{
		if ( visibilityRulesIterator( handle ) != null )
		{
			try
			{
				if ( handle instanceof ReportItemHandle )
				{
					handle.clearProperty( ReportItemHandle.VISIBILITY_PROP );
				}
				else if ( handle instanceof RowHandle )
				{
					handle.clearProperty( RowHandle.VISIBILITY_PROP );
				}
				else if ( handle instanceof ColumnHandle )
				{
					handle.clearProperty( ColumnHandle.VISIBILITY_PROP );
				}

			}
			catch ( SemanticException e )
			{
				WidgetUtil.processError( this.getShell( ), e );
				return false;
			}
		}
		return true;
	}

	/**
	 * Gets the array of ReportItemHandle.
	 * 
	 * @return a ReportItemHandle array.
	 */
	private DesignElementHandle getFirstElementHandle( )
	{
		Object obj = input.get( 0 );
		if ( obj instanceof ReportItemHandle )
		{
			return (ReportItemHandle) obj;
		}
		else if ( obj instanceof RowHandle )
		{
			return (RowHandle) obj;
		}
		else if ( obj instanceof ColumnHandle )
		{
			return (ColumnHandle) obj;
		}
		else
			return null;
	}

	/**
	 * Gets the PropertyHandle of VISIBILITY_PROP property.
	 * 
	 * @return PropertyHandle
	 */
	private PropertyHandle getVisibilityPropertyHandle(
			DesignElementHandle handle )
	{
		if ( handle == null )
		{
			return null;
		}
		if ( handle instanceof ReportItemHandle )
		{
			return handle.getPropertyHandle( ReportItemHandle.VISIBILITY_PROP );
		}
		else if ( handle instanceof RowHandle )
		{
			return handle.getPropertyHandle( RowHandle.VISIBILITY_PROP );
		}
		else if ( handle instanceof ColumnHandle )
		{
			return handle.getPropertyHandle( ColumnHandle.VISIBILITY_PROP );
		}
		else
		{
			return null;
		}

	}

	/**
	 * Gets the DE CommandStack instance
	 * 
	 * @return CommandStack instance
	 */
	private CommandStack getActionStack( )
	{
		return SessionHandleAdapter.getInstance( ).getCommandStack( );
	}

	private Iterator visibilityRulesIterator( DesignElementHandle handle )
	{
		Iterator visibilities = null;
		if ( handle instanceof ReportItemHandle )
		{
			visibilities = ( (ReportItemHandle) handle ).visibilityRulesIterator( );
		}
		else if ( handle instanceof RowHandle )
		{
			visibilities = ( (RowHandle) handle ).visibilityRulesIterator( );
		}
		else if ( handle instanceof ColumnHandle )
		{
			visibilities = ( (ColumnHandle) handle ).visibilityRulesIterator( );
		}
		return visibilities;
	}

}