/*******************************************************************************
 * 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.ui.preferences;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.birt.core.preference.IPreferences;
import org.eclipse.birt.report.designer.ui.ReportPlugin;
import org.eclipse.birt.report.designer.ui.util.PixelConverter;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
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.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Link;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.plugin.AbstractUIPlugin;

/**
 * Abstract options configuration block providing a general implementation for
 * setting up an options configuration page.
 * 
 * @since 2.1
 */
public abstract class OptionsConfigurationBlock
{

	public static final class Key
	{

		private String fQualifier;
		private String fKey;
		private String value;

		public Key( String qualifier, String key )
		{
			fQualifier = qualifier;
			fKey = key;
		}

		public String getName( )
		{
			return fKey;
		}

		public void apply( IPreferences preference )
		{
			if ( value != null )
				preference.setValue( fKey, value );
		}

		public String getStoredValue( IPreferences preference )
		{
			if ( value == null )
				value = preference.getString( fKey );
			return value;
		}

		public String getDefaultValue( IPreferences preference )
		{
			return preference.getDefaultString( fKey );
		}

		public void setStoredValue( IPreferences preference, String value )
		{
			if ( value != null )
			{
				this.value = value;
			}
		}

		public void setToDefault( IPreferences preference )
		{
			preference.setToDefault( fKey );
			this.value = preference.getString( fKey );
		}

		public String toString( )
		{
			return fQualifier + '/' + fKey;
		}

		public String getQualifier( )
		{
			return fQualifier;
		}

		public void clear( )
		{
			value = null;
		}

	}

	protected static class ControlData
	{

		private Key fKey;
		private String[] fValues;

		public ControlData( Key key, String[] values )
		{
			fKey = key;
			fValues = values;
		}

		public Key getKey( )
		{
			return fKey;
		}

		public String getValue( boolean selection )
		{
			int index = selection ? 0 : 1;
			return fValues[index];
		}

		public String getValue( int index )
		{
			return fValues[index];
		}

		public int getSelection( String value )
		{
			if ( value != null )
			{
				for ( int i = 0; i < fValues.length; i++ )
				{
					if ( value.equals( fValues[i] ) )
					{
						return i;
					}
				}
			}
			return fValues.length - 1; // assume the last option is the least
			// severe
		}
	}

	protected final ArrayList fCheckBoxes;
	protected final ArrayList fComboBoxes;
	protected final ArrayList fTextBoxes;
	protected final ArrayList fRadioButtons;
	protected final HashMap fLabels;
	protected final ArrayList fExpandedComposites;

	private SelectionListener fSelectionListener;
	private ModifyListener fTextModifyListener;

	protected IStatusChangeListener fContext;
	protected IProject fProject; // project or null
	protected Key[] fAllKeys;
	protected AbstractUIPlugin fPlugin;

	protected IPreferences fPref;

	private Shell fShell;

	private Map fDisabledProjectSettings; // null when project specific

	public OptionsConfigurationBlock( IStatusChangeListener context,
			AbstractUIPlugin plugin, IProject project )
	{
		fContext = context;
		fProject = project;

		fPlugin = plugin;
		fPref = PreferenceFactory.getInstance( ).getPreferences( fPlugin,
				fProject );

		fCheckBoxes = new ArrayList( );
		fComboBoxes = new ArrayList( );
		fTextBoxes = new ArrayList( 2 );
		fRadioButtons = new ArrayList( );
		fLabels = new HashMap( );
		fExpandedComposites = new ArrayList( );
	}

	public void setKeys( Key[] keys )
	{
		fAllKeys = keys;
		if ( fProject == null || hasProjectSpecificOptions( fProject ) )
		{
			fDisabledProjectSettings = null;
		}
		else
		{
			/*
			 * set default show value
			 */
			fDisabledProjectSettings = new IdentityHashMap( );
			for ( int i = 0; i < keys.length; i++ )
			{
				Key curr = keys[i];
				fDisabledProjectSettings.put( curr, curr.getStoredValue( fPref ) );
			}
		}
		settingsUpdated( );
	}

