/*******************************************************************************
 * Copyright (c) 2004, 2009 Tasktop Technologies 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:
 *     Tasktop Technologies - initial API and implementation
 *******************************************************************************/

package org.eclipse.mylyn.tasks.ui.editors;

import java.util.Map;

import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.dialogs.IInputValidator;
import org.eclipse.jface.fieldassist.ComboContentAdapter;
import org.eclipse.jface.fieldassist.ContentProposalAdapter;
import org.eclipse.jface.fieldassist.ControlDecoration;
import org.eclipse.jface.fieldassist.FieldDecoration;
import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
import org.eclipse.jface.fieldassist.IContentProposalProvider;
import org.eclipse.jface.fieldassist.IControlContentAdapter;
import org.eclipse.jface.fieldassist.TextContentAdapter;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.mylyn.internal.provisional.commons.ui.CommonTextSupport;
import org.eclipse.mylyn.internal.provisional.commons.ui.CommonThemes;
import org.eclipse.mylyn.internal.tasks.ui.PersonProposalLabelProvider;
import org.eclipse.mylyn.internal.tasks.ui.PersonProposalProvider;
import org.eclipse.mylyn.internal.tasks.ui.editors.EditorUtil;
import org.eclipse.mylyn.internal.tasks.ui.editors.Messages;
import org.eclipse.mylyn.internal.tasks.ui.editors.PersonAttributeEditor;
import org.eclipse.mylyn.internal.tasks.ui.editors.RepositoryTextViewerConfiguration.Mode;
import org.eclipse.mylyn.internal.tasks.ui.editors.RichTextAttributeEditor;
import org.eclipse.mylyn.tasks.core.data.TaskAttribute;
import org.eclipse.mylyn.tasks.core.data.TaskDataModelEvent;
import org.eclipse.mylyn.tasks.core.data.TaskDataModelEvent.EventKind;
import org.eclipse.mylyn.tasks.core.data.TaskDataModelListener;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CCombo;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.fieldassist.ContentAssistCommandAdapter;
import org.eclipse.ui.handlers.IHandlerActivation;
import org.eclipse.ui.keys.IBindingService;
import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
import org.eclipse.ui.themes.IThemeManager;

/**
 * @author Steffen Pingel
 * @since 3.0
 */
// TODO EDITOR rename to AttributeUiToolkit?
public class AttributeEditorToolkit {

	private final Color colorIncoming;

	private Menu menu;

	private AbstractRenderingEngine renderingEngine;

	private final CommonTextSupport textSupport;

	@Deprecated
	public IHandlerActivation contentAssistHandlerActivation;

	AttributeEditorToolkit(CommonTextSupport textSupport) {
		this.textSupport = textSupport;
		IThemeManager themeManager = PlatformUI.getWorkbench().getThemeManager();
		colorIncoming = themeManager.getCurrentTheme().getColorRegistry().get(CommonThemes.COLOR_INCOMING_BACKGROUND);
	}

	public void adapt(AbstractAttributeEditor editor) {
		if (editor.getControl() instanceof Text || editor.getControl() instanceof CCombo
				|| editor instanceof PersonAttributeEditor) {
			Control control = (editor instanceof PersonAttributeEditor)
					? ((PersonAttributeEditor) editor).getText()
					: editor.getControl();
			if (control == null) {
				// fall back in case getText() returns null
				control = editor.getControl();
			}
			if (!editor.isReadOnly() && hasContentAssist(editor.getTaskAttribute())) {
				IContentProposalProvider contentProposalProvider = createContentProposalProvider(editor.getTaskAttribute());
				ILabelProvider labelPropsalProvider = createLabelProposalProvider(editor.getTaskAttribute());
				if (contentProposalProvider != null && labelPropsalProvider != null) {
					ContentAssistCommandAdapter adapter = new ContentAssistCommandAdapter(control,
							getContentAdapter(control), contentProposalProvider,
							ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS, new char[0], true);
					adapter.setLabelProvider(labelPropsalProvider);
					adapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);
				}
			}
		} else if (editor instanceof RichTextAttributeEditor) {
			RichTextAttributeEditor richTextEditor = (RichTextAttributeEditor) editor;
			boolean spellCheck = hasSpellChecking(editor.getTaskAttribute());
			final SourceViewer viewer = richTextEditor.getViewer();
			textSupport.install(viewer, spellCheck);
			if (!editor.isReadOnly() && richTextEditor.getMode() == Mode.TASK_RELATION) {
				installContentAssistControlDecoration(viewer.getControl());
			}
			installMenu(viewer.getControl());
		} else {
			final TextViewer viewer = CommonTextSupport.getTextViewer(editor.getControl());
			if (viewer != null) {
				textSupport.install(viewer, false);
				installMenu(viewer.getControl());
			}
		}

		// for outline
		EditorUtil.setMarker(editor.getControl(), editor.getTaskAttribute().getId());

