package org.jboss.as.clustering.infinispan.cs.deployment;

import java.lang.reflect.Constructor;
import java.util.List;

import org.jboss.as.clustering.infinispan.InfinispanLogger;
import org.jboss.as.clustering.infinispan.InfinispanMessages;
import org.jboss.as.clustering.infinispan.cs.factory.DeployedCacheStoreFactory;
import org.jboss.as.clustering.infinispan.cs.factory.DeployedCacheStoreFactoryService;
import org.jboss.as.server.deployment.Attachments;
import org.jboss.as.server.deployment.DeploymentPhaseContext;
import org.jboss.as.server.deployment.DeploymentUnit;
import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
import org.jboss.as.server.deployment.DeploymentUnitProcessor;
import org.jboss.as.server.deployment.ServicesAttachment;
import org.jboss.modules.Module;
import org.jboss.modules.ModuleClassLoader;
import org.jboss.msc.service.Service;
import org.jboss.msc.service.ServiceBuilder;
import org.jboss.msc.service.ServiceController;
import org.jboss.msc.service.ServiceName;
import org.jboss.msc.service.StartContext;
import org.jboss.msc.service.StopContext;
import org.jboss.msc.value.InjectedValue;

public abstract class AbstractCacheStoreExtensionProcessor<T> implements DeploymentUnitProcessor {

   @Override
   public void deploy(DeploymentPhaseContext ctx) throws DeploymentUnitProcessingException {
      DeploymentUnit deploymentUnit = ctx.getDeploymentUnit();
      Module module = deploymentUnit.getAttachment(Attachments.MODULE);
      ServicesAttachment servicesAttachment = deploymentUnit.getAttachment(Attachments.SERVICES);
      if (module != null && servicesAttachment != null)
         addServices(ctx, servicesAttachment, module);
   }

   @Override
   public void undeploy(DeploymentUnit deploymentUnit) {
      // Deploy only adds services, so no need to do anything here
      // since these services are automatically removed.
   }

   private void addServices(DeploymentPhaseContext ctx, ServicesAttachment servicesAttachment, Module module) {
      Class<T> serviceClass = getServiceClass();
      List<String> implementationNames = servicesAttachment.getServiceImplementations(serviceClass.getName());
      ModuleClassLoader classLoader = module.getClassLoader();
      for (String serviceClassName : implementationNames) {
         try {
            Class<? extends T> clazz = classLoader.loadClass(serviceClassName).asSubclass(serviceClass);
            Constructor<? extends T> ctor = clazz.getConstructor();
            T instance = ctor.newInstance();
            installService(ctx, serviceClassName, instance);
         } catch (Exception e) {
            InfinispanMessages.MESSAGES.unableToInstantiateClass(serviceClassName);
         }
      }
   }

   public final void installService(DeploymentPhaseContext ctx, String implementationClassName, T instance) {
      AbstractExtensionManagerService<T> service = createService(implementationClassName, instance);
      ServiceName extensionServiceName = ServiceName.JBOSS.append(service.getServiceTypeName(), implementationClassName.replaceAll("\\.", "_"));
      InfinispanLogger.ROOT_LOGGER.installDeployedCacheStore(implementationClassName);
      ServiceBuilder<T> serviceBuilder = ctx.getServiceTarget().addService(extensionServiceName, service);
      serviceBuilder.setInitialMode(ServiceController.Mode.ACTIVE);
      serviceBuilder.addDependency(DeployedCacheStoreFactoryService.SERVICE_NAME, DeployedCacheStoreFactory.class, service.getDeployedCacheStoreFactory());
      serviceBuilder.install();
   }

   public abstract Class<T> getServiceClass();

   public abstract AbstractExtensionManagerService<T> createService(String serviceName, T instance);

   protected static abstract class AbstractExtensionManagerService<T> implements Service<T> {

      protected final T extension;
      protected final String className;
      protected final InjectedValue<DeployedCacheStoreFactory> deployedCacheStoreFactory = new InjectedValue<>();

      protected AbstractExtensionManagerService(String className, T extension) {
         this.extension = extension;
         this.className = className;
      }

      @Override
      public void start(StartContext context) {
         InfinispanLogger.ROOT_LOGGER.deployedStoreStarted(className);
         deployedCacheStoreFactory.getValue().addInstance(extension);
      }

      @Override
      public void stop(StopContext context) {
         InfinispanLogger.ROOT_LOGGER.deployedStoreStopped(className);
         deployedCacheStoreFactory.getValue().removeInstance(extension);
      }

      InjectedValue<DeployedCacheStoreFactory> getDeployedCacheStoreFactory() {
         return deployedCacheStoreFactory;
      }

      public abstract String getServiceTypeName();
   }
}
