/*******************************************************************************
* Copyright (c) 2006 IONA Technologies PLC
* 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:
*     IONA Technologies PLC - initial API and implementation
*******************************************************************************/
package org.eclipse.stp.sc.common.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;


import org.eclipse.core.resources.IProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.stp.common.logging.LoggingProxy;
import org.eclipse.stp.sc.common.annotations.ext.AnnotationSupportProxy;
import org.eclipse.stp.sc.common.utils.JDTUtils;

/**
 * simple utility to handle JAX-WS annotations artefacts:
 * <br> - identify valid annotation candidates for specific Java artefacts
 * <br> - generate JDT nodes representing those annotations
 * <br> - providing sensible defaults value
 */
public class ScAnnotationSupportUtils {
    
	public enum JavaTypes {
		ENUM,
		CLASS,
		INTERFACE,
		ANNOTATION,
	}
    public static final LoggingProxy LOG = LoggingProxy.getlogger(ScAnnotationSupportUtils.class);

    protected ScAnnotationSupportUtils() {
    }

    public static List<Class> getAvailableAnnotationsForClass(IProject project) {
    	List<Class> typeAnnotations = getAnnotationClassesForJavaElement(ElementType.TYPE, project);
    	for (int i = 0; i < typeAnnotations.size(); i++) {
        	if (!isTypeAllowed(typeAnnotations.get(i), JavaTypes.CLASS)) {
        		typeAnnotations.remove(i);
        	}
        }
        return typeAnnotations;
    }

    public static List<Class> getAvailableAnnotationsForInterface(IProject project) {
    	List<Class> typeAnnotations = getAnnotationClassesForJavaElement(ElementType.TYPE, project);
    	Iterator itor = typeAnnotations.iterator();
    	while (itor.hasNext()) {
    		Class cls = (Class)itor.next();
    		if (!isTypeAllowed(cls, JavaTypes.INTERFACE)) {
    			itor.remove();
    		}
    	}
    	
        return typeAnnotations;
    }

    public static List<Class> getAvailableAnnotationsForAnno(IProject project) {
        return getAnnotationClassesForJavaElement(ElementType.ANNOTATION_TYPE, project);
    }

    public static List<Class> getAvailableAnnotationsForType(IProject project) {
        return getAnnotationClassesForJavaElement(ElementType.TYPE, project);
    }

    public static List<Class> getAvailableAnnotationsForType(IProject project, IType type) {
    	try {
        	if (type.isClass()) {
        		return getAvailableAnnotationsForClass(project);
        	}
        	if (type.isInterface()) {
        		return getAvailableAnnotationsForInterface(project);
        	}
        }
        catch (JavaModelException e) {
        	LOG.error("error to check sub-types of java element type");
        }
		return getAvailableAnnotationsForType(project);
    }

    public static List<Class> getAvailableAnnotationsForField(IProject project) {
    	/*
    	if (fieldAnnotations == null || fieldAnnotations.size() == 0) {
    		fieldAnnotations = getAnnotationClassesForJavaElement(ElementType.FIELD, project);
    	}
    	return fieldAnnotations;
    	*/
    	return getAnnotationClassesForJavaElement(ElementType.FIELD, project);
    }

    public static List<Class> getAvailableAnnotationsForMethod(IProject project) {
        return getAnnotationClassesForJavaElement(ElementType.METHOD, project);
    }

    public static List<Class> getAvailableAnnotationsForParam(IProject project) {
        return getAnnotationClassesForJavaElement(ElementType.PARAMETER, project);
    }

    public static String getAnnotationImport(Annotation annotation) {
        String annotationName = annotation.getTypeName().toString();

        if (AnnotationSupportProxy.hasAnnotation(annotationName)) {
        	return AnnotationSupportProxy.getAnnotationClass(annotationName).getCanonicalName();
        }

        return null;
    }
    
