/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2008, 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.jboss.system.server.profileservice;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import org.jboss.beans.info.spi.BeanInfo;
import org.jboss.beans.metadata.spi.BeanMetaData;
import org.jboss.bootstrap.api.as.config.JBossASBasedServerConfig;
import org.jboss.bootstrap.api.as.config.JBossASServerConfig;
import org.jboss.bootstrap.api.server.Server;
import org.jboss.bootstrap.spi.as.JBossASBootstrap;
import org.jboss.bootstrap.spi.as.server.JBossASBasedServerProvider;
import org.jboss.bootstrap.spi.as.server.JBossASServerProvider;
import org.jboss.dependency.spi.ControllerContext;
import org.jboss.dependency.spi.ControllerState;
import org.jboss.deployers.client.spi.IncompleteDeploymentException;
import org.jboss.deployers.client.spi.main.MainDeployer;
import org.jboss.deployers.spi.DeploymentException;
import org.jboss.deployers.spi.deployer.managed.ManagedDeploymentCreator;
import org.jboss.deployers.spi.management.KnownComponentTypes;
import org.jboss.deployers.spi.management.ManagementView;
import org.jboss.deployers.structure.spi.DeploymentUnit;
import org.jboss.deployers.vfs.deployer.kernel.KernelDeploymentDeployer.KernelDeploymentVisitor;
import org.jboss.kernel.Kernel;
import org.jboss.kernel.plugins.registry.BeanKernelRegistryEntry;
import org.jboss.kernel.spi.config.KernelConfigurator;
import org.jboss.kernel.spi.dependency.KernelController;
import org.jboss.kernel.spi.deployment.KernelDeployment;
import org.jboss.kernel.spi.registry.KernelRegistryEntry;
import org.jboss.kernel.spi.registry.KernelRegistryPlugin;
import org.jboss.logging.Logger;
import org.jboss.managed.api.ComponentType;
import org.jboss.managed.api.Fields;
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.MutableManagedObject;
import org.jboss.managed.api.annotation.ManagementComponent;
import org.jboss.managed.api.annotation.ManagementObject;
import org.jboss.managed.api.annotation.ViewUse;
import org.jboss.managed.api.factory.ManagedObjectFactory;
import org.jboss.managed.plugins.DefaultFieldsImpl;
import org.jboss.managed.plugins.ManagedComponentImpl;
import org.jboss.managed.plugins.ManagedObjectImpl;
import org.jboss.managed.plugins.ManagedOperationImpl;
import org.jboss.managed.plugins.ManagedPropertyImpl;
import org.jboss.metatype.api.types.ArrayMetaType;
import org.jboss.metatype.api.types.MetaType;
import org.jboss.metatype.api.types.SimpleMetaType;
import org.jboss.metatype.api.values.ArrayValueSupport;
import org.jboss.metatype.api.values.EnumValue;
import org.jboss.metatype.api.values.EnumValueSupport;
import org.jboss.profileservice.spi.MutableProfile;
import org.jboss.profileservice.spi.NoSuchProfileException;
import org.jboss.profileservice.spi.Profile;
import org.jboss.profileservice.spi.ProfileKey;
import org.jboss.profileservice.spi.ProfileService;
import org.jboss.profileservice.spi.types.ControllerStateMetaType;
import org.jboss.system.server.profileservice.repository.AbstractBootstrapProfileFactory;

/**
 * Bootstraps the profile service
 * 
 * @author Scott.Stark@jboss.org
 * @author adrian@jboss.org
 * @author <a href="mailto:emuckenh@redhat.com">Emanuel Muckenhuber</a>
 * @version $Revision: 97479 $
 */
