/*
 * Copyright 2009 JBoss, a divison Red Hat, Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.errai.container;

import javassist.util.proxy.ProxyFactory;
import javassist.util.proxy.ProxyFactory.ClassLoaderProvider;

import javax.enterprise.inject.spi.BeanManager;
import javax.servlet.ServletContextEvent;

import org.jboss.weld.bootstrap.api.Bootstrap;
import org.jboss.weld.bootstrap.api.Environments;
import org.jboss.weld.environment.jetty.JettyWeldInjector;
import org.jboss.weld.environment.servlet.Listener;
import org.jboss.weld.environment.servlet.deployment.ServletDeployment;
import org.jboss.weld.environment.servlet.services.ServletResourceInjectionServices;
import org.jboss.weld.environment.servlet.util.Reflections;
import org.jboss.weld.environment.tomcat.WeldForwardingAnnotationProcessor;
import org.jboss.weld.environment.tomcat7.WeldForwardingInstanceManager;
import org.jboss.weld.injection.spi.ResourceInjectionServices;
import org.jboss.weld.manager.api.WeldManager;
import org.jboss.weld.servlet.api.ServletListener;
import org.jboss.weld.servlet.api.helpers.ForwardingServletListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A fork of the Listener class from Weld 1.1.0.Beta1. There are two differences from the original:
 *
 * <ol>
 *  <li>Validation of CDI beans is skipped on startup
 *  <li>JSP support is omitted
 * </ol>
 *
 * @author Pete Muir
 */
public class CDIServletStateListener extends ForwardingServletListener {
  private static final Logger log = LoggerFactory.getLogger(CDIServletStateListener.class);

  private static final String BOOTSTRAP_IMPL_CLASS_NAME = "org.jboss.weld.bootstrap.WeldBootstrap";
  private static final String WELD_LISTENER_CLASS_NAME = "org.jboss.weld.servlet.WeldListener";
  private static final String EXPRESSION_FACTORY_NAME = "org.jboss.weld.el.ExpressionFactory";
  private static final String JETTY_REQUIRED_CLASS_NAME = "org.mortbay.jetty.servlet.ServletHandler";
  public static final String INJECTOR_ATTRIBUTE_NAME = "org.jboss.weld.environment.jetty.JettyWeldInjector";
  public static final String BEAN_MANAGER_ATTRIBUTE_NAME = Listener.class.getPackage().getName() + "."
      + BeanManager.class.getName();

  private final transient Bootstrap bootstrap;
  private final transient ServletListener weldListener;
  private WeldManager manager;

  public CDIServletStateListener() {
    try {
      bootstrap = Reflections.newInstance(BOOTSTRAP_IMPL_CLASS_NAME);
    } catch (IllegalArgumentException e) {
      throw new IllegalStateException("Error loading Weld bootstrap, check that Weld is on the classpath", e);
    }
    try {
      weldListener = Reflections.newInstance(WELD_LISTENER_CLASS_NAME);
    } catch (IllegalArgumentException e) {
      throw new IllegalStateException("Error loading Weld listener, check that Weld is on the classpath", e);
    }
  }

  @Override
  public void contextDestroyed(ServletContextEvent sce) {
    bootstrap.shutdown();
    try {
      Reflections.classForName("org.apache.AnnotationProcessor");
      WeldForwardingAnnotationProcessor.restoreAnnotationProcessor(sce);
    } catch (IllegalArgumentException ignore) {}
    try {
      Reflections.classForName(JETTY_REQUIRED_CLASS_NAME);
      sce.getServletContext().removeAttribute(INJECTOR_ATTRIBUTE_NAME);
    } catch (IllegalArgumentException ignore) {}
    super.contextDestroyed(sce);
  }

  @Override
  public void contextInitialized(ServletContextEvent sce) {
    // Make Javassist always use the TCCL to load classes
    ProxyFactory.classLoaderProvider = new ClassLoaderProvider() {

      @Override
      public ClassLoader get(ProxyFactory pf) {
        return Thread.currentThread().getContextClassLoader();
      }

    };

    ServletDeployment deployment = new ServletDeployment(sce.getServletContext(), bootstrap);
    try {
      deployment.getWebAppBeanDeploymentArchive().getServices()
          .add(ResourceInjectionServices.class, new ServletResourceInjectionServices() {});
    } catch (NoClassDefFoundError e) {
      // Support GAE
      log.warn("@Resource injection not available in simple beans");
    }

    bootstrap.startContainer(Environments.SERVLET, deployment).startInitialization();
    manager = bootstrap.getManager(deployment.getWebAppBeanDeploymentArchive());

    boolean tomcat = true;
    boolean tomcat7 = false;
    try {
      Reflections.classForName("org.apache.AnnotationProcessor");
    } catch (IllegalArgumentException e) {
      tomcat = false;
    }

    if (tomcat) {
      try {
        WeldForwardingAnnotationProcessor.replaceAnnotationProcessor(sce, manager);
        log.info("Tomcat 6 detected, CDI injection will be available in Servlets and Filters. Injection into Listeners is not supported");
      } catch (Exception e) {
        log.error(
            "Unable to replace Tomcat AnnotationProcessor. CDI injection will not be available in Servlets, Filters, or Listeners",
            e);
      }
    }
    try {
      Reflections.classForName("org.apache.tomcat.InstanceManager");
      tomcat7 = true;
    } catch (IllegalArgumentException e) {
      tomcat7 = false;
    }

    boolean jetty = true;
    try {
      Reflections.classForName(JETTY_REQUIRED_CLASS_NAME);
    } catch (IllegalArgumentException e) {
      jetty = false;
    }

    if (jetty) {
      // Try pushing a Jetty AbstractInjector into the servlet context
      try {
        Class<?> clazz = Reflections.classForName(JettyWeldInjector.class.getName());
        Object injector = clazz.getConstructor(WeldManager.class).newInstance(manager);
        sce.getServletContext().setAttribute(INJECTOR_ATTRIBUTE_NAME, injector);
        log.info("Jetty detected, JSR-299 injection will be available in Servlets and Filters. Injection into Listeners is not supported.");
      } catch (Exception e) {
        log.error(
            "Unable to create JettyWeldInjector. CDI injection will not be available in Servlets, Filters or Listeners",
            e);
      }
    }
    if (tomcat7) {
      try {
        WeldForwardingInstanceManager.replacInstanceManager(sce, manager);
        log.info("Tomcat 7 detected, CDI injection will be available in Servlets and Filters. Injection into Listeners is not supported");
      } catch (Exception e) {
        log.error(
            "Unable to replace Tomcat 7 AnnotationProcessor. CDI injection will not be available in Servlets, Filters, or Listeners",
            e);
      }
    }
    if (!tomcat && !jetty && !tomcat7) {
      log.info("No supported servlet container detected, CDI injection will NOT be available in Servlets, Filtersor or Listeners");
    }

    // Push the manager into the servlet context so we can access in JSF
    sce.getServletContext().setAttribute(BEAN_MANAGER_ATTRIBUTE_NAME, manager);

    bootstrap.deployBeans().endInitialization();
    super.contextInitialized(sce);
  }

  @Override
  protected ServletListener delegate() {
    return weldListener;
  }

  private void attachErraiBus() {

  }

}