		editor.decorate(getColorIncoming());
	}

	private IControlContentAdapter getContentAdapter(Control control) {
		if (control instanceof Combo) {
			return new ComboContentAdapter();
		} else if (control instanceof Text) {
			return new TextContentAdapter();
		}
		return null;
	}

	private void installMenu(final Control control) {
		if (menu != null) {
			control.setMenu(menu);
			control.addDisposeListener(new DisposeListener() {
				public void widgetDisposed(DisposeEvent e) {
					control.setMenu(null);
				}
			});
		}
	}

	/**
	 * Adds content assist to the given text field.
	 * 
	 * @param text
	 *            text field to decorate.
	 * @param proposalProvider
	 *            instance providing content proposals
	 * @return the ContentAssistCommandAdapter for the field.
	 */
	private ControlDecoration installContentAssistControlDecoration(Control control) {
		ControlDecoration controlDecoration = new ControlDecoration(control, (SWT.TOP | SWT.LEFT));
		controlDecoration.setShowOnlyOnFocus(true);
		FieldDecoration contentProposalImage = FieldDecorationRegistry.getDefault().getFieldDecoration(
				FieldDecorationRegistry.DEC_CONTENT_PROPOSAL);
		controlDecoration.setImage(contentProposalImage.getImage());
		IBindingService bindingService = (IBindingService) PlatformUI.getWorkbench().getService(IBindingService.class);
		controlDecoration.setDescriptionText(NLS.bind(Messages.AttributeEditorToolkit_Content_Assist_Available__X_,
				bindingService.getBestActiveBindingFormattedFor(ContentAssistCommandAdapter.CONTENT_PROPOSAL_COMMAND)));
		return controlDecoration;
	}

	/**
	 * Creates an IContentProposalProvider to provide content assist proposals for the given attribute.
	 * 
	 * @param attribute
	 *            attribute for which to provide content assist.
	 * @return the IContentProposalProvider.
	 */
	private IContentProposalProvider createContentProposalProvider(TaskAttribute attribute) {
		Map<String, String> proposals = attribute.getTaskData().getAttributeMapper().getOptions(attribute);
		return new PersonProposalProvider(null, attribute.getTaskData(), proposals);
	}

	private ILabelProvider createLabelProposalProvider(TaskAttribute attribute) {
		return new PersonProposalLabelProvider();
	}

	void dispose() {
		// FIXME textSupport.deactivateHandlers();
	}

	public Color getColorIncoming() {
		return colorIncoming;
	}

	Menu getMenu() {
		return menu;
	}

	/**
	 * Subclasses that support HTML preview of ticket description and comments override this method to return an
	 * instance of AbstractRenderingEngine
	 * 
	 * @return <code>null</code> if HTML preview is not supported for the repository (default)
	 * @since 2.1
	 */
	public AbstractRenderingEngine getRenderingEngine(TaskAttribute attribute) {
		return renderingEngine;
	}

	/**
	 * Called to check if there's content assist available for the given attribute.
	 * 
	 * @param attribute
	 *            the attribute
	 * @return true if content assist is available for the specified attribute.
	 */
	private boolean hasContentAssist(TaskAttribute taskAttribute) {
		String type = taskAttribute.getMetaData().getType();
		if (TaskAttribute.TYPE_PERSON.equals(type)) {
			return true;
		} else if (TaskAttribute.TYPE_TASK_DEPENDENCY.equals(type)) {
			return true;
		}
		return false;
	}

	boolean hasSpellChecking(TaskAttribute taskAttribute) {
		String type = taskAttribute.getMetaData().getType();
		if (TaskAttribute.TYPE_LONG_RICH_TEXT.equals(type) || TaskAttribute.TYPE_SHORT_RICH_TEXT.equals(type)) {
			return true;
		}
		return false;
	}

	void setMenu(Menu menu) {
		this.menu = menu;
	}

	public void setRenderingEngine(AbstractRenderingEngine renderingEngine) {
		this.renderingEngine = renderingEngine;
	}

	/**
	 * Adds input validation to an attribute editor and a controlDecoration if invalid
	 * 
	 * @since 3.5
	 */
	public static void createValidator(final AbstractAttributeEditor attributeEditor, Control control,
			final IInputValidator validator) {
		Assert.isNotNull(validator);
		Assert.isNotNull(control);
		Assert.isNotNull(attributeEditor);
		final ControlDecoration decoration = new ControlDecoration(control, SWT.BOTTOM | SWT.LEFT);
		decoration.setMarginWidth(2);
		FieldDecoration errorDecoration = FieldDecorationRegistry.getDefault().getFieldDecoration(
				FieldDecorationRegistry.DEC_ERROR);
		decoration.setImage(errorDecoration.getImage());
		decoration.hide();
		final TaskDataModelListener validationListener = new TaskDataModelListener() {
			@Override
			public void attributeChanged(TaskDataModelEvent event) {
				if (event.getTaskAttribute().equals(attributeEditor.getTaskAttribute())) {
					String validationMessage = validator.isValid(attributeEditor.getTaskAttribute().getValue());
					if (validationMessage == null) {
						decoration.hide();
					} else {
						decoration.setDescriptionText(validationMessage);
						decoration.show();
					}
				}
			}
		};
		attributeEditor.getModel().addModelListener(validationListener);
		control.addDisposeListener(new DisposeListener() {
			public void widgetDisposed(DisposeEvent e) {
				decoration.dispose();
				attributeEditor.getModel().removeModelListener(validationListener);
			}
		});
		validationListener.attributeChanged(new TaskDataModelEvent(attributeEditor.getModel(), EventKind.CHANGED,
				attributeEditor.getTaskAttribute()));
	}

}