public class ProfileServiceBootstrap<K extends JBossASBasedServerProvider<K, T>, T extends JBossASBasedServerConfig<T>>
 implements JBossASBootstrap<K,T>, KernelRegistryPlugin
{
   /** The log */
   private static final Logger log = Logger.getLogger(ProfileServiceBootstrap.class);
   
   /** The root profile key. */
   protected ProfileKey profileKey;

   /** The loaded profiles. */
   private List<ProfileKey> bootstrapProfiles = new ArrayList<ProfileKey>();
   
   /** The server MainDeployer */
   protected MainDeployer mainDeployer;

   /** The server ProfileService */
   protected ProfileService profileService;

   /** The ManagedDeploymentCreator plugin */
   private ManagedDeploymentCreator mgtDeploymentCreator = null;
   /** The ManagedObjectFactory for building the bootstrap deployment ManagedObjects */
   private ManagedObjectFactory mof;
   /** The ManagedDeployment map for the MCServer KernelDeployments */
   private Map<String, ManagedDeployment> bootstrapMDs = new HashMap<String, ManagedDeployment>();
   
   /** The profile bootstrap factory */
   private AbstractBootstrapProfileFactory profileFactory; 

   /** Whether we are shutdown */
   private AtomicBoolean shutdown = new AtomicBoolean(false);
   /** */
   private Map<Object, KernelRegistryEntry> bootstrapEntries = new HashMap<Object, KernelRegistryEntry>();
   /** */
   private KernelConfigurator configurator;

   /**
    * Create a new ProfileServiceBootstrap.
    */
   public ProfileServiceBootstrap()
   {
   }

   /**
    * Return the MainDeployer bean.
    * 
    * @return the MainDeployer bean if bootstrap succeeded, null otherwise.
    */
   public MainDeployer getMainDeployer()
   {
      return mainDeployer;
   }
   public void setMainDeployer(MainDeployer mainDeployer)
   {
      this.mainDeployer = mainDeployer;
   }

   public KernelConfigurator getConfigurator()
   {
      return configurator;
   }
   public void setConfigurator(KernelConfigurator configurator)
   {
      this.configurator = configurator;
   }

   /**
    * Return the ProfileService bean.
    * 
    * @return the ProfileService bean if bootstrap succeeded, null otherwise
    */
   public ProfileService getProfileService()
   {
      return profileService;
   }
   public void setProfileService(ProfileService profileService)
   {
      this.profileService = profileService;
   }

   public ProfileKey getProfileKey()
   {
      return profileKey;
   }
   
   public void setProfileKey(ProfileKey profileKey)
   {
      this.profileKey = profileKey;
   }

   public ManagedObjectFactory getMof()
   {
      return mof;
   }
   public void setMof(ManagedObjectFactory mof)
   {
      this.mof = mof;
   }

   public ManagedDeploymentCreator getMgtDeploymentCreator()
   {
      return mgtDeploymentCreator;
   }
   public void setMgtDeploymentCreator(ManagedDeploymentCreator mgtDeploymentCreator)
   {
      this.mgtDeploymentCreator = mgtDeploymentCreator;
   }

   public AbstractBootstrapProfileFactory getBootstrapProfileFactory()
   {
      return profileFactory;
   }
   
   public void setBootstrapProfileFactory(AbstractBootstrapProfileFactory profileFactory)
   {
      this.profileFactory = profileFactory;
   }
   
   public Map<String, ManagedDeployment> getBootstrapMDs()
   {
      return bootstrapMDs;
   }
   public void setBootstrapMDs(Map<String, ManagedDeployment> bootstrapMDs)
   {
      this.bootstrapMDs = bootstrapMDs;
   }

   /**
    * 
    */
   public void start(K server)
      throws Exception
   {
      shutdown.set(false);

      if(profileService == null)
         throw new IllegalStateException("The ProfileService has not been injected"); 
      log.debug("Using ProfileService: " + profileService);
      if(mainDeployer == null)
         throw new IllegalStateException("The MainDeployer has not been injected"); 
      log.debug("Using MainDeployer: " + mainDeployer);

      // Validate that everything is ok
      mainDeployer.checkComplete();

      // Expose the bootstrap ManagedDeployments
      initBootstrapMDs(server);

      // Load the profiles
      if(this.profileKey == null)
         this.profileKey = new ProfileKey(server.getConfiguration().getServerName());
      
      // TODO check if there is a predetermined ProfileMetaData attachment
      // Map<String, Object> metaData = server.getMetaData();
      // ProfileMetaData pmd = (ProfileMetaData) metaData.get(ProfileMetaData.class.getName());
 
      // Register the profiles
      Collection<Profile> bootstrapProfiles = profileFactory.createProfiles(profileKey, null);
      for(Profile profile : bootstrapProfiles)
      {
         profileService.registerProfile(profile);
         // Add to loaded profiles
         if(this.profileKey.equals(profile.getKey()) == false)
            this.bootstrapProfiles.add(0, profile.getKey());
      }
      // Activate the root profile
      log.info("Loading profile: " + this.profileKey);
      this.profileService.activateProfile(this.profileKey);
      this.profileService.validateProfile(this.profileKey);
      try
      {
         // Check if everything is complete
         mainDeployer.checkComplete();
      }
      catch (IncompleteDeploymentException e)
      {
         log.error("Failed to load profile: " + e.getMessage());
      }
      catch (Exception e)
      {
         log.error("Failed to load profile: ", e);
      }
      // Enable modification checks on all mutable profiles 
      for(ProfileKey key : profileService.getActiveProfileKeys())
      {
         try
         {
            Profile profile = profileService.getActiveProfile(key);
            if(profile.isMutable() && profile instanceof MutableProfile)
            {
               ((MutableProfile) profile).enableModifiedDeploymentChecks(true);
            }
         }
         catch(NoSuchProfileException ignore) { }
      }
   }

   public void prepareShutdown(K server)
   {
      shutdown.set(true);
      // Disable modification checks on all mutable profiles
      for(ProfileKey key : profileService.getActiveProfileKeys())
      {
         try
         {
            Profile profile = profileService.getActiveProfile(key);
            if(profile.isMutable() && profile instanceof MutableProfile)
            {
               ((MutableProfile) profile).enableModifiedDeploymentChecks(false);
            }
         }
         catch(NoSuchProfileException ignore) { }
      }
      if (mainDeployer != null)
      {
         mainDeployer.prepareShutdown();
      }
   }

   public void shutdown(K server)
   {
      // Deactivate the root profile
      try
      {
         // Release 
    	 if(profileService.getActiveProfileKeys().contains(profileKey))
    	 {
    		 profileService.deactivateProfile(profileKey);
    	 }
      }
      catch(Throwable t)
      {
         log.warn("Error deactivating profile: " + this.profileKey, t);
      }
      try
      {
         // Unregister
    	 if(profileService.getProfileKeys().contains(profileKey))
    	 {
    		 profileService.unregisterProfile(profileKey);
    	 }
      }
      catch(Throwable t)
      {
         log.warn("Error unregistering profile: " + this.profileKey, t);         
      }
      
      // Deactivate all profiles we registered
      deactivateProfiles(this.bootstrapProfiles);
      
      // Deactivate all still active profiles
      deactivateProfiles(this.profileService.getActiveProfileKeys());
      
      // Unregister all profiles at once
      for(ProfileKey key : profileService.getProfileKeys())
      {
         try
         {
            profileService.unregisterProfile(key);
         }
         catch(Throwable t)
         {
            // Ignore
         }
      }
      
      try
      {
         mainDeployer.shutdown();
      }
      catch (Throwable t)
      {
         log.warn("Error shutting down the main deployer", t);
      }
   }
   
   public KernelRegistryEntry getEntry(Object name)
   {
      KernelRegistryEntry entry = bootstrapEntries.get(name);
      return entry;
   }

   protected void deactivateProfiles(Collection<ProfileKey> profiles)
   {
      if(profiles != null && profiles.isEmpty() == false)
      {
         for(ProfileKey key : profiles)
         {
            try
            {
               profileService.deactivateProfile(key);
            }
            catch(NoSuchProfileException e)
            {
               // ignore
            }
            catch(Throwable t)
            {
               log.warn("Error unloading profile: " + this.profileKey, t);
            }
         }
      }
   }

   /**
    * Create ManagedDeployments for the MCServer KernelDeployments. This allows
    * the bootstrap deployments outside of the profile service to be visible in
    * the ManagementView
    * @see {@link ManagementView}
    * 
    * @param server - the Bootstrap.start Server instance. This must be an
    * MCServer in order for there to be KernelDeployments available.
    */
   protected void initBootstrapMDs(K server)
   {
      if (mof == null || mgtDeploymentCreator == null)
      {
         log.warn("Skipping ManagedDeployment creation due to missing mof, mgtDeploymentCreator");
         return;
      }

      Map<String, KernelDeployment> serverDeployments = null;
      // Build ManagedDeployments for the KernelDeployments
      Kernel kernel = server.getKernel();
      serverDeployments = server.getDeployments();
      ManagedDeployment firstDeployment = null;
      for (KernelDeployment kd : serverDeployments.values())
      {
         BootstrapDeployment deploymentUnit = new BootstrapDeployment(kd);
         KernelDeploymentVisitor visitor = new KernelDeploymentVisitor();
         try
         {
            visitor.deploy(deploymentUnit, kd);
         }
         catch (DeploymentException e)
         {
            log.debug("Failed to build ManagedDeployment for: " + kd, e);
            continue;
         }

         /* Create minimal deployment ManagedObject. Don't use the ManagedObjectFactory
          * as this will create ManagedObjects for the beans via the beansFactory
          * property. We handle the beans below.
         */
         Set<ManagedProperty> kdProperties = new HashSet<ManagedProperty>();
         HashSet<ManagedOperation> ops = null;
         ManagedObject kdMO = new ManagedObjectImpl(kd.getName(), "", KernelDeployment.class.getName(), kdProperties,
               ops, (Serializable) kd);
         Map<String, ManagedObject> kdMOs = new HashMap<String, ManagedObject>();
         kdMOs.put(kd.getName(), kdMO);

         // Traverse the deployment components 
         for (DeploymentUnit compUnit : deploymentUnit.getComponents())
         {
            BeanMetaData bmd = compUnit.getAttachment(BeanMetaData.class);
            ManagedObject bmdMO = mof.initManagedObject(bmd, compUnit.getMetaData());
            if (bmdMO == null)
               continue;

            Map<String, Annotation> moAnns = bmdMO.getAnnotations();
            ManagementObject mo = (ManagementObject) moAnns.get(ManagementObject.class.getName());
            // Reset the name to the bean name rather than the attachment name
            if (bmdMO instanceof MutableManagedObject)
            {
               MutableManagedObject mmo = (MutableManagedObject) bmdMO;
               // Reset the name to the bean name if its the attachment name
               if (mmo.getName().equals(mmo.getAttachmentName()))
                  mmo.setName(bmd.getName());
               mmo.setParent(kdMO);
               // Add an alias property
               Set<Object> bmdAliases = bmd.getAliases();
               Map<String, ManagedProperty> oldProps = mmo.getProperties();
               Map<String, ManagedProperty> newProps = new HashMap<String, ManagedProperty>(oldProps);
               if (bmdAliases != null && bmdAliases.size() > 0)
               {
                  ArrayMetaType aliasType = new ArrayMetaType(SimpleMetaType.STRING, false);
                  DefaultFieldsImpl fields = getFields("alias", aliasType);
                  fields.setDescription("Aliases of the bean");
                  String[] aliases = new String[bmdAliases.size()];
                  Iterator<?> i = bmdAliases.iterator();
                  for (int n = 0; i.hasNext(); n++)
                  {
                     aliases[n] = i.next().toString();
                  }
                  ArrayValueSupport value = new ArrayValueSupport(aliasType, aliases);
                  fields.setValue(value);
                  ManagedPropertyImpl aliasesMP = new ManagedPropertyImpl(bmdMO, fields);
                  newProps.put("alias", aliasesMP);
               }
               // Add a state property
               DefaultFieldsImpl stateFields = getFields("state", ControllerStateMetaType.TYPE);
               stateFields.setViewUse(new ViewUse[]
               {ViewUse.STATISTIC});
               EnumValue stateValue = getState(bmd.getName(), kernel);
               stateFields.setValue(stateValue);
               stateFields.setDescription("The bean controller state");
               ManagedPropertyImpl stateMP = new ManagedPropertyImpl(mmo, stateFields);
               newProps.put("state", stateMP);
               // Update the properties
               mmo.setProperties(newProps);
            }
            log.debug("Created ManagedObject: " + bmdMO + " for bean: " + bmd.getName());
            kdMOs.put(bmd.getName(), bmdMO);

         }
         // Create the ManagedDeployment
         ManagedDeployment md = mgtDeploymentCreator.build(deploymentUnit, kdMOs, null);
         if (firstDeployment == null)
            firstDeployment = md;
         // Create the ManagedComponents
         for (ManagedObject bmdMO : kdMOs.values())
         {
            if (bmdMO.getAttachmentName().equals(KernelDeployment.class.getName()))
               continue;

            ComponentType type = KnownComponentTypes.MCBean.Any.getType();
            Map<String, Annotation> moAnns = bmdMO.getAnnotations();
            ManagementComponent mc = (ManagementComponent) moAnns.get(ManagementComponent.class.getName());
            if (mc != null)
            {
               type = new ComponentType(mc.type(), mc.subtype());
            }
            ManagedComponentImpl comp = new ManagedComponentImpl(type, md, bmdMO);
            md.addComponent(bmdMO.getName(), comp);
            log.debug("Created ManagedComponent(" + comp.getName() + ") of type: " + type + " for MO: "
                  + bmdMO.getName() + ", componentName: " + bmdMO.getComponentName());
         }

         if (md != null)
            bootstrapMDs.put(kd.getName(), md);
      }

      // Add other Server managed objects
      if (firstDeployment != null)
      {
         ComponentType type = new ComponentType("MCBean", "MCServer");
         ManagedObject serverMO = mof.initManagedObject(server, null);
         if (serverMO.getOperations() != null && serverMO.getOperations().size() == 0)
         {
            ManagedOperationImpl shutdown = new ManagedOperationImpl("Shutdown the server", "shutdown");
            if (serverMO instanceof MutableManagedObject)
            {
               HashSet<ManagedOperation> ops = new HashSet<ManagedOperation>();
               ops.add(shutdown);
               MutableManagedObject mmo = MutableManagedObject.class.cast(serverMO);
               mmo.setOperations(ops);
            }
         }
         ManagedComponentImpl serverComp = new ManagedComponentImpl(type, firstDeployment, serverMO);
         firstDeployment.addComponent("MCServer", serverComp);
         try
         {
            BeanInfo mcserverInfo = configurator.getBeanInfo(server.getClass());
            BeanKernelRegistryEntry entry = new BeanKernelRegistryEntry(server, mcserverInfo);
            bootstrapEntries.put(serverComp.getComponentName(), entry);
         }
         catch (Throwable t)
         {
            log.error("Failed to create BeanInfo for: " + serverComp, t);
         }
         // ServerConfig
         type = new ComponentType("MCBean", "ServerConfig");
         T config = server.getConfiguration();
         ManagedObject mo = mof.initManagedObject(config, null);
         ManagedComponentImpl configComp = new ManagedComponentImpl(type, firstDeployment, mo);
         firstDeployment.addComponent("ServerConfig", configComp);
         log.debug("Created ManagedComponent of type: " + type + " for ServerConfig");
      }

   }

   /**
    * 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 DefaultFieldsImpl getFields(String name, MetaType type)
   {
      DefaultFieldsImpl fields = new DefaultFieldsImpl();
      fields.setMetaType(type);
      fields.setName(name);
      fields.setField(Fields.MAPPED_NAME, name);
      fields.setMandatory(false);

      return fields;
   }

   /**
    * Get the state of a bean
    * 
    * @param name the bean name
    * @return state enum value
    */
   protected EnumValue getState(Object name, Kernel kernel)
   {
      KernelController controller = kernel.getController();
      ControllerContext context = controller.getContext(name, null);
      if (context == null)
         throw new IllegalStateException("Context not installed: " + name);

      ControllerState state = context.getState();
      return new EnumValueSupport(ControllerStateMetaType.TYPE, state.getStateString());
   }
}
