/*******************************************************************************
 * Copyright (c) 2007 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.internal.ui.preferences;

import java.util.Arrays;
import java.util.List;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
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.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.StatusDialog;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.jface.window.Window;

import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.SelectionDialog;
import org.eclipse.ui.preferences.IWorkbenchPreferenceContainer;

import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaConventions;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;

import org.eclipse.jdt.ui.IJavaElementSearchConstants;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jdt.ui.PreferenceConstants;

import org.eclipse.jdt.internal.ui.IJavaHelpContextIds;
import org.eclipse.jdt.internal.ui.JavaPluginImages;
import org.eclipse.jdt.internal.ui.dialogs.StatusInfo;
import org.eclipse.jdt.internal.ui.dialogs.TextFieldNavigationHandler;
import org.eclipse.jdt.internal.ui.util.BusyIndicatorRunnableContext;
import org.eclipse.jdt.internal.ui.util.ExceptionHandler;
import org.eclipse.jdt.internal.ui.util.PixelConverter;
import org.eclipse.jdt.internal.ui.viewsupport.JavaElementImageProvider;
import org.eclipse.jdt.internal.ui.wizards.IStatusChangeListener;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.DialogField;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.IDialogFieldListener;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.IListAdapter;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.IStringButtonAdapter;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.LayoutUtil;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.ListDialogField;
import org.eclipse.jdt.internal.ui.wizards.dialogfields.StringButtonDialogField;


/**
 * Configures the Content Assist > Static Members preference page.
 * 
 * @since 3.3
 */
class CodeAssistFavoritesConfigurationBlock extends OptionsConfigurationBlock {
	
	
	private static class FavoriteStaticMemberInputDialog extends StatusDialog {
		
		private class StringButtonAdapter implements IDialogFieldListener, IStringButtonAdapter {
			/*
			 * @see IDialogFieldListener#dialogFieldChanged(DialogField)
			 */
			public void dialogFieldChanged(DialogField field) {
				doDialogFieldChanged(field);
			}			

			/*
			 * @see IStringButtonAdapter#changeControlPressed(DialogField)
			 */
			public void changeControlPressed(DialogField field) {
				doBrowseTypes();
			}
		}
		
		private StringButtonDialogField fNameDialogField;
		private List fExistingEntries;
		private final boolean fIsEditingMember;
			
		public FavoriteStaticMemberInputDialog(Shell parent, List existingEntries, boolean isEditingMember) {
			super(parent);
			fIsEditingMember= isEditingMember;
			setShellStyle(getShellStyle() | SWT.RESIZE);
			
			fExistingEntries= existingEntries;
			
			String label, title;
			if (isEditingMember) {
				title= PreferencesMessages.FavoriteStaticMemberInputDialog_member_title; 
				label= PreferencesMessages.FavoriteStaticMemberInputDialog_member_labelText; 
			} else {
				title= PreferencesMessages.FavoriteStaticMemberInputDialog_type_title; 
				label= PreferencesMessages.FavoriteStaticMemberInputDialog_type_labelText;
			}
			setTitle(title);

			StringButtonAdapter adapter= new StringButtonAdapter();

			fNameDialogField= new StringButtonDialogField(adapter);
			fNameDialogField.setLabelText(label); 
			fNameDialogField.setButtonLabel(PreferencesMessages.FavoriteStaticMemberInputDialog_browse_button); 
			fNameDialogField.setDialogFieldListener(adapter);
			fNameDialogField.setText(""); //$NON-NLS-1$
		}
			
		public void setInitialSelection(String editedEntry) {
			Assert.isNotNull(editedEntry);
			if (editedEntry.length() == 0)
				fNameDialogField.setText(""); //$NON-NLS-1$
			else
				fNameDialogField.setText(editedEntry);
		}
		
		public String getResult() {
			String val= fNameDialogField.getText();
			if (!fIsEditingMember)
				val= val + WILDCARD;
			return val;
		}
			
