/*******************************************************************************
 * Copyright (c) 2007 Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is 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:
 *     Red Hat, Inc. - initial API and implementation
 ******************************************************************************/ 
package org.jboss.tools.common.model.ui.attribute.adapter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.ListIterator;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.CompletionProposal;
import org.eclipse.jdt.core.CompletionRequestor;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IBuffer;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jface.fieldassist.ContentProposalAdapter;
import org.eclipse.jface.fieldassist.IContentProposal;
import org.eclipse.jface.fieldassist.IContentProposalProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.pde.internal.ui.PDEPluginImages;
import org.eclipse.pde.internal.ui.editor.contentassist.TypeContentProposal;
import org.eclipse.pde.internal.ui.editor.contentassist.TypePackageCompletionProcessor;
import org.eclipse.swt.graphics.Image;
import org.jboss.tools.common.meta.XAttribute;
import org.jboss.tools.common.meta.action.XEntityData;
import org.jboss.tools.common.model.XModelObject;
import org.jboss.tools.common.model.ui.ModelUIPlugin;
import org.jboss.tools.common.model.ui.attribute.IAttributeContentProposalProvider;
import org.jboss.tools.common.model.ui.attribute.IValueFilter;
import org.jboss.tools.common.model.ui.attribute.editor.JavaHyperlinkCueLabelProvider;
import org.jboss.tools.common.model.util.ModelFeatureFactory;

public class JavaClassContentAssistProvider implements
		IAttributeContentProposalProvider {
	XModelObject object;
	XAttribute attribute;
	IValueFilter valueFilter = null;

	public IContentProposalProvider getContentProposalProvider() {
		IProject project = (IProject)object.getModel().getProperties().get("project"); //$NON-NLS-1$
		return (project == null) ? null : new TypeContentProposalProvider(project, IJavaSearchConstants.TYPE, valueFilter);
	}

	public int getProposalAcceptanceStyle() {
		return ContentProposalAdapter.PROPOSAL_REPLACE;
	}

	public void init(XModelObject object, XEntityData data, XAttribute attribute) {
		this.object = object;
		this.attribute = attribute;
		createValueFilter();
	}

	void createValueFilter() {
		if(attribute == null) return;
		String cls = attribute.getProperty("valueFilter");
		if(cls == null || cls.length() == 0) return;
		try {
			valueFilter = (IValueFilter)ModelFeatureFactory.getInstance().createFeatureInstance(cls);
		} catch (ClassCastException exc) {
			ModelUIPlugin.getPluginLog().logError(exc);
		}
		if(valueFilter != null) {
			if(!valueFilter.init(object, attribute)) {
				valueFilter = null;
			}
		}
	}

	public boolean isRelevant(XModelObject object, XAttribute attribute) {
		if(object == null || attribute == null) return false;
		String editorName = attribute.getEditor().getName();
		return editorName != null && editorName.indexOf("AccessibleJava") >= 0; //$NON-NLS-1$
	}

	public LabelProvider getCustomLabelProbider() {
		return JavaHyperlinkCueLabelProvider.INSTANCE;
	}

	public void dispose() {
		this.object = null;
		this.attribute = null;
	}

}



class TypeContentProposalProvider extends TypePackageCompletionProcessor implements IContentProposalProvider {
	public static final char F_DOT = '.';
	private IProject fProject;
	private int fTypeScope;
	private ArrayList fInitialContentProposals;
	private String fInitialContent;
	private Comparator fComparator;

	IValueFilter valueFilter;

	/**
	 * 
	 */
	public TypeContentProposalProvider(IProject project, int scope, IValueFilter valueFilter) {
		fProject = project;
		fTypeScope = scope;
		fComparator = new TypeComparator();
		
		this.valueFilter = valueFilter;

		reset();
	}

	/**
	 * TypeComparator
	 *
	 */
	private static class TypeComparator implements Comparator {
		/**
		 * 
		 */
		public TypeComparator() {
			// NO-OP
		}

