 /*******************************************************************************
  * 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
  *
  * Contributor:
  *     Red Hat, Inc. - initial API and implementation
  ******************************************************************************/
package org.jboss.tools.seam.internal.core.scanner.java;

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

import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.jboss.tools.common.model.util.EclipseJavaUtil;
import org.jboss.tools.seam.core.SeamCorePlugin;
import org.jboss.tools.seam.internal.core.scanner.Util;

/**
 * Processes AST tree to find annotated type, fields and methods.
 * 
 * @author Viacheslav Kabanovich
 */
public class ASTVisitorImpl extends ASTVisitor implements SeamAnnotations {
	
	public static class TypeData {
		TypeData parent = null;
		List<TypeData> children = new ArrayList<TypeData>();

		public IType type;
		int innerLock = 0;
		
		public AnnotatedASTNode<AbstractTypeDeclaration> annotatedType = null;
		public Set<AnnotatedASTNode<FieldDeclaration>> annotatedFields = new HashSet<AnnotatedASTNode<FieldDeclaration>>();
		public Set<AnnotatedASTNode<MethodDeclaration>> annotatedMethods = new HashSet<AnnotatedASTNode<MethodDeclaration>>();

		AnnotatedASTNode<?> currentAnnotatedNode = null;
		AnnotatedASTNode<FieldDeclaration> currentAnnotatedField = null;
		AnnotatedASTNode<MethodDeclaration> currentAnnotatedMethod = null;
		
		public boolean hasSeamComponentItself() {
			if(!annotatedFields.isEmpty() || !annotatedMethods.isEmpty()) return true;
			if(annotatedType != null && annotatedType.getAnnotations() != null) return true;
			return false;
		}
		
		public boolean hasSeamComponent() {
			if(hasSeamComponentItself()) return true;
			for (TypeData c: children) {
				if(c.hasSeamComponent()) return true;
			}
			return false;
		}
		
	}
	
	public TypeData root = null;	
	TypeData current = null;	
	
	public ASTVisitorImpl() {}
	
	public void setType(IType type) {
		root = new TypeData();
		root.type = type;
	}
	
	
	public boolean hasSeamComponent() {
		return root.hasSeamComponent();
	}

	public boolean visit(SingleMemberAnnotation node) {
		if(current.innerLock > 0) return false;
		String type = resolveType(node);
		if(Util.isSeamAnnotationType(type) && current.currentAnnotatedNode != null) {
			current.currentAnnotatedNode.addAnnotation(new ResolvedAnnotation(type, node));
		}
		return false;
	}

	public boolean visit(NormalAnnotation node) {
		if(current.innerLock > 0) return false;
		String type = resolveType(node);
		if(Util.isSeamAnnotationType(type) && current.currentAnnotatedNode != null) {
			current.currentAnnotatedNode.addAnnotation(new ResolvedAnnotation(type, node));
		}
		return false;
	}

	public boolean visit(MarkerAnnotation node) {
		if(current.innerLock > 0) return false;
		String type = resolveType(node);
		if(Util.isSeamAnnotationType(type) && current.currentAnnotatedNode != null) {
			current.currentAnnotatedNode.addAnnotation(new ResolvedAnnotation(type, node));
		}
		return true;
	}

	boolean checkAnnotationType(Annotation node, String annotationType) {
		String n = resolveType(node);
		return n != null && n.equals(annotationType);
	}
	
	String resolveType(Annotation node) {
		return resolveType(current.type, node);
	}

	static String resolveType(IType type, Annotation node) {
		Name nm = node.getTypeName();
		if(nm instanceof SimpleName) {
			SimpleName sn = (SimpleName)nm;
			String n = sn.getIdentifier();
			if(type != null) {
				return JavaScanner.getResolvedType(type, n);
			}
		} else if(nm instanceof QualifiedName) {
			QualifiedName qn = (QualifiedName)nm;
			return qn.getFullyQualifiedName();
		}
		return null;
	}

	public boolean visit(Block node) {
		return false;
	}

