/**
 * Copyright (c) 2011 RedEngine Ltd, http://www.RedEngine.co.nz. All rights reserved.
 *
 * This program is licensed to you under the Apache License Version 2.0,
 * and you may not use this file except in compliance with the Apache License Version 2.0.
 * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the Apache License Version 2.0 is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
 */
package net.stickycode.bootstrap.guice3;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import net.stickycode.metadata.MetadataResolverRegistry;
import net.stickycode.stereotype.StickyComponent;
import net.stickycode.stereotype.StickyFramework;
import net.stickycode.stereotype.component.StickyRepository;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.MembersInjector;
import com.google.inject.Scopes;
import com.google.inject.Singleton;
import com.google.inject.matcher.Matchers;
import com.google.inject.spi.TypeListener;

import de.devsurf.injection.guice.install.InstallationContext.BindingStage;
import de.devsurf.injection.guice.scanner.features.BindingScannerFeature;

@Singleton
public class StickyStereotypeScannerFeature
    extends BindingScannerFeature {

  private Logger log = LoggerFactory.getLogger(getClass());

  @Inject
  MetadataResolverRegistry metadataResolver;

  @Override
  public BindingStage accept(Class<Object> annotatedClass, Map<String, Annotation> annotations) {
    if (isFrameworkComponent(annotatedClass))
      return BindingStage.IGNORE;

    if (metadataResolver.is(annotatedClass).metaAnnotatedWith(getComponentAnnotation()))
      return deriveStage(annotatedClass);

    return BindingStage.IGNORE;
  }

  protected boolean isFrameworkComponent(Class<Object> annotatedClass) {
    return metadataResolver.is(annotatedClass).metaAnnotatedWith(StickyFramework.class);
    // if (annotatedClass.isAnnotationPresent(StickyFramework.class))
    // return true;
    //
    // for (Class<?> type = annotatedClass; type != null; type = type.getSuperclass())
    // for (Class<?> contract : type.getInterfaces()) {
    // if (contract.isAnnotationPresent(StickyFramework.class))
    // return true;
    //
    // if (contract.isAssignableFrom(MembersInjector.class))
    // return true;
    //
    // if (contract.isAssignableFrom(TypeListener.class))
    // return true;
    //
    // if (contract.isAssignableFrom(InjectionListener.class))
    // return true;
    // }
    //
    // return false;
  }

  protected Class<? extends Annotation> getComponentAnnotation() {
    return StickyComponent.class;
  }

  protected BindingStage deriveStage(Class<Object> annotatedClass) {
    BindingStage calculateStage = calculateStage(annotatedClass);
    log.debug("adding {} at {}", annotatedClass.getName(), calculateStage);
    return calculateStage;
  }

  private BindingStage calculateStage(Class<Object> annotatedClass) {
    for (Class<?> contract : annotatedClass.getInterfaces()) {
      if (contract.isAnnotationPresent(StickyRepository.class))
        return BindingStage.BOOT_BEFORE;
    }

    return BindingStage.BOOT;
  }

  @Override
  @SuppressWarnings("unchecked")
  public void process(Class<Object> annotatedClass, Map<String, Annotation> annotations) {
    Class<Object>[] interfaces = (Class<Object>[]) annotatedClass.getInterfaces();

    if (interfaces.length == 0) {
      List<Class<?>> interfaceCollection = new ArrayList<Class<?>>();
      Class<? super Object> parent = annotatedClass.getSuperclass();
      while (parent != null && !parent.equals(Object.class)) {
        Collections.addAll(interfaceCollection, parent.getInterfaces());
        parent = parent.getSuperclass();
      }
      interfaces = interfaceCollection.toArray(new Class[interfaceCollection.size()]);
    }

    bind(annotatedClass, null, Scopes.SINGLETON);

    for (Class<Object> interf : interfaces) {
      if (interf.isAssignableFrom(TypeListener.class))
        bindListener(annotatedClass);
      else
        if (!interf.isAssignableFrom(MembersInjector.class))
          bind(annotatedClass, interf, (Annotation) null, Scopes.SINGLETON);
    }
  }

  private void bindListener(Class<Object> annotatedClass) {
    TypeListener typeListener = typeListener(annotatedClass);
    _binder.requestInjection(typeListener);
    _binder.bindListener(Matchers.any(), typeListener);
  }

  private TypeListener typeListener(Class<Object> annotatedClass) {
    try {
      return (TypeListener) annotatedClass.newInstance();
    }
    catch (InstantiationException e) {
      throw new RuntimeException(e);
    }
    catch (IllegalAccessException e) {
      throw new RuntimeException(e);
    }
  }
}

@@ -3683,31 +3683,30 @@
 ) %7B%0A    
+List%3C
 Class%3C
-Object%3E%5B%5D
+?%3E%3E
  interfa
@@ -3715,113 +3715,71 @@
 s = 
-(Class%3CObject%3E%5B%5D) annotatedClass.getInterfaces
+new ArrayList%3CClass%3C?%3E%3E
 ();%0A
-%0A 
    
-if (interfaces.length == 0) %7B%0A      List%3C
+ processInterface(annotated
 Class
-%3C?%3E%3E
+,
  int
@@ -3788,74 +3788,33 @@
 face
-Collection = new ArrayList%3CClass%3C?%3E%3E(
+s
 );%0A    
+%0A
   
+  for (
 Class%3C?
- super Object
 %3E pa
@@ -3855,251 +3855,99 @@
 s();
-%0A      while (parent != null && !parent.equals(Object.class)) %7B%0A        Collections.addAll(interfaceCollection, parent.getInterfaces());%0A        parent = parent.getSuperclass();%0A      %7D%0A      interfaces = interfaceCollection.toArray(new 
+ parent != null; parent = parent.getSuperclass()) %7B%0A      processInterface(annotated
 Class
-%5B
+, 
 inte
@@ -3955,26 +3955,9 @@
 face
-Collection.size()%5D
+s
 );%0A 
@@ -4028,22 +4028,17 @@
  (Class%3C
-Object
+?
 %3E interf
@@ -4245,24 +4245,40 @@
 otatedClass,
+ (Class%3CObject%3E)
  interf, (An
@@ -4319,24 +4319,240 @@
 %0A    %7D%0A  %7D%0A%0A
+  private void processInterface(Class%3C?%3E target, List%3CClass%3C?%3E%3E interfaces) %7B%0A    for (Class%3C?%3E class1 : target.getInterfaces()) %7B%0A      interfaces.add(class1);%0A      processInterface(class1, interfaces);%0A    %7D%0A  %7D%0A%0A
   private vo

Tidy up the processing of classes, much simpler now