/*******************************************************************************
 * Copyright (c) 2000, 2007 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
 *
 
 *******************************************************************************/
package org.eclipse.dltk.internal.core.search;

import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.dltk.compiler.util.SimpleSet;
import org.eclipse.dltk.core.IBuildpathEntry;
import org.eclipse.dltk.core.IScriptProject;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.core.IScriptModel;
import org.eclipse.dltk.core.ModelException;
import org.eclipse.dltk.core.search.IDLTKSearchScope;
import org.eclipse.dltk.core.search.SearchPattern;
import org.eclipse.dltk.core.search.indexing.IndexManager;
import org.eclipse.dltk.core.search.matching.MatchLocator;
import org.eclipse.dltk.internal.core.ArchiveProjectFragment;
import org.eclipse.dltk.internal.core.ScriptProject;
import org.eclipse.dltk.internal.core.ModelManager;

/**
 * Selects the indexes that correspond to projects in a given search scope and
 * that are dependent on a given focus element.
 */
public class IndexSelector {
	IDLTKSearchScope searchScope;
	SearchPattern pattern;
	IPath[] indexLocations; // cache of the keys for looking index up
//	public boolean mixin = false; // Set to true then mixin search are used.

	// Filter some builtin elements.

	public IndexSelector(IDLTKSearchScope searchScope, SearchPattern pattern) {
		this.searchScope = searchScope;
		this.pattern = pattern;
	}

	/**
	 * Returns whether elements of the given project or jar can see the given
	 * focus (an IScriptProject or a JarScriptFolderRot) either because the
	 * focus is part of the project or the jar, or because it is accessible
	 * throught the project's classpath
	 */
	public static boolean canSeeFocus(IModelElement focus,
			boolean isPolymorphicSearch, IPath projectOrArchivePath) {
		try {
			IBuildpathEntry[] focusEntries = null;
			if (isPolymorphicSearch) {
				ScriptProject focusProject = focus instanceof ArchiveProjectFragment ? (ScriptProject) focus
						.getParent()
						: (ScriptProject) focus;
				focusEntries = focusProject.getExpandedBuildpath(true);
			}
			IScriptModel model = focus.getModel();
			IScriptProject project = getScriptProject(projectOrArchivePath, model);
			if (project != null)
				return canSeeFocus(focus, (ScriptProject) project, focusEntries);
			// projectOrJarPath is a jar
			// it can see the focus only if it is on the buildpath of a project
			// projectOrArchivePath is a archive
			// it can see the focus only if it is on the classpath of a project
			// that can see the focus
			IScriptProject[] allProjects = model.getScriptProjects();
			for (int i = 0, length = allProjects.length; i < length; i++) {
				ScriptProject otherProject = (ScriptProject) allProjects[i];
				IBuildpathEntry[] entries = otherProject.getResolvedBuildpath(
						true/* ignoreUnresolvedEntry */,
						false/* don't generateMarkerOnError */, false/*
																		 * don't
																		 * returnResolutionInProgress
																		 */);
				for (int j = 0, length2 = entries.length; j < length2; j++) {
					IBuildpathEntry entry = entries[j];
					if (entry.getEntryKind() == IBuildpathEntry.BPE_LIBRARY
							&& entry.getPath().equals(projectOrArchivePath))
						if (canSeeFocus(focus, otherProject, focusEntries))
							return true;
				}
			}
			return false;
		} catch (ModelException e) {
			return false;
		}
	}