	protected void settingsUpdated( )
	{
		// TODO Auto-generated method stub

	}

	protected Key getKey( String plugin, String key )
	{
		return new Key( plugin, key );
	}

	protected final Key getReportKey( String key )
	{
		return getKey( ReportPlugin.REPORT_UI, key );
	}

	public void selectOption( String key, String qualifier )
	{
		for ( int i = 0; i < fAllKeys.length; i++ )
		{
			Key curr = fAllKeys[i];
			if ( curr.getName( ).equals( key )
					&& curr.getQualifier( ).equals( qualifier ) )
			{
				selectOption( curr );
			}
		}
	}

	public void selectOption( Key key )
	{
		Control control = findControl( key );
		if ( control != null )
		{
			control.setFocus( );
		}
	}

	protected Shell getShell( )
	{
		return fShell;
	}

	protected void setShell( Shell shell )
	{
		fShell = shell;
	}

	protected abstract Control createContents( Composite parent );

	public static class RadioComposite extends Group
	{

		List<Button> radioBtns = new ArrayList<Button>( );;

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

		protected void checkSubclass( )
		{
			/* Do nothing to make sure subclassing is allowed */
		}

		private void IniRadioButtons( )
		{
			if ( radioBtns.size( ) > 0 )
			{
				return;
			}
			Control[] controls = this.getChildren( );
			for ( int i = 0; i < controls.length; i++ )
			{
				if ( controls[i] != null && controls[i] instanceof Button )
				{
					radioBtns.add( (Button) controls[i] );
				}
			}
		}

		protected int getBtnIndex( Button btn )
		{
			int index = radioBtns.indexOf( btn );
			return index;
		}

		protected void setSelection( int index )
		{
			IniRadioButtons( );
			if ( radioBtns.size( ) <= 0 )
				return;
			if ( index < 0 || index >= radioBtns.size( ) )
			{
				index = 0;
			}

			for ( int i = 0; i < radioBtns.size( ); i++ )
			{
				Button btn = radioBtns.get( i );
				if ( i == index )
				{
					btn.setSelection( true );
				}
				else
				{
					btn.setSelection( false );
				}
			}
		}

		public void addSelectionListener( SelectionListener selectionListener )
		{
			IniRadioButtons( );
			if ( radioBtns.size( ) <= 0 )
				return;
			for ( int i = 0; i < radioBtns.size( ); i++ )
			{
				radioBtns.get( i ).addSelectionListener( selectionListener );
			}

		}
	}

	protected RadioComposite addRadioButton( Composite parent, String[] labels,
			Key key, String[] values, int indent )
	{
		ControlData data = new ControlData( key, values );

		GridData gd = new GridData( GridData.HORIZONTAL_ALIGN_FILL );
		gd.horizontalSpan = 3;
		gd.horizontalIndent = indent;

		RadioComposite composite = new RadioComposite( parent, SWT.NONE );
		composite.setLayoutData( gd );
		GridLayout layout = new GridLayout( 3, false );
		composite.setLayout( layout );
		composite.setText( labels[0] );

		for ( int i = 1; i < labels.length; i++ )
		{
			Button radioBtn = new Button( composite, SWT.RADIO );
			radioBtn.setFont( JFaceResources.getDialogFont( ) );
			radioBtn.setLayoutData( new GridData( ) );
			radioBtn.setText( labels[i] );
		}

		composite.setData( data );
		composite.addSelectionListener( getSelectionListener( ) );
		String currValue = getValue( key );
		composite.setSelection( data.getSelection( currValue ) );
		fRadioButtons.add( composite );

		return composite;
	}

