/*******************************************************************************
 * Copyright (c) 2013-2014 Red Hat, Inc. 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:
 *     JBoss by Red Hat - initial API and implementation
 *******************************************************************************/
package org.jboss.tools.arquillian.core.internal.dependencies;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.jboss.tools.arquillian.core.ArquillianCoreActivator;

/**
 * 
 * @author snjeza
 *
 */
public class DependencyCache {
	
	private static Map<ICompilationUnit, Set<DependencyType>> dependencies = new HashMap<ICompilationUnit, Set<DependencyType>>();

	public static Map<ICompilationUnit, Set<DependencyType>> getDependencies() {
		return dependencies;
	}
	
	private static void collectDependencies(ICompilationUnit unit, boolean excludeSuperclass) {
		if (unit == null || getDependencies().get(unit) != null) {
			return;
		}
		CompilationUnit cu = getAST(unit);
		IType primaryType = unit.findPrimaryType();
		if (primaryType == null) {
			return;
		}
		Set<String> excludeSet = new HashSet<String>();
		String name = primaryType.getFullyQualifiedName();
		if (excludeSuperclass) {
			try {
				ITypeHierarchy hierarchy = primaryType.newSupertypeHierarchy(null);
				IType superclass = hierarchy.getSuperclass(primaryType);
				if (superclass != null) {
					excludeSet.add(superclass.getFullyQualifiedName());
				}
				IType[] superInterfaces = hierarchy.getAllSuperInterfaces(primaryType);
				for (IType superInterface:superInterfaces) {
					excludeSet.add(superInterface.getFullyQualifiedName());
				}
			} catch (JavaModelException e) {
				ArquillianCoreActivator.logWarning(e.getLocalizedMessage());
			}
		}
		DependencyVisitor visitor = new DependencyVisitor(cu, name, excludeSet, unit.getJavaProject());
		cu.accept(visitor);
		getDependencies().put(unit, visitor.getTypes());
		IJavaProject javaProject = unit.getJavaProject();
		for (DependencyType dependencyType: visitor.getTypes()) {
			String typeName = dependencyType.getName();
			try {
				IType type = javaProject.findType(typeName);
				if (type == null) {
					continue;
				}
				ICompilationUnit compilationUnit = type.getCompilationUnit();
				collectDependencies(compilationUnit, false);
			} catch (JavaModelException e) {
				ArquillianCoreActivator.log(e);
			}
		}
		return;
	}
	
	public static Set<DependencyType> getDependentTypes(ICompilationUnit unit) {
		Set<DependencyType> types = new HashSet<DependencyType>();
		if (unit == null) {
			return types;
		}
		IJavaProject javaProject = unit.getJavaProject();
		DependencyCache.collectDependencies(unit, true);
		Set<DependencyType> toVisit = new HashSet<DependencyType>();
		toVisit.addAll(getDependencies().get(unit));
		types.addAll(toVisit);
		Set<DependencyType> visited = new HashSet<DependencyType>();
		Set<DependencyType> add = new HashSet<DependencyType>();
		while (!toVisit.isEmpty()) {
			Iterator<DependencyType> iter = toVisit.iterator();
			add.clear();
			while (iter.hasNext()) {
				DependencyType type = iter.next();
				if (visited.contains(type)) {
					iter.remove();
					continue;
				}
				
				try {
					IType javaType = javaProject.findType(type.getName());
					if (javaType != null) {
						ICompilationUnit cu = javaType.getCompilationUnit();
						if (cu != null && DependencyCache.getDependencies().get(cu) != null) {
							add.addAll(DependencyCache.getDependencies().get(cu));
							types.addAll(DependencyCache.getDependencies().get(cu));
						}
					}
				} catch (JavaModelException e) {
					ArquillianCoreActivator.log(e);
				}
				visited.add(type);
			}
			toVisit.addAll(add);
		}
		return types;
	}
	
	private static CompilationUnit getAST(ICompilationUnit cu) {
		ASTParser parser= ASTParser.newParser(AST.JLS8);
		parser.setSource(cu);
		parser.setResolveBindings(true);
		parser.setKind(ASTParser.K_COMPILATION_UNIT);
		parser.setProject(cu.getJavaProject());
		return (CompilationUnit) parser.createAST(null);
	}

	public static void removeDependencies(IResource resource) {
		if (resource instanceof IProject) {
			IProject project = (IProject) resource;
			Set<ICompilationUnit> keySet = getDependencies().keySet();
			Iterator<ICompilationUnit> iter = keySet.iterator();
			while (iter.hasNext()) {
				ICompilationUnit unit = iter.next();
				if (project.equals(unit.getJavaProject().getProject())) {
					iter.remove();
				}
			}
		} else if (resource instanceof IFile) {
			IFile file = (IFile) resource;
			IJavaElement element = JavaCore.create(file);
    		if (!(element instanceof ICompilationUnit)) {
    			return;
    		}
    		ICompilationUnit unit = (ICompilationUnit) element;
    		getDependencies().remove(unit);
		}
	}
}