	public static boolean canSeeFocus(IModelElement focus,
			ScriptProject scriptProject,
			IBuildpathEntry[] focusEntriesForPolymorphicSearch) {
		try {
			if (focus.equals(scriptProject))
				return true;
			if (focusEntriesForPolymorphicSearch != null) {
				// look for refering project
				IPath projectPath = scriptProject.getProject().getFullPath();
				for (int i = 0, length = focusEntriesForPolymorphicSearch.length; i < length; i++) {
					IBuildpathEntry entry = focusEntriesForPolymorphicSearch[i];
					if (entry.getEntryKind() == IBuildpathEntry.BPE_PROJECT
							&& entry.getPath().equals(projectPath))
						return true;
				}
			}
			if (focus instanceof ArchiveProjectFragment) {
				// focus is part of a archive
				IPath focusPath = focus.getPath();
				IBuildpathEntry[] entries = scriptProject
						.getExpandedBuildpath(true);
				for (int i = 0, length = entries.length; i < length; i++) {
					IBuildpathEntry entry = entries[i];
					if (entry.getEntryKind() == IBuildpathEntry.BPE_LIBRARY
							&& entry.getPath().equals(focusPath))
						return true;
				}
				return false;
			}
			// look for dependent projects
			IPath focusPath = ((ScriptProject) focus).getProject().getFullPath();
			IBuildpathEntry[] entries = scriptProject
					.getExpandedBuildpath(true);
			for (int i = 0, length = entries.length; i < length; i++) {
				IBuildpathEntry entry = entries[i];
				if (entry.getEntryKind() == IBuildpathEntry.BPE_PROJECT
						&& entry.getPath().equals(focusPath))
					return true;
			}
			return false;
		} catch (ModelException e) {
			return false;
		}
	}

	/*
	 * Compute the list of paths which are keying index files.
	 */
	private void initializeIndexLocations() {
		IPath[] projectsAndArchives = this.searchScope
				.enclosingProjectsAndZips();
//		System.out.println("********************IndexSelector************************");
//		for (int i = 0; i < projectsAndArchives.length; i++) {
//			System.out.println("Root:" + projectsAndArchives[i].toString());
//		}
//		System.out.println("********************IndexSelector************************");
		IndexManager manager = ModelManager.getModelManager().getIndexManager();
		SimpleSet locations = new SimpleSet();
		IModelElement focus = MatchLocator.projectOrArchiveFocus(this.pattern);
		// Add all special indexes for selected project.
		/*
		 * if( focus != null && focus.getElementType() ==
		 * IModelElement.SCRIPT_PROJECT ) { String prjPath = "#special#mixin:" +
		 * ((IScriptProject)focus).getProject().getFullPath().toString();
		 * checkSpecialCase(manager, locations, prjPath); // builtin index file
		 * prjPath = "#special#builtin:" +
		 * ((IScriptProject)focus).getProject().getFullPath().toString();
		 * checkSpecialCase(manager, locations, prjPath); }
		 */
		
		boolean mix = false;//this.pattern instanceof MixinPattern;
		
		IScriptModel model = ModelManager.getModelManager().getModel();
		if (focus == null) {
			for (int i = 0; i < projectsAndArchives.length; i++) {
//				if (!mixin) {
				
				if (!mix) {				
					locations.add(manager
							.computeIndexLocation(projectsAndArchives[i]));
				} 
//				}

					checkSpecial(projectsAndArchives[i], manager, locations, model);
				
			}
		} else {
			try {
				// find the projects from projectsAndArchives that see the focus
				// then walk those projects looking for the archives from
				// projectsAndArchives
				int length = projectsAndArchives.length;
				ScriptProject[] projectsCanSeeFocus = new ScriptProject[length];
				SimpleSet visitedProjects = new SimpleSet(length);
				int projectIndex = 0;
				SimpleSet archivesToCheck = new SimpleSet(length);
				IBuildpathEntry[] focusEntries = null;
				if (this.pattern != null
						&& MatchLocator.isPolymorphicSearch(this.pattern)) { // isPolymorphicSearch
					ScriptProject focusProject = focus instanceof ArchiveProjectFragment ? (ScriptProject) focus
							.getParent()
							: (ScriptProject) focus;
					focusEntries = focusProject.getExpandedBuildpath(true);
				}

				for (int i = 0; i < length; i++) {
					IPath path = projectsAndArchives[i];
					ScriptProject project = (ScriptProject) getScriptProject(path,
							model);
					if (project != null) {
						visitedProjects.add(project);
						if (canSeeFocus(focus, project, focusEntries)) {
//							if (!mixin) {
							if (!mix) {
								locations.add(manager
										.computeIndexLocation(path));
							} 
//							}
								checkSpecial(path, manager, locations, model);
							
							projectsCanSeeFocus[projectIndex++] = project;
						}
					} else {
						archivesToCheck.add(path);
					}
				}
				for (int i = 0; i < projectIndex
						&& archivesToCheck.elementSize > 0; i++) {
					IBuildpathEntry[] entries = projectsCanSeeFocus[i]
							.getResolvedBuildpath(
									true/* ignoreUnresolvedEntry */,
									false/* don't generateMarkerOnError */,
									false/* don't returnResolutionInProgress */);
					for (int j = entries.length; --j >= 0;) {
						IBuildpathEntry entry = entries[j];
						if (entry.getEntryKind() == IBuildpathEntry.BPE_LIBRARY) {
							IPath path = entry.getPath();
							if (archivesToCheck.includes(path)) {
//								if (!mixin) {
								if (!mix) {
									locations.add(manager
											.computeIndexLocation(entry
													.getPath()));
								}
//								}
								archivesToCheck.remove(path);
							}
						}
					}
				}
				// archive files can be included in the search scope without
				// including one of the projects that references them, so scan
				// all projects that have not been visited
				if (archivesToCheck.elementSize > 0) {
					IScriptProject[] allProjects = model.getScriptProjects();
					for (int i = 0, l = allProjects.length; i < l
							&& archivesToCheck.elementSize > 0; i++) {
						ScriptProject project = (ScriptProject) allProjects[i];
						if (!visitedProjects.includes(project)) {
							IBuildpathEntry[] entries = project
									.getResolvedBuildpath();
							for (int j = entries.length; --j >= 0;) {
								IBuildpathEntry entry = entries[j];
								if (entry.getEntryKind() == IBuildpathEntry.BPE_LIBRARY) {
									IPath path = entry.getPath();
									if (archivesToCheck.includes(path)) {
//										if (!mixin) {
										if (!mix) {
											locations.add(manager
													.computeIndexLocation(entry
															.getPath()));
										}
//										}
										archivesToCheck.remove(path);
									}
								}
							}
						}
					}
				}
			} catch (ModelException e) {
				// ignored
			}
		}
		this.indexLocations = new IPath[locations.elementSize];
		Object[] values = locations.values;
		int count = 0;
		for (int i = values.length; --i >= 0;)
			if (values[i] != null)
				this.indexLocations[count++] = new Path((String) values[i]);
	}