		protected Control createDialogArea(Composite parent) {
			Composite composite= (Composite) super.createDialogArea(parent);
			initializeDialogUnits(parent);
			
			GridLayout layout= (GridLayout) composite.getLayout();
			layout.numColumns= 2;
			
			fNameDialogField.doFillIntoGrid(composite, 3);
			
			fNameDialogField.getChangeControl(null).setVisible(!fIsEditingMember);
			
			LayoutUtil.setHorizontalSpan(fNameDialogField.getLabelControl(null), 2);
			
			int fieldWidthHint= convertWidthInCharsToPixels(60);
			Text text= fNameDialogField.getTextControl(null);
			LayoutUtil.setWidthHint(text, fieldWidthHint);
			LayoutUtil.setHorizontalGrabbing(text);
			LayoutUtil.setHorizontalSpan(text, fIsEditingMember ? 2 : 1);
			TextFieldNavigationHandler.install(text);
			
			DialogField.createEmptySpace(composite, 1);
			
			fNameDialogField.postSetFocusOnDialogField(parent.getDisplay());
			
			
			applyDialogFont(composite);		
			return composite;
		}
		
		private void doBrowseTypes() {		
			IRunnableContext context= new BusyIndicatorRunnableContext();
			IJavaSearchScope scope= SearchEngine.createWorkspaceScope();
			int style= IJavaElementSearchConstants.CONSIDER_ALL_TYPES;
			try {
				SelectionDialog dialog= JavaUI.createTypeDialog(getShell(), context, scope, style, false, fNameDialogField.getText());
				dialog.setTitle(PreferencesMessages.FavoriteStaticMemberInputDialog_ChooseTypeDialog_title); 
				dialog.setMessage(PreferencesMessages.FavoriteStaticMemberInputDialog_ChooseTypeDialog_description); 
				if (dialog.open() == Window.OK) {
					IType res= (IType) dialog.getResult()[0];
					fNameDialogField.setText(res.getFullyQualifiedName('.'));
				}
			} catch (JavaModelException e) {
				ExceptionHandler.handle(e, getShell(), PreferencesMessages.FavoriteStaticMemberInputDialog_ChooseTypeDialog_title, PreferencesMessages.FavoriteStaticMemberInputDialog_ChooseTypeDialog_error_message);  
			}
		}
		
		void doDialogFieldChanged(DialogField field) {
			doValidation();
		}
		
		private void doValidation() {
			StatusInfo status= new StatusInfo();
			String newText= fNameDialogField.getText();
			if (newText.length() == 0) {
				status.setError(""); //$NON-NLS-1$
			} else {
				IStatus val= JavaConventions.validateJavaTypeName(newText, JavaCore.VERSION_1_3, JavaCore.VERSION_1_3);
				if (val.matches(IStatus.ERROR)) {
					if (fIsEditingMember) 
						status.setError(PreferencesMessages.FavoriteStaticMemberInputDialog_error_invalidMemberName);
					else
						status.setError(PreferencesMessages.FavoriteStaticMemberInputDialog_error_invalidTypeName);
				} else {
					if (doesExist(newText, fIsEditingMember)) {
						status.setError(PreferencesMessages.FavoriteStaticMemberInputDialog_error_entryExists); 
					}
				}
			}
			updateStatus(status);
		}
		
		private boolean doesExist(String name, boolean isStatic) {
			for (int i= 0; i < fExistingEntries.size(); i++) {
				String entry= (String) fExistingEntries.get(i);
				if (name.equals(entry)) {
					return true;
				}
			}
			return false;
		}
		

		/*
		 * @see org.eclipse.jface.window.Window#configureShell(Shell)
		 */
		protected void configureShell(Shell newShell) {
			super.configureShell(newShell);
			PlatformUI.getWorkbench().getHelpSystem().setHelp(newShell, IJavaHelpContextIds.JAVA_EDITOR_PREFERENCE_PAGE);
		}

	}

	
	private static class ListLabelProvider extends LabelProvider {
		
		public final Image MEMBER_ICON;
		private final Image CLASS_ICON;

		public ListLabelProvider() {
			MEMBER_ICON= JavaElementImageProvider.getDecoratedImage(JavaPluginImages.DESC_MISC_PUBLIC, 0, JavaElementImageProvider.SMALL_SIZE);
			CLASS_ICON= JavaElementImageProvider.getDecoratedImage(JavaPluginImages.DESC_OBJS_CLASS, 0, JavaElementImageProvider.SMALL_SIZE);
		}
		