	protected Button addCheckBox( Composite parent, String label, Key key,
			String[] values, int indent )
	{
		ControlData data = new ControlData( key, values );

		GridData gd = new GridData( GridData.HORIZONTAL_ALIGN_FILL );
		gd.horizontalSpan = 3;
		gd.horizontalIndent = indent;

		Button checkBox = new Button( parent, SWT.CHECK );
		checkBox.setFont( JFaceResources.getDialogFont( ) );
		checkBox.setText( label );
		checkBox.setData( data );
		checkBox.setLayoutData( gd );
		checkBox.addSelectionListener( getSelectionListener( ) );

		String currValue = getValue( key );
		checkBox.setSelection( data.getSelection( currValue ) == 0 );

		fCheckBoxes.add( checkBox );

		return checkBox;
	}

	protected Button addCheckBoxWithLink( Composite parent, String label,
			Key key, String[] values, int indent, int widthHint,
			SelectionListener listener )
	{
		ControlData data = new ControlData( key, values );

		GridData gd = new GridData( GridData.FILL, GridData.FILL, true, false );
		gd.horizontalSpan = 3;
		gd.horizontalIndent = indent;

		Composite composite = new Composite( parent, SWT.NONE );
		GridLayout layout = new GridLayout( );
		layout.marginHeight = 0;
		layout.marginWidth = 0;
		layout.numColumns = 2;
		composite.setLayout( layout );
		composite.setLayoutData( gd );

		Button checkBox = new Button( composite, SWT.CHECK );
		checkBox.setFont( JFaceResources.getDialogFont( ) );
		checkBox.setData( data );
		checkBox.setLayoutData( new GridData( GridData.FILL,
				GridData.BEGINNING,
				false,
				false ) );
		checkBox.addSelectionListener( getSelectionListener( ) );

		gd = new GridData( GridData.FILL, GridData.CENTER, true, false );
		gd.widthHint = widthHint;

		Link link = new Link( composite, SWT.NONE );
		link.setText( label );
		link.setLayoutData( gd );
		if ( listener != null )
		{
			link.addSelectionListener( listener );
		}

		String currValue = getValue( key );
		checkBox.setSelection( data.getSelection( currValue ) == 0 );

		fCheckBoxes.add( checkBox );

		return checkBox;
	}

	protected Combo addComboBox( Composite parent, String label, Key key,
			String[] values, String[] valueLabels, int indent )
	{
		GridData gd = new GridData( );
		gd.horizontalIndent = indent;

		Label labelControl = new Label( parent, SWT.NONE );
		labelControl.setFont( JFaceResources.getDialogFont( ) );
		labelControl.setText( label );
		labelControl.setLayoutData( gd );

		Combo comboBox = newComboControl( parent, key, values, valueLabels );
		gd = new GridData( );
		gd.widthHint = 200;
		comboBox.setLayoutData( gd );

		fLabels.put( comboBox, labelControl );

		return comboBox;
	}

	protected Combo addInversedComboBox( Composite parent, String label,
			Key key, String[] values, String[] valueLabels, int indent )
	{
		GridData gd = new GridData( GridData.HORIZONTAL_ALIGN_BEGINNING );
		gd.horizontalIndent = indent;
		gd.horizontalSpan = 3;

		Composite composite = new Composite( parent, SWT.NONE );
		GridLayout layout = new GridLayout( );
		layout.marginHeight = 0;
		layout.marginWidth = 0;
		layout.numColumns = 2;
		composite.setLayout( layout );
		composite.setLayoutData( gd );

		Combo comboBox = newComboControl( composite, key, values, valueLabels );
		comboBox.setFont( JFaceResources.getDialogFont( ) );
		comboBox.setLayoutData( new GridData( GridData.HORIZONTAL_ALIGN_FILL ) );

		Label labelControl = new Label( composite, SWT.LEFT | SWT.WRAP );
		labelControl.setText( label );
		labelControl.setLayoutData( new GridData( ) );

		fLabels.put( comboBox, labelControl );
		return comboBox;
	}

