/*******************************************************************************
 * Copyright (c) 2000, 2005 QNX Software Systems and others.
 * 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:
 *     QNX Software Systems - Initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.debug.mi.internal.ui.preferences;

import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Iterator;
import org.eclipse.cdt.debug.mi.core.IMIConstants;
import org.eclipse.cdt.debug.mi.core.MIPlugin;
import org.eclipse.cdt.debug.mi.internal.ui.IMIHelpContextIds;
import org.eclipse.cdt.debug.mi.internal.ui.MIUIPlugin;
import org.eclipse.cdt.utils.ui.controls.ControlFactory;
import org.eclipse.core.runtime.Preferences;
import org.eclipse.jface.preference.BooleanFieldEditor;
import org.eclipse.jface.preference.FieldEditor;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.IntegerFieldEditor;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.jface.preference.StringFieldEditor;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;

/**
 * Page for preferences that apply specifically to GDB MI.
 */
public class MIPreferencePage extends PreferencePage implements IWorkbenchPreferencePage, IPropertyChangeListener {

	/**
	 * This class exists to provide visibility to the
	 * <code>refreshValidState</code> method and to perform more intelligent
	 * clearing of the error message.
	 */
	protected class MIIntegerFieldEditor extends IntegerFieldEditor {						
		
		public MIIntegerFieldEditor(String name, String labelText, Composite parent) {
			super(name, labelText, parent);
		}
		
		/**
		 * @see org.eclipse.jface.preference.FieldEditor#refreshValidState()
		 */
		protected void refreshValidState() {
			super.refreshValidState();
		}
		
		/**
		 * Clears the error message from the message line if the error
		 * message is the error message from this field editor.
		 */
		protected void clearErrorMessage() {
			if (canClearErrorMessage()) {
				super.clearErrorMessage();
			}
		}
	}
	public class MIPreferenceStore implements IPreferenceStore {
		
		private Preferences fPreferences;

		private HashMap fListeners = new HashMap();
		
