package org.jboss.tools.seam.ui.text.java.scanner;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
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.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.Flags;
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.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTRequestor;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.jboss.tools.common.model.util.EclipseResourceUtil;
import org.jboss.tools.seam.internal.core.scanner.ScannerException;
import org.jboss.tools.seam.internal.core.scanner.java.ASTVisitorImpl;
import org.jboss.tools.seam.internal.core.scanner.java.AnnotatedASTNode;
import org.jboss.tools.seam.internal.core.scanner.java.ResolvedAnnotation;
import org.jboss.tools.seam.ui.SeamGuiPlugin;

public class JavaAnnotationScanner {
	
	public JavaAnnotationScanner() {}

	/**
	 * Returns component or list of component
	 * TODO change return type
	 * @param f
	 * @return
	 * @throws ScannerException
	 */
/*	public LoadedDeclarations parse(IFile f) throws ScannerException {
		ICompilationUnit u = null;
		try {
			u = getCompilationUnit(f);
		} catch (CoreException e) {
			throw new ScannerException(
					NLS.bind(SeamCoreMessages.JAVA_SCANNER_CANNOT_GET_COMPILATION_UNIT_FOR,f), e);
		}
		if(u == null) return null;
		ASTRequestorImpl requestor = new ASTRequestorImpl(f);
		ICompilationUnit[] us = new ICompilationUnit[]{u};
		ASTParser p = ASTParser.newParser(AST.JLS3);
		p.setSource(u);
		p.setResolveBindings(true);
		p.createASTs(us, new String[0], requestor, null);
		return requestor.getDeclarations();
	}
*/
	/**
	 * Returns component or list of component
	 * TODO change return type
	 * @param u
	 * @return
	 * @throws ScannerException
	 */
	public void parse(ICompilationUnit u) throws ScannerException {
		resolvedAnnotations = null;
		resolvedType = null;
		if(u == null) return;
		ASTRequestorImpl requestor = new ASTRequestorImpl(u);
		ICompilationUnit[] us = new ICompilationUnit[]{u};
		ASTParser p = ASTParser.newParser(AST.JLS3);
		p.setSource(u);
		p.setResolveBindings(true);
		p.createASTs(us, new String[0], requestor, null);
		resolvedType = requestor.getType();
		resolvedAnnotations = requestor.getAnnotations();
	}

	Map<ResolvedAnnotation, AnnotatedASTNode<ASTNode>> resolvedAnnotations = null;
	IType resolvedType = null;
	
	public Map<ResolvedAnnotation, AnnotatedASTNode<ASTNode>> getResolvedAnnotations() {
		return resolvedAnnotations;
	}

	public IType getResolvedType() {
		return resolvedType;
	}
	
	
	private ICompilationUnit getCompilationUnit(IFile f) throws CoreException {
		IProject project = f.getProject();
		IJavaProject javaProject = (IJavaProject)project.getNature(JavaCore.NATURE_ID);
		IResource[] rs = EclipseResourceUtil.getJavaSourceRoots(project);
		for (int i = 0; i < rs.length; i++) {
			if(rs[i].getFullPath().isPrefixOf(f.getFullPath())) {
				IPath path = f.getFullPath().removeFirstSegments(rs[i].getFullPath().segmentCount());
				IJavaElement e = javaProject.findElement(path);
				if(e instanceof ICompilationUnit) {
					return (ICompilationUnit)e;
				}
			}
		}
		return null;
	}


	class ASTRequestorImpl extends ASTRequestor {
		private ASTVisitorImpl visitor = new ASTVisitorImpl();
//		LoadedDeclarations ds = new LoadedDeclarations();
		List<ResolvedAnnotation> annotations = new ArrayList<ResolvedAnnotation>();
		IResource resource;
		IPath sourcePath;
		ICompilationUnit unit;
		
		public ASTRequestorImpl(IResource resource) {
			this.resource = resource;
			this.sourcePath = resource.getFullPath();
		}
		public ASTRequestorImpl(ICompilationUnit unit) {
			this.unit = unit;
			this.resource = unit.getResource();
			this.sourcePath = resource.getFullPath();
		}

//		public LoadedDeclarations getDeclarations() {
//			return ds;
//		}
		
		public Map<ResolvedAnnotation, AnnotatedASTNode<ASTNode>> getAnnotations() {
			return annotationMap;
		}
		