	protected Combo newComboControl( Composite composite, Key key,
			String[] values, String[] valueLabels )
	{
		ControlData data = new ControlData( key, values );

		Combo comboBox = new Combo( composite, SWT.READ_ONLY );
		comboBox.setItems( valueLabels );
		comboBox.setData( data );
		comboBox.addSelectionListener( getSelectionListener( ) );
		comboBox.setFont( JFaceResources.getDialogFont( ) );

		String currValue = getValue( key );
		comboBox.select( data.getSelection( currValue ) );

		fComboBoxes.add( comboBox );
		return comboBox;
	}

	protected Text addTextField( Composite parent, String label, Key key,
			int indent, int widthHint )
	{
		return addTextField( parent, label, key, indent, widthHint, SWT.BORDER
				| SWT.SINGLE );
	}

	protected Text addTextField( Composite parent, String label, Key key,
			int indent, int widthHint, int style )
	{
		Label labelControl = null;
		if ( label != null && label.length( ) > 0 )
		{
			labelControl = new Label( parent, SWT.WRAP );
			labelControl.setText( label );
			labelControl.setFont( JFaceResources.getDialogFont( ) );
			labelControl.setLayoutData( new GridData( ) );
		}
		Text textBox = new Text( parent, style );
		textBox.setData( key );
		textBox.setLayoutData( new GridData( ) );

		if ( labelControl != null )
			fLabels.put( textBox, labelControl );

		String currValue = getValue( key );
		if ( currValue != null )
		{
			textBox.setText( currValue );
		}
		textBox.addModifyListener( getTextModifyListener( ) );

		GridData data = new GridData( GridData.HORIZONTAL_ALIGN_FILL );
		if ( widthHint != 0 )
		{
			data.widthHint = widthHint;
		}
		else
			data.grabExcessHorizontalSpace = true;
		data.horizontalIndent = indent;
		if ( labelControl != null )
			data.horizontalSpan = 2;
		else
			data.horizontalSpan = 3;
		textBox.setLayoutData( data );

		fTextBoxes.add( textBox );
		return textBox;
	}

	protected SelectionListener getSelectionListener( )
	{
		if ( fSelectionListener == null )
		{
			fSelectionListener = new SelectionListener( ) {

				public void widgetDefaultSelected( SelectionEvent e )
				{
				}

				public void widgetSelected( SelectionEvent e )
				{
					controlChanged( e.widget );
				}
			};
		}
		return fSelectionListener;
	}

	protected ModifyListener getTextModifyListener( )
	{
		if ( fTextModifyListener == null )
		{
			fTextModifyListener = new ModifyListener( ) {

				public void modifyText( ModifyEvent e )
				{
					textChanged( (Text) e.widget );
				}
			};
		}
		return fTextModifyListener;
	}

	protected void controlChanged( Widget widget )
	{

		ControlData data = (ControlData) widget.getData( );
		String newValue = null;
		if ( widget instanceof Button )
		{
			if ( ( ( (Button) widget ).getStyle( ) & SWT.RADIO ) != 0 )
			{
				RadioComposite parent = (RadioComposite) ( (Button) widget ).getParent( );
				data = (ControlData) parent.getData( );
				newValue = data.getValue( parent.getBtnIndex( (Button) widget ) );
			}
			else
			{
				newValue = data.getValue( ( (Button) widget ).getSelection( ) );
			}

		}
		else if ( widget instanceof Combo )
		{
			newValue = data.getValue( ( (Combo) widget ).getSelectionIndex( ) );
		}
		else
		{
			return;
		}
		String oldValue = setValue( data.getKey( ), newValue );
		validateSettings( data.getKey( ), oldValue, newValue );
	}

	protected void textChanged( Text textControl )
	{
		Key key = (Key) textControl.getData( );
		String number = textControl.getText( );
		String oldValue = setValue( key, number );
		validateSettings( key, oldValue, number );
	}

	protected boolean checkValue( Key key, String value )
	{
		return value.equals( getValue( key ) );
	}

	protected String getValue( Key key )
	{
		if ( fDisabledProjectSettings != null )
		{
			return (String) fDisabledProjectSettings.get( key );
		}
		return key.getStoredValue( fPref );
	}

	protected boolean getBooleanValue( Key key )
	{
		return Boolean.valueOf( getValue( key ) ).booleanValue( );
	}

