/*
 * 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.deployers.plugins.managed;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jboss.beans.metadata.spi.BeanMetaData;
import org.jboss.beans.metadata.spi.BeanMetaDataFactory;
import org.jboss.deployers.spi.DeploymentException;
import org.jboss.deployers.spi.deployer.managed.ManagedObjectCreator;
import org.jboss.deployers.structure.spi.DeploymentUnit;
import org.jboss.kernel.plugins.deployment.AbstractKernelDeployment;
import org.jboss.kernel.spi.deployment.KernelDeployment;
import org.jboss.logging.Logger;
import org.jboss.managed.api.Fields;
import org.jboss.managed.api.ManagedObject;
import org.jboss.managed.api.ManagedProperty;
import org.jboss.managed.api.MutableManagedObject;
import org.jboss.managed.api.factory.ManagedObjectFactory;
import org.jboss.managed.plugins.DefaultFieldsImpl;
import org.jboss.managed.plugins.ManagedObjectImpl;
import org.jboss.managed.plugins.ManagedPropertyImpl;
import org.jboss.managed.plugins.factory.AbstractManagedObjectFactory;
import org.jboss.metadata.spi.MetaData;
import org.jboss.metatype.api.types.CollectionMetaType;
import org.jboss.metatype.api.types.MetaType;
import org.jboss.metatype.api.values.CollectionValueSupport;
import org.jboss.metatype.api.values.GenericValue;
import org.jboss.metatype.api.values.GenericValueSupport;
import org.jboss.profileservice.spi.types.ControllerStateMetaType;

/**
 * ManagedObjectCreator for KernelDeployment
 * 
 * @author Scott.Stark@jboss.org
 * @author Ales.Justin@jboss.org
 * @version $Revision: 89429 $
 */
public class KernelDeploymentManagedObjectCreator implements ManagedObjectCreator
{
   private static Logger log = Logger.getLogger(KernelDeploymentManagedObjectCreator.class);
   private ManagedObjectFactory mof;

   public KernelDeploymentManagedObjectCreator(ManagedObjectFactory mof)
   {
      if (mof == null)
         throw new IllegalArgumentException("Null ManagedObjectFactory.");
      this.mof = mof;
   }