		public IType getType() {
			return type;
		}
		
		public void acceptAST(ICompilationUnit source, CompilationUnit ast) {
			IType[] ts = null;
			try {
				ts = source.getTypes();
			} catch (JavaModelException e) {
				SeamGuiPlugin.getDefault().logError(e);
			}
			if(ts == null || ts.length == 0) return;
			for (int i = 0; i < ts.length; i++) {
				visitor.setType(null);
				int f = 0;
				try {
					f = ts[i].getFlags();
				} catch (JavaModelException e) {
					//ignore
					continue;
				}
				if(Flags.isPublic(f)) {
					visitor.setType(ts[i]);
					ast.accept(visitor);
					if(!visitor.hasSeamComponent()) continue;
					processTypeData(visitor.root);
				}
			}
		}
		
		IType type = null;
		AnnotatedASTNode<AbstractTypeDeclaration> annotatedType = null;
		Set<AnnotatedASTNode<FieldDeclaration>> annotatedFields = null;
		Set<AnnotatedASTNode<MethodDeclaration>> annotatedMethods = null;
		
		Map<ResolvedAnnotation, AnnotatedASTNode<ASTNode>> annotationMap = new HashMap<ResolvedAnnotation, AnnotatedASTNode<ASTNode>>();
		
		private void processTypeData(ASTVisitorImpl.TypeData data) {
			type = data.type;
			annotatedType = data.annotatedType;
			annotatedFields = data.annotatedFields;
			annotatedMethods = data.annotatedMethods;

			ResolvedAnnotation[] annotations = annotatedType.getAnnotations();
			for (int i = 0; annotations != null && i < annotations.length; i++) {
				annotationMap.put(annotations[i], (AnnotatedASTNode)annotatedType);
			}
			
			if (annotatedMethods != null) {
				for (AnnotatedASTNode annotatedMethod : annotatedMethods) {
					annotations = annotatedMethod.getAnnotations();
					for (int i = 0; annotations != null && i < annotations.length; i++) {
						annotationMap.put(annotations[i], (AnnotatedASTNode)annotatedMethod);
					}
				}
			}

			if (annotatedFields != null) {
				for (AnnotatedASTNode annotatedField : annotatedFields) {
					annotations = annotatedField.getAnnotations();
					for (int i = 0; annotations != null && i < annotations.length; i++) {
						annotationMap.put(annotations[i], (AnnotatedASTNode)annotatedField);
					}
				}
			}
//			this.annotations = annotations;
		}
	}
	
	public ResolvedAnnotation findAnnotationByValueOffset(int offset) {
		if (resolvedAnnotations == null)
			return null;

		for (ResolvedAnnotation a : resolvedAnnotations.keySet()) {
			if (a.getAnnotation() instanceof SingleMemberAnnotation) {
				SingleMemberAnnotation sma = (SingleMemberAnnotation)a.getAnnotation();
				Object vpd = sma.getStructuralProperty(SingleMemberAnnotation.VALUE_PROPERTY);
				if (vpd instanceof ASTNode) {
					ASTNode node = (ASTNode)vpd;
					int start = node.getStartPosition();
					int length = node.getLength();
					if (offset >= start && offset < start + length) {
						return a;
					}
				}
			} else if (a.getAnnotation() instanceof NormalAnnotation) {
				NormalAnnotation na = (NormalAnnotation)a.getAnnotation();
				Object vpd = na.getStructuralProperty(NormalAnnotation.VALUES_PROPERTY);
				if (vpd instanceof List) {
					for (Object item : (List)vpd) {
						if (item instanceof ASTNode) {
							ASTNode node = (ASTNode)item;
							if (node.getNodeType() != ASTNode.MEMBER_VALUE_PAIR) 
								continue;
							MemberValuePair mvp = (MemberValuePair)node;
							SimpleName name = mvp.getName();
							if (!"value".equals(name.getIdentifier())) {
								continue;
							}
							int start = node.getStartPosition();
							int length = node.getLength();
							if (offset >= start && offset < start + length) {
								return a;
							}
						}
					}
				}
			}
		}
		return null;
	}

	/**
	 * Returns the annotation's value text
	 * 
	 * @param annotation
	 * @return
	 */
	public String getAnnotationValue(ResolvedAnnotation annotation) {
		if (annotation.getAnnotation() instanceof SingleMemberAnnotation) {
			SingleMemberAnnotation sma = (SingleMemberAnnotation)annotation.getAnnotation();
			Object vpd = sma.getStructuralProperty(SingleMemberAnnotation.VALUE_PROPERTY);
			if (vpd instanceof StringLiteral) {
				return ((StringLiteral)vpd).getLiteralValue();
			} 
			return vpd.toString();
		} else if (annotation.getAnnotation() instanceof NormalAnnotation) {
			NormalAnnotation na = (NormalAnnotation)annotation.getAnnotation();
			Object vpd = na.getStructuralProperty(NormalAnnotation.VALUES_PROPERTY);
			if (vpd instanceof List) {
				for (Object item : (List)vpd) {
					if (item instanceof ASTNode) {
						ASTNode node = (ASTNode)item;
						if (node.getNodeType() != ASTNode.MEMBER_VALUE_PAIR) 
							continue;
						MemberValuePair mvp = (MemberValuePair)node;
						SimpleName name = mvp.getName();
						if (!"value".equals(name.getIdentifier())) {
							continue;
						}
						return ((StringLiteral)mvp.getValue()).getLiteralValue();
					}
				}
			}
		}
		
		return null;
	}

	/**
	 * Returns the annotation's value region
	 * 
	 * @param annotation
	 * @return
	 */
	public IRegion getAnnotationValueRegion(ResolvedAnnotation annotation) {
		if (annotation.getAnnotation() instanceof SingleMemberAnnotation) {
			SingleMemberAnnotation sma = (SingleMemberAnnotation)annotation.getAnnotation();
			Object vpd = sma.getStructuralProperty(SingleMemberAnnotation.VALUE_PROPERTY);
			if (vpd instanceof StringLiteral) {
				StringLiteral sl = (StringLiteral)vpd;
				return new Region(sl.getStartPosition() + 1, sl.getLength());
			}
			if (vpd instanceof ASTNode) {
				ASTNode astNode = (ASTNode)vpd;
				return new Region(astNode.getStartPosition(),astNode.getLength()); 
			} 
			return null;
		} else if (annotation.getAnnotation() instanceof NormalAnnotation) {
			NormalAnnotation na = (NormalAnnotation)annotation.getAnnotation();
			Object vpd = na.getStructuralProperty(NormalAnnotation.VALUES_PROPERTY);
			if (vpd instanceof List) {
				for (Object item : (List)vpd) {
					if (item instanceof ASTNode) {
						ASTNode node = (ASTNode)item;
						if (node.getNodeType() != ASTNode.MEMBER_VALUE_PAIR) 
							continue;
						MemberValuePair mvp = (MemberValuePair)node;
						SimpleName name = mvp.getName();
						if (!"value".equals(name.getIdentifier())) {
							continue;
						}
						Object sValuesObj = mvp.getStructuralProperty(MemberValuePair.VALUE_PROPERTY);
						if (sValuesObj instanceof StringLiteral) {
							StringLiteral sl = (StringLiteral)sValuesObj;
							return new Region(sl.getStartPosition() + 1, sl.getLength());
						}
						if (sValuesObj instanceof ASTNode) {
							ASTNode astNode = (ASTNode)sValuesObj;
							return new Region(astNode.getStartPosition(),astNode.getLength()); 
						} 
						return null;
					}
				}
			}
		}
		
		return null;
	}

	/**
	 * Detects if the type of annotation equals to the selected SeamAnnotations' type
	 * 
	 * @param annotation
	 * @param typeName
	 * 
	 * @return 
	 */
	public boolean isAnnotationOfType(ResolvedAnnotation annotation, String typeName) {
		if (annotation == null || typeName == null)
			return false;
		
		return (typeName.equals(annotation.getType()));
	}

	static String getResolvedType(IType type, String n) {

		String[][] rs;
		try {
			rs = type.resolveType(n);
			if(rs != null && rs.length > 0) {
				return (rs[0][0].length() == 0) ? rs[0][1] : rs[0][0] + "." + rs[0][1]; //$NON-NLS-1$
			}
		} catch (JavaModelException e) {
			SeamGuiPlugin.getDefault().logError(e);
		}

		return n;
	}

}