	protected String setValue( Key key, String value )
	{
		if ( fDisabledProjectSettings != null )
		{
			return (String) fDisabledProjectSettings.put( key, value );
		}
		String oldValue = getValue( key );
		key.setStoredValue( fPref, value );
		return oldValue;
	}

	protected String setValue( Key key, boolean value )
	{
		return setValue( key, String.valueOf( value ) );
	}

	/**
	 * Returns the value as actually stored in the preference store.
	 * 
	 * @param key
	 * @return the value as actually stored in the preference store.
	 */
	protected String getStoredValue( Key key )
	{
		return key.getStoredValue( fPref );
	}

	public void useProjectSpecificSettings( boolean enable )
	{
		boolean hasProjectSpecificOption = fDisabledProjectSettings == null;
		if ( enable != hasProjectSpecificOption && fProject != null )
		{
			if ( enable )
			{
				for ( int i = 0; i < fAllKeys.length; i++ )
				{
					Key curr = fAllKeys[i];
					String val = (String) fDisabledProjectSettings.get( curr );
					curr.setStoredValue( fPref, val );
				}
				fDisabledProjectSettings = null;
				updateControls( );
				validateSettings( null, null, null );
			}
			else
			{
				fDisabledProjectSettings = new IdentityHashMap( );
				for ( int i = 0; i < fAllKeys.length; i++ )
				{
					Key curr = fAllKeys[i];
					String oldSetting = curr.getStoredValue( fPref );
					fDisabledProjectSettings.put( curr, oldSetting );
				}
			}
		}
	}

	public boolean hasProjectSpecificOptions( IProject project )
	{
		if ( project != null )
		{
			Key[] allKeys = fAllKeys;
			IPreferences prefs = PreferenceFactory.getInstance( )
					.getPreferences( this.fPlugin, project );
			for ( int i = 0; i < allKeys.length; i++ )
			{
				if ( prefs.isDefault( allKeys[i].getName( ) ) )
				{
					return true;
				}
			}
		}
		return false;
	}

	public boolean areSettingsEnabled( )
	{
		return fDisabledProjectSettings == null || fProject == null;
	}

	public boolean performOk( )
	{
		return performApply( );
	}

	public boolean performApply( )
	{
		return processChanges( ); // apply directly
	}

	protected boolean processChanges( )
	{
		if ( fDisabledProjectSettings != null )
		{
			for ( int i = 0; i < fAllKeys.length; i++ )
			{
				fAllKeys[i].setToDefault( fPref );
			}
		}
		else
		{

			for ( int i = 0; i < fAllKeys.length; i++ )
			{
				fAllKeys[i].apply( fPref );
			}
		}
		try
		{
			fPref.save( );
			return true;
		}
		catch ( IOException e )
		{
			return false;
		}
	}

	public void performDefaults( )
	{

		for ( int i = 0; i < fAllKeys.length; i++ )
		{
			fAllKeys[i].setToDefault( fPref );
		}
		try
		{
			fPref.save( );
		}
		catch ( IOException e )
		{
		}
		settingsUpdated( );
		updateControls( );
		validateSettings( null, null, null );
	}

	public void dispose( )
	{
	}

	protected void updateControls( )
	{
		// update the UI
		for ( int i = fCheckBoxes.size( ) - 1; i >= 0; i-- )
		{
			updateCheckBox( (Button) fCheckBoxes.get( i ) );
		}
		for ( int i = fComboBoxes.size( ) - 1; i >= 0; i-- )
		{
			updateCombo( (Combo) fComboBoxes.get( i ) );
		}
		for ( int i = fTextBoxes.size( ) - 1; i >= 0; i-- )
		{
			updateText( (Text) fTextBoxes.get( i ) );
		}

		for ( int i = fRadioButtons.size( ) - 1; i >= 0; i-- )
		{
			updateRadioComposite( (RadioComposite) fRadioButtons.get( i ) );
		}
	}

