/*******************************************************************************
 * Copyright (c) 2000, 2004 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.text.correction;

import java.util.Iterator;

import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Shell;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;

import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBufferManager;

import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.util.Assert;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DefaultInformationControl;
import org.eclipse.jface.text.IInformationControl;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.contentassist.ContentAssistant;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;

import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IStorageEditorInput;
import org.eclipse.ui.texteditor.ITextEditor;

import org.eclipse.jdt.ui.PreferenceConstants;
import org.eclipse.jdt.ui.text.IColorManager;
import org.eclipse.jdt.ui.text.JavaTextTools;

import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.propertiesfileeditor.IPropertiesFilePartitions;
import org.eclipse.jdt.internal.ui.text.HTMLTextPresenter;


/**
 * PropertiesFileCorrectionAssistant.
 *
 * @since 3.1
 */
public class PropertiesFileCorrectionAssistant extends ContentAssistant {

	private ITextViewer fViewer;
	private ITextEditor fEditor;
	private Position fPosition;


	/**
	 * Constructor for PropertiesFileCorrectionAssistant.
	 */
	public PropertiesFileCorrectionAssistant(ITextEditor editor) {
		Assert.isNotNull(editor);
		fEditor= editor;

		PropertiesFileCorrectionProcessor processor= new PropertiesFileCorrectionProcessor(this);
		setContentAssistProcessor(processor, IPropertiesFilePartitions.PROPERTY_VALUE);
		setContentAssistProcessor(processor, IPropertiesFilePartitions.COMMENT);

		enableAutoActivation(false);
		enableAutoInsert(false);

		setContextInformationPopupOrientation(CONTEXT_INFO_ABOVE);
		setInformationControlCreator(getInformationControlCreator());

		JavaTextTools textTools= JavaPlugin.getDefault().getJavaTextTools();
		IColorManager manager= textTools.getColorManager();

		IPreferenceStore store=  JavaPlugin.getDefault().getPreferenceStore();

		Color c= getColor(store, PreferenceConstants.CODEASSIST_PROPOSALS_FOREGROUND, manager);
		setProposalSelectorForeground(c);

		c= getColor(store, PreferenceConstants.CODEASSIST_PROPOSALS_BACKGROUND, manager);
		setProposalSelectorBackground(c);
	}

	public IEditorPart getEditor() {
		return fEditor;
	}


	private IInformationControlCreator getInformationControlCreator() {
		return new IInformationControlCreator() {
			public IInformationControl createInformationControl(Shell parent) {
				return new DefaultInformationControl(parent, new HTMLTextPresenter());
			}
		};
	}

	private static Color getColor(IPreferenceStore store, String key, IColorManager manager) {
		RGB rgb= PreferenceConverter.getColor(store, key);
		return manager.getColor(rgb);
	}

	/*
	 * @see org.eclipse.jface.text.contentassist.IContentAssistant#install(org.eclipse.jface.text.ITextViewer)
	 */
	public void install(ITextViewer textViewer) {
		super.install(textViewer);
		fViewer= textViewer;
	}



	/*
	 * @see org.eclipse.jface.text.contentassist.ContentAssistant#uninstall()
	 */
	public void uninstall() {
		fViewer= null;
	}

	/**
	 * Show completions at caret position. If current
	 * position does not contain quick fixes look for
	 * next quick fix on same line by moving from left
	 * to right and restarting at end of line if the
	 * beginning of the line is reached.
	 *
	 * @see org.eclipse.jface.text.contentassist.IContentAssistant#showPossibleCompletions()
	 */
	public String showPossibleCompletions() {
		if (fViewer == null || fViewer.getDocument() == null)
			// Let superclass deal with this
			return super.showPossibleCompletions();

		Point selectedRange= fViewer.getSelectedRange();
		fPosition= null;

		if (selectedRange.y == 0) {
			int invocationOffset= computeOffsetWithCorrection(selectedRange.x);
			if (invocationOffset != -1) {
				storePosition();
				fViewer.setSelectedRange(invocationOffset, 0);
				fViewer.revealRange(invocationOffset, 0);
			}
		}
		return super.showPossibleCompletions();
	}

