/*
 * Copyright 2012-2013 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Eclipse Public License version 1.0, available at
 * http://www.eclipse.org/legal/epl-v10.html
 */

package org.jboss.forge.roaster.model.ast;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.IExtendedModifier;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.jboss.forge.roaster.model.Annotation;
import org.jboss.forge.roaster.model.impl.AnnotationImpl;
import org.jboss.forge.roaster.model.source.AnnotationSource;
import org.jboss.forge.roaster.model.source.AnnotationTargetSource;
import org.jboss.forge.roaster.model.source.Import;
import org.jboss.forge.roaster.model.source.JavaSource;
import org.jboss.forge.roaster.model.util.Types;

/**
 * @author <a href="mailto:lincolnbaxter@gmail.com">Lincoln Baxter, III</a>
 */
public class AnnotationAccessor<O extends JavaSource<O>, T>
{

   public AnnotationSource<O> addAnnotation(final AnnotationTargetSource<O, T> target, final ASTNode body)
   {
      return addAnnotation(target, getModifiers(body));
   }

   public AnnotationSource<O> addAnnotation(final AnnotationTargetSource<O, T> target,
            final SingleVariableDeclaration variableDeclaration)
   {
      return addAnnotation(target, variableDeclaration.modifiers());
   }

   private AnnotationSource<O> addAnnotation(final AnnotationTargetSource<O, T> target, final List<?> modifiers)
   {
      @SuppressWarnings("unchecked")
      ListIterator<IExtendedModifier> iter = (ListIterator<IExtendedModifier>) modifiers.listIterator();
      while (iter.hasNext() && iter.next().isAnnotation())
      {
         // ignore these nodes
      }

      // the effect of this is to back up only if the last encountered modifier is _not_ an annotation:
      if (iter.hasPrevious() && iter.previous().isAnnotation())
      {
         iter.next();
      }
      AnnotationSource<O> annotation = new AnnotationImpl<>(target);
      iter.add((IExtendedModifier) annotation.getInternal());
      return annotation;
   }

   public AnnotationSource<O> addAnnotation(final AnnotationTargetSource<O, T> target, final ASTNode body,
            final Class<?> clazz)
   {
      return addAnnotation(target, getModifiers(body), clazz.getName());
   }

   public AnnotationSource<O> addAnnotation(final AnnotationTargetSource<O, T> target,
            final SingleVariableDeclaration variableDeclaration,
            final Class<?> clazz)
   {
      return addAnnotation(target, variableDeclaration.modifiers(), clazz.getName());
   }

   public AnnotationSource<O> addAnnotation(final AnnotationTargetSource<O, T> target, final ASTNode body,
            final String className)
   {
      return addAnnotation(target, getModifiers(body), className);
   }

   public AnnotationSource<O> addAnnotation(final AnnotationTargetSource<O, T> target,
            final SingleVariableDeclaration variableDeclaration,
            final String className)
   {
      return addAnnotation(target, variableDeclaration.modifiers(), className);
   }

   private AnnotationSource<O> addAnnotation(final AnnotationTargetSource<O, T> target, final List<?> modifiers,
            final String className)
   {
      String classNameToUse = className;
      Import imprt = target.getOrigin().addImport(className);
      if (imprt != null)
      {
         classNameToUse = imprt.getSimpleName();
      }
      else if (Types.isJavaLang(className))
      {
         classNameToUse = Types.toSimpleName(className);
      }
      return addAnnotation(target, modifiers).setName(classNameToUse);
   }

   public List<AnnotationSource<O>> getAnnotations(final AnnotationTargetSource<O, T> target, final ASTNode body)
   {
      return getAnnotations(target, getModifiers(body));
   }

   public List<AnnotationSource<O>> getAnnotations(final AnnotationTargetSource<O, T> target,
            final SingleVariableDeclaration variableDeclaration)
   {
      return getAnnotations(target, variableDeclaration.modifiers());
   }

   private List<AnnotationSource<O>> getAnnotations(final AnnotationTargetSource<O, T> target, final List<?> modifiers)
   {
      List<AnnotationSource<O>> result = new ArrayList<>();

      for (Object object : modifiers)
      {
         if (object instanceof org.eclipse.jdt.core.dom.Annotation)
         {
            AnnotationSource<O> annotation = new AnnotationImpl<>(target, object);
            result.add(annotation);
         }
      }

      return Collections.unmodifiableList(result);
   }

