/*
 * JBoss, Home of Professional Open Source
 * Copyright 2006, 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.deployers.managed.support;

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

import org.jboss.deployers.client.spi.DeployerClient;
import org.jboss.deployers.client.spi.Deployment;
import org.jboss.deployers.spi.DeploymentException;
import org.jboss.logging.Logger;
import org.jboss.managed.api.ComponentType;
import org.jboss.managed.api.ManagedDeployment;
import org.jboss.managed.api.ManagedObject;
import org.jboss.managed.api.ManagedOperation;
import org.jboss.managed.api.ManagedProperty;
import org.jboss.managed.api.annotation.ManagementComponent;
import org.jboss.managed.api.annotation.ManagementObject;
import org.jboss.managed.api.annotation.ManagementObjectID;
import org.jboss.managed.api.annotation.ManagementObjectRef;
import org.jboss.managed.plugins.ManagedComponentImpl;
import org.jboss.managed.plugins.ManagedObjectImpl;
import org.jboss.managed.plugins.factory.AbstractManagedObjectFactory;
import org.jboss.metatype.api.types.ArrayMetaType;
import org.jboss.metatype.api.types.MetaType;
import org.jboss.metatype.api.types.CollectionMetaType;
import org.jboss.metatype.api.values.ArrayValue;
import org.jboss.metatype.api.values.GenericValue;
import org.jboss.metatype.api.values.SimpleValue;
import org.jboss.metatype.api.values.MetaValue;
import org.jboss.metatype.api.values.CollectionValue;

/**
 * Mock profile service for testing implementation details.
 * 
 * @author Scott.Stark@jboss.org
 * @version $Revision: 70207 $
 */
public class MockProfileService
{
   private static final Logger log = Logger.getLogger(MockProfileService.class);

   private DeployerClient main;
   /** id/type key to ManagedObject map */
   private Map<String, ManagedObject> moRegistry = new HashMap<String, ManagedObject>();
   /** The deployment map */
   private Map<String, Deployment> deployments = new HashMap<String, Deployment>();
   /** The deployment name to ManagedDeployment map */
   private Map<String, ManagedDeployment> managedDeployments = new HashMap<String, ManagedDeployment>();
   /** The ManagedPropertys with unresolved ManagementObjectRefs */
   private Map<String, Set<ManagedProperty>> unresolvedRefs = new HashMap<String, Set<ManagedProperty>>();
   /** A map of runtime ManagedObjects needing to be merged with their
    * mathing ManagedObject.
    */
   private Map<String, ManagedObject> runtimeMOs = new HashMap<String, ManagedObject>();

   public MockProfileService(DeployerClient main)
   {
      this.main = main;
   }

   public void addDeployment(Deployment ctx)
      throws DeploymentException
   {
      main.addDeployment(ctx);
      deployments.put(ctx.getName(), ctx);
   }
   public void process()
      throws DeploymentException
   {
      main.process();
      for(String name : deployments.keySet())
      {
         ManagedDeployment md = main.getManagedDeployment(name);
         log.info(name+" ManagedDeployment: " + md);
         Map<String, ManagedObject> mos = md.getManagedObjects();
         log.info(name+" ManagedObjects: " + mos);
         for(ManagedObject mo : mos.values())
         {
            processManagedObject(mo, md);
         }
         managedDeployments.put(name, md);
      }
   }

   public ManagedObject getManagedObject(String name)
   {
      ManagedObject mo = moRegistry.get(name);
      return mo;
   }
   public ManagedDeployment getManagedDeployment(String name)
   {
      ManagedDeployment md = managedDeployments.get(name);
      return md;
   }