	private void checkSpecial(IPath projectsAndArchives, IndexManager manager,
			SimpleSet locations, IScriptModel model) {
		// check for special cases
		String prjPath = "#special#mixin#" + projectsAndArchives.toString(); //$NON-NLS-1$
		// checkSpecialCase(manager, locations, prjPath);
		locations.add(manager.computeIndexLocation(new Path(prjPath)));
		// add builtin indexes
//		IPath path = projectsAndArchives;
////		if (!mixin) {
//			if (!path.toString().startsWith(IBuildpathEntry.BUILTIN_EXTERNAL_ENTRY_STR)) {
//				ScriptProject project = (ScriptProject) getScriptProject(path,
//						model);
//				if (project != null) {
//					IPath p = new Path("#special#builtin#")
//							.append(projectsAndArchives);
//					locations.add(manager.computeIndexLocation(p));
//				}
//			}
//			else {
//				path = path.removeFirstSegments(1);
//				ScriptProject project = (ScriptProject) getScriptProject(path,
//						model);
//				if (project != null) {
//					IPath p = new Path("#special#builtin#")
//							.append(projectsAndArchives);
//					locations.add(manager.computeIndexLocation(p));
//				}
//			}
////		}
	}

//	private void checkSpecialCase(IndexManager manager, SimpleSet locations,
//			String prjPath) {
//		Object[] keyTable = manager.indexLocations.keyTable;
//		for (int i = 0; i < keyTable.length; ++i) {
//			IPath path = (IPath) keyTable[i];
//			if (path != null) {
//				String sPath = path.toString();
//				if (sPath.startsWith(prjPath)) {
//					locations.add(manager.indexLocations.get(path));
//				}
//			}
//		}
//	}

	public IPath[] getIndexLocations() {
		if (this.indexLocations == null) {
			this.initializeIndexLocations();
		}
		return this.indexLocations;
	}

	/**
	 * Returns thescriptproject that corresponds to the given path. Returns null
	 * if the path doesn't correspond to a project.
	 */
	private static IScriptProject getScriptProject(IPath path, IScriptModel model) {
		IScriptProject project = model.getScriptProject(path.lastSegment());
		if (project.exists()) {
			return project;
		}
		return null;
	}
}