		public Image getImage(Object element) {
			return ((String)element).endsWith(WILDCARD) ? CLASS_ICON : MEMBER_ICON;
		}

		public String getText(Object element) {
			return (String)element;
		}
	}
	
	
	private class ListAdapter implements IListAdapter, IDialogFieldListener {

		private boolean canEdit(ListDialogField field) {
			List selected= field.getSelectedElements();
			return selected.size() == 1;
		}

        public void customButtonPressed(ListDialogField field, int index) {
        	doButtonPressed(index);
        }

        public void selectionChanged(ListDialogField field) {
			fList.enableButton(IDX_EDIT, canEdit(field));
        }

        public void dialogFieldChanged(DialogField field) {
        	doDialogFieldChanged(field);
        }
        
        public void doubleClicked(ListDialogField field) {
        	if (canEdit(field)) {
				doButtonPressed(IDX_EDIT);
        	}
        }
	}
	
	
	private static final Key PREF_CODEASSIST_FAVORITE_STATIC_MEMBERS= getJDTUIKey(PreferenceConstants.CODEASSIST_FAVORITE_STATIC_MEMBERS);
	
	private static final String WILDCARD= ".*"; //$NON-NLS-1$

	private static final int IDX_NEW_TYPE= 0;
	private static final int IDX_NEW_MEMBER= 1;
	private static final int IDX_EDIT= 2;
	private static final int IDX_REMOVE= 3;

	private ListDialogField fList;
	

	private static Key[] getAllKeys() {
		return new Key[] {
				PREF_CODEASSIST_FAVORITE_STATIC_MEMBERS
		};	
	}
	

	public CodeAssistFavoritesConfigurationBlock(IStatusChangeListener statusListener, IWorkbenchPreferenceContainer workbenchcontainer) {
		super(statusListener, null, getAllKeys(), workbenchcontainer);
	}

	protected Control createContents(Composite parent) {
		ScrolledPageContent scrolled= new ScrolledPageContent(parent, SWT.H_SCROLL | SWT.V_SCROLL);
		scrolled.setExpandHorizontal(true);
		scrolled.setExpandVertical(true);
		
		Composite control= new Composite(scrolled, SWT.NONE);
		GridLayout layout= new GridLayout();
		layout.numColumns= 2;
		layout.marginWidth= 0;
		layout.marginHeight= 0;
		control.setLayout(layout);
		
		createFavoriteList(control);
		
		initialize();
		
		scrolled.setContent(control);
		final Point size= control.computeSize(SWT.DEFAULT, SWT.DEFAULT);
		scrolled.setMinSize(size.x, size.y);

		Dialog.applyDialogFont(scrolled);
		
		return scrolled;
	}
	
	private void createFavoriteList(Composite parent) {
		String[] buttonLabels= new String[] { 
				PreferencesMessages.CodeAssistStaticMembersConfigurationBlock_newType_button, 
				PreferencesMessages.CodeAssistStaticMembersConfigurationBlock_newMember_button, 
				PreferencesMessages.CodeAssistStaticMembersConfigurationBlock_edit_button, 
				PreferencesMessages.CodeAssistStaticMembersConfigurationBlock_remove_button
		};

		ListAdapter adapter= new ListAdapter();

		fList= new ListDialogField(adapter, buttonLabels, new ListLabelProvider());
		fList.setDialogFieldListener(adapter);
		fList.setLabelText(PreferencesMessages.CodeAssistStaticMembersConfigurationBlock_description); 
		fList.setRemoveButtonIndex(IDX_REMOVE);
		fList.enableButton(IDX_EDIT, false);
		fList.setViewerComparator(new ViewerComparator());
		
		PixelConverter pixelConverter= new PixelConverter(parent);
		
		fList.doFillIntoGrid(parent, 3);
		LayoutUtil.setHorizontalSpan(fList.getLabelControl(null), 2);
		LayoutUtil.setWidthHint(fList.getLabelControl(null), pixelConverter.convertWidthInCharsToPixels(60));
		LayoutUtil.setHorizontalGrabbing(fList.getListControl(null));
		
		Control listControl= fList.getListControl(null);
		GridData gd= (GridData)listControl.getLayoutData();
		gd.verticalAlignment= GridData.BEGINNING;
		gd.heightHint= pixelConverter.convertHeightInCharsToPixels(7);
	}
	