		public MIPreferenceStore( Preferences pref ) {
			fPreferences = pref;
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#addPropertyChangeListener(org.eclipse.jface.util.IPropertyChangeListener)
		 */
		public void addPropertyChangeListener( final IPropertyChangeListener listener ) {
			Preferences.IPropertyChangeListener l = new Preferences.IPropertyChangeListener() {
				
				public void propertyChange( org.eclipse.core.runtime.Preferences.PropertyChangeEvent event ) {
					listener.propertyChange( new PropertyChangeEvent( MIPreferenceStore.this, event.getProperty(), event.getNewValue(), event.getOldValue() ) );
				}
			};
			fListeners.put( listener, l );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#contains(java.lang.String)
		 */
		public boolean contains( String name ) {
			return getPreferences().contains( name );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#firePropertyChangeEvent(java.lang.String, java.lang.Object, java.lang.Object)
		 */
		public void firePropertyChangeEvent( String name, Object oldValue, Object newValue ) {
			Iterator it = fListeners.keySet().iterator();
			while( it.hasNext() ) {
				((IPropertyChangeListener)it.next()).propertyChange( new PropertyChangeEvent( this, name, oldValue, newValue ) );
			}
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#getBoolean(java.lang.String)
		 */
		public boolean getBoolean( String name ) {
			return fPreferences.getBoolean( name );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#getDefaultBoolean(java.lang.String)
		 */
		public boolean getDefaultBoolean( String name ) {
			return fPreferences.getDefaultBoolean( name );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#getDefaultDouble(java.lang.String)
		 */
		public double getDefaultDouble( String name ) {
			return fPreferences.getDefaultDouble( name );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#getDefaultFloat(java.lang.String)
		 */
		public float getDefaultFloat( String name ) {
			return fPreferences.getDefaultFloat( name );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#getDefaultInt(java.lang.String)
		 */
		public int getDefaultInt( String name ) {
			return fPreferences.getDefaultInt( name );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#getDefaultLong(java.lang.String)
		 */
		public long getDefaultLong( String name ) {
			return fPreferences.getDefaultLong( name );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#getDefaultString(java.lang.String)
		 */
		public String getDefaultString( String name ) {
			return fPreferences.getDefaultString( name );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#getDouble(java.lang.String)
		 */
		public double getDouble( String name ) {
			return fPreferences.getDouble( name );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#getFloat(java.lang.String)
		 */
		public float getFloat( String name ) {
			return fPreferences.getFloat( name );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#getInt(java.lang.String)
		 */
		public int getInt( String name ) {
			return fPreferences.getInt( name );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#getLong(java.lang.String)
		 */
		public long getLong( String name ) {
			return fPreferences.getLong( name );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#getString(java.lang.String)
		 */
		public String getString( String name ) {
			return fPreferences.getString( name );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#isDefault(java.lang.String)
		 */
		public boolean isDefault( String name ) {
			return fPreferences.isDefault( name );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#needsSaving()
		 */
		public boolean needsSaving() {
			return getPreferences().needsSaving();
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#putValue(java.lang.String, java.lang.String)
		 */
		public void putValue( String name, String value ) {
			getPreferences().setValue( name, value );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#removePropertyChangeListener(org.eclipse.jface.util.IPropertyChangeListener)
		 */
		public void removePropertyChangeListener( IPropertyChangeListener listener ) {
			fListeners.remove( listener );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#setDefault(java.lang.String, double)
		 */
		public void setDefault( String name, double value ) {
			getPreferences().setDefault( name, value );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#setDefault(java.lang.String, float)
		 */
		public void setDefault( String name, float value ) {
			getPreferences().setDefault( name, value );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#setDefault(java.lang.String, int)
		 */
		public void setDefault( String name, int value ) {
			getPreferences().setDefault( name, value );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#setDefault(java.lang.String, long)
		 */
		public void setDefault( String name, long value ) {
			getPreferences().setDefault( name, value );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#setDefault(java.lang.String, java.lang.String)
		 */
		public void setDefault( String name, String defaultObject ) {
			getPreferences().setDefault( name, defaultObject );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#setDefault(java.lang.String, boolean)
		 */
		public void setDefault( String name, boolean value ) {
			getPreferences().setDefault( name, value );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#setToDefault(java.lang.String)
		 */
		public void setToDefault( String name ) {
			getPreferences().setToDefault( name );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#setValue(java.lang.String, double)
		 */
		public void setValue( String name, double value ) {
			getPreferences().setValue( name, value );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#setValue(java.lang.String, float)
		 */
		public void setValue( String name, float value ) {
			getPreferences().setValue( name, value );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#setValue(java.lang.String, int)
		 */
		public void setValue( String name, int value ) {
			getPreferences().setValue( name, value );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#setValue(java.lang.String, long)
		 */
		public void setValue( String name, long value ) {
			getPreferences().setValue( name, value );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#setValue(java.lang.String, java.lang.String)
		 */
		public void setValue( String name, String value ) {
			getPreferences().setValue( name, value );
		}

		/* (non-Javadoc)
		 * @see org.eclipse.jface.preference.IPreferenceStore#setValue(java.lang.String, boolean)
		 */
		public void setValue( String name, boolean value ) {
			getPreferences().setValue( name, value );
		}

		protected Preferences getPreferences() {
			return fPreferences;
		}
	}

	private IWorkbench fWorkbench;

	// Debugger timeout preference widgets
	private MIIntegerFieldEditor fDebugTimeoutText;

	// Launch timeout preference widgets
	private MIIntegerFieldEditor fLaunchTimeoutText;

	private BooleanFieldEditor fRefreshSolibsButton;
	
	private BooleanFieldEditor fMemorySpacesButton;

	private MIPreferenceStore fMICorePreferenceStore = new MIPreferenceStore( MIPlugin.getDefault().getPluginPreferences() );

	/**
	 * Constructor for MIPreferencePage.
	 */
	public MIPreferencePage() {
		super();
		setPreferenceStore( MIUIPlugin.getDefault().getPreferenceStore() );
		setDescription( PreferenceMessages.getString( "MIPreferencePage.0" ) ); //$NON-NLS-1$
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.jface.preference.PreferencePage#createContents(Composite)
	 */
	protected Control createContents( Composite parent ) {
		getWorkbench().getHelpSystem().setHelp( getControl(), IMIHelpContextIds.MI_PREFERENCE_PAGE );
		//The main composite
		Composite composite = new Composite( parent, SWT.NULL );
		GridLayout layout = new GridLayout();
		layout.numColumns = 1;
		layout.marginHeight = 0;
		layout.marginWidth = 0;
		composite.setLayout( layout );
		GridData data = new GridData();
		data.verticalAlignment = GridData.FILL;
		data.horizontalAlignment = GridData.FILL;
		composite.setLayoutData( data );
		createSpacer( composite, 1 );
		createCommunicationPreferences( composite );
		createSpacer( composite, 1 );
		fMemorySpacesButton = createCheckbox(IMIConstants.PREF_ENABLE_MEMORY_SPACES, PreferenceMessages.getString( "MIPreferencePage.7" ), composite ); //$NON-NLS-1$
		return composite;
	}

	/**
	 * Creates composite group and sets the default layout data.
	 * 
	 * @param parent the parent of the new composite
	 * @param numColumns the number of columns for the new composite
	 * @param labelText the text label of the new composite
	 * @return the newly-created composite
	 */
	private Composite createGroupComposite( Composite parent, int numColumns, String labelText ) {
		return ControlFactory.createGroup( parent, labelText, numColumns );
	}

	/**
	 * @see IPreferencePage#performOk()
	 */
	public boolean performOk() {
		boolean result = super.performOk();
		storeValues();
		MIUIPlugin.getDefault().savePluginPreferences();
		MIPlugin.getDefault().savePluginPreferences();
		return result;
	}

	/**
	 * Sets the default preferences.
	 * 
	 * @see PreferencePage#performDefaults()
	 */
	protected void performDefaults() {
		setDefaultValues();
		super.performDefaults();
	}

	private void setDefaultValues() {
		fDebugTimeoutText.loadDefault();
		fLaunchTimeoutText.loadDefault();
		fRefreshSolibsButton.loadDefault();
		fMemorySpacesButton.loadDefault();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.IWorkbenchPreferencePage#init(IWorkbench)
	 */
	public void init( IWorkbench workbench ) {
		fWorkbench = workbench;
	}

	protected void createSpacer( Composite composite, int columnSpan ) {
		Label label = new Label( composite, SWT.NONE );
		GridData gd = new GridData();
		gd.horizontalSpan = columnSpan;
		label.setLayoutData( gd );
	}

	private void createCommunicationPreferences( Composite composite ) {
		Composite comp = createGroupComposite( composite, 1, PreferenceMessages.getString( "MIPreferencePage.1" ) ); //$NON-NLS-1$
		//Add in an intermediate composite to allow for spacing
		Composite spacingComposite = new Composite( comp, SWT.NONE );
		GridLayout layout = new GridLayout();
		spacingComposite.setLayout( layout );
		GridData data = new GridData();
		data.horizontalSpan = 2;
		spacingComposite.setLayoutData( data );
		fDebugTimeoutText = createTimeoutField( IMIConstants.PREF_REQUEST_TIMEOUT, PreferenceMessages.getString( "MIPreferencePage.2" ), spacingComposite ); //$NON-NLS-1$
		fDebugTimeoutText.setPropertyChangeListener( this );
		fLaunchTimeoutText = createTimeoutField( IMIConstants.PREF_REQUEST_LAUNCH_TIMEOUT, PreferenceMessages.getString( "MIPreferencePage.3" ), spacingComposite ); //$NON-NLS-1$
		fLaunchTimeoutText.setPropertyChangeListener( this );
		fRefreshSolibsButton = createCheckbox( IMIConstants.PREF_SHARED_LIBRARIES_AUTO_REFRESH, PreferenceMessages.getString( "MIPreferencePage.6" ), spacingComposite ); //$NON-NLS-1$
	}

	/**
	 * Store the preference values based on the state of the component widgets
	 */
	private void storeValues() {
		fDebugTimeoutText.store();
		fLaunchTimeoutText.store();
		fRefreshSolibsButton.store();
		fMemorySpacesButton.store();
	}

	private MIIntegerFieldEditor createTimeoutField( String preference, String label, Composite parent ) {
		MIIntegerFieldEditor toText = new MIIntegerFieldEditor( preference, label, parent );
		GridData data = new GridData();
		data.widthHint = convertWidthInCharsToPixels( 10 );
		toText.getTextControl( parent ).setLayoutData( data );
		toText.setPreferenceStore( getMICorePreferenceStore() );
		toText.setPage( this );
		toText.setValidateStrategy( StringFieldEditor.VALIDATE_ON_KEY_STROKE );
		toText.setValidRange( IMIConstants.MIN_REQUEST_TIMEOUT, IMIConstants.MAX_REQUEST_TIMEOUT );
		String minValue = Integer.toString( IMIConstants.MIN_REQUEST_TIMEOUT );
		String maxValue = Integer.toString( IMIConstants.MAX_REQUEST_TIMEOUT );
		toText.setErrorMessage( MessageFormat.format( PreferenceMessages.getString( "MIPreferencePage.4" ), new String[]{ minValue, maxValue } ) ); //$NON-NLS-1$
		toText.load();
		return toText;
	}

	private BooleanFieldEditor createCheckbox( String preference, String label, Composite parent ) {
		BooleanFieldEditor field = new BooleanFieldEditor( preference, label, parent );
		field.setPage( this );
		field.setPreferenceStore( new MIPreferenceStore( MIPlugin.getDefault().getPluginPreferences() ) );
		field.load();
		return field;
	}
	
	protected MIPreferenceStore getMICorePreferenceStore() {
		return fMICorePreferenceStore;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.dialogs.IDialogPage#dispose()
	 */
	public void dispose() {
		fDebugTimeoutText.dispose();
		fLaunchTimeoutText.dispose();
		fRefreshSolibsButton.dispose();
		fMemorySpacesButton.dispose();
		super.dispose();
	}

	protected MIIntegerFieldEditor getLaunchTimeoutText() {
		return fLaunchTimeoutText;
	}

	protected MIIntegerFieldEditor getDebugTimeoutText() {
		return fDebugTimeoutText;
	}

	/**
	 * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
	 */
	public void propertyChange(PropertyChangeEvent event) {

		if (event.getProperty().equals(FieldEditor.IS_VALID)) {
			boolean newValue = ((Boolean) event.getNewValue()).booleanValue();
			// If the new value is true then we must check all field editors.
			// If it is false, then the page is invalid in any case.
			MIIntegerFieldEditor launchTimeout = getLaunchTimeoutText();
			MIIntegerFieldEditor debugTimeout = getDebugTimeoutText();
			if (newValue) {
				if (launchTimeout != null && event.getSource() != launchTimeout) {
					launchTimeout.refreshValidState();
				} 
				if (debugTimeout != null && event.getSource() != debugTimeout) {
					debugTimeout.refreshValidState();
				}
			} 
			setValid(launchTimeout.isValid() && debugTimeout.isValid());
			getContainer().updateButtons();
			updateApplyButton();
		}
	}

	protected boolean canClearErrorMessage() {
		MIIntegerFieldEditor launchTimeout = getLaunchTimeoutText();
		MIIntegerFieldEditor debugTimeout = getDebugTimeoutText();
		boolean validLaunch = false;
		boolean validDebug = false;
		if (launchTimeout != null) {
			validLaunch = launchTimeout.isValid();
		}
		if (debugTimeout != null) {
			validDebug = debugTimeout.isValid();
		}
		return validLaunch && validDebug;
	}

	private IWorkbench getWorkbench() {
		return fWorkbench;
	}
	
}
