/******************************************************************************* 
 * Copyright (c) 2007 Red Hat, Inc. 
 * Distributed under license by Red Hat, Inc. All rights reserved. 
 * This program is 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: 
 * Red Hat, Inc. - initial API and implementation 
 ******************************************************************************/ 
package org.jboss.tools.seam.internal.core;

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

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.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.jboss.tools.common.model.XModel;
import org.jboss.tools.common.model.plugin.ModelPlugin;
import org.jboss.tools.common.model.util.EclipseResourceUtil;
import org.jboss.tools.jst.web.WebUtils;
import org.jboss.tools.jst.web.model.helpers.InnerModelHelper;
import org.jboss.tools.seam.core.SeamCorePlugin;
import org.jboss.tools.seam.internal.core.scanner.IFileScanner;
import org.jboss.tools.seam.internal.core.scanner.LoadedDeclarations;
import org.jboss.tools.seam.internal.core.scanner.ScannerException;
import org.jboss.tools.seam.internal.core.scanner.java.JavaScanner;
import org.jboss.tools.seam.internal.core.scanner.xml.PropertiesScanner;
import org.jboss.tools.seam.internal.core.scanner.xml.XMLScanner;

/**
 * @author Viacheslav Kabanovich
 */
public class SeamResourceVisitor implements IResourceVisitor, IResourceDeltaVisitor {
	static IFileScanner[] FILE_SCANNERS = {
		new JavaScanner(), 
		new XMLScanner(), 
		new PropertiesScanner(),
	};
	SeamProject p;
	
	IPath[] outs = new IPath[0];
	IPath[] srcs = new IPath[0];
	IPath[] webinfs = new IPath[0];
	
	public SeamResourceVisitor(SeamProject p) {
		this.p = p;

		if(p.getProject() != null && p.getProject().isOpen()) {
			getJavaSourceRoots(p.getProject());

			XModel model = InnerModelHelper.createXModel(p.getProject());
			IContainer[] cs = WebUtils.getWebRootFolders(p.getProject());
			List<IPath> ws = new ArrayList<IPath>();
			if(model != null && cs.length > 0) {
				for (IContainer c: cs) {
					IFolder f = c.getFolder(new Path("WEB-INF"));
					if(f.exists()) {
						ws.add(f.getFullPath());
					}
				}
				webinfs = ws.toArray(new IPath[0]);
			}
		}
	}

	public boolean visit(IResource resource) {
		if(resource instanceof IFile) {
			IFile f = (IFile)resource;
			if(!shouldVisitFile(f)) return false;
			for (int i = 0; i < FILE_SCANNERS.length; i++) {
				IFileScanner scanner = FILE_SCANNERS[i];
				if(scanner.isRelevant(f)) {
					long t = System.currentTimeMillis();
					if(!scanner.isLikelyComponentSource(f)) {
						p.pathRemoved(f.getFullPath());
						return false;
					}
					LoadedDeclarations c = null;
					try {
						c = scanner.parse(f, p);
					} catch (ScannerException e) {
						SeamCorePlugin.getDefault().logError(e);
					}
					if(c != null) componentsLoaded(c, f);
					long dt = System.currentTimeMillis() - t;
					timeUsed += dt;
//					System.out.println("Time=" + timeUsed);
				}
			}
			return true;
		}
		return shouldVisitFolder(resource);
	}
	
	static long timeUsed = 0;
	
	void componentsLoaded(LoadedDeclarations c, IFile resource) {
		if(c == null || c.getComponents().size() + c.getFactories().size() + c.getNamespaces().size() + c.getImports().size() == 0) return;
		p.registerComponents(c, resource.getFullPath());
	}

	void getJavaSourceRoots(IProject project) {
		IJavaProject javaProject = EclipseResourceUtil.getJavaProject(project);
		if(javaProject == null) return;
		List<IPath> ps = new ArrayList<IPath>();
		List<IPath> os = new ArrayList<IPath>();
		try {
			IPath output = javaProject.getOutputLocation();
			if(output != null) os.add(output);
			IClasspathEntry[] es = javaProject.getResolvedClasspath(true);
			for (int i = 0; i < es.length; i++) {
				if(es[i].getEntryKind() == IClasspathEntry.CPE_SOURCE) {
					IResource findMember = ResourcesPlugin.getWorkspace().getRoot().findMember(es[i].getPath());
					if(findMember != null && findMember.exists()) {
						ps.add(findMember.getFullPath());
					}
					IPath out = es[i].getOutputLocation();
					if(out != null && !os.contains(out)) {
						os.add(out);
					}
				} 
			}
			srcs = ps.toArray(new IPath[ps.size()]);
			outs = os.toArray(new IPath[os.size()]);
		} catch(CoreException ce) {
			ModelPlugin.getPluginLog().logError("Error while locating java source roots for " + project, ce);
		}
	}

	public boolean visit(IResourceDelta delta) throws CoreException {
		IResource resource = delta.getResource();
		switch (delta.getKind()) {
		case IResourceDelta.ADDED:
		case IResourceDelta.CHANGED:
			if(resource instanceof IFile) {
				IFile f = (IFile)resource;
				if(!shouldVisitFile(f)) return false;
				for (int i = 0; i < FILE_SCANNERS.length; i++) {
					IFileScanner scanner = FILE_SCANNERS[i];
					if(scanner.isRelevant(f)) {
						long t = System.currentTimeMillis();
						if(!scanner.isLikelyComponentSource(f)) {
							p.pathRemoved(f.getFullPath());
							return false;
						}
						LoadedDeclarations c = null;
						try {
							c = scanner.parse(f, p);
						} catch (ScannerException e) {
							SeamCorePlugin.getDefault().logError(e);
						}
						if(c != null) componentsLoaded(c, f);
						long dt = System.currentTimeMillis() - t;
						timeUsed += dt;
//						System.out.println("Time=" + timeUsed);
					}
				}
			}
			if(resource instanceof IFolder) {
				return shouldVisitFolder(resource);
			}
			//return true to continue visiting children.
			return true;
		case IResourceDelta.REMOVED:
			p.pathRemoved(resource.getFullPath());
			break;
		}
		return true;
	}

	boolean shouldVisitFile(IResource resource) {
		for (int i = 0; i < outs.length; i++) {
			if(outs[i].isPrefixOf(resource.getFullPath())) {
				return false;
			}
		}
		return true;
	}

	boolean shouldVisitFolder(IResource resource) {
		IPath path = resource.getFullPath();
		for (int i = 0; i < outs.length; i++) {
			if(outs[i].isPrefixOf(path)) {
				return false;
			}
		}
		for (int i = 0; i < srcs.length; i++) {
			if(srcs[i].isPrefixOf(path) || path.isPrefixOf(srcs[i])) {
				return true;
			}
		}
		for (IPath webinf: webinfs) {
			if(webinf.isPrefixOf(path) || path.isPrefixOf(webinf)) {
				return true;
			}
		}
		if(resource == resource.getProject()) {
			return true;
		}
		return false;
	}

}
