/*
 * 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.config.inject;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.enterprise.context.Dependent;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Annotated;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.InjectionPoint;
import javax.enterprise.inject.spi.InjectionTarget;
import javax.enterprise.util.Nonbinding;

import com.caucho.config.xml.XmlCookie;
import com.caucho.naming.ObjectProxy;
import com.caucho.util.Base64;
import com.caucho.util.L10N;
import com.caucho.util.NullOutputStream;
import com.caucho.util.Sha256OutputStream;
import com.caucho.vfs.Vfs;
import com.caucho.vfs.WriteStream;

/**
 * Common bean introspection for Produces and ManagedBean.
 */
abstract public class AbstractBean<T>
  implements Bean<T>, ObjectProxy, AnnotatedBean
{
  private static final L10N L = new L10N(AbstractBean.class);
  private static final Logger log
    = Logger.getLogger(AbstractBean.class.getName());

  private static final Set<Annotation> _currentBindings;
  private static final Set<InjectionPoint> _nullInjectionPoints
    = new HashSet<InjectionPoint>();

  private InjectManager _beanManager;

  private String _passivationId;

  public AbstractBean(InjectManager beanManager)
  {
    _beanManager = beanManager;
  }

  public InjectManager getBeanManager()
  {
    return _beanManager;
  }

  public String getId()
  {
    if (_passivationId == null)
      _passivationId = calculatePassivationId();

    return _passivationId;
  }

  @Override
  public Annotated getAnnotated()
  {
    return null;
  }

  public AnnotatedType<T> getAnnotatedType()
  {
    Annotated annotated = getAnnotated();

    if (annotated instanceof AnnotatedType<?>)
      return (AnnotatedType<T>) annotated;
    else
      return null;
  }

  public InjectionTarget<T> getInjectionTarget()
  {
    return null;
  }

  public void introspect()
  {
  }

  @Override
  abstract public T create(CreationalContext<T> creationalContext);

  public void destroy(T instance, CreationalContext<T> env)
  {
  }

  //
  // metadata for the bean
  //

  abstract public Set<Type> getTypes();

  public Class<?> getBeanClass()
  {
    return null;
  }
  
  public Class<?> getJavaClass()
  {
    return null;
  }

  public Set<Annotation> getQualifiers()
  {
    return _currentBindings;
  }

  public Set<Class<? extends Annotation>> getStereotypes()
  {
    return null;
  }

  public Set<InjectionPoint> getInjectionPoints()
  {
    return _nullInjectionPoints;
  }

  public String getName()
  {
    return null;
  }

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

  public boolean isNullable()
  {
    return false;
  }

  public boolean isPassivationCapable()
  {
    return false;
  }

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

  protected String calculatePassivationId()
  {
    try {
      Sha256OutputStream os = new Sha256OutputStream(new NullOutputStream());
      WriteStream out = Vfs.openWrite(os);

      out.print(getJavaClass());

      ArrayList<String> typeList = new ArrayList<String>();

      for (Type type : getTypes()) {
        typeList.add(type.toString());
      }
      Collections.sort(typeList);

      for (String typeString : typeList) {
        out.print(";");
        out.print(typeString);
      }

      if (getName() != null) {
        out.print(";name=");
        out.print(getName());
      }

      ArrayList<String> annList = new ArrayList<String>();

      for (Annotation ann : getQualifiers()) {
        annList.add(bindingToString(ann));
      }

      Collections.sort(annList);

      for (String annString : annList) {
        out.print(";");
        out.print(annString);
      }
      
      // ioc/0l34
      if (getAnnotatedType() != null) {
        XmlCookie xmlCookie = getAnnotatedType().getAnnotation(XmlCookie.class);
        if (xmlCookie != null) {
          out.print(";xmlcookie=" + xmlCookie.value());
        }
      }

      out.close();

      return Base64.encodeFromByteArray(os.getDigest());
    } catch (IOException e) {
      throw new IllegalStateException(e);
    }
  }

  private String bindingToString(Annotation ann)
  {
    StringBuilder sb = new StringBuilder(ann.annotationType().getName());

    ArrayList<String> propList = new ArrayList<String>();

    for (Method method : ann.annotationType().getDeclaredMethods()) {
      if (method.getName().equals("annotationType"))
        continue;

      if (method.isAnnotationPresent(Nonbinding.class))
        continue;

      if (method.getParameterTypes().length != 0)
        continue;

      try {
        String prop = method.getName() + "," + method.invoke(ann);

        propList.add(prop);
      } catch (Exception e) {
        log.log(Level.FINER, e.toString());
      }
    }

    Collections.sort(propList);
    for (String prop : propList) {
      sb.append(",").append(prop);
    }

    return sb.toString();
  }

  /**
   * Creates the object from the proxy.
   *
   * @return the object named by the proxy.
   */
  public Object createObject(Hashtable env)
  {
    return _beanManager.getReference(this);
  }

  public String toDisplayString()
  {
    if (getInjectionTarget() instanceof DisplayableInjectionTarget)
      return ((DisplayableInjectionTarget) getInjectionTarget()).toDisplayString();
    
    if (getBeanClass() == null)
      return toString();
    
    String display = toDisplayStringImpl();
    
    Class<?> beanClass = getBeanClass();
    
    if (beanClass.getClassLoader() != null) {
      URL url = beanClass.getResource("");
      
      if (url != null)
        return display + "\n  in " + url;
    }
    
    return display;
    
  }
  
  protected String toDisplayStringImpl()
  {
    StringBuilder sb = new StringBuilder();
    
    Class<?> beanClass = getBeanClass();

    sb.append(beanClass.getName());
    sb.append("[");

    ArrayList<Annotation> bindings
      = new ArrayList<Annotation>(getQualifiers());

    for (int i = 0; i < bindings.size(); i++) {
      Annotation ann = bindings.get(i);

      if (i != 0)
        sb.append(", ");

      sb.append(ann);
    }

    if (getName() != null) {
      sb.append(", ");
      sb.append("@Named=");
      sb.append(getName());
    }

    if (getScope() != null && getScope() != Dependent.class) {
      sb.append(", @");
      sb.append(getScope().getSimpleName());
    }
    
    sb.append(", " + getClass().getSimpleName());

    sb.append("]");

    return sb.toString();
  }

  @Override
  public String toString()
  {
    StringBuilder sb = new StringBuilder();

    sb.append(getClass().getSimpleName());
    sb.append("[");

    if (getBeanClass() != null) {
      sb.append(getBeanClass().getSimpleName());
    }
    
    sb.append(", {");

    ArrayList<Annotation> bindings
      = new ArrayList<Annotation>(getQualifiers());

    for (int i = 0; i < bindings.size(); i++) {
      Annotation ann = bindings.get(i);

      if (i != 0)
        sb.append(", ");

      sb.append(ann);
    }

    sb.append("}");

    if (getName() != null) {
      sb.append(", ");
      sb.append("name=");
      sb.append(getName());
    }

    if (getScope() != null && getScope() != Dependent.class) {
      sb.append(", @");
      sb.append(getScope().getSimpleName());
    }

    sb.append("]");

    return sb.toString();
  }

  static {
    _currentBindings = new HashSet<Annotation>();
    _currentBindings.add(DefaultLiteral.DEFAULT);
  }
}