	public void initialize() {
		initializeFields();
	}

	private void initializeFields() {
		fList.setElements(Arrays.asList(getFavoriteStaticMembersPreference()));
 	}
	
    public void performDefaults() {
		super.performDefaults();
		initializeFields();
	}
	
	protected String[] getFullBuildDialogStrings(boolean workspaceSettings) {
		return null;
	}
	
	protected void validateSettings(Key key, String oldValue, String newValue) {
		// no validation
	}

	protected void setControlEnabled(Key key, boolean enabled) {
		Control control= getControl(key);
		control.setEnabled(enabled);
		Label label= (Label) fLabels.get(control);
		if (label != null)
			label.setEnabled(enabled);
	}

	private Control getControl(Key key) {
		for (int i= fComboBoxes.size() - 1; i >= 0; i--) {
			Control curr= (Control) fComboBoxes.get(i);
			ControlData data= (ControlData) curr.getData();
			if (key.equals(data.getKey())) {
				return curr;
			}
		}
		for (int i= fCheckBoxes.size() - 1; i >= 0; i--) {
			Control curr= (Control) fCheckBoxes.get(i);
			ControlData data= (ControlData) curr.getData();
			if (key.equals(data.getKey())) {
				return curr;
			}
		}
		for (int i= fTextBoxes.size() - 1; i >= 0; i--) {
			Control curr= (Control) fTextBoxes.get(i);
			Key currKey= (Key) curr.getData();
			if (key.equals(currKey)) {
				return curr;
			}
		}
		return null;		
	}
	
	private void doButtonPressed(int index) {
		if (index == IDX_NEW_TYPE || index == IDX_NEW_MEMBER) { // add new
			List existing= fList.getElements();
			FavoriteStaticMemberInputDialog dialog= new FavoriteStaticMemberInputDialog(getShell(), existing, index == IDX_NEW_MEMBER);
			if (dialog.open() == Window.OK) {
				fList.addElement(dialog.getResult());
			}
		} else if (index == IDX_EDIT) { // edit
			List selected= fList.getSelectedElements();
			if (selected.isEmpty())
				return;

			String editedEntry= (String)selected.get(0);
			
			List existing= fList.getElements();
			existing.remove(editedEntry);
			boolean isType= editedEntry.endsWith(WILDCARD);
			FavoriteStaticMemberInputDialog dialog= new FavoriteStaticMemberInputDialog(getShell(), existing, !isType);
			if (isType)
				dialog.setInitialSelection(editedEntry.substring(0, editedEntry.length() - 2));
			else
				dialog.setInitialSelection(editedEntry);
			
			if (dialog.open() == Window.OK) {
				fList.replaceElement(editedEntry, dialog.getResult());
			}
		}
	}
	
	protected final void doDialogFieldChanged(DialogField field) {
		// set values in working copy
		if (field == fList)
	  		setValue(PREF_CODEASSIST_FAVORITE_STATIC_MEMBERS, serializeFavorites(fList.getElements()));
	}

	private String[] getFavoriteStaticMembersPreference() {
		String str= getValue(PREF_CODEASSIST_FAVORITE_STATIC_MEMBERS);
		if (str != null && str.length() > 0)
			return deserializeFavorites(str);
		return new String[0];
	}

	private static String[] deserializeFavorites(String str) {
		return str.split(";"); //$NON-NLS-1$
	}
	
	private static String serializeFavorites(List favorites) {
		int size= favorites.size();
		StringBuffer buf= new StringBuffer();
		for (int i= 0; i < size; i++) {
			buf.append((String)favorites.get(i));
			if (i < size -1)
				buf.append(';');
		}
		return buf.toString();
	}

}
