/**********************************************************************
 Copyright (c) 2002, 2005 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:
 Adrian Colyer, Andy Clement, Tracy Gardner - initial version
 Helen Hawkins - updated for new ajde interface (bug 148190) - no longer
 required to set the current project
 ...
 **********************************************************************/
package org.eclipse.ajdt.internal.ui.editor;

import java.util.HashSet;
import java.util.Set;

import org.eclipse.ajdt.core.AJLog;
import org.eclipse.ajdt.core.CoreUtils;
import org.eclipse.ajdt.core.javaelements.AJCompilationUnit;
import org.eclipse.ajdt.core.javaelements.AJCompilationUnitManager;
import org.eclipse.ajdt.internal.ui.editor.actions.AJOpenAction;
import org.eclipse.ajdt.internal.ui.editor.actions.AJOrganizeImportsAction;
import org.eclipse.ajdt.internal.ui.editor.quickfix.JavaCorrectionAssistant;
import org.eclipse.ajdt.internal.ui.help.AspectJUIHelp;
import org.eclipse.ajdt.internal.ui.help.IAJHelpContextIds;
import org.eclipse.ajdt.internal.ui.text.UIMessages;
import org.eclipse.ajdt.internal.ui.xref.XRefUtils;
import org.eclipse.ajdt.ui.AspectJUIPlugin;
import org.eclipse.contribution.xref.internal.ui.utils.XRefUIUtils;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.help.IContextProvider;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IProblemRequestor;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.core.CompilationUnit;
import org.eclipse.jdt.internal.core.JavaModelManager;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.javaeditor.CompilationUnitEditor;
import org.eclipse.jdt.internal.ui.javaeditor.JavaOutlinePage;
import org.eclipse.jdt.internal.ui.javaeditor.JavaSourceViewer;
import org.eclipse.jdt.internal.ui.packageview.PackageExplorerPart;
import org.eclipse.jdt.ui.IPackagesViewPart;
import org.eclipse.jdt.ui.IWorkingCopyManager;
import org.eclipse.jdt.ui.IWorkingCopyManagerExtension;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jdt.ui.actions.IJavaEditorActionDefinitionIds;
import org.eclipse.jdt.ui.text.IJavaPartitions;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.Assert;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextOperationTarget;
import org.eclipse.jface.text.information.IInformationPresenter;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationAccess;
import org.eclipse.jface.text.source.IAnnotationAccessExtension;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.SourceViewerConfiguration;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.progress.UIJob;
import org.eclipse.ui.texteditor.IDocumentProvider;

/**
 * AspectJ Editor extends internal JDT editor in order to use our TextTools.
 * Better would be a clean interface in JDT to achieve the same effect. We also
 * override getAdapter( ) to plug-in the AspectJ-aware outline view.
 */
public class AspectJEditor extends CompilationUnitEditor {
	

	public static final String ASPECTJ_EDITOR_ID = "org.eclipse.ajdt.internal.ui.editor.CompilationUnitEditor"; //$NON-NLS-1$

	private AnnotationAccessWrapper annotationAccessWrapper;

	private static Set activeEditorList = new HashSet();	

	private AspectJEditorTitleImageUpdater aspectJEditorErrorTickUpdater;

	/**
	 * Constructor for AspectJEditor
	 */
	public AspectJEditor() {
		
		super();	
		setRulerContextMenuId("#AJCompilationUnitRulerContext"); //$NON-NLS-1$	
		// Bug 78182
		aspectJEditorErrorTickUpdater= new AspectJEditorTitleImageUpdater(this);
		if (AspectJUIPlugin.usingXref) {
			XRefUIUtils.addWorkingCopyManagerForEditor(this, JavaUI.getWorkingCopyManager());
		}
	}
	
	// Existing in this map means the modification has occurred
	static Set modifiedAspectToClass = new HashSet();

	private AJSourceViewerConfiguration fAJSourceViewerConfiguration;

	private boolean isEditingAjFile = false;

