/*******************************************************************************
 * Copyright (c) 2008, 2012 Wind River Systems, Inc. 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:
 *     Markus Schorn - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.internal.core.pdom.indexer;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.cdt.core.index.IIndexFileLocation;
import org.eclipse.cdt.internal.core.dom.IIncludeFileResolutionHeuristics;
import org.eclipse.cdt.internal.core.pdom.ASTFilePathResolver;
import org.eclipse.cdt.internal.core.resources.ResourceLookup;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;

/**
 * Heuristics for picking up includes from the project
 */
public class ProjectIndexerIncludeResolutionHeuristics implements IIncludeFileResolutionHeuristics {
	private static final String TRUE = String.valueOf(true);

	private IProject fProject;
	private IProject[] fProjects;
	private final ASTFilePathResolver fResolver;
	private final boolean fIgnoreCase;

	public ProjectIndexerIncludeResolutionHeuristics(IProject project, ASTFilePathResolver resolver) {
		fProject = project;
		fResolver = resolver;
		fIgnoreCase = resolver.isCaseInsensitiveFileSystem();
	}

	@Override
	public String findInclusion(String include, String currentFile) {
		final IIndexFileLocation ifl = fResolver.resolveASTPath(currentFile);
		if (ifl == null || ifl.getFullPath() == null) {
			return null;
		}

		if (fProject == null)
			return null;

		if (fProjects == null) {
			if (fProject.isOpen()) {
				String val = IndexerPreferences.get(fProject, IndexerPreferences.KEY_INCLUDE_HEURISTICS, TRUE);
				if (TRUE.equals(val)) {
					fProjects = getOpenReferencedProjects(fProject);
				}
			}
			if (fProjects == null) {
				fProject = null;
				return null;
			}
		}

		IFile[] files = ResourceLookup.findFilesByName(new Path(include), fProjects, fIgnoreCase);
		if (files.length == 0)
			return null;

		final IPath bestLocation = selectBest(files, ifl.getFullPath().toCharArray()).getLocation();
		if (bestLocation == null)
			return null;

		return bestLocation.toString();
	}

	private IResource selectBest(IFile[] files, char[] currentFullPath) {
		IFile best = files[0];
		int bestScore = computeScore(best.getFullPath().toString().toCharArray(), currentFullPath);

		for (int i = 1; i < files.length; i++) {
			IFile file = files[i];
			int score = computeScore(file.getFullPath().toString().toCharArray(), currentFullPath);
			if (score > bestScore) {
				bestScore = score;
				best = file;
			}
		}
		return best;
	}

	private int computeScore(char[] path1, char[] path2) {
		final int limit = Math.min(path1.length, path2.length);
		int match = 0;
		for (int i = 0; i < limit; i++) {
			if (path1[i] != path2[i])
				break;
			if (path1[i] == '/')
				match = i;
		}
		// Prefer shortest path with longest matches with.
		return (match << 16) - path1.length;
	}

	private IProject[] getOpenReferencedProjects(IProject prj) {
		Set<IProject> result = new HashSet<>();

		if (prj.isOpen()) {
			result.add(prj);

			List<IProject> projectsToSearch = new ArrayList<>();
			projectsToSearch.add(prj);
			for (int i = 0; i < projectsToSearch.size(); i++) {
				IProject project = projectsToSearch.get(i);
				IProject[] nextLevel;
				try {
					nextLevel = project.getReferencedProjects();
					for (IProject prjNextLevel : nextLevel) {
						if (prjNextLevel.isOpen() && result.add(prjNextLevel)) {
							projectsToSearch.add(prjNextLevel);
						}
					}
				} catch (CoreException e) {
				}
			}
		}
		return result.toArray(new IProject[result.size()]);
	}
}
