/*******************************************************************************
 * Copyright (c) 2004 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:
 *     Luzius Meisser - initial implementation
 *******************************************************************************/

package org.eclipse.ajdt.core.javaelements;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.eclipse.ajdt.core.AspectJPlugin;
import org.eclipse.ajdt.core.CoreUtils;
import org.eclipse.ajdt.internal.core.AJWorkingCopyOwner;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
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.WorkingCopyOwner;
import org.eclipse.jdt.internal.core.JavaElement;
import org.eclipse.jdt.internal.core.JavaModelManager;
import org.eclipse.jdt.internal.core.OpenableElementInfo;

/**
 * Maintains a cache containing ICompilationUnits for .aj files and is
 * responsible for their instantiation.
 * 
 * @author Luzius Meisser
 */
public class AJCompilationUnitManager {

	public final static AJCompilationUnitManager INSTANCE = new AJCompilationUnitManager();

	private HashMap compilationUnitStore = new HashMap();

	public AJCompilationUnit getAJCompilationUnit(IFile file) {
		AJCompilationUnit unit = getAJCompilationUnitFromCache(file);
		if (unit != null)
			return unit;
		if (creatingCUisAllowedFor(file))
			unit = createCU(file);
		return unit;
	}

	public AJCompilationUnit getAJCompilationUnitFromCache(IFile file) {
		return (AJCompilationUnit) compilationUnitStore.get(file);
	}
	
	/**
	 * Returns the AJCompilationUnit corresponding to the given
	 * CompilationUnit, if there is one, otherwise return the unit itself
	 * @param cu
	 * @return
	 */
	public static ICompilationUnit mapToAJCompilationUnit(ICompilationUnit cu) {
		if (AspectJPlugin.USING_CU_PROVIDER) { // mapping not required
			return cu;
		}
		if (cu == null) {
			return cu;
		}
		IResource res = cu.getResource();
		if (res.getType() == IResource.FILE) {
			AJCompilationUnit ajcu = INSTANCE.getAJCompilationUnit((IFile) res);
			if (ajcu != null) {
				return ajcu;
			}
		}
		return cu;
	}
	
	/**
	 * Returns the WorkingCopyOwner used to create AJCompilationUnits
	 * @return
	 */
	public static WorkingCopyOwner defaultAJWorkingCopyOwner() {
		return AJWorkingCopyOwner.INSTANCE;
	}
	
	//returns true if it was already there, and false if it needed to be inserted
	public boolean ensureUnitIsInModel(AJCompilationUnit unit) throws JavaModelException{
		//ensure unit is in the model
		OpenableElementInfo info = (OpenableElementInfo) ((JavaElement) unit.getParent()).getElementInfo();
		IJavaElement[] elems = info.getChildren();
		for (int i = 0; i < elems.length; i++) {
			IJavaElement element = elems[i];
			if (element == unit)
				return true;
		}
		info.addChild(unit);
		return false;
	}
	
	public List getAJCompilationUnitsForPackage(IPackageFragment pFragment) throws CoreException, JavaModelException {
		final List ajcus = new ArrayList();
		final IResource folder = pFragment.getCorrespondingResource();
		if(folder != null) {
			folder.accept(new IResourceVisitor(){
	
				public boolean visit(IResource resource) {
					if(resource instanceof IFile) {
						if (CoreUtils.ASPECTJ_SOURCE_ONLY_FILTER.accept(resource.getName())) {
							ajcus.add(getAJCompilationUnit((IFile)resource));
						}
					}
					return resource.equals(folder);
				}
			});
		}
		return ajcus;
	}
	
	public List getAJCompilationUnits(IJavaProject jp) throws CoreException {
		final List ajcus = new ArrayList();
		jp.getProject().accept(new IResourceVisitor(){

			public boolean visit(IResource resource) {
				if(resource instanceof IFile && AspectJPlugin.AJ_FILE_EXT.equals(resource.getFileExtension())) {
					AJCompilationUnit ajcu = getAJCompilationUnit((IFile)resource);
					if(ajcu != null) {
						ajcus.add(ajcu);
					}
				}				
				return resource.getType() == IResource.FOLDER || resource.getType() == IResource.PROJECT;
			}});
		return ajcus;
	}
	
	public List getAJCompilationUnits(IPackageFragmentRoot root) throws CoreException {
		final List ajcus = new ArrayList();
		root.getResource().accept(new IResourceVisitor(){

			public boolean visit(IResource resource) {
				if(resource instanceof IFile && AspectJPlugin.AJ_FILE_EXT.equals(resource.getFileExtension())) {
					AJCompilationUnit ajcu = getAJCompilationUnit((IFile)resource);
					if(ajcu != null) {
						ajcus.add(ajcu);
					}
				}				
				return resource.getType() == IResource.FOLDER || resource.getType() == IResource.PROJECT;
			}});
		return ajcus;
	}