	protected void updateCombo( Combo curr )
	{
		ControlData data = (ControlData) curr.getData( );

		String currValue = getValue( data.getKey( ) );
		curr.select( data.getSelection( currValue ) );
	}

	protected void updateCheckBox( Button curr )
	{
		ControlData data = (ControlData) curr.getData( );

		String currValue = getValue( data.getKey( ) );
		curr.setSelection( data.getSelection( currValue ) == 0 );
	}

	protected void updateRadioComposite( RadioComposite curr )
	{
		ControlData data = (ControlData) curr.getData( );

		String currValue = getValue( data.getKey( ) );
		curr.setSelection( data.getSelection( currValue ) );
	}

	protected void updateText( Text curr )
	{
		Key key = (Key) curr.getData( );

		String currValue = getValue( key );
		if ( currValue != null )
		{
			curr.setText( currValue );
		}
	}

	protected Button getCheckBox( Key key )
	{
		for ( int i = fCheckBoxes.size( ) - 1; i >= 0; i-- )
		{
			Button curr = (Button) fCheckBoxes.get( i );
			ControlData data = (ControlData) curr.getData( );
			if ( key.equals( data.getKey( ) ) )
			{
				return curr;
			}
		}
		return null;
	}

	protected Combo getComboBox( Key key )
	{
		for ( int i = fComboBoxes.size( ) - 1; i >= 0; i-- )
		{
			Combo curr = (Combo) fComboBoxes.get( i );
			ControlData data = (ControlData) curr.getData( );
			if ( key.equals( data.getKey( ) ) )
			{
				return curr;
			}
		}
		return null;
	}

	protected Text getTextControl( Key key )
	{
		for ( int i = fTextBoxes.size( ) - 1; i >= 0; i-- )
		{
			Text curr = (Text) fTextBoxes.get( i );
			ControlData data = (ControlData) curr.getData( );
			if ( key.equals( data.getKey( ) ) )
			{
				return curr;
			}
		}
		return null;
	}

	protected Control findControl( Key key )
	{
		Combo comboBox = getComboBox( key );
		if ( comboBox != null )
		{
			return comboBox;
		}
		Button checkBox = getCheckBox( key );
		if ( checkBox != null )
		{
			return checkBox;
		}
		Text text = getTextControl( key );
		if ( text != null )
		{
			return text;
		}
		return null;
	}

	protected void setComboEnabled( Key key, boolean enabled )
	{
		Combo combo = getComboBox( key );
		Label label = (Label) fLabels.get( combo );
		combo.setEnabled( enabled );
		label.setEnabled( enabled );
	}

	protected Text addLabelledTextField( Composite parent, String label,
			Key key, int textlimit, int indent, boolean dummy )
	{
		PixelConverter pixelConverter = new PixelConverter( parent );

		Label labelControl = new Label( parent, SWT.WRAP );
		labelControl.setText( label );
		labelControl.setLayoutData( new GridData( ) );

		Text textBox = new Text( parent, SWT.BORDER | SWT.SINGLE );
		textBox.setData( key );
		textBox.setLayoutData( new GridData( ) );

		fLabels.put( textBox, labelControl );

		String currValue = getValue( key );
		if ( currValue != null )
		{
			textBox.setText( currValue );
		}
		textBox.addModifyListener( getTextModifyListener( ) );

		GridData data = new GridData( GridData.HORIZONTAL_ALIGN_FILL );
		if ( textlimit != 0 )
		{
			textBox.setTextLimit( textlimit );
			data.widthHint = pixelConverter.convertWidthInCharsToPixels( textlimit + 1 );
		}
		data.horizontalIndent = indent;
		data.horizontalSpan = 2;
		textBox.setLayoutData( data );

		fTextBoxes.add( textBox );
		return textBox;
	}

	/*
	 * (non-javadoc) Update fields and validate. @param changedKey Key that
	 * changed, or null, if all changed.
	 */
	protected void validateSettings( Key changedKey, String oldValue,
			String newValue )
	{

	}
}
