/*
 * 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.test.system.controller;

import java.lang.reflect.UndeclaredThrowableException;
import java.net.URL;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.ObjectName;

import junit.framework.AssertionFailedError;

import org.jboss.mx.server.ServerConstants;
import org.jboss.system.ServiceContext;
import org.jboss.system.ServiceControllerMBean;
import org.jboss.test.AbstractSystemTest;
import org.jboss.test.AbstractTestDelegate;
import org.jboss.test.system.controller.support.Order;

/**
 * ControllerTestDelegate.
 * 
 * @author <a href="adrian@jboss.com">Adrian Brock</a>
 * @version $Revision: 85120 $
 */
public abstract class ControllerTestDelegate extends AbstractTestDelegate
{
   private MBeanServer server; 
   
   private ServiceControllerMBean serviceController;

   private SimpleSARDeployer deployer;
   
   public ControllerTestDelegate(Class<?> clazz)
   {
      super(clazz);
   }

   public MBeanServer getServer()
   {
      return server;
   }

   public ServiceControllerMBean getController()
   {
      return serviceController;
   }

   /**
    * Override to:
    * Set javax.management.builder.initial to org.jboss.mx.server.MBeanServerBuilderImpl
    * Create jboss domain MBeanServer
    * Create and register jboss.system:service=ServiceController mbean
    * Create SimpleSARDeployer
    * deploy the test service mbeans
    * @see #deploy()
    */
   public void setUp() throws Exception
   {
      super.setUp();
      
      System.setProperty(ServerConstants.MBEAN_SERVER_BUILDER_CLASS_PROPERTY, ServerConstants.DEFAULT_MBEAN_SERVER_BUILDER_CLASS);
      server = createMBeanServer();
      serviceController = createServiceController();
      server.registerMBean(serviceController, ServiceControllerMBean.OBJECT_NAME);
      
      deployer = new SimpleSARDeployer(server, serviceController);

      Order.reset();
      
      deploy();
      
      if (isValidateAtSetUp())
         validate();
   }

   // TODO Temporary until the IncompleteDeploymentDescription is integrated 
   protected boolean isValidateAtSetUp()
   {
      return true;
   }

   protected MBeanServer createMBeanServer()
   {
      return MBeanServerFactory.newMBeanServer("jboss");
   }

   public abstract ServiceControllerMBean createServiceController() throws Exception;
   
   public void tearDown() throws Exception
   {
      deployer.uninstall();
      super.tearDown();
   }

   protected void uninstallTemporary() throws Exception
   {
      deployer.uninstallTemporary();
   }
   
   protected void validate() throws Exception
   {
      Collection<ServiceContext> waitingForClasses = new HashSet<ServiceContext>();
      Collection<ServiceContext> waitingForDepends = serviceController.listIncompletelyDeployed();
      Collection<ServiceContext> allServices = serviceController.listDeployed();
      
      // Weed services that are waiting for other deployments
      Collection<ServiceContext> rootCause = new HashSet<ServiceContext>(waitingForDepends);
      Collection<ServiceContext> missing = new HashSet<ServiceContext>();
      for (Iterator i = rootCause.iterator(); i.hasNext();)
      {
         ServiceContext ctx = (ServiceContext) i.next();
         for (Iterator j = ctx.iDependOn.iterator(); j.hasNext(); )
         {
            ServiceContext dependee = (ServiceContext) j.next();
            if (dependee.state != ServiceContext.RUNNING)
            {
               // Add missing mbean
               if (allServices.contains(dependee) == false)
                  missing.add(dependee);
               // We are not a root cause
               i.remove();
               break;
            }
         }
      }
      // Add missing mbeans to the root cause
      rootCause.addAll(missing);
      
      IncompleteDeploymentException ide = new IncompleteDeploymentException(
         waitingForClasses,
         waitingForDepends,
         rootCause,
         Collections.emptyList(),
         Collections.emptyList());

      if (ide.isEmpty() == false)
         throw ide;
   }
   
   /**
    * Deploy the beans by looking for an xml descriptor named
    * clazz.getName().replace('.', '/') + ".xml". If the test
    * clazz includes a NewUnitTestCase or OldUnitTestCase suffix,
    * this is removed.
    * 
    * @throws Exception for any error
    */
   protected void deploy() throws Exception
   {
      String testName = clazz.getName();
      testName = testName.replace('.', '/');

      int index = testName.indexOf("NewUnitTestCase");
      if (index != -1)
         testName = testName.substring(0, index);
      index = testName.indexOf("OldUnitTestCase");
      if (index != -1)
         testName = testName.substring(0, index);
      
      testName += ".xml";
      
      URL url = clazz.getClassLoader().getResource(testName);
      if (url != null)
         deploy(url, false);
      else
         log.debug("No test specific deployment " + testName);
   }