	/**
	 * Find offset which contains corrections.
	 * Search on same line by moving from left
	 * to right and restarting at end of line if the
	 * beginning of the line is reached.
	 *
	 * @return an offset where corrections are available or -1 if none
	 */
	private int computeOffsetWithCorrection(int initalOffset) {
		IRegion lineInfo= null;
		try {
			lineInfo= fViewer.getDocument().getLineInformationOfOffset(initalOffset);
		} catch (BadLocationException ex) {
			return -1;
		}
		int startOffset= lineInfo.getOffset();
		int endOffset= startOffset + lineInfo.getLength();

		int result= computeOffsetWithCorrection(startOffset, endOffset, initalOffset);
		if (result > 0 && result != initalOffset)
			return result;
		else
			return -1;
	}

	/**
	 * @return the best matching offset with corrections or -1 if nothing is found
	 */
	private int computeOffsetWithCorrection(int startOffset, int endOffset, int initialOffset) {
		IAnnotationModel model= null;
		IEditorInput input= fEditor.getEditorInput();
		if (!(input instanceof IStorageEditorInput))
			return -1;

		ITextFileBufferManager manager= FileBuffers.getTextFileBufferManager();
		IPath path= null;
		try {
			path= ((IStorageEditorInput)input).getStorage().getFullPath();
			if (path == null)
				return -1;
			manager.connect(path, null);
		} catch (CoreException e) {
			JavaPlugin.log(e.getStatus());
			return -1;
		}

		try {
			model= manager.getTextFileBuffer(path).getAnnotationModel();

			int invocationOffset= -1;
			int offsetOfFirstProblem= Integer.MAX_VALUE;

			Iterator iter= model.getAnnotationIterator();
			while (iter.hasNext()) {
				Annotation annot= (Annotation) iter.next();
				if (JavaCorrectionProcessor.isQuickFixableType(annot)) {
					Position pos= model.getPosition(annot);
					if (isIncluded(pos, startOffset, endOffset)) {
						if (PropertiesFileCorrectionProcessor.hasCorrections(annot)) {
							offsetOfFirstProblem= Math.min(offsetOfFirstProblem, pos.getOffset());
							invocationOffset= computeBestOffset(invocationOffset, pos, initialOffset);
							if (initialOffset == invocationOffset)
								return initialOffset;
						}
					}
				}
			}
			if (initialOffset < offsetOfFirstProblem && offsetOfFirstProblem != Integer.MAX_VALUE)
				return offsetOfFirstProblem;
			else
				return invocationOffset;
		} finally {
			try {
				manager.disconnect(path, null);
			} catch (CoreException e) {
				JavaPlugin.log(e.getStatus());
			}
		}
	}

	private boolean isIncluded(Position pos, int lineStart, int lineEnd) {
		return (pos != null) && (pos.getOffset() >= lineStart && (pos.getOffset() +  pos.getLength() <= lineEnd));
	}

	/**
	 * Computes and returns the invocation offset given a new
	 * position, the initial offset and the best invocation offset
	 * found so far.
	 * <p>
	 * The closest offset to the left of the initial offset is the
	 * best. If there is no offset on the left, the closest on the
	 * right is the best.</p>
	 */
	private int computeBestOffset(int invocationOffset, Position pos, int initalOffset) {
		int newOffset= pos.offset;
		if (newOffset <= initalOffset && initalOffset <= newOffset + pos.length)
			return initalOffset;

		if (invocationOffset < 0)
			return newOffset;

		if (newOffset <= initalOffset && invocationOffset >= initalOffset)
			return newOffset;

		if (newOffset <= initalOffset && invocationOffset < initalOffset)
			return Math.max(invocationOffset, newOffset);

		if (invocationOffset <= initalOffset)
			return invocationOffset;

		return Math.max(invocationOffset, newOffset);
	}

	/*
	 * @see org.eclipse.jface.text.contentassist.ContentAssistant#possibleCompletionsClosed()
	 */
	protected void possibleCompletionsClosed() {
		super.possibleCompletionsClosed();
		restorePosition();
	}

	private void storePosition() {
		int initalOffset= fViewer.getSelectedRange().x;
		int length= fViewer.getSelectedRange().y;
		fPosition= new Position(initalOffset, length);
	}

	private void restorePosition() {
		if (fPosition != null && !fPosition.isDeleted() && fViewer.getDocument() != null) {
			fViewer.setSelectedRange(fPosition.offset, fPosition.length);
			fViewer.revealRange(fPosition.offset, fPosition.length);
		}
		fPosition= null;
	}

	/**
	 * Returns true if the last invoked completion was called with an updated offset.
	 */
	public boolean isUpdatedOffset() {
		return fPosition != null;
	}
}