	private CompilationUnitAnnotationModelWrapper.GlobalAnnotationModelListener fGlobalAnnotationModelListener;

	private IAnnotationModel annotationModel;

	private class AJTextOperationTarget implements ITextOperationTarget {
		private ITextOperationTarget parent;

		private JavaCorrectionAssistant fCorrectionAssistant;

		private IInformationPresenter fOutlinePresenter;
		
		public AJTextOperationTarget(ITextOperationTarget parent) {
			this.parent = parent;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.jface.text.ITextOperationTarget#canDoOperation(int)
		 */
		public boolean canDoOperation(int operation) {
			return parent.canDoOperation(operation);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.jface.text.ITextOperationTarget#doOperation(int)
		 */
		public void doOperation(int operation) {
			if (operation == ISourceViewer.QUICK_ASSIST) {
				// use our own correction assistant
				if (fCorrectionAssistant == null) {
					fCorrectionAssistant = new JavaCorrectionAssistant(
							AspectJEditor.this);
					fCorrectionAssistant.install(getSourceViewer());
				}
				String msg = fCorrectionAssistant.showPossibleQuickAssists();
				setStatusLineErrorMessage(msg);
			} else if (operation == JavaSourceViewer.SHOW_OUTLINE) {
				// use our own outline presenter
				// not needed if/when eclipse bug 79489 is fixed
				if (fOutlinePresenter == null) {
					fOutlinePresenter = fAJSourceViewerConfiguration.getOutlinePresenter(getSourceViewer(),false);
					fOutlinePresenter.install(getSourceViewer());
				}
				fOutlinePresenter.showInformation();
			} else {
				parent.doOperation(operation);
			}
		}

	}

	
	public Object getAdapter(Class key) {
		if (key.equals(ITextOperationTarget.class)) {
			// use our own wrapper around the one returned by the superclass
			return new AJTextOperationTarget((ITextOperationTarget) super
					.getAdapter(key));
		}
		if (key.equals(IAnnotationAccess.class)) {
			Object o = super.getAdapter(key);
			if (o instanceof IAnnotationAccessExtension) {
				if (annotationAccessWrapper == null) {
					annotationAccessWrapper = new AnnotationAccessWrapper();
				}
				annotationAccessWrapper
						.setWrapped((IAnnotationAccessExtension) o);
				return annotationAccessWrapper;
			}
			return o;
		}
		if (key.equals(IContextProvider.class)) {
			return AspectJUIHelp.getHelpContextProvider(this, IAJHelpContextIds.ASPECTJ_EDITOR);
		}

		return super.getAdapter(key);
	}

	//Wrapper for IAnnotationAccessExtension. Purpose: this wrapper returns
	//an increased layer for overrideIndicator markers. This does not affect
	//the layer it gets displayed on, but it fixes the context menu so we
	//are still able to choose "open super implementation" when right-clicking
	//the vertical ruler (Matt/Luzius)
	class AnnotationAccessWrapper implements IAnnotationAccessExtension {

		private IAnnotationAccessExtension wrapped;

		public void setWrapped(IAnnotationAccessExtension w) {
			wrapped = w;
		}

		public String getTypeLabel(Annotation annotation) {
			return wrapped.getTypeLabel(annotation);
		}

		public int getLayer(Annotation annotation) {
			int x = wrapped.getLayer(annotation);
			if ("org.eclipse.jdt.ui.overrideIndicator".equals(annotation //$NON-NLS-1$
					.getType())) {
				x += 2;
			}
			return x;
		}

		public void paint(Annotation annotation, GC gc, Canvas canvas,
				Rectangle bounds) {
			wrapped.paint(annotation, gc, canvas, bounds);
		}

		public boolean isPaintable(Annotation annotation) {
			return wrapped.isPaintable(annotation);
		}

		public boolean isSubtype(Object annotationType,
				Object potentialSupertype) {
			return wrapped.isSubtype(annotationType, potentialSupertype);
		}

		public Object[] getSupertypes(Object annotationType) {
			return wrapped.getSupertypes(annotationType);
		}
	}
	
	/*
	 * @see JavaEditor#setOutlinePageInput(JavaOutlinePage, IEditorInput)
	 */
	protected void setOutlinePageInput(JavaOutlinePage page, IEditorInput input) {
		if (page != null) {
			IWorkingCopyManager manager= JavaUI.getWorkingCopyManager();
			page.setInput(manager.getWorkingCopy(input));
		}
	}
	
	/**
	 * Override of doSave to comment-out call to getStatusLineManager - always
	 * returns null (why?) in our environment. Also ask the contentOutlinePage
	 * to update - not strictly required at the moment, but will be in the
	 * future.
	 */
	public void doSave(IProgressMonitor progressMonitor) {

		IDocumentProvider p = getDocumentProvider();
		if (p == null)
			return;

		if (p.isDeleted(getEditorInput())) {

			if (isSaveAsAllowed()) {

				/*
				 * 1GEUSSR: ITPUI:ALL - User should never loose changes made in
				 * the editors. Changed Behavior to make sure that if called
				 * inside a regular save (because of deletion of input element)
				 * there is a way to report back to the caller.
				 */
				performSaveAs(progressMonitor);

			} else {

				/*
				 * 1GF5YOX: ITPJUI:ALL - Save of delete file claims it's still
				 * there Missing resources.
				 */
				Shell shell = getSite().getShell();
				MessageDialog
						.openError(
								shell,
								"CompilationUnitEditor.error.saving.title1", "CompilationUnitEditor.error.saving.message1"); //$NON-NLS-1$ //$NON-NLS-2$
			}

		} else {

			IWorkingCopyManager manager = JavaUI
					.getWorkingCopyManager();
			ICompilationUnit unit = manager.getWorkingCopy(getEditorInput());

			if (unit != null) {
				synchronized (unit) {
					performSave(false, progressMonitor);
				}
			} else {
				performSave(false, progressMonitor);
			}
		}

	}
	
	/**
	 * Override to replace some of the java editor actions
	 * @see org.eclipse.jdt.internal.ui.javaeditor.CompilationUnitEditor#createActions()
	 */
	// Fix for bug 88474
	protected void createActions() {
		super.createActions();
		IAction organizeImports = new AJOrganizeImportsAction(this);
		organizeImports
				.setActionDefinitionId(IJavaEditorActionDefinitionIds.ORGANIZE_IMPORTS);
		setAction("OrganizeImports", organizeImports); //$NON-NLS-1$

		IAction openDeclaration = new AJOpenAction(this);
		openDeclaration
				.setActionDefinitionId(IJavaEditorActionDefinitionIds.OPEN_EDITOR);
		setAction("OpenEditor", openDeclaration); //$NON-NLS-1$
	}


	//override this function to prevent others from setting the
	// SourceViewConfiguration
	//to a non-AspectJ one once it was set to AJSourceViewConfiguration
	protected void setSourceViewerConfiguration(
			SourceViewerConfiguration configuration) {
		Assert.isNotNull(configuration);
		SourceViewerConfiguration myConf = this.getSourceViewerConfiguration();
		if ((myConf != null) && (myConf instanceof AJSourceViewerConfiguration)
				&& !(configuration instanceof AJSourceViewerConfiguration))
			return;
		super.setSourceViewerConfiguration(configuration);
	}

	public void createPartControl(Composite parent) {
		super.createPartControl(parent);
		if(annotationModel != null) {
			if(annotationModel instanceof CompilationUnitAnnotationModelWrapper) {
				((CompilationUnitAnnotationModelWrapper)annotationModel).setDelegate(getSourceViewer().getAnnotationModel());
			}
			if(fGlobalAnnotationModelListener == null) {
				fGlobalAnnotationModelListener = new CompilationUnitAnnotationModelWrapper.GlobalAnnotationModelListener();
				fGlobalAnnotationModelListener.addListener(JavaPlugin.getDefault().getProblemMarkerManager());
			}
			annotationModel.addAnnotationModelListener(fGlobalAnnotationModelListener);			
			IDocument document = getDocumentProvider().getDocument(getEditorInput());
			ISourceViewer sourceViewer= getSourceViewer();		
			sourceViewer.setDocument(document, annotationModel);
			IAnnotationModel model = getDocumentProvider().getAnnotationModel(getEditorInput());
			if(model != null) { // this is null in a linked source folder due to an eclipse bug..
				model.connect(document);
			}	
		}
	}
	
	public void doSetInput(IEditorInput input) throws CoreException {
		super.doSetInput(input);
		if (input instanceof IFileEditorInput) {
			IFileEditorInput fInput = (IFileEditorInput) input;
			ICompilationUnit unit = null;
			//in case it is a .aj file, we need to register it in the
			// WorkingCopyManager
			if (CoreUtils.ASPECTJ_SOURCE_ONLY_FILTER.accept(fInput
					.getFile().getName())) {
				JavaUI.getWorkingCopyManager().connect(input);	
				unit = AJCompilationUnitManager.INSTANCE
					.getAJCompilationUnitFromCache(fInput.getFile());
				if (unit != null){
					isEditingAjFile = true;	
					IAnnotationModel annotationModel = getDocumentProvider().getAnnotationModel(input);
					JavaModelManager.getJavaModelManager().discardPerWorkingCopyInfo((CompilationUnit)unit);
					unit.becomeWorkingCopy((IProblemRequestor) annotationModel, null);
					((IWorkingCopyManagerExtension) JavaUI
							.getWorkingCopyManager()).setWorkingCopy(input, unit);
				
				}					
			} else if (CoreUtils.ASPECTJ_SOURCE_FILTER.accept(fInput
					.getFile().getName())){ // It's a .java file
				unit = JavaCore.createCompilationUnitFrom(fInput.getFile());
				annotationModel = new CompilationUnitAnnotationModelWrapper(unit);
						
				if(unit instanceof CompilationUnit) {
					JavaModelManager.getJavaModelManager().discardPerWorkingCopyInfo((CompilationUnit)unit);
				}
				unit.becomeWorkingCopy((IProblemRequestor)annotationModel, null);
			   ((IWorkingCopyManagerExtension) JavaUI
					.getWorkingCopyManager()).setWorkingCopy(input, unit);
			
			}

			AJLog.log("Editor opened on " + fInput.getFile().getName()); //$NON-NLS-1$
			// Ensure any advice markers are created since they are not
			// persisted.
			synchronized(activeEditorList) {
				activeEditorList.add(this);
			}
			IDocument document = getDocumentProvider().getDocument(fInput);
			AspectJTextTools textTools = AspectJUIPlugin.getDefault()
					.getAspectJTextTools();

			textTools.setupJavaDocumentPartitioner(document,
					IJavaPartitions.JAVA_PARTITIONING);
			
//			 Part of the fix for 89793 - editor icon is not always correct
			resetTitleImage();
			XRefUtils.openXRefViewIfFirstTime();
		}
		
	}

	public void dispose() {
		AJLog.log("Disposing editor for:" + getTitle()); //$NON-NLS-1$
		IEditorInput input = getEditorInput();
		if (input instanceof IFileEditorInput) {
			IFileEditorInput fInput = (IFileEditorInput) input;			
			// Fix for bug 79633 - editor buffer is not refreshed
			JavaUI.getWorkingCopyManager().disconnect(input);
			
			AJLog.log("Editor closed - " + fInput.getFile().getName()); //$NON-NLS-1$
			synchronized(activeEditorList) {
				activeEditorList.remove(this);
			}

			try {
				ICompilationUnit unit = AJCompilationUnitManager.INSTANCE.getAJCompilationUnitFromCache(fInput.getFile());
				if (unit != null) {
					unit.discardWorkingCopy();					
				}
			} catch (JavaModelException e) {
			}

		}
		if (aspectJEditorErrorTickUpdater != null) {
			aspectJEditorErrorTickUpdater.dispose();
			aspectJEditorErrorTickUpdater = null;
		}
		if (AspectJUIPlugin.usingXref) {
			XRefUIUtils.removeWorkingCopyManagerForEditor(this);
		}
		super.dispose();
	}
	
	/**
	 * @see org.eclipse.ui.IWorkbenchPart#setFocus()
	 */
	public void setFocus() {
		// AMC -added this method to catch user switching between multiple open
		// files
		// in the editor - used to trigger a structured selection event, but now
		// triggers a TextSelection event. TextSelection event does not give AJP
		// enouhg info to determine project, so have to do from here instead.
		IEditorInput input = getEditorInput();
		super.setFocus();
		
		// Sian: Added the code below to fix bug 77479 - link with editor does not work for .aj files 
		if(isEditingAjFile) {
			IViewPart view = getEditorSite().getPage().findView( PackageExplorerPart.VIEW_ID);
			if(view != null) {
				IPackagesViewPart packageExplorer = (IPackagesViewPart)view;
				if(packageExplorer.isLinkingEnabled()) {
					IFileEditorInput fInput = (IFileEditorInput) input;
					AJCompilationUnit ajc = AJCompilationUnitManager.INSTANCE.getAJCompilationUnit(fInput.getFile());
					if (ajc != null) {
						packageExplorer.selectAndReveal(ajc);
					}
				}
			}
		}
	}
	
	protected void initializeEditor() {
		super.initializeEditor();
		IPreferenceStore store = this.getPreferenceStore();
		AspectJTextTools textTools = new AspectJTextTools(store);
		fAJSourceViewerConfiguration = new AJSourceViewerConfiguration(
				textTools, this);
		setSourceViewerConfiguration(fAJSourceViewerConfiguration);
	}


	/**
	 * Removes unsupported menu options. This is obviously not a nice way to do
	 * that. It would be much better do never add them in the first place. But
	 * this cannot easily be done since we cannot control specific actions - we
	 * can only have all or none by calling or not calling our super method.
	 * 
	 * @author Luzius
	 */
	public void editorContextMenuAboutToShow(IMenuManager menu) {
		super.editorContextMenuAboutToShow(menu);
		if (isEditingAjFile) {
			menu.remove("org.eclipse.jdt.ui.refactoring.menu"); //$NON-NLS-1$

			//remove open type & call hierarchy
			IContributionItem[] items = menu.getItems();
			for (int i = 0; i < items.length; i++) {
				IContributionItem item = items[i];
				if ("group.open".equals(item.getId())) { //$NON-NLS-1$
					menu.remove(items[i + 2]);
					menu.remove(items[i + 3]);
					break;
				}
			}
		}

	}
	
	protected IJavaElement getInputJavaElement() {
		return JavaUI.getWorkingCopyManager().getWorkingCopy(getEditorInput()); 	
	}
	
	/**
	 * @return Returns the activeEditorList.
	 */
	public static Set getActiveEditorList() {
		synchronized(activeEditorList) {
			return activeEditorList;
		}
	}
	
	public synchronized void updatedTitleImage(Image image) {
		// only let us update the image (fix for 105299)
	}

	public synchronized void customUpdatedTitleImage(Image image) {
		// only let us update the image (fix for 105299)
		super.updatedTitleImage(image);
	}
	
	/**
	 * Update the title image
	 */
	// Part of the fix for 89793 - editor icon is not always correct
	public  void resetTitleImage() {
		refreshJob.setElement(getInputJavaElement());
		refreshJob.schedule();
	}
	
	private UpdateTitleImageJob refreshJob = new UpdateTitleImageJob();
	
	private class UpdateTitleImageJob extends UIJob {
		private IJavaElement elem;
		
		UpdateTitleImageJob() {
			super(UIMessages.editor_title_refresh_job);
			setSystem(true);
		}

		public void setElement(IJavaElement element) {
			elem = element;
		}
		
		public IStatus runInUIThread(IProgressMonitor monitor) {
			if (elem != null) {
				aspectJEditorErrorTickUpdater.updateEditorImage(elem);
			}
			return Status.OK_STATUS;
		}
	}
}