package org.jboss.forge.container.services;

import java.lang.reflect.Method;
import java.util.concurrent.Callable;

import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.InjectionPoint;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import org.jboss.forge.container.exception.ContainerException;
import org.jboss.forge.container.util.ClassLoaders;

public class ExportedInstanceImpl<R> implements ExportedInstance<R>
{
   private ClassLoader loader;
   private BeanManager manager;
   private CreationalContext<R> context;

   private Bean<R> requestedBean;
   private Class<R> requestedType;
   private Class<? extends R> actualType;

   public ExportedInstanceImpl(ClassLoader loader, BeanManager manager, Bean<R> requestedBean, Class<R> requestedType,
            Class<? extends R> actualType)
   {
      this.loader = loader;
      this.manager = manager;
      this.requestedBean = requestedBean;
      this.requestedType = requestedType;
      this.actualType = actualType;
   }

   @Override
   @SuppressWarnings("unchecked")
   public R get()
   {
      Callable<Object> task = new Callable<Object>()
      {
         @Override
         public Object call() throws Exception
         {
            context = manager.createCreationalContext(requestedBean);
            Object delegate = manager.getReference(requestedBean, actualType, context);
            return Enhancer.create(requestedType, new RemoteClassLoaderInterceptor(loader, delegate));
         }
      };

      return (R) ClassLoaders.executeIn(loader, task);
   }

   @SuppressWarnings("unchecked")
   public Object get(final InjectionPoint injectionPoint)
   {
      // FIXME remove the need for this method (which is currently still not working right for producer methods that
      // require an InjectionPoint
      Callable<Object> task = new Callable<Object>()
      {
         @Override
         public Object call() throws Exception
         {
            Bean<R> bean = (Bean<R>) manager.resolve(manager.getBeans(actualType));
            context = manager.createCreationalContext(bean);
            Object delegate = manager.getInjectableReference(injectionPoint, context);
            return Enhancer.create(requestedType, new RemoteClassLoaderInterceptor(loader, delegate));
         }
      };

      return ClassLoaders.executeIn(loader, task);
   }

   @Override
   public void release(R instance)
   {
      context.release();
   }

   @Override
   public String toString()
   {
      StringBuilder builder = new StringBuilder();
      builder.append("ExportedInstanceImpl [");
      if (requestedType != null)
         builder.append("requestedType=").append(requestedType).append(", ");
      if (actualType != null)
         builder.append("actualType=").append(actualType).append(", ");
      if (loader != null)
         builder.append("loader=").append(loader);
      builder.append("]");
      return builder.toString();
   }

   private class RemoteClassLoaderInterceptor implements MethodInterceptor
   {
      private ClassLoader loader;
      private Object delegate;

      public RemoteClassLoaderInterceptor(ClassLoader loader, Object delegate)
      {
         this.loader = loader;
         this.delegate = delegate;
      }

      @Override
      public Object intercept(final Object obj, final Method method, final Object[] args, final MethodProxy proxy)
               throws Throwable
      {
         Callable<Object> task = new Callable<Object>()
         {
            @Override
            public Object call() throws Exception
            {
               try
               {
                  convertParameterValues(args, method);
                  return method.invoke(delegate, args);
               }
               catch (Throwable e)
               {
                  throw new ContainerException(
                           "Failed during invocation of proxy method [" + delegate.getClass().getName() + "."
                                    + method.getName() + "()] in ClassLoader [" + loader + "]", e);
               }
            }
         };

         return ClassLoaders.executeIn(loader, task);
      }

      private void convertParameterValues(final Object[] args, Method delegateMethod)
      {
         for (int i = 0; i < args.length; i++)
         {
            Object parameterValue = args[i];

            // If it is a class, use the loader version
            if (parameterValue instanceof Class<?>)
            {
               Class<?> paramClassValue = (Class<?>) parameterValue;
               Class<?> loadedClass;
               try
               {
                  loadedClass = loader.loadClass(paramClassValue.getName());
               }
               catch (ClassNotFoundException e)
               {
                  // Oh oh, there is no class with this type in the target.
                  // Trying with delegate ClassLoader;
                  try
                  {
                     loadedClass = delegate.getClass().getClassLoader().loadClass(paramClassValue.getName());
                  }
                  catch (ClassNotFoundException cnfe)
                  {
                     // No way, here is the original class and god bless you :)
                     loadedClass = paramClassValue;
                  }
               }
               parameterValue = loadedClass;
            }
            args[i] = parameterValue;
         }
      }

   }
}

@@ -2479,32 +2479,36 @@
 %7D;%0A%0A      return
+ (R)
  ClassLoaders.ex
@@ -2522,32 +2522,32 @@
 (loader, task);%0A
-
    %7D%0A%0A   @Overri
@@ -3793,64 +3793,8 @@
   %7B%0A
-                  convertParameterValues(args, method);%0A
     
@@ -4212,24 +4212,24 @@
         %7D;%0A%0A
+
          ret
@@ -4283,1292 +4283,8 @@
  %7D%0A%0A
-      private void convertParameterValues(final Object%5B%5D args, Method delegateMethod)%0A      %7B%0A         for (int i = 0; i %3C args.length; i++)%0A         %7B%0A            Object parameterValue = args%5Bi%5D;%0A%0A            // If it is a class, use the loader version%0A            if (parameterValue instanceof Class%3C?%3E)%0A            %7B%0A               Class%3C?%3E paramClassValue = (Class%3C?%3E) parameterValue;%0A               Class%3C?%3E loadedClass;%0A               try%0A               %7B%0A                  loadedClass = loader.loadClass(paramClassValue.getName());%0A               %7D%0A               catch (ClassNotFoundException e)%0A               %7B%0A                  // Oh oh, there is no class with this type in the target.%0A                  // Trying with delegate ClassLoader;%0A                  try%0A                  %7B%0A                     loadedClass = delegate.getClass().getClassLoader().loadClass(paramClassValue.getName());%0A                  %7D%0A                  catch (ClassNotFoundException cnfe)%0A                  %7B%0A                     // No way, here is the original class and god bless you :)%0A                     loadedClass = paramClassValue;%0A                  %7D%0A               %7D%0A               parameterValue = loadedClass;%0A            %7D%0A            args%5Bi%5D = parameterValue;%0A         %7D%0A      %7D%0A%0A
    %7D

Revert "Fixed Classloading issues between addons when parameter is Class"