		/* (non-Javadoc)
		 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
		 */
		public int compare(Object arg0, Object arg1) {
			String proposalSortKey1 = ((IContentProposal) arg0).getLabel();
			String proposalSortKey2 = ((IContentProposal) arg1).getLabel();
			return proposalSortKey1.compareToIgnoreCase(proposalSortKey2);
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.fieldassist.IContentProposalProvider#getProposals(java.lang.String, int)
	 */
	public IContentProposal[] getProposals(String contents, int position) {
		// Generate a list of proposals based on the current contents
		ArrayList currentContentProposals = null;
		// Determine method to obtain proposals based on current field contents
		if (position == 0) {
			// If the document offset is at the 0 position (i.e. no input entered), 
			// do not perform content assist.  The operation is too expensive 
			// because all classes and interfaces (depending on the specified scope)
			// will need to be resolved as proposals
			currentContentProposals = null;
		} else if ((fInitialContentProposals == null) || (!contents.startsWith(fInitialContent)) || (endsWithDot(contents))) {
			// Generate new proposals if the content assist session was just
			// started
			// Or generate new proposals if the current contents of the field
			// is less than the initial contents of the field used to 
			// generate the original proposals; thus, widening the search
			// scope.  This can occur when the user types backspace
			// Or generate new proposals if the current contents ends with a
			// dot
			currentContentProposals = generateContentProposals(contents.substring(0, position));
		} else {
			// Filter existing proposals from a prevous search; thus, narrowing
			// the search scope.  This can occur when the user types additional
			// characters in the field causing new characters to be appended to
			// the initial field contents
			currentContentProposals = filterContentProposals(contents);
			
		}

		currentContentProposals = filterContentProposalsByValueFilter(currentContentProposals);

		return convertResultsToSortedProposals(currentContentProposals);
	}

	/**
	 * 
	 */
	public void reset() {
		fInitialContentProposals = null;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.pde.internal.ui.editor.contentassist.TypePackageCompletionProcessor#addProposalToCollection(java.util.Collection, int, int, java.lang.String, java.lang.String, org.eclipse.swt.graphics.Image)
	 */
	protected void addProposalToCollection(Collection collection, int startOffset, int length, String label, String content, Image image) {
		// Create content proposals for field assist
		// start offset and length not required
		IContentProposal proposal = new TypeContentProposal(label, content, null, image);
		// Add the proposal to the list of proposals
		collection.add(proposal);
	}

	/**
	 * @param string
	 * @return
	 */
	private boolean endsWithDot(String string) {
		int index = string.lastIndexOf(F_DOT);
		if ((index + 1) == string.length()) {
			return true;
		}
		return false;
	}

	/**
	 * @param currentContent
	 * @return
	 */
	private ArrayList generateContentProposals(String currentContent) {
		fInitialContentProposals = new ArrayList();
		// Store the initial field contents to determine if we need to
		// widen the scope later
		fInitialContent = currentContent;
		generateTypePackageProposals(currentContent, fProject, fInitialContentProposals, 0, fTypeScope, true);
		return fInitialContentProposals;
	}

	/**
	 * @param list
	 * @return
	 */
	private IContentProposal[] convertResultsToSortedProposals(ArrayList list) {
		IContentProposal[] proposals = null;
		if ((list != null) && (list.size() != 0)) {
			// Convert the results array list into an array of completion
			// proposals
			proposals = (IContentProposal[]) list.toArray(new IContentProposal[list.size()]);
			// Sort the proposals alphabetically
			Arrays.sort(proposals, fComparator);

		} else {
			proposals = new IContentProposal[0];
		}
		return proposals;
	}

	/**
	 * @param currentContent
	 * @return
	 */
	private ArrayList filterContentProposals(String currentContent) {
		String lowerCaseCurrentContent = currentContent.toLowerCase();

		ListIterator iterator = fInitialContentProposals.listIterator();
		// Maintain a list of filtered search results
		ArrayList filteredContentProposals = new ArrayList();
		// Iterate over the initial search results
		while (iterator.hasNext()) {
			Object object = iterator.next();
			IContentProposal proposal = (IContentProposal) object;
			String compareString = null;
			if (lowerCaseCurrentContent.indexOf(F_DOT) == -1) {
				// Use only the type name
				compareString = proposal.getLabel().toLowerCase();
			} else {
				// Use the fully qualified type name
				compareString = proposal.getContent().toLowerCase();
			}
			// Filter out any proposal not matching the current contents
			// except for the edge case where the proposal is identical to the
			// current contents
			if (compareString.startsWith(lowerCaseCurrentContent, 0)) {
				filteredContentProposals.add(proposal);
			}
		}

		return filteredContentProposals;
	}

	private ArrayList filterContentProposalsByValueFilter(ArrayList filteredContentProposals) {
		if(valueFilter != null && filteredContentProposals != null /*&& filteredContentProposals.size() < 200*/) {
			ArrayList filteredContentProposals2 = new ArrayList();
			ListIterator iterator = filteredContentProposals.listIterator();
			while (iterator.hasNext()) {
				Object object = iterator.next();
				IContentProposal proposal = (IContentProposal) object;
				String value = proposal.getContent();
				if(valueFilter.accept(value)) {
					filteredContentProposals2.add(proposal);
				}				
			}
			filteredContentProposals = filteredContentProposals2;
		}
		return filteredContentProposals;
	}

	
	protected void generateTypePackageProposals(String currentContent, IProject project, Collection c, int startOffset, int typeScope, boolean replaceEntireContents) {
		currentContent = removeLeadingSpaces(currentContent);
		if (c == null || currentContent.length() == 0)
			return;
		int length = (replaceEntireContents) ? -1 : currentContent.length();
		generateProposals(currentContent, project, c, startOffset, length, typeScope);
	}

	private void generateProposals(String currentContent, IProject project, final Collection c, final int startOffset, final int length, final int typeScope) {

		class TypePackageCompletionRequestor extends CompletionRequestor {

			public TypePackageCompletionRequestor() {
				super(true);
				setIgnored(CompletionProposal.PACKAGE_REF, false);
				setIgnored(CompletionProposal.TYPE_REF, false);
			}

			public void accept(CompletionProposal proposal) {
				if (proposal.getKind() == CompletionProposal.PACKAGE_REF) {
					String pkgName = new String(proposal.getCompletion());
					addProposalToCollection(c, startOffset, length, pkgName, pkgName, PDEPluginImages.get(PDEPluginImages.OBJ_DESC_PACKAGE));
				} else {
					boolean isInterface = Flags.isInterface(proposal.getFlags());
					String completion = new String(proposal.getCompletion());
					if (isInterface && typeScope == IJavaSearchConstants.CLASS || (!isInterface && typeScope == IJavaSearchConstants.INTERFACE) || completion.equals("Dummy2")) //$NON-NLS-1$
						// don't want Dummy class showing up as option.
						return;
					int period = completion.lastIndexOf('.');
					String cName = null, pName = null;
					if (period == -1) {
						cName = completion;
					} else {
						cName = completion.substring(period + 1);
						pName = completion.substring(0, period);
					}
					if(pName == null) {
						char[] declaration = proposal.getDeclarationSignature();
						pName = declaration == null || declaration.length == 0 ? "(default)" : new String(declaration);
					}
					Image image = isInterface ? PDEPluginImages.get(PDEPluginImages.OBJ_DESC_GENERATE_INTERFACE) : PDEPluginImages.get(PDEPluginImages.OBJ_DESC_GENERATE_CLASS);
					addProposalToCollection(c, startOffset, length, cName + " - " + pName, //$NON-NLS-1$
							completion, image);
				}
			}

		}

		try {
			ICompilationUnit unit = getWorkingCopy(project);
			if (unit == null) {
				generateTypeProposals(currentContent, project, c, startOffset, length, 1);
				return;
			}
			IBuffer buff = unit.getBuffer();
			buff.setContents("class Dummy2 { " + currentContent); //$NON-NLS-1$

			CompletionRequestor req = new TypePackageCompletionRequestor();
			unit.codeComplete(15 + currentContent.length(), req);
			unit.discardWorkingCopy();
		} catch (JavaModelException e) {
			ModelUIPlugin.getPluginLog().logError(e);
		}
	}

	private ICompilationUnit getWorkingCopy(IProject project) throws JavaModelException {
		IPackageFragmentRoot[] roots = JavaCore.create(project).getPackageFragmentRoots();
		if (roots.length > 0) {
			IPackageFragment frag = null;
			for (int i = 0; i < roots.length; i++)
				if (roots[i].getKind() == IPackageFragmentRoot.K_SOURCE || project.equals(roots[i].getCorrespondingResource()) || (roots[i].isArchive() && !roots[i].isExternal())) {
					IJavaElement[] elems = roots[i].getChildren();
					if ((elems.length > 0) && (i < elems.length) && (elems[i] instanceof IPackageFragment)) {
						frag = (IPackageFragment) elems[i];
						break;
					}
				}
			if (frag != null)
				return frag.getCompilationUnit("Dummy2.java").getWorkingCopy(new NullProgressMonitor()); //$NON-NLS-1$
		}
		return null;
	}


}