   /**
    * 
    * @param mo
    * @param md - 
    */
   @SuppressWarnings("unchecked")
   protected void processManagedObject(ManagedObject mo, ManagedDeployment md)
   {
      String key = mo.getName() + "/" + mo.getNameType();
      log.debug("ID for ManagedObject: "+key+", attachmentName: "+mo.getAttachmentName());

      // See if this is a runtime ManagedObject
      Map<String, Annotation> moAnns = mo.getAnnotations();
      ManagementObject managementObject = (ManagementObject) moAnns.get(ManagementObject.class.getName());
      if (managementObject.isRuntime())
      {
         // Merge this with the ManagedObject
         ManagedObject parentMO = moRegistry.get(key);
         if (parentMO == null)
         {
            // Save the runtime mo for merging
            runtimeMOs.put(key, mo);
            return;
         }
         mergeRuntimeMO(parentMO, mo);
      }
      else
      {
         // See if there is runtime info to merge
         ManagedObject runtimeMO = runtimeMOs.get(key);
         if (runtimeMO != null)
            mergeRuntimeMO(mo, runtimeMO);
      }

      // Update the MO registry
      ManagedObject prevMO = moRegistry.put(key, mo);
      if( prevMO != null )
         log.warn("Duplicate mo for key: "+key+", prevMO: "+prevMO);
      // Check for unresolved refs
      checkForReferences(key, mo);

      // Create ManagedComponents for 
      ManagementComponent mc = (ManagementComponent) moAnns.get(ManagementComponent.class.getName());
      if (mc != null)
      {
         ComponentType type = new ComponentType(mc.type(), mc.subtype());
         ManagedComponentImpl comp = new ManagedComponentImpl(type, md, mo);
         md.addComponent(mo.getName(), comp);
      }

      // Scan for @ManagementObjectRef
      for(ManagedProperty prop : mo.getProperties().values())
      {
         log.debug("Checking property: "+prop);
         // See if this is a ManagementObjectID
         ManagementObjectID id = (ManagementObjectID) prop.getAnnotations().get(ManagementObjectID.class.getName());
         if (id != null)
         {
            Object refName = getRefName(prop.getValue());
            if (refName == null)
               refName = id.name();
            String propKey = refName + "/" + id.type();
            log.debug("ManagedProperty level ID for ManagedObject: "+propKey+", attachmentName: "+mo.getAttachmentName());
            moRegistry.put(propKey, mo);
            checkForReferences(propKey, mo);
         }

         // See if this is a ManagementObjectRef
         ManagementObjectRef ref = (ManagementObjectRef) prop.getAnnotations().get(ManagementObjectRef.class.getName());
         if ( ref != null )
         {
            // The reference key is the prop value + ref.type()
            log.debug("Property("+prop.getName()+") references: "+ref);
            Object refName = getRefName(prop.getValue());
            if (refName == null)
               refName = ref.name();
            String targetKey = refName + "/" + ref.type();
            ManagedObject target = moRegistry.get(targetKey);
            if (target != null)
            {
               log.debug("Resolved property("+prop.getName()+") reference to: "+targetKey);
               prop.setTargetManagedObject(target);
            }
            else
            {
               Set<ManagedProperty> referers =  unresolvedRefs.get(targetKey);
               if (referers == null)
               {
                  referers = new HashSet<ManagedProperty>();
                  unresolvedRefs.put(targetKey, referers);
               }
               referers.add(prop);
            }
         }

         MetaType propType = prop.getMetaType();
         if (propType == AbstractManagedObjectFactory.MANAGED_OBJECT_META_TYPE)
         {
            GenericValue gv = (GenericValue) prop.getValue();
            ManagedObject propMO = (ManagedObject) gv.getValue();
            processManagedObject(propMO, md);
         }
         else if (propType.isArray())
         {
            ArrayMetaType amt = (ArrayMetaType) propType;
            MetaType etype = amt.getElementType();
            if (etype == AbstractManagedObjectFactory.MANAGED_OBJECT_META_TYPE)
            {
               ArrayValue avalue = (ArrayValue) prop.getValue();
               for(int n = 0; n < avalue.getLength(); n ++)
               {
                  GenericValue gv = (GenericValue) avalue.getValue(n);
                  ManagedObject propMO = (ManagedObject) gv.getValue();
                  processManagedObject(propMO, md);
               }
            }
         }
         else if (propType.isCollection())
         {
            CollectionMetaType amt = (CollectionMetaType) propType;
            MetaType etype = amt.getElementType();
            if (etype == AbstractManagedObjectFactory.MANAGED_OBJECT_META_TYPE)
            {
               CollectionValue avalue = (CollectionValue) prop.getValue();
               MetaValue[] elements = avalue.getElements();
               for(int n = 0; n < avalue.getSize(); n ++)
               {
                  GenericValue gv = (GenericValue) elements[n];
                  ManagedObject propMO = (ManagedObject) gv.getValue();
                  processManagedObject(propMO, md);
               }
            }
         }
      }
   }

   @SuppressWarnings("unchecked")
   protected Object getRefName(Object value)
   {
      if (value instanceof MetaValue)
      {
         MetaValue metaValue = (MetaValue)value;
         if (metaValue.getMetaType().isSimple() == false)
            throw new IllegalArgumentException("Can only get ref from simple value: " + value);
         return ((SimpleValue)metaValue).getValue();
      }
      return value;
   }

   public Map<String, Set<ManagedProperty>> getUnresolvedRefs()
   {
      return unresolvedRefs;
   }

   protected void checkForReferences(String key, ManagedObject mo)
   {
      Set<ManagedProperty> referers =  unresolvedRefs.get(key);
      log.debug("checkForReferences, "+key+" has referers: "+referers);
      if (referers != null)
      {
         for(ManagedProperty prop : referers)
         {
            prop.setTargetManagedObject(mo);
         }
         unresolvedRefs.remove(key);
      }      
   }

   /**
    * Merge the runtime props and ops
    * TODO: need a plugin to access the ManagedObject impl
    * @param mo
    * @param runtimeMO
    */
   protected void mergeRuntimeMO(ManagedObject mo, ManagedObject runtimeMO)
   {
      Map<String, ManagedProperty> moProps = mo.getProperties();
      Set<ManagedOperation> moOps = mo.getOperations();
      HashMap<String, ManagedProperty> props = new HashMap<String, ManagedProperty>(moProps);
      HashSet<ManagedOperation> ops = new HashSet<ManagedOperation>(moOps);

      Map<String, ManagedProperty> runtimeProps = runtimeMO.getProperties();
      Set<ManagedOperation> runtimeOps = runtimeMO.getOperations();

      if (runtimeProps != null)
         props.putAll(runtimeProps);
      if (runtimeOps != null)
         ops.addAll(runtimeOps);

      ManagedObjectImpl moi = (ManagedObjectImpl) mo;
      moi.setProperties(props);
      moi.setOperations(ops);
   }
}
