/*
 * Copyright (c) 1998-2012 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.ejb.inject;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.LinkedHashSet;
import java.util.Set;

import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.AnnotatedMethod;
import javax.enterprise.inject.spi.AnnotatedParameter;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.InjectionPoint;
import javax.enterprise.inject.spi.PassivationCapable;

import com.caucho.config.ConfigException;
import com.caucho.config.event.EventManager;
import com.caucho.config.inject.CreationalContextImpl;
import com.caucho.config.inject.InjectEnvironmentBean;
import com.caucho.config.inject.InjectManager;
import com.caucho.config.inject.ManagedBeanImpl;
import com.caucho.config.inject.ScopeAdapterBean;
import com.caucho.config.reflect.AnnotatedMethodImpl;
import com.caucho.config.reflect.AnnotatedParameterImpl;
import com.caucho.config.reflect.AnnotatedTypeUtil;
import com.caucho.ejb.session.AbstractSessionContext;
import com.caucho.inject.Module;
import com.caucho.util.L10N;

/**
 * Internal implementation for a Bean
 */
@Module
public class SessionBeanImpl<X,T>
  implements ScopeAdapterBean<T>, Bean<T>, PassivationCapable, EjbGeneratedBean,
             InjectEnvironmentBean
{
  private static final L10N L = new L10N(SessionBeanImpl.class);
  
  private AbstractSessionContext<X,T> _context;
  private ManagedBeanImpl<X> _bean;
  private LinkedHashSet<Type> _types = new LinkedHashSet<Type>();
  
  public SessionBeanImpl(AbstractSessionContext<X,T> context,
                         ManagedBeanImpl<X> bean,
                         Set<Type> apiList,
                         AnnotatedType<X> extAnnType)
  {
    _context = context;
    _bean = bean;
    
    _types.addAll(apiList);

    introspectObservers(bean.getAnnotatedType(), extAnnType);
  }
  
  @Override
  public InjectManager getCdiManager()
  {
    return _context.getInjectManager();
  }
  
  protected ManagedBeanImpl<X> getBean()
  {
    return _bean;
  }
  
  @Override
  public Set<Type> getTypes()
  {
    return _types;
  }

  @Override
  public T getScopeAdapter(Bean<?> topBean, CreationalContextImpl<T> context)
  {
    return null;
  }

  @Override
  public T create(CreationalContext<T> env)
  {
    T value;
    
    if (env instanceof CreationalContextImpl<?>)
      value = _context.createProxy((CreationalContextImpl<T>) env);
    else
      value = _context.createProxy(null);
    
    if (env != null)
      env.push(value);
    
    return value;
  }
  
  @Override
  public void destroy(T instance, CreationalContext<T> cxt)
  {
    CreationalContextImpl<T> env;

    if (cxt instanceof CreationalContextImpl<?>)
      env = (CreationalContextImpl<T>) cxt;
    else
      env = null;
    
    // ejb/5012
    if (env != null)
      env.release();
    else
      _context.destroyProxy(instance, env);
  }

  /**
   * Returns the injection points.
   */
  public Set<InjectionPoint> getInjectionPoints()
  {
    // ejb/1210, ioc/05al
    return getBean().getInjectionPoints();
    
    /*
    HashSet<InjectionPoint> injectionPoints = new HashSet<InjectionPoint>();
    
    return injectionPoints;
    */
  }

  @Override
  public Class<?> getBeanClass()
  {
    return getBean().getBeanClass();
  }

  @Override
  public String getName()
  {
    return getBean().getName();
  }

  @Override
  public Set<Annotation> getQualifiers()
  {
    return getBean().getQualifiers();
  }

  @Override
  public Class<? extends Annotation> getScope()
  {
    return getBean().getScope();
  }

  @Override
  public Set<Class<? extends Annotation>> getStereotypes()
  {
    return getBean().getStereotypes();
  }

  @Override
  public boolean isAlternative()
  {
    return getBean().isAlternative();
  }

  @Override
  public boolean isNullable()
  {
    return false;
  }

  @Override
  public String getId()
  {
    return getBean().getId();
  }
  
  /**
   * Introspects the methods for any @Produces
   */
  private void introspectObservers(AnnotatedType<X> beanType,
                                   AnnotatedType<X> extAnnType)
  {
    EventManager eventManager = _context.getModuleInjectManager().getEventManager();

    // ioc/0b26
    for (AnnotatedMethod<? super X> beanMethod : beanType.getMethods()) {
      /*
      if (! beanMethod.getJavaMember().getDeclaringClass().equals(beanType.getJavaClass())
          && ! beanType.isAnnotationPresent(Specializes.class)) {
        continue;
      }
      */
      
      AnnotatedMethod<? super X> apiMethod
        = AnnotatedTypeUtil.findMethod(extAnnType, beanMethod);
      boolean isBeanMethod = false; 

      if (apiMethod == null) {
        apiMethod = beanMethod;
        isBeanMethod = true;
      }
      else if (apiMethod instanceof AnnotatedMethodImpl<?>) {
        // ioc/0b0h
        AnnotatedMethodImpl<? super X> apiMethodImpl
          = (AnnotatedMethodImpl<? super X>) apiMethod;

        apiMethodImpl.addAnnotations(beanMethod.getAnnotations());
        
        for (int i = 0; i < apiMethod.getParameters().size(); i++) {
          AnnotatedParameterImpl<?> paramImpl
            = (AnnotatedParameterImpl<?>) apiMethod.getParameters().get(i);
          AnnotatedParameter<?> beanParam = beanMethod.getParameters().get(i);
          
          paramImpl.addAnnotations(beanParam.getAnnotations());
        }
      }
        
      
      int param = EventManager.findObserverAnnotation(apiMethod);
      
      if (param >= 0) {
        // ioc/0b0i - TCK
        
        if (! isBeanMethod) {
        }
        else if (apiMethod.isStatic()) {
        }
        else if (_types.contains(beanType.getJavaClass())) {
          // ioc/0b26
        }
        else {
          throw new ConfigException(L.l("{0}.{1} is an invalid @Observes method because @Observes must be in the @Local API.",
                                        beanMethod.getDeclaringType().getJavaClass().getSimpleName(),
                                        beanMethod.getJavaMember().getName()));
        }
          
        eventManager.addObserver(this, apiMethod);
      }
    }
  }
  
  @Override
  public String toString()
  {
    return (getClass().getSimpleName()
            + "[" + getBeanClass().getSimpleName()
            // + "," + _context
            + ", " + getQualifiers() + "]");
  }
}
