/*
 * JBoss, Home of Professional Open Source
 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
 * by the @authors tag. See the copyright.txt 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.jboss.test.managed.factory.support.beans;

import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.jboss.beans.info.spi.BeanInfo;
import org.jboss.beans.info.spi.PropertyInfo;
import org.jboss.logging.Logger;
import org.jboss.managed.api.ManagedProperty;
import org.jboss.managed.api.annotation.ManagementObject;
import org.jboss.managed.api.annotation.ManagementObjectClass;
import org.jboss.managed.api.annotation.ManagementProperty;
import org.jboss.managed.api.annotation.ViewUse;
import org.jboss.managed.spi.factory.InstanceClassFactory;
import org.jboss.metadata.spi.MetaData;
import org.jboss.metatype.api.values.MetaValue;
import org.jboss.metatype.api.values.MetaValueFactory;

/**
 * An InstanceClassFactory for BeanMetaData
 * 
 * @author Scott.Stark@jboss.org
 * @version $Revision:$
 */
public class BeanMetaDataICF
   implements InstanceClassFactory<IBeanMetaData>
{
   private static final Logger log = Logger.getLogger(BeanMetaDataICF.class);
   /** The meta value factory */
   private MetaValueFactory metaValueFactory = MetaValueFactory.getInstance(); 
   private static HashMap<String, Object> beanMap = new HashMap<String, Object>();
   private InstanceClassFactory delegateICF;

   public MetaValueFactory getMetaValueFactory()
   {
      return metaValueFactory;
   }
   public void setMetaValueFactory(MetaValueFactory metaValueFactory)
   {
      this.metaValueFactory = metaValueFactory;
   }

   public static Object getBean(String name)
   {
      return beanMap.get(name);
   }
   public static void setBean(String name, Object bean)
   {
      beanMap.put(name, bean);
   }

   
   public InstanceClassFactory getDelegateICF()
   {
      return delegateICF;
   }
   public void setDelegateICF(InstanceClassFactory delegateICF)
   {
      this.delegateICF = delegateICF;
   }

   public Object locateBean(String beanName)
   {
      return beanMap.get(beanName);
   }

   
   public Class<IBeanMetaData> getType()
   {
      return IBeanMetaData.class;
   }

   public Object getComponentName(BeanInfo beanInfo, ManagedProperty property,
         IBeanMetaData attachment, MetaValue value)
   {
      return attachment.getName();
   }

   public Class<?> getManagedObjectClass(IBeanMetaData attachment)
      throws ClassNotFoundException
   {
      Class<?> mocClass = null;

      // Look for a ManagementObjectClass annotation
      Set<IAnnotationMetaData> annotations = attachment.getAnnotations();
      if(annotations != null)
      {
         for(IAnnotationMetaData amd : annotations)
         {
            Annotation ann = amd.getAnnotationInstance();
            if(ann instanceof ManagementObjectClass)
            {
               ManagementObjectClass moc = (ManagementObjectClass) ann;
               mocClass = moc.code();
            }
         }
      }
      // Use the bean from the metadata
      if(mocClass == null)
      {
         String beanClassName = attachment.getBean();
         if(beanClassName != null && beanClassName.length() > 0)
         {
            // TODO: TCL may not be correct
            ClassLoader loader = getClassLoader(attachment);
            mocClass = loader.loadClass(beanClassName);
            // Make sure it has an ManagementObject annotation
            ManagementObject moAnn = mocClass.getAnnotation(ManagementObject.class);
            if(moAnn == null)
            {
               // Revert back to the BeanMetaData class
               mocClass = attachment.getClass();
            }
            log.debug("Using bean class:, "+mocClass+" for bean: "+attachment);
         }
      }
      return mocClass;
   }

   public MetaValue getValue(BeanInfo beanInfo, ManagedProperty property,
         MetaData metaData, IBeanMetaData attachment)
   {
      // Get the property from the bean
      // First look to the mapped name
      String name = property.getMappedName();
      if (name == null)
         property.getName();
      PropertyInfo propertyInfo = beanInfo.getProperty(name);
      Object bean = locateBean(attachment.getName());
      Object value = null;
      MetaValue mvalue = null;
      if(propertyInfo.isReadable() == false)
      {
         if(log.isTraceEnabled())
            log.trace("Skipping get of non-readable property: "+propertyInfo);
         return null;
      }

      try
      {
         String getterClassName = propertyInfo.getGetter().getDeclaringClass().getName();
         if(getterClassName.equals(attachment.getClass().getName()))
            mvalue = delegateICF.getValue(beanInfo, property, metaData, attachment);
         else
            mvalue = delegateICF.getValue(beanInfo, property, metaData, bean);
      }
      catch(Throwable e)
      {
         log.debug("Failed to get property value for bean: "+beanInfo.getName()
               +", property: "+propertyInfo.getName(), e);
         mvalue = metaValueFactory.create(null, propertyInfo.getType());
         return mvalue;
      }

      return mvalue;
   }

   public void setValue(BeanInfo beanInfo, ManagedProperty property,
         IBeanMetaData attachment, MetaValue value)
   {
      ClassLoader prevLoader = SecurityActions.getContextClassLoader();
      String beanName = attachment.getName();
      // First look to the mapped name
      String name = property.getMappedName();
      if (name == null)
         property.getName();

      // See if this is a RUNTIME property
      Map<String, Annotation> annotations = property.getAnnotations();
      boolean isRuntime = false;
      if(annotations != null)
      {
         ManagementProperty annotation = (ManagementProperty) annotations.get(ManagementProperty.class);
         if(annotation != null)
         {
            ViewUse[] uses = annotation.use();
            for(ViewUse use : uses)
            {
               if(use == ViewUse.RUNTIME)
                  isRuntime = true;
            }
         }
      }

      try
      {
         ClassLoader loader = getClassLoader(attachment);
         // Set the mbean class loader as the TCL
         SecurityActions.setContextClassLoader(loader);

         PropertyInfo propertyInfo = beanInfo.getProperty(name);
         if(propertyInfo == null)
            throw new IllegalArgumentException("No matching property found: " + name + "/" + beanName);

         Object plainValue = metaValueFactory.unwrap(value, propertyInfo.getType());
         Object bean = locateBean(beanName);
         propertyInfo.set(bean, plainValue);
      }
      catch(Throwable e)
      {
         throw new IllegalStateException("Failed to set property value: "+name + "/" + beanName, e);
      }
      finally
      {
         SecurityActions.setContextClassLoader(prevLoader);
      }
   }

   protected ClassLoader getClassLoader(IBeanMetaData deployment)
   {
      ClassLoader loader = null;
      // TODO...
      // Fallback to TCL if there is no
      if(loader == null)
         loader = SecurityActions.getContextClassLoader();
      return loader;
   }
}
