/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2009, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.exoplatform.container.mc.impl;

import org.jboss.beans.info.spi.BeanAccessMode;
import org.jboss.beans.metadata.plugins.AbstractBeanMetaData;
import org.jboss.beans.metadata.spi.builder.BeanMetaDataBuilder;
import org.jboss.dependency.plugins.helpers.StatelessController;
import org.jboss.dependency.spi.ControllerState;
import org.jboss.kernel.plugins.dependency.AbstractKernelControllerContext;
import org.jboss.kernel.spi.dependency.KernelController;
import org.jboss.kernel.spi.dependency.KernelControllerContext;
import org.picocontainer.ComponentAdapter;
import org.picocontainer.PicoContainer;
import org.picocontainer.PicoInitializationException;
import org.picocontainer.PicoIntrospectionException;
import org.picocontainer.PicoVisitor;

/**
 * A wrapping adapter, that takes care of mc integration at component instantiation time.
 *
 * @author <a href="mailto:ajustin@redhat.com">Ales Justin</a>
 * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
 */
public class MCComponentAdapter implements ComponentAdapter
{
   /**
    * Kernel controller retrieved from associated mc kernel.
    */
   private KernelController controller;

   /**
    * Original component adapter.
    */
   private ComponentAdapter delegate;

   /**
    * Interception marking annotation, configured for the component.
    */
   private InterceptMC interceptMC;

   /**
    * A component instance handled by this component adapter.
    */
   private Object lastComponentInstance;

   /**
    * BeanMetaData parsed from mc-int-config.xml
    */
   private AbstractBeanMetaData metaData;

   /**
    * The only constructor.
    *
    * @param controller Kernel controller to use
    * @param delegate original component adapter
    * @param interceptMC mc-integration configuration in the form of annotation
    */
   public MCComponentAdapter(KernelController controller, ComponentAdapter delegate, 
                             InterceptMC interceptMC, AbstractBeanMetaData data)
   {
      if (controller == null)
      {
         throw new IllegalArgumentException("Null controller");
      }
      if (delegate == null)
      {
         throw new IllegalArgumentException("Null delegate");
      }

      if (controller instanceof StatelessController)
      {
         throw new IllegalArgumentException("controller is instanceof StatelessController");
      }

      this.controller = controller;
      this.delegate = delegate;
      this.interceptMC = interceptMC;
      this.metaData = data;
   }

   /**
    * Getter method for component key.
    * 
    * @return String or Class representing a key
    */
   public Object getComponentKey()
   {
      return delegate.getComponentKey();
   }

   /**
    * Getter for component implementation class.
    *
    * @return component implementation class
    */
   public Class getComponentImplementation()
   {
      return delegate.getComponentImplementation();
   }

   /**
    * This is where mc integration happens.
    * Instantiation is first delegated to original component adapter, the resulting
    * instance is then sent through kernel controller to be injected by mc kernel.
    *
    * @param container pico container that holds this component adapter
    * @return object with injections already performed on it
    * @throws PicoInitializationException
    * @throws PicoIntrospectionException
    */
   public Object getComponentInstance(PicoContainer container) throws PicoInitializationException, PicoIntrospectionException
   {
      Object target = lastComponentInstance;
      if (target != null)
      {
         return target;
      }

      String key = delegate.getComponentKey().toString();
      Object instance = delegate.getComponentInstance(container);
      try
      {
         if (metaData == null)
         {
            metaData = new AbstractBeanMetaData();
         }
         metaData.setName(key);
         metaData.setBean(instance.getClass().getName());
         BeanMetaDataBuilder builder = BeanMetaDataBuilder.createBuilder(metaData);
         builder.setConstructorValue(instance);
         builder.ignoreCreate();
         builder.ignoreStart();
         builder.ignoreStop();
         builder.ignoreDestroy();
         builder.setAccessMode(getInjectionMode(interceptMC));
         KernelControllerContext ctx = new AbstractKernelControllerContext(null, builder.getBeanMetaData(), null);

         StatelessController ctrl = new StatelessController(controller);
         ctrl.install(ctx);
         if (ctx.getError() != null)
         {
            throw ctx.getError();
         }
         if (ctrl.getStates().isBeforeState(ctx.getState(), ControllerState.INSTALLED))
         {
            throw new IllegalArgumentException("Missing some dependency: " + ctx.getDependencyInfo().getUnresolvedDependencies(null));
         }

         target = ctx.getTarget();
         lastComponentInstance = target;
         return target;
      }
      catch (Throwable ex)
      {
         throw new RuntimeException("Failed to perform MC interception on component: " + delegate.getComponentImplementation(), ex);
      }
   }

   /**
    * Method to determine injection mode to be used by mc kernel.
    *
    * @param interceptMC configuration in form of InterceptMC annotation
    * @return jboss-beans-info BeanAccessMode
    */
   private BeanAccessMode getInjectionMode(InterceptMC interceptMC)
   {
      MCInjectionMode mode = interceptMC.injectionMode();

      switch (mode)
      {
         case ALL:
            return BeanAccessMode.ALL;
         case FIELDS:
            return BeanAccessMode.FIELDS;
         default:
            return BeanAccessMode.STANDARD;
      }
   }

   /**
    * Delegation-only method.
    *
    * @param container
    * @throws PicoIntrospectionException
    */
   public void verify(PicoContainer container) throws PicoIntrospectionException
   {
      delegate.verify(container);
   }

   /**
    * Delegation-only method.
    *
    * @param visitor
    */
   public void accept(PicoVisitor visitor)
   {
      delegate.accept(visitor);
   }
}