   protected List<ObjectName> deploy(URL url, boolean temporary) throws Exception
   {
      return deployer.deploy(url, temporary);
   }
   
   protected void undeploy(List<ObjectName> objectNames)
   {
      deployer.undeploy(objectNames);
   }

   protected List<ObjectName> install(URL url) throws Exception
   {
      return deployer.install(url);
   }
   
   protected void uninstall(List<ObjectName> objectNames)
   {
      deployer.uninstall(objectNames);
   }
   
   public abstract void assertMBeanFailed(ObjectName name, boolean registered) throws Exception;
   
   protected IncompleteDeploymentException assertInvalidDeployments() throws Exception
   {
      try
      {
         validate();
         throw new AssertionFailedError("Deployments should not be valid!");
      }
      catch (IncompleteDeploymentException expected)
      {
         log.debug("Got expected " + expected.getClass().getName());
         return expected;
      }
   }
   
   protected void assertInitialDeployFailure(URL url, ObjectName name, Class<? extends Throwable> expected) throws Exception
   {
      try
      {
         deploy(url, true);
         throw new AssertionFailedError("Should have got a " + expected.getName());
      }
      catch (Throwable t)
      {
         AbstractSystemTest.checkThrowableDeep(expected, t);
      }
      if (name != null)
         assertNoService(name);
   }

   public List<ObjectName> assertDeployFailure(URL url, ObjectName name, Class<? extends Throwable> expected) throws Exception
   {
      List<ObjectName> result = deploy(url, true);
      IncompleteDeploymentException e = assertInvalidDeployments();
      checkIncomplete(e, name, expected);
      return result;
   }
   
   public List<ObjectName> assertMaybeDeployFailure(URL url, ObjectName name, Class<? extends Throwable> expected) throws Exception
   {
      return assertDeployFailure(url, name, expected);
   }
   
   public void assertMaybeParseFailure(URL url, ObjectName name, Class<? extends Throwable> expected) throws Exception
   {
      assertDeployFailure(url, name, expected);
      assertServiceFailed(name);
   }
   
   protected void checkIncomplete(IncompleteDeploymentException e, ObjectName name, Class<? extends Throwable> expected) throws Exception
   {
      Collection incomplete = e.getMbeansWaitingForDepends();
      for (Iterator i = incomplete.iterator(); i.hasNext();)
      {
         ServiceContext ctx = (ServiceContext) i.next();
         if (name.equals(ctx.objectName))
         {
            Throwable problem = ctx.getProblem();
            if (e != null || expected != null)
            {
               if (expected != null && problem == null)
                  throw new AssertionFailedError("Did not get expected " + expected.getName() + " for " + ctx);
               if (expected == null && problem != null)
               {
                  if (problem instanceof Exception)
                     throw (Exception) problem;
                  if (problem instanceof Error)
                     throw (Error) problem;
                  throw new UndeclaredThrowableException(problem);
               }
               if (expected != null)
               {
                  AbstractSystemTest.checkThrowableDeep(expected, problem);
                  if (ServiceContext.FAILED != ctx.state)
                     throw new AssertionFailedError("Context is not in the FAILED state: " + ctx);
               }
            }
            return;
         }
      }
      
      throw new AssertionFailedError("Did not find " + name + " in incomplete deployments " + incomplete);
   }
   
   protected ServiceContext getServiceContext(ObjectName name) throws Exception
   {
      return getController().getServiceContext(name);
   }
   
   protected void assertNoService(ObjectName name) throws Exception
   {
      ServiceContext ctx = getServiceContext(name);
      if (ctx != null && ctx.state != ServiceContext.NOTYETINSTALLED)
         throw new AssertionFailedError("Should not be a service context for " + ctx);
   }

   protected void assertServiceFailed(ObjectName name) throws Exception
   {
      assertServiceFailed(name, AbstractControllerTest.OLD_REGISTERED);
   }
   
   protected void assertServiceFailed(ObjectName name, boolean registered) throws Exception
   {
      assertServiceState(name, ServiceContext.FAILED, registered);
      assertMBeanFailed(name, registered);
   }
   
   protected void assertServiceState(ObjectName name, int expectedState, boolean registered) throws Exception
   {
      ServiceContext ctx = getServiceContext(name);
      if (registered == false && ctx == null)
         return;
      if (ctx == null)
         throw new AssertionFailedError("Incorrect state for " + name + " expected " + ServiceContext.getStateString(expectedState) + " but there is no context/state");
      if (expectedState != ctx.state)
         throw new AssertionFailedError("Incorrect state for " + name + " expected " + ServiceContext.getStateString(expectedState) + " got " + ctx.getStateString());
   }
}