   /**
    * Called by the KernelDeploymentDeployer to update the ManagedObjects map
    * created by the default ManagedObjectCreator. 
    *
    * @param unit the deployment unit
    * @param managedObjects map of managed objects
    * @throws DeploymentException for any deployment exception
    */
   public void build(DeploymentUnit unit, Set<String> attachments, Map<String, ManagedObject> managedObjects) throws DeploymentException
   {
      KernelDeployment deployment = unit.getAttachment(KernelDeployment.class);
      if(deployment == null)
         return;

      List<BeanMetaDataFactory> beanFactories = deployment.getBeanFactories();

      // Get the KernelDeployment ManagedObject
      ManagedObject deploymentMO = managedObjects.get(KernelDeployment.class.getName());
      if(deploymentMO == null)
      {
         // Check for the KernelDeployment impl name
         deploymentMO = managedObjects.get(AbstractKernelDeployment.class.getName());
         if(deploymentMO == null)
         {
            // Should not happen?
            deploymentMO = mof.createManagedObject(deployment.getClass());
         }
         else
         {
            // Put the MO under the attachment interface name
            managedObjects.remove(AbstractKernelDeployment.class.getName());
         }
         managedObjects.put(KernelDeployment.class.getName(), deploymentMO);
      }
      if(deploymentMO instanceof MutableManagedObject)
         ((MutableManagedObject) deploymentMO).setName(KernelDeployment.class.getName());
      if(deploymentMO instanceof ManagedObjectImpl)
         ((ManagedObjectImpl) deploymentMO).setAttachmentName(KernelDeployment.class.getName());

      MetaData metaData = unit.getMetaData();
      // Update the beanFactories value to a list of BeanMetaDataFactory with BeanMetaDatas
      ManagedProperty beanFactoriesMP = deploymentMO.getProperty("beanFactories");
      List<GenericValue> tmpBFs = new ArrayList<GenericValue>();
      CollectionMetaType beansFactoryType = new CollectionMetaType(BeanMetaDataFactory.class.getName(), AbstractManagedObjectFactory.MANAGED_OBJECT_META_TYPE);
      if(beanFactories != null)
      {
         List<GenericValue> tmpBeans = new ArrayList<GenericValue>();
         CollectionMetaType beansType = new CollectionMetaType(BeanMetaDataFactory.class.getName(), AbstractManagedObjectFactory.MANAGED_OBJECT_META_TYPE);
         for(BeanMetaDataFactory bmdf : beanFactories)
         {
            
            ManagedObject bmdfMO = mof.initManagedObject(bmdf, metaData);
            if(bmdfMO == null)
            {
               // Create a container managed object
               bmdfMO = createFactoryManagedObject(bmdf, deploymentMO, metaData);
            }

            if((bmdfMO instanceof MutableManagedObject) == false)
            {
               // Just go with the default ManagedObject
               GenericValueSupport gv = new GenericValueSupport(AbstractManagedObjectFactory.MANAGED_OBJECT_META_TYPE, bmdfMO);
               tmpBFs.add(gv);
               continue;
            }

            MutableManagedObject bmdfMMO = (MutableManagedObject) bmdfMO;
            bmdfMMO.setParent(deploymentMO);
            Map<String, ManagedProperty> oldProps = bmdfMMO.getProperties();
            ManagedProperty beansMPCheck = oldProps.get("beans");
            // If there already is a beans property assume it's correct
            if(beansMPCheck != null)
            {
               // Need to map 
               continue;
            }

            Map<String, ManagedProperty> newProps = new HashMap<String, ManagedProperty>(oldProps);
            // Create a beans ManagedProperty, a list of BeanMetaData ManagedObjects
            Fields fields = getFields("beans", beansType);
            ManagedPropertyImpl beansMP = new ManagedPropertyImpl(bmdfMO, fields);
            newProps.put("beans", beansMP);

            // Create a ManagedObject for each of the beans BeanMetaData
            List<BeanMetaData> beans = bmdf.getBeans();
            if(beans != null)
            {
               for(BeanMetaData bmd : beans)
               {
                  DeploymentUnit compUnit = unit.getComponent(bmd.getName());
                  if(compUnit == null)
                  {
                     log.debug("Failed to find component for bean: "+bmd.getName());
                     continue;
                  }
                  MetaData compMetaData = compUnit.getMetaData();
                  GenericValue gv = getManagedObjectValue(bmd, compMetaData, bmdfMO);
                  if(gv != null)
                  {
                     // The component managed objects need to be in the root map
                     ManagedObject compMO = (ManagedObject) gv.getValue();
                     // Use the ManagedObject name if it's not the same as the attachmentName
                     String managedObjectName = compUnit.getName();
                     if(compMO != null && compMO.getAttachmentName() != null)
                     {
                        managedObjectName = compMO.getAttachmentName().equals(compMO.getName()) ?
                              compUnit.getName() : compMO.getName();
                     }
                     // Add the managed object 
                     managedObjects.put(managedObjectName, compMO);
                     // Add the bean MO to the beans list
                     tmpBeans.add(gv);
                  }
               }
            }
            GenericValue[] beanMOs = new GenericValue[tmpBeans.size()];
            tmpBeans.toArray(beanMOs);
            CollectionValueSupport values = new CollectionValueSupport(beansType, beanMOs);
            beansMP.setValue(values);
            // Update the bean factory properties
            bmdfMMO.setProperties(newProps);
         }
      }
      GenericValue[] mos = new GenericValue[tmpBFs.size()];
      tmpBFs.toArray(mos);
      CollectionValueSupport values = new CollectionValueSupport(beansFactoryType, mos);
      // This bypasses the write through back to the metadata
      beanFactoriesMP.getFields().setField(Fields.VALUE, values);
   }

   protected ManagedObject createFactoryManagedObject(BeanMetaDataFactory bmdf,
         ManagedObject parent, MetaData metaData)
   {
      ManagedObjectImpl bmdfMO = new ManagedObjectImpl(bmdf.getClass().getName());
      Map<String, ManagedProperty> newProps = new HashMap<String, ManagedProperty>();
      bmdfMO.setParent(parent);
      bmdfMO.setProperties(newProps);
      return bmdfMO;
   }

   /**
    * 
    * @param bmd
    * @param metaData
    * @param parentMO
    * @return
    */
   protected GenericValue getManagedObjectValue(BeanMetaData bmd, MetaData metaData, ManagedObject parentMO)
   {
      String name = bmd.getName();
      ManagedObject mo = mof.initManagedObject(bmd, null, metaData, name, null);
      if(parentMO != null && mo instanceof MutableManagedObject)
      {
         MutableManagedObject mmo = (MutableManagedObject) mo;
         mmo.setParent(parentMO);
         Map<String, ManagedProperty> oldProps = mmo.getProperties();
         Map<String, ManagedProperty> newProps = new HashMap<String, ManagedProperty>(oldProps);
         // Add a state property
         Fields stateFields = getFields("state", ControllerStateMetaType.TYPE);
         ManagedPropertyImpl stateMP = new ManagedPropertyImpl(mmo, stateFields);
         newProps.put("state", stateMP);
         mmo.setProperties(newProps);
      }
      return new GenericValueSupport(AbstractManagedObjectFactory.MANAGED_OBJECT_META_TYPE, mo);
   }

   /**
    * Create a DefaultFieldsImpl for the given property name and type
    * @param name - the property name
    * @param type - the property type
    * @return return the fields implementation
    */
   protected Fields getFields(String name, MetaType type)
   {
      DefaultFieldsImpl fields = new DefaultFieldsImpl();
      fields.setMetaType(type);
      fields.setName(name);
      fields.setField(Fields.MAPPED_NAME, name);
      fields.setMandatory(false);
      fields.setDescription("The bean controller state");

      return fields;
   }
}