	public void removeFileFromModel(IFile file) {
		AJCompilationUnit unit = (AJCompilationUnit) compilationUnitStore
				.get(file);
		if (unit != null) {
			try {
				// Fix for bug 106813 - check if the project is open first
				if(file.getProject().isOpen()) {					
					OpenableElementInfo info = (OpenableElementInfo) ((JavaElement) unit
							.getParent()).getElementInfo();
					info.removeChild(unit);
				}
				JavaModelManager.getJavaModelManager().removeInfoAndChildren(
						unit);

			} catch (JavaModelException e) {
			}
			compilationUnitStore.remove(file);
		}
	}

	private AJCompilationUnit createCU(IFile file) {
		AJCompilationUnit unit = new AJCompilationUnit(file);

		try {
			OpenableElementInfo info = (OpenableElementInfo) ((JavaElement) unit
					.getParent()).getElementInfo();
			info.removeChild(unit); // Remove identical CompilationUnit if it exists
			info.addChild(unit);

			compilationUnitStore.put(file, unit);
		} catch (JavaModelException e) {
		}
		return unit;
	}

	private boolean creatingCUisAllowedFor(IFile file) {
		return file != null && (CoreUtils.ASPECTJ_SOURCE_ONLY_FILTER.accept(file.getName())
				&& AspectJPlugin.isAJProject(file.getProject()) && (JavaCore
				.create(file.getProject()).isOnClasspath(file)));
	}

	public void initCompilationUnits(IProject project) {

		List l = new ArrayList(30);
		addProjectToList(project, l);
		Iterator iter = l.iterator();
		while (iter.hasNext()) {
			IFile ajfile = (IFile) iter.next();
			createCU(ajfile);
		}
	}

	public List removeCUsfromJavaModel(IProject project) {
		List l = new ArrayList(30);
		addProjectToList(project, l);
		Iterator iter = l.iterator();
		while (iter.hasNext()) {
			removeFileFromModel((IFile) iter.next());
		}
		return l;
	}

	public void initCompilationUnits(IWorkspace workspace) {
		ArrayList l = new ArrayList(20);
		IProject[] projects = workspace.getRoot().getProjects();
		for (int i = 0; i < projects.length; i++) {
			IProject project = projects[i];
			addProjectToList(project, l);
			Iterator iter = l.iterator();
			while (iter.hasNext()) {
				IFile f = (IFile) iter.next();
				createCU(f);
			}
			l.clear();
		}		
	}

	public List /*AJCompilationUnit*/ getCachedCUs(IProject project) {
		List ajList = new ArrayList();
		for (Iterator iter = compilationUnitStore.keySet().iterator(); iter.hasNext();) {
			IFile f = (IFile) iter.next();
			if (f.getProject().equals(project)) {
				ajList.add(compilationUnitStore.get(f));
			}
		}
		return ajList;
	}
	
	private void addProjectToList(IProject project, List l) {
		//check if aj project
		//		ProjectBuildConfigurator pbc =
		// BuildConfigurator.getBuildConfigurator()
		//				.getProjectBuildConfigurator(project);
		//		if (pbc != null) {
		if (AspectJPlugin.isAJProject(project)) {
			try {
				IJavaProject jp = JavaCore.create(project);
				IClasspathEntry[] cpes = jp.getRawClasspath();
				for (int i = 0; i < cpes.length; i++) {
					IClasspathEntry entry = cpes[i];
					if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
						IPath p = entry.getPath();
						if (p.segmentCount() == 1)
							addAllAJFilesInFolder(project, l);
						else
							addAllAJFilesInFolder(project.getFolder(p
									.removeFirstSegments(1)), l);
					}
				}
			} catch (JavaModelException e) {
			}
		}
	}

	/**
	 * @param folder
	 * @param list
	 */
	private void addAllAJFilesInFolder(IContainer folder, List l) {
		if ((folder == null) || !folder.exists())
			return;
		try {
			IResource[] children = folder.members();
			for (int i = 0; i < children.length; i++) {
				IResource resource = children[i];
				if (resource.getType() == IResource.FOLDER)
					addAllAJFilesInFolder((IFolder) resource, l);
				else if ((resource.getType() == IResource.FILE)
						&& CoreUtils.ASPECTJ_SOURCE_ONLY_FILTER
								.accept(resource.getName()))
					l.add(resource);
			}
		} catch (CoreException e) {
		}
	}
	
	/**
	 * useful for testing
	 */
	public void clearCache() {
	   compilationUnitStore.clear(); 
	}
}