/***********************************************************************
 * Copyright (c) 2006 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.chart.ui.swt.composites;

import java.util.Vector;

import org.eclipse.birt.chart.ui.util.UIHelper;
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.KeyEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
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.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;

public abstract class CustomChooserComposite extends Composite
{

	private transient Composite cmpDropDown = null;

	private transient ICustomChoice cnvSelection = null;

	private transient Button btnDown = null;

	private transient Object iCurrentValue = null;

	private transient Vector vListeners = new Vector( );;

	public static final int STYLE_CHANGED_EVENT = 1;

	private transient boolean bEnabled = true;

	private boolean bJustFocusLost = false;

	private transient ICustomChoice popupSelection;

	private transient ICustomChoice[] popupCanvases;

	private Object[] items;

	Listener canvasListener = new Listener( ) {

		public void handleEvent( Event event )
		{
			if ( event.widget == cnvSelection )
			{
				handleEventCanvasSelection( event );
			}
			else
			{
				handleEventCanvasPopup( event );
			}
		}
	};

	public CustomChooserComposite( Composite parent, int style,
			Object choiceValue )
	{
		super( parent, style );
		this.iCurrentValue = choiceValue;
	}

	public CustomChooserComposite( Composite parent, int style )
	{
		super( parent, style );
	}

	public void setItems( Object[] items )
	{
		this.items = items;
		init( );
		placeComponents( );
		initAccessible( );
	}

	public Object[] getItems( )
	{
		return this.items;
	}

	public void select( int index )
	{
		setChoiceValue( items[index] );
	}

	protected abstract ICustomChoice createChoice( Composite parent,
			Object choiceValue );

	private void init( )
	{
		this.setSize( getParent( ).getClientArea( ).width,
				getParent( ).getClientArea( ).height );
		popupCanvases = new ICustomChoice[items.length];
	}

	private void placeComponents( )
	{
		// THE LAYOUT OF THIS COMPOSITE (FILLS EVERYTHING INSIDE IT)
		FillLayout flMain = new FillLayout( );
		flMain.marginHeight = 0;
		flMain.marginWidth = 0;
		setLayout( flMain );

		// THE LAYOUT OF THE INNER COMPOSITE (ANCHORED NORTH AND ENCAPSULATES
		// THE CANVAS + BUTTON)
		Composite cmpContent = new Composite( this, SWT.BORDER );
		GridLayout glContentInner = new GridLayout( );
		glContentInner.verticalSpacing = 0;
		glContentInner.horizontalSpacing = 0;
		glContentInner.marginHeight = 0;
		glContentInner.marginWidth = 0;
		glContentInner.numColumns = 2;
		cmpContent.setLayout( glContentInner );

		final int iSize = 18;
		// THE CANVAS
		cnvSelection = createChoice( cmpContent, null );
		GridData gdCNVSelection = new GridData( GridData.FILL_BOTH );
		gdCNVSelection.heightHint = iSize;
		cnvSelection.setLayoutData( gdCNVSelection );
		cnvSelection.setValue( iCurrentValue );
		cnvSelection.addListener( SWT.KeyDown, canvasListener );
		cnvSelection.addListener( SWT.Traverse, canvasListener );
		cnvSelection.addListener( SWT.FocusIn, canvasListener );
		cnvSelection.addListener( SWT.FocusOut, canvasListener );
		cnvSelection.addListener( SWT.MouseDown, canvasListener );

		// THE BUTTON
		btnDown = new Button( cmpContent, SWT.ARROW | SWT.DOWN );
		GridData gdBDown = new GridData( GridData.FILL );
		gdBDown.verticalAlignment = GridData.BEGINNING;
		gdBDown.widthHint = iSize - 2;
		gdBDown.heightHint = iSize;
		btnDown.setLayoutData( gdBDown );
		btnDown.addListener( SWT.Selection, new Listener( ) {

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

	public void setEnabled( boolean bState )
	{
		btnDown.setEnabled( bState );
		cnvSelection.setEnabled( bState );
		this.bEnabled = bState;
	}

	public boolean isEnabled( )
	{
		return this.bEnabled;
	}

	/**
	 * 
	 */
	private void createDropDownComponent( int iXLoc, int iYLoc )
	{
		if ( !bEnabled )
		{
			return;
		}
		int shellWidth = this.getSize( ).x;
		Shell shell = new Shell( this.getShell( ), SWT.NONE );
		shell.setLayout( new FillLayout( ) );
		if ( ( getStyle( ) & SWT.RIGHT_TO_LEFT ) != 0 )
		{
			iXLoc -= shellWidth;
		}
		shell.setLocation( iXLoc, iYLoc );
		cmpDropDown = new Composite( shell, SWT.NONE );
		FillLayout fillDropDown = new FillLayout( );
		fillDropDown.type = SWT.VERTICAL;
		cmpDropDown.setLayout( fillDropDown );

		Listener listenerCmpDropDown = new Listener( ) {

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

		cmpDropDown.addListener( SWT.KeyDown, listenerCmpDropDown );
		cmpDropDown.addListener( SWT.FocusOut, listenerCmpDropDown );
		
		final int choiceHigh = 18;

		for ( int iC = 0; iC < items.length; iC++ )
		{
			ICustomChoice cnv = createChoice( cmpDropDown, items[iC] );
			GridData gd = new GridData( );
			gd.heightHint = choiceHigh;
			cnv.setLayoutData( gd );
			cnv.addListener( SWT.MouseDown, canvasListener );
			cnv.addListener( SWT.MouseEnter, canvasListener );
			cnv.addListener( SWT.KeyDown, canvasListener );

			popupCanvases[iC] = cnv;
			if ( cnvSelection.getValue( ).equals( cnv.getValue( ) ) )
			{
				cnv.notifyListeners( SWT.FocusIn, new Event( ) );
				popupSelection = cnv;
			}
		}
		shell.setSize( shellWidth, choiceHigh * items.length );
		shell.layout( );
		shell.open( );
	}

	void handleEventCmpDropDown( Event event )
	{
		switch ( event.type )
		{
			case SWT.KeyDown :
			{
				keyPressed( new KeyEvent( event ) );
				break;
			}
			case SWT.FocusOut :
			{
				Control cTmp = Display.getCurrent( ).getCursorControl( );

				if ( cTmp != null )
				{
					if ( cTmp.equals( cnvSelection ) || cTmp.equals( btnDown ) )
					{
						bJustFocusLost = true;
					}
				}
				if ( !isPopupControl( cTmp ) )
				{
					cmpDropDown.getShell( ).close( );
				}

				break;
			}
		}
	}

	/**
	 * Returns the current selected choice
	 * 
	 */
	public Object getChoiceValue( )
	{
		return this.iCurrentValue;
	}

	/**
	 * Sets the value as selected choice, and redraws UI.
	 * 
	 * @param iValue
	 *            value as selected choice
	 */
	public void setChoiceValue( Object iValue )
	{
		iCurrentValue = iValue;
		cnvSelection.setValue( iCurrentValue );
		cnvSelection.redraw( );
	}

	public void addListener( Listener listener )
	{
		vListeners.add( listener );
	}

	private void toggleDropDown( )
	{
		// fix for Linux, since it not send the event correctly to other than
		// current shell.
		if ( bJustFocusLost )
		{
			bJustFocusLost = false;
			return;
		}

		if ( cmpDropDown == null
				|| cmpDropDown.isDisposed( ) || !cmpDropDown.isVisible( ) )
		{
			Point pLoc = UIHelper.getScreenLocation( this );
			createDropDownComponent( pLoc.x, pLoc.y + this.getSize( ).y );
		}
		else
		{
			cmpDropDown.getShell( ).close( );
		}
	}

	private void fireEvent( )
	{
		Event e = new Event( );
		e.widget = this;
		e.data = this.iCurrentValue;
		e.type = STYLE_CHANGED_EVENT;
		for ( int i = 0; i < vListeners.size( ); i++ )
		{
			( (Listener) vListeners.get( i ) ).handleEvent( e );
		}
	}

	void keyPressed( KeyEvent event )
	{
		if ( cmpDropDown != null && !cmpDropDown.getShell( ).isDisposed( ) )
		{
			if ( event.keyCode == SWT.ESC )
			{
				cmpDropDown.getShell( ).close( );
			}
			else if ( event.keyCode == SWT.CR || event.keyCode == SWT.KEYPAD_CR )
			{
				ICustomChoice currentControl = popupSelection == null
						? popupCanvases[findChoiceIndex( ( cnvSelection ).getValue( ) )]
						: popupSelection;
				setChoiceValue( currentControl.getValue( ) );
				fireEvent( );
				cmpDropDown.getShell( ).close( );
			}
			else if ( event.keyCode == SWT.ARROW_UP
					|| event.keyCode == SWT.ARROW_DOWN )
			{
				ICustomChoice currentControl = popupSelection == null
						? popupCanvases[findChoiceIndex( ( cnvSelection ).getValue( ) )]
						: popupSelection;
				int styleIndex = findChoiceIndex( currentControl.getValue( ) );
				if ( styleIndex >= 0 )
				{
					currentControl.notifyListeners( SWT.FocusOut, new Event( ) );
					currentControl.redraw( );

					if ( event.keyCode == SWT.ARROW_UP )
					{
						if ( styleIndex > 0 )
						{
							styleIndex--;
							setChoiceValue( items[styleIndex] );
							fireEvent( );
						}
					}
					else if ( event.keyCode == SWT.ARROW_DOWN )
					{
						if ( styleIndex < items.length - 1 )
						{
							styleIndex++;
							setChoiceValue( items[styleIndex] );
							fireEvent( );
						}
					}

					popupSelection = popupCanvases[styleIndex];
					popupSelection.notifyListeners( SWT.FocusIn, new Event( ) );
					popupSelection.redraw( );
				}
			}
		}
	}

	private boolean isPopupControl( Object control )
	{
		return control != null
				&& control instanceof Control
				&& ( (Control) control ).getShell( ) == cmpDropDown.getShell( );
	}

	void handleEventCanvasSelection( Event event )
	{
		switch ( event.type )
		{
			case SWT.FocusIn :
			{
				cnvSelection.redraw( );
				break;
			}
			case SWT.FocusOut :
			{
				cnvSelection.redraw( );
				break;
			}
			case SWT.KeyDown :
			{
				// At this point the widget may have been disposed.
				// If so, do not continue.
				if ( isDisposed( ) )
					break;

				if ( event.keyCode == SWT.ARROW_UP
						|| event.keyCode == SWT.ARROW_DOWN )
				{
					int styleIndex = findChoiceIndex( cnvSelection.getValue( ) );
					if ( styleIndex >= 0 )
					{
						if ( event.keyCode == SWT.ARROW_UP )
						{
							if ( styleIndex > 0 )
							{
								styleIndex--;
								setChoiceValue( items[styleIndex] );
								fireEvent( );
							}
						}
						else if ( event.keyCode == SWT.ARROW_DOWN )
						{
							if ( styleIndex < items.length - 1 )
							{
								styleIndex++;
								setChoiceValue( items[styleIndex] );
								fireEvent( );
							}
						}
					}
				}
				break;
			}
			case SWT.Traverse :
			{
				switch ( event.detail )
				{
					case SWT.TRAVERSE_ESCAPE:
						getShell( ).close( );
						break;
					case SWT.TRAVERSE_RETURN :
					case SWT.TRAVERSE_TAB_NEXT :
					case SWT.TRAVERSE_TAB_PREVIOUS :
					case SWT.TRAVERSE_ARROW_PREVIOUS :
					case SWT.TRAVERSE_ARROW_NEXT :
						event.doit = true;
						cnvSelection.redraw( );
				}
				break;
			}
			case SWT.MouseDown :
				toggleDropDown( );
				break;
		}
	}

	void handleEventCanvasPopup( Event event )
	{
		switch ( event.type )
		{
			case SWT.MouseDown :
				setChoiceValue( ( (ICustomChoice) event.widget ).getValue( ) );
				this.cmpDropDown.getShell( ).close( );
				fireEvent( );
				break;

			case SWT.MouseEnter :
				if ( popupSelection != null )
				{
					// Redraw the selection canvas in popup
					popupSelection.notifyListeners( SWT.FocusOut, new Event( ) );
					popupSelection.redraw( );
					// popupSelection = null;
				}
				popupSelection = (ICustomChoice) event.widget;
				popupSelection.notifyListeners( SWT.FocusIn, event );
				popupSelection.redraw( );
				break;

		}
	}

	private int findChoiceIndex( Object value )
	{
		for ( int i = 0; i < items.length; i++ )
		{
			if ( items[i].equals( value ) )
			{
				return i;
			}
		}
		return -1;
	}

	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;
			}
		} );
	}
}