	public boolean visit(TypeDeclaration node) {
		return _visit(node);
	}
	
	public boolean visit(EnumDeclaration node) {
		return _visit(node);
	}

	public boolean visit(AnnotationTypeDeclaration node) {
		return _visit(node);
	}

	private boolean _visit(AbstractTypeDeclaration node) {
		if(current == null) {
			String n = node.getName().getFullyQualifiedName();
			if(n != null && n.indexOf('.') < 0) n = EclipseJavaUtil.resolveType(root.type, n);
			String nr = root.type.getFullyQualifiedName();
			if(n == null || !n.equals(nr)) return false;
			current = root;
		}
		if(current.annotatedType == null) {
			current.annotatedType = new AnnotatedASTNode<AbstractTypeDeclaration>(node);
			current.currentAnnotatedNode = current.annotatedType;
		} else {
			String n = node.getName().getFullyQualifiedName();
			if(n != null && n.indexOf('.') < 0) n = EclipseJavaUtil.resolveType(current.type, n);
			IType[] ts = null;
			try {
				ts = current.type.getTypes();
			} catch (JavaModelException e) {
				SeamCorePlugin.getDefault().logError(e);
			}
			IType t = null;
			if(ts != null) for (int i = 0; t == null && i < ts.length; i++) {
				try {
					if(!Flags.isStatic(ts[i].getFlags())) continue;
				} catch (JavaModelException e) {
					continue;
				}
				String ni = ts[i].getFullyQualifiedName();
				if(ni != null) ni = ni.replace('$', '.');
				if(n == null || !n.equals(ni)) continue;
				t = ts[i];
			}
			if(t == null) {
				current.innerLock++;
				return false;
			} else {
				TypeData d = new TypeData();
				d.type = t;
				d.parent = current;
				current.children.add(d);
				current = d;
				current.annotatedType = new AnnotatedASTNode<AbstractTypeDeclaration>(node);
				current.currentAnnotatedNode = current.annotatedType;
			}			
		}
		return true;
	}

	public void endVisit(TypeDeclaration node) {
		_endVisit(node);
	}
	public void endVisit(AnnotationTypeDeclaration node) {
		_endVisit(node);
	}
	public void endVisit(EnumDeclaration node) {
		_endVisit(node);
	}
	public void _endVisit(AbstractTypeDeclaration node) {
		if(current == null) return;
		if(current.currentAnnotatedNode != null && current.currentAnnotatedNode.node == node) {
			current.currentAnnotatedNode = null;
			current = current.parent;
		} else {
			current.innerLock--;
		}
	}
	
	public boolean visit(FieldDeclaration node) {
		if(current == null || current.innerLock > 0) return false;
		current.currentAnnotatedField = new AnnotatedASTNode<FieldDeclaration>(node);
		current.currentAnnotatedNode = current.currentAnnotatedField;
		return true;
	}

	public void endVisit(FieldDeclaration node) {
		if(current == null || current.innerLock > 0) return;
		if(current.currentAnnotatedField != null && current.currentAnnotatedField.getAnnotations() != null) {
			current.annotatedFields.add(current.currentAnnotatedField);
		}
		current.currentAnnotatedField = null;
		current.currentAnnotatedNode = current.annotatedType;
	}
	
	public boolean visit(MethodDeclaration node) {
		if(current == null || current.innerLock > 0) return false;
		current.currentAnnotatedMethod = new AnnotatedASTNode<MethodDeclaration>(node);
		current.currentAnnotatedNode = current.currentAnnotatedMethod;
		return true;
	}

	public void endVisit(MethodDeclaration node) {
		if(current == null || current.innerLock > 0) return;
		if(current.currentAnnotatedMethod != null && current.currentAnnotatedMethod.getAnnotations() != null) {
			current.annotatedMethods.add(current.currentAnnotatedMethod);
		}
		current.currentAnnotatedMethod = null;
		current.currentAnnotatedNode = current.annotatedType;
	}
	
}