   public <E extends AnnotationTargetSource<O, T>> E removeAnnotation(final E target, final ASTNode body,
            final Annotation<O> annotation)
   {
      return removeAnnotation(target, getModifiers(body), annotation);
   }

   public <E extends AnnotationTargetSource<O, T>> E removeAnnotation(final E target,
            final SingleVariableDeclaration variableDeclaration,
            final Annotation<O> annotation)
   {
      return removeAnnotation(target, variableDeclaration.modifiers(), annotation);
   }

   @SuppressWarnings("unlikely-arg-type")
   private <E extends AnnotationTargetSource<O, T>> E removeAnnotation(final E target, final List<?> modifiers,
            final Annotation<O> annotation)
   {
      for (Object object : modifiers)
      {
         if (object.equals(annotation.getInternal()))
         {
            modifiers.remove(object);
            break;
         }
      }
      return target;
   }

   public void removeAllAnnotations(final ASTNode body)
   {
      removeAllAnnotations(getModifiers(body));

   }

   public void removeAllAnnotations(final SingleVariableDeclaration variableDeclaration)
   {
      removeAllAnnotations(variableDeclaration.modifiers());
   }

   private static void removeAllAnnotations(final List<?> modifiers)
   {
      Iterator<?> iterator = modifiers.iterator();
      while (iterator.hasNext())
      {
         Object object = iterator.next();
         if (object instanceof org.eclipse.jdt.core.dom.Annotation)
         {
            iterator.remove();
         }
      }
   }

   public <E extends AnnotationTargetSource<O, T>> boolean hasAnnotation(final E target, final ASTNode body,
            final String type)
   {
      return hasAnnotation(target, getModifiers(body), type);
   }

   public <E extends AnnotationTargetSource<O, T>> boolean hasAnnotation(final E target,
            final SingleVariableDeclaration variableDeclaration,
            final String type)
   {
      return hasAnnotation(target, variableDeclaration.modifiers(), type);
   }

   private <E extends AnnotationTargetSource<O, T>> boolean hasAnnotation(final E target, final List<?> modifiers,
            final String type)
   {
      for (Object object : modifiers)
      {
         if (object instanceof org.eclipse.jdt.core.dom.Annotation)
         {
            AnnotationSource<O> annotation = new AnnotationImpl<>(target, object);
            String annotationType = annotation.getName();
            if (Types.areEquivalent(type, annotationType))
            {
               return true;
            }
         }
      }
      return false;
   }

   public AnnotationSource<O> getAnnotation(final AnnotationTargetSource<O, T> target, final ASTNode body,
            final Class<? extends java.lang.annotation.Annotation> type)
   {
      return getAnnotation(target, getModifiers(body), type.getName());
   }

   public AnnotationSource<O> getAnnotation(final AnnotationTargetSource<O, T> target,
            final SingleVariableDeclaration variableDeclaration,
            final Class<? extends java.lang.annotation.Annotation> type)
   {
      return getAnnotation(target, variableDeclaration.modifiers(), type.getName());
   }

   public AnnotationSource<O> getAnnotation(final AnnotationTargetSource<O, T> target, final ASTNode body,
            final String type)
   {
      return getAnnotation(target, getModifiers(body), type);
   }

   public AnnotationSource<O> getAnnotation(final AnnotationTargetSource<O, T> target,
            final SingleVariableDeclaration variableDeclaration, final String type)
   {
      return getAnnotation(target, variableDeclaration.modifiers(), type);
   }

   private AnnotationSource<O> getAnnotation(final AnnotationTargetSource<O, T> target, final List<?> modifiers,
            final String type)
   {
      String annotationName = type;
      if (Types.isSimpleName(annotationName))
      {
         annotationName = target.getOrigin().resolveType(annotationName);
      }

      List<AnnotationSource<O>> annotations = getAnnotations(target, modifiers);
      for (AnnotationSource<O> annotation : annotations)
      {
         if (Types.areEquivalent(annotationName, annotation.getQualifiedName()))
         {
            return annotation;
         }
      }
      return null;
   }

   private static List<?> getModifiers(final ASTNode body)
   {
      if (body instanceof BodyDeclaration)
      {
         return ((BodyDeclaration) body).modifiers();
      }
      else if (body instanceof PackageDeclaration)
      {
         return ((PackageDeclaration) body).annotations();
      }
      return Collections.emptyList();
   }
}