    public static Class getAnnotationClass(Annotation annotation) {
        String annotationName = annotation.getTypeName().toString();

        return AnnotationSupportProxy.getAnnotationClass(annotationName);
    }
    /**
     * create a new JDT <code>Annotation</code> node on the specified <code>IMember</code> with defaulted
     *  attributes.
     * @param annClass the class of the required annotation (i.e. javax.jws.WebMethod.class)
     * @param astRoot AST <code>CompilationUnit</code>
     * @param jdtMember the <code>IMember</code> target for this new annotation
     * @return a defaulted JDT <code>Annotation</code> node or null if the annotation type is incorrect for
     *  the specified java artefact 
     */
    public static Annotation getDefaultedAnnotationNode(Class<? extends java.lang.annotation.Annotation> annClass,
                                                        CompilationUnit astRoot,
                                                        IMember jdtMember,
                                                        SingleVariableDeclaration jdtMemberParam) {

        if (!isAnnotationValidForMember(annClass, jdtMember, jdtMemberParam)) {
            return null;
        }

        List<MemberValuePair> annotValues = AnnotationSupportProxy.getDefaultAttributes(annClass,
                                                                                        astRoot,
                                                                                        jdtMember,
                                                                                        jdtMemberParam);
        return JDTUtils.newNormalAnnotation(astRoot, annClass.getSimpleName(), annotValues);
    }

    private static boolean isAnnotationValidForMember(Class annotationClass,
                                                      IMember jdtMember,
                                                      SingleVariableDeclaration jdtMemberParam) {
        IProject project = null;
    	try {
    		if (jdtMember == null) {
    			return false;
    		}
    	    project = jdtMember.getUnderlyingResource().getProject();
    	    if (project == null) {
    	    	return false;
    	    }
    	} catch (Exception e) {
    		LOG.error("error to get project", e);
    	}
        return (jdtMember instanceof IMethod 
                && getAvailableAnnotationsForMethod(project).contains(annotationClass))
                    || (jdtMember instanceof IType 
                        && getAvailableAnnotationsForType(project, (IType)jdtMember).contains(annotationClass))
                            || (jdtMember instanceof IField
                            		&& getAvailableAnnotationsForField(project).contains(annotationClass))
                            			|| (getAvailableAnnotationsForParam(project).contains(annotationClass)
                            					&& jdtMemberParam != null);
    }


    private static List<Class> getAnnotationClassesForJavaElement(ElementType aType, IProject project) {

    	List<Class> theList = new ArrayList<Class>();
        
        Iterator<Class <? extends java.lang.annotation.Annotation> > iter = 
        	AnnotationSupportProxy.getAllAnnotationClasses();

        while (iter.hasNext()) {
            
            Class<? extends java.lang.annotation.Annotation> nextAnnotationClass = iter.next();
            Target annotationTarget = (Target)nextAnnotationClass.getAnnotation(Target.class);
            ElementType[] targetedTypes = annotationTarget.value();
            
            for (int i = 0; i < targetedTypes.length; i++) {
                if (targetedTypes[i] == aType) {
                	String[] natureIDs = new String[0];
                	try {
                		natureIDs = project.getDescription().getNatureIds();
                	} catch (Exception e) {
                		LOG.error("error during get project nature", e);
                	}
                	if (AnnotationSupportProxy.verifyNature(nextAnnotationClass.getName(), natureIDs)) {
                		theList.add(nextAnnotationClass);
                	}
                }
            }
            
            // according to specs if no target is specified then the annotation is valid for any java element 
            if (targetedTypes.length == 0) {
                theList.add(nextAnnotationClass);
            }
        }
        
        return theList;
    }

    private static boolean isTypeAllowed(Class anno, JavaTypes type) {

    	boolean classOnly = AnnotationSupportProxy.isClassOnly(anno.getCanonicalName());
   		boolean interfaceOnly = AnnotationSupportProxy.isInterfaceOnly(anno.getCanonicalName());

   		if (classOnly && type != JavaTypes.CLASS) return false;
    	if (interfaceOnly && type != JavaTypes.INTERFACE) return false;

    	return true;
    }
}
