/*******************************************************************************
 * Copyright (c) 2000, 2014 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * 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<>();
			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;
	}

	@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;
	}
}
