/*******************************************************************************
 * Copyright (c) 2000, 2010 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
 *     Anton Leherbauer (Wind River Systems)
 *******************************************************************************/
package org.eclipse.cdt.internal.ui.search;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.cdt.core.dom.ast.IASTFileLocation;
import org.eclipse.cdt.core.dom.ast.IASTName;
import org.eclipse.cdt.core.dom.ast.IASTNode;
import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit;
import org.eclipse.cdt.core.dom.ast.IBinding;
import org.eclipse.cdt.core.dom.ast.IVariable;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPASTTemplateId;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPConstructor;
import org.eclipse.cdt.core.parser.Keywords;
import org.eclipse.cdt.core.parser.util.CharArrayUtils;

import org.eclipse.cdt.internal.core.dom.parser.cpp.semantics.CPPVisitor;

import org.eclipse.cdt.internal.ui.util.Messages;

public class OccurrencesFinder implements IOccurrencesFinder {
	
	public static final String ID= "OccurrencesFinder"; //$NON-NLS-1$
	
	/**
	 * If set, don't search for implicit references.
	 */
	public static final int OPTION_EXCLUDE_IMPLICIT_REFERENCES = 1;
	
	private IASTTranslationUnit fRoot;
	private IASTName fSelectedNode;
	private IBinding fTarget;

	private List<OccurrenceLocation> fResult;

	private String fReadDescription;
	private String fWriteDescription;
	
	private int fOptions;
	
	public OccurrencesFinder() {
		super();
	}
	
	@Override
	public String initialize(IASTTranslationUnit root, IASTNode node) {
		if (!(node instanceof IASTName))
			return CSearchMessages.OccurrencesFinder_no_element; 
		fRoot= root;
		fSelectedNode= (IASTName)node;
		fTarget= fSelectedNode.resolveBinding();
		if (fTarget == null)
			return CSearchMessages.OccurrencesFinder_no_binding; 
		
		fReadDescription= Messages.format(CSearchMessages.OccurrencesFinder_occurrence_description, fTarget.getName());
		fWriteDescription= Messages.format(CSearchMessages.OccurrencesFinder_occurrence_write_description, fTarget.getName());
		return null;
	}
	
	/**
	 * Specify search options.
	 * 
	 * @param options
	 */
	public void setOptions(int options) {
		fOptions = options;
	}
	
	private void performSearch() {
		if (fResult == null) {
			fResult= new ArrayList<OccurrenceLocation>();
			IASTName[] names= fRoot.getDeclarationsInAST(fTarget);
			for (IASTName candidate : names) {
				if (candidate.isPartOfTranslationUnitFile()) {
					addUsage(candidate, candidate.resolveBinding());
				}
			}
			names= fRoot.getReferences(fTarget);
			for (IASTName candidate : names) {
				if (candidate.isPartOfTranslationUnitFile()) {
					addUsage(candidate, candidate.resolveBinding());
				}
			}
			if (needImplicitReferences() && canHaveImplicitReference(fTarget)) {
				names= CPPVisitor.getImplicitReferences(fRoot, fTarget);
				for (IASTName candidate : names) {
					if (candidate.isPartOfTranslationUnitFile()) {
						addUsage(candidate, candidate.resolveBinding());
					}
				}
			}
		}
	}
	
	private boolean needImplicitReferences() {
		return (fOptions & OPTION_EXCLUDE_IMPLICIT_REFERENCES) == 0;
	}

	private boolean canHaveImplicitReference(IBinding binding) {
		final char[] op = Keywords.cOPERATOR;
		final char[] nameCharArray = binding.getNameCharArray();
		if (nameCharArray.length > 0) {
			if (nameCharArray[0] == '~') {
				return true;
			}
			if (CharArrayUtils.equals(nameCharArray, 0, op.length, op)) {
				return true;
			}
		}
		if (binding instanceof ICPPConstructor)
			return true;
		
		return false;
	}

	@Override
	public OccurrenceLocation[] getOccurrences() {
		performSearch();
		if (fResult.isEmpty())
			return null;
		return fResult.toArray(new OccurrenceLocation[fResult.size()]);
	}

	@Override
	public IASTTranslationUnit getASTRoot() {
		return fRoot;
	}
		
	/*
	 * @see org.eclipse.cdt.internal.ui.search.IOccurrencesFinder#getJobLabel()
	 */
	public String getJobLabel() {
		return CSearchMessages.OccurrencesFinder_searchfor ; 
	}
	
	@Override
	public String getElementName() {
		if (fSelectedNode != null) {
			return new String(fSelectedNode.toCharArray());
		}
		return null;
	}
	
	@Override
	public String getUnformattedPluralLabel() {
		return CSearchMessages.OccurrencesFinder_label_plural;
	}
	
	@Override
	public String getUnformattedSingularLabel() {
		return CSearchMessages.OccurrencesFinder_label_singular;
	}
	
	private boolean addUsage(IASTName node, IBinding binding) {
		if (binding != null /* && Bindings.equals(binding, fTarget) */) {
			if (node instanceof ICPPASTTemplateId) {
				node= ((ICPPASTTemplateId) node).getTemplateName();
			}
			IASTFileLocation fileLocation= node.getImageLocation();
			if (fileLocation == null || !fRoot.getFilePath().equals(fileLocation.getFileName())) {
				fileLocation= node.getFileLocation();
			}
			if (fileLocation != null) {
				final int offset= fileLocation.getNodeOffset();
				final int length= fileLocation.getNodeLength();
				if (offset >= 0 && length > 0) {
					if (binding instanceof IVariable) {
						final boolean isWriteOccurrence = CSearchUtil.isWriteOccurrence(node, binding);
						int flag = isWriteOccurrence ? F_WRITE_OCCURRENCE : F_READ_OCCURRENCE;
						String description = isWriteOccurrence ? fWriteDescription : fReadDescription;
						fResult.add(new OccurrenceLocation(offset, length, flag, description));
					}
					else {
						fResult.add(new OccurrenceLocation(offset, length, F_READ_OCCURRENCE, fWriteDescription));
					}
				}
			}
			return true;
		}
		return false;
	}

	@Override
	public int getSearchKind() {
		return K_OCCURRENCE;
	}
	
	@Override
	public String getID() {
		return ID;
	}
}
