/*******************************************************************************
* 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.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Annotation;
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.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.stp.common.logging.LoggingProxy;
import org.eclipse.stp.sc.common.utils.JDTUtils;
import org.eclipse.stp.sc.common.utils.TextEditorHelper;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;

public class ScJDTAnnUtils {
private static final LoggingProxy LOG = LoggingProxy.getlogger(ScJDTAnnUtils.class);
	

    public static void addAnnotationImport(
			CompilationUnit compilationUnitAstNode, Annotation annotationNode,
			ASTRewrite rewrite) {
		String fullAnno = ScAnnotationSupportUtils
				.getAnnotationImport(annotationNode);
		List<String> imports = new ArrayList<String>();
		
		if (fullAnno != null) {
			// usually happens when the annotation was manually added in the
			// code with its fully qualified name
			// (so no need for an import)
			JDTUtils.addImport(compilationUnitAstNode, fullAnno, rewrite);			

			if(!(annotationNode instanceof NormalAnnotation)){
				return ;
			}
			imports.add(fullAnno);
			Class annoCls = ScAnnotationSupportUtils.getAnnotationClass(annotationNode);	    	
	    	NormalAnnotation normalAnnotation = (NormalAnnotation)annotationNode;
	    	List pairs = normalAnnotation.values();    	
	    	for(Iterator it = pairs.iterator(); it.hasNext();){
	    		MemberValuePair pair = (MemberValuePair)it.next();
	    		String name = pair.getName().getIdentifier();
	    		Method method;
				try {
					method = annoCls.getDeclaredMethod(name, (Class[])null);
					Class clsReturnType = method.getReturnType();
					
					if (!clsReturnType.isPrimitive() 
							&& !clsReturnType.getPackage().getName().startsWith("java.lang")) {
						String fullQName = clsReturnType.getCanonicalName();
						
						//if the class is a inner class of other class, only its enclosing class would be imported
						if(clsReturnType.isMemberClass()){
							fullQName = clsReturnType.getEnclosingClass().getCanonicalName();					
						}								
						
						//if a class is already imported, it's no need to be imported again
						if(!imports.contains(fullQName)){
							JDTUtils.addImport(compilationUnitAstNode, fullQName, rewrite);
						}
						imports.add(fullQName);
					}
				} catch (SecurityException e) {
					e.printStackTrace();
				} catch (NoSuchMethodException e) {
					e.printStackTrace();							
				}

	    	}
			
		}
	}
	
    /**
	 * @param compilationUnitAstNode
	 *            the root AST node against which we want to work ... and later
	 *            on make the changes to...
	 * @param annotationNode
	 * @param field
	 * @param rewrite
	 */
    public static void addAnnotationOnField(CompilationUnit compilationUnitAstNode,
                                            Annotation annotationNode,
                                            IField field,
                                            ASTRewrite rewrite) {
        //todo: check the annotation exists first?
        addAnnotationImport(compilationUnitAstNode, annotationNode, rewrite);
        
        Annotation oldAnnotationNode = JDTUtils.findAnnotation(compilationUnitAstNode, field, annotationNode);
        if (oldAnnotationNode != null) {
        	rewrite.remove(oldAnnotationNode, null);
        }
        
        IType type = field.getDeclaringType();
        
        for (Iterator it = compilationUnitAstNode.types().iterator(); it.hasNext();) {
            TypeDeclaration t = (TypeDeclaration)it.next();

            if (t.getName().getIdentifier().equals(type.getElementName())) {
                String fieldName = field.getElementName();
                FieldDeclaration[] fields = t.getFields();

                for (int inx = 0; inx < fields.length; inx++) {
                	for (Object obj : fields[inx].fragments()) {
                		VariableDeclarationFragment var = (VariableDeclarationFragment)obj;
                		if (var.getName().getFullyQualifiedName().equals(fieldName)) {
                            ListRewrite lrw2 = rewrite.getListRewrite((ASTNode)fields[inx],
                                                                      FieldDeclaration.MODIFIERS2_PROPERTY);
                            lrw2.insertFirst(annotationNode, null);
                		}
                	}
                }
            }
        }
    }
    
    
    /**
     * 
     * @param compilationUnitAstNode the root AST node against which we want to work ... and later on
     *  make the changes to...
     * @param annotationNode
     * @param method
     * @param rewrite
     * @param overwrite
     */
    public static void addAnnotationOnMethod(CompilationUnit compilationUnitAstNode,
                                             Annotation annotationNode,
                                             IMethod method,
                                             ASTRewrite rewrite,
                                             boolean overwrite) {
    	
        addAnnotationImport(compilationUnitAstNode, annotationNode, rewrite);

        // Check if an annotation of this type already exists
        Annotation oldAnnotationNode = JDTUtils.findAnnotation(compilationUnitAstNode, method, annotationNode);
        if (oldAnnotationNode != null) {
            if (overwrite) {
                LOG.debug("existing annotation found");
                if (!overwrite) {
                    return;
                }
                LOG.debug("overwriting...");
                rewrite.remove(oldAnnotationNode, null);
            }
        }

        IType type = method.getDeclaringType();

        for (Iterator it = compilationUnitAstNode.types().iterator(); it.hasNext();) {
            TypeDeclaration t = (TypeDeclaration)it.next();

            if (t.getName().getIdentifier().equals(type.getElementName())) {
                String methodName = method.getElementName();
                MethodDeclaration[] meths = t.getMethods();

                for (int inx = 0; inx < meths.length; inx++) {
                    if (meths[inx].getName().getIdentifier().equals(methodName)) {
                        ListRewrite lrw2 = rewrite.getListRewrite((ASTNode)meths[inx],
                                                                  MethodDeclaration.MODIFIERS2_PROPERTY);
                        lrw2.insertFirst(annotationNode, null);
                    }
                }
            }
        }
    }
    
    
    /**
     * @param compilationUnitAstNode the root AST node against which we want to work ... and later on
     *  make the changes to...
     * @param annotationNode
     * @param methodParamDecl
     * @param rewrite
     * @param overwrite
     */
    public static void addAnnotationOnMethodParam(CompilationUnit compilationUnitAstNode,
                                                  Annotation annotationNode,
                                                  SingleVariableDeclaration methodParamDecl,
                                                  ASTRewrite rewrite,
                                                  boolean overwrite) {

    	
        addAnnotationImport(compilationUnitAstNode, annotationNode, rewrite);

        // Check if an annotation of this type already exists
        Iterator<Annotation> oldAnnotationNodesIter = JDTUtils.getAnnotationsFromParamDecl(methodParamDecl).iterator();
        String annotName = JDTUtils.getBasicAnnotName(annotationNode);
        while (oldAnnotationNodesIter.hasNext()) {
            Annotation oldAnnotationNode = (Annotation) oldAnnotationNodesIter.next();
            if (annotName.equals(JDTUtils.getBasicAnnotName(oldAnnotationNode))) {
                LOG.debug("existing annotation found");
                if (!overwrite) {
                    return;
                }
                LOG.debug("overwriting...");
                rewrite.remove(oldAnnotationNode, null);
            }
        }
        
        ListRewrite lrw2 = rewrite.getListRewrite(methodParamDecl,
                                                  SingleVariableDeclaration.MODIFIERS2_PROPERTY);
        lrw2.insertAt(annotationNode, -1, null);
    }

    /**
     * 
     * @param compilationUnitAstNode the root AST node against which we want to work ... and later on
     *  make the changes to...
     * @param annotationNode
     * @param type
     * @param rewrite
     * @param overwrite
     */
    public static void addAnnotationOnType(CompilationUnit compilationUnitAstNode,
                                           Annotation annotationNode,
                                           IType type, 
                                           ASTRewrite rewrite,
                                           boolean overwrite) {
 
        addAnnotationImport(compilationUnitAstNode, annotationNode, rewrite);

        Annotation oldAnnotationNode = JDTUtils.findAnnotation(compilationUnitAstNode, type, annotationNode);
        if (oldAnnotationNode != null) {
            if (overwrite) {
                LOG.debug("existing annotation found");
                if (!overwrite) {
                    return;
                }
                LOG.debug("overwriting...");
                rewrite.remove(oldAnnotationNode, null);
            }
        }

        for (Iterator it = compilationUnitAstNode.types().iterator(); it.hasNext();) {
            TypeDeclaration t = (TypeDeclaration)it.next();

            if (t.getName().getIdentifier().equals(type.getElementName())) {
                ListRewrite lrw2 = rewrite.getListRewrite(t, TypeDeclaration.MODIFIERS2_PROPERTY);
                lrw2.insertFirst(annotationNode, null);
            }
        }
        
    }
    
    
    /**
     * add a series of annotations to a member of a java compilation unit.
     * this method actually goes through all the process of updating the physical java file 
     * @param compilationUnitMember
     * @param compilationUnitAstNode
     * @param annotationNodes
     * @param member the java element against which we want to add the annotation. If supplied, it is superclasse by the
     * "methodParam" parameter 
     * @param methodParam added to support annotation against parameter as they haven't been modelized as 
     * <code>IMember</code>. if it isn't null it superclasses the "member" parameter
     * @throws JavaModelException
     * @throws MalformedTreeException
     * @throws BadLocationException
     */
     public static void addAnnotationsToCu(ICompilationUnit compilationUnitMember,
                                          CompilationUnit compilationUnitAstNode,
                                          List<Annotation> annotationNodes,
                                          IMember member,
                                          SingleVariableDeclaration methodParam, boolean save) throws JavaModelException,
                                                                                        MalformedTreeException,
                                                                                        BadLocationException {
        //creation of a Document
        String source = compilationUnitMember.getBuffer().getContents();
        Document document = new Document(source);

                
        //creation of ASTRewrite
        ASTRewrite rewrite = ASTRewrite.create(compilationUnitAstNode.getAST());

        Iterator<Annotation> iter = annotationNodes.iterator();
        while (iter.hasNext()) {
            Annotation currentAnnotationNode = iter.next();
            if (member instanceof IField) {
                addAnnotationOnField(compilationUnitAstNode, currentAnnotationNode, (IField)member, rewrite);
        
            } else if (methodParam != null) {
                addAnnotationOnMethodParam(compilationUnitAstNode,
                                           currentAnnotationNode,
                                           methodParam,
                                           rewrite,
                                           true);
            
            } else if (member instanceof IMethod && methodParam == null) {
                addAnnotationOnMethod(compilationUnitAstNode,
                                      currentAnnotationNode,
                                      (IMethod)member,
                                      rewrite,
                                      true);
            
            } else if (member instanceof IType) {
                addAnnotationOnType(compilationUnitAstNode, currentAnnotationNode, (IType)member, rewrite, true);
            }
        }
    
        //Johnson: changed below to fix bug 200632.
        //we shouldn't reset the buffer with new contents every time.
        //instead, i changed to modify the active editor with update content
//        // computation of the text edits
//        TextEdit edits = rewrite.rewriteAST(document, compilationUnitMember.getJavaProject().getOptions(true));
//        // computation of the new source code
//        edits.apply(document);
        // update of the compilation unit
//        compilationUnitMember.getBuffer().setContents(document.get());
        
        TextEditorHelper.applyRewrite(compilationUnitMember, rewrite, save); 
        
    }
     
     
     
     /**
      * add 1 annotation to a member of a java compilation unit.
      * this method actually goes through all the process of updating the physical java file 
      * @param compilationUnitMember
      * @param compilationUnitAstNode
      * @param annotationNode
      * @param member the java element against which we want to add the annotation. If supplied, it is superclasse by the
      * "methodParam" parameter 
      * @param methodParam added to support annotation against parameter as they haven't been modelized as 
      * <code>IMember</code>. if it isn't null it superclasses the "member" parameter
      * @param save, save the file with new annotation or not
      * @throws JavaModelException
      * @throws MalformedTreeException
      * @throws BadLocationException
      */
      public static void addAnnotationToCu(ICompilationUnit compilationUnitMember,
                                          CompilationUnit compilationUnitAstNode,
                                          Annotation annotationNode,
                                          IMember member,
                                          SingleVariableDeclaration methodParam, 
                                          boolean save) throws JavaModelException,
                                                                                        MalformedTreeException,
                                                                                        BadLocationException {
         List<Annotation> annotationNodes = new ArrayList<Annotation>(1);
         annotationNodes.add(annotationNode);
         addAnnotationsToCu(compilationUnitMember, compilationUnitAstNode, annotationNodes, member, methodParam, save);
     }
     
      /**
       * add 1 annotation to a member of a java compilation unit.
       * this method actually goes through all the process of updating the physical java file 
       * @param compilationUnitMember
       * @param compilationUnitAstNode
       * @param annotationNode
       * @param member the java element against which we want to add the annotation. If supplied, it is superclasse by the
       * "methodParam" parameter 
       * @param methodParam added to support annotation against parameter as they haven't been modelized as 
       * <code>IMember</code>. if it isn't null it superclasses the "member" parameter
       * @throws JavaModelException
       * @throws MalformedTreeException
       * @throws BadLocationException
       */
       public static void addAnnotationToCu(ICompilationUnit compilationUnitMember,
                                           CompilationUnit compilationUnitAstNode,
                                           Annotation annotationNode,
                                           IMember member,
                                           SingleVariableDeclaration methodParam) throws JavaModelException,
                                                                                         MalformedTreeException,
                                                                                         BadLocationException {
    	   addAnnotationToCu(compilationUnitMember, compilationUnitAstNode,
    			   annotationNode, member, methodParam, false);
       }


}
