/*
 * JBoss, Home of Professional Open Source
 * Copyright 2010 Red Hat Inc. and/or its affiliates and other contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.arquillian.container.test.impl.client.deployment;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.jboss.arquillian.container.spi.Container;
import org.jboss.arquillian.container.spi.ContainerRegistry;
import org.jboss.arquillian.container.spi.client.deployment.Deployment;
import org.jboss.arquillian.container.spi.client.deployment.DeploymentDescription;
import org.jboss.arquillian.container.spi.client.deployment.DeploymentScenario;
import org.jboss.arquillian.container.spi.client.deployment.TargetDescription;
import org.jboss.arquillian.container.spi.client.protocol.ProtocolDescription;
import org.jboss.arquillian.container.test.api.TargetsContainer;
import org.jboss.arquillian.container.test.impl.client.deployment.event.GenerateDeployment;
import org.jboss.arquillian.container.test.impl.domain.ProtocolDefinition;
import org.jboss.arquillian.container.test.impl.domain.ProtocolRegistry;
import org.jboss.arquillian.container.test.spi.TestDeployment;
import org.jboss.arquillian.container.test.spi.client.deployment.ApplicationArchiveProcessor;
import org.jboss.arquillian.container.test.spi.client.deployment.AuxiliaryArchiveAppender;
import org.jboss.arquillian.container.test.spi.client.deployment.AuxiliaryArchiveProcessor;
import org.jboss.arquillian.container.test.spi.client.deployment.DeploymentPackager;
import org.jboss.arquillian.container.test.spi.client.deployment.DeploymentScenarioGenerator;
import org.jboss.arquillian.container.test.spi.client.deployment.ProtocolArchiveProcessor;
import org.jboss.arquillian.container.test.spi.client.protocol.Protocol;
import org.jboss.arquillian.core.api.Instance;
import org.jboss.arquillian.core.api.InstanceProducer;
import org.jboss.arquillian.core.api.annotation.Inject;
import org.jboss.arquillian.core.api.annotation.Observes;
import org.jboss.arquillian.core.spi.ServiceLoader;
import org.jboss.arquillian.test.spi.TestClass;
import org.jboss.arquillian.test.spi.annotation.ClassScoped;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.container.ClassContainer;

/**
 * DeploymentGenerator
 *
 * @author <a href="mailto:aslak@redhat.com">Aslak Knutsen</a>
 * @version $Revision: $
 */
public class DeploymentGenerator
{
   @Inject
   private Instance<ServiceLoader> serviceLoader;
   
   @Inject @ClassScoped
   private InstanceProducer<DeploymentScenario> deployment;

   @Inject
   private Instance<ContainerRegistry> containerRegistry;
   
   @Inject
   private Instance<ProtocolRegistry> protocolRegistry;

   public void generateDeployment(@Observes GenerateDeployment event)
   {
      DeploymentScenarioGenerator generator = serviceLoader.get().onlyOne(
            DeploymentScenarioGenerator.class, AnnotationDeploymentScenarioGenerator.class);
      
      DeploymentScenario scenario = new DeploymentScenario();
      
      for(DeploymentDescription deployment : generator.generate(event.getTestClass())) 
      {
         scenario.addDeployment(deployment);
      }

      validate(scenario);
      createTestableDeployments(scenario, event.getTestClass());

      deployment.set(scenario);
   }

   //-------------------------------------------------------------------------------------||
   // Validate DeploymentScenario --------------------------------------------------------||
   //-------------------------------------------------------------------------------------||

   protected void validate(DeploymentScenario scenario)
   {
      ContainerRegistry conReg = containerRegistry.get();
      for (TargetDescription target : scenario.targets())
      {
         Container container = conReg.getContainer(target);
         if (container == null)
         {
            throwTargetNotFoundValidationException(conReg, target);
         }
      }

      for(Deployment deployment : scenario.deployments())
      {
         Container container = conReg.getContainer(deployment.getDescription().getTarget());
         if("custom".equalsIgnoreCase(container.getContainerConfiguration().getMode()))
         {
            if(deployment.getDescription().managed())
            {
               throw new ValidationException(
                     "Deployment " + deployment.getDescription().getName() + " is targeted against container " + 
                     container.getName() + ". This container is set to mode custom which can not handle managed deployments. " +
                     "Please verify the @" + TargetsContainer.class.getName() + " annotation or container@mode in arquillian.xml");
            }
         }
      }

      ProtocolRegistry proReg = protocolRegistry.get();
      for (ProtocolDescription proDesc : scenario.protocols())
      {
         if (ProtocolDescription.DEFAULT.equals(proDesc))
         {
            continue;
         }
         ProtocolDefinition protocol = proReg.getProtocol(proDesc);
         if (protocol == null)
         {
            throw new ValidationException(DeploymentScenario.class.getSimpleName()
                  + " contains protocols not maching any defined Protocol in the registry. " + proDesc.getName());
         }
      }

   }

   //-------------------------------------------------------------------------------------||
   // Enrich with Protocol Packaging -----------------------------------------------------||
   //-------------------------------------------------------------------------------------||

   /**
    * @param scenario
    */
   private void createTestableDeployments(DeploymentScenario scenario, TestClass testCase)
   {
      ProtocolRegistry protoReg = protocolRegistry.get();
      buildTestableDeployments(scenario, testCase, protoReg);
   }

   private void buildTestableDeployments(DeploymentScenario scenario, TestClass testCase, ProtocolRegistry protoReg)
   {
      for(Deployment deployment : scenario.deployments())
      {
         DeploymentDescription description = deployment.getDescription();
         if(!description.testable() || !description.isArchiveDeployment())
         {
            continue;
         }
         List<Archive<?>> auxiliaryArchives = loadAuxiliaryArchives(description);
         
         ProtocolDefinition protocolDefinition = protoReg.getProtocol(description.getProtocol());
         if(protocolDefinition == null)
         {
            protocolDefinition = protoReg.getProtocol(
                  containerRegistry.get().getContainer(description.getTarget()).getDeployableContainer().getDefaultProtocol());
         }
         Protocol<?> protocol = protocolDefinition.getProtocol();
         DeploymentPackager packager = protocol.getPackager();

         Archive<?> applicationArchive = description.getArchive();
         applyApplicationProcessors(description.getArchive(), testCase);
         applyAuxiliaryProcessors(auxiliaryArchives);

         try
         {
            // this should be made more reliable, does not work with e.g. a EnterpriseArchive
            if(ClassContainer.class.isInstance(applicationArchive)) 
            {
               ClassContainer<?> classContainer = ClassContainer.class.cast(applicationArchive);
               classContainer.addClass(testCase.getJavaClass());
            }
         } 
         catch (UnsupportedOperationException e) 
         { 
            /*
             * Quick Fix: https://jira.jboss.org/jira/browse/ARQ-118
             * Keep in mind when rewriting for https://jira.jboss.org/jira/browse/ARQ-94
             * that a ShrinkWrap archive might not support a Container if even tho the 
             * ContianerBase implements it. Check the Archive Interface..  
             */
         }
         description.setTestableArchive(
               packager.generateDeployment(
                     new TestDeployment(deployment.getDescription(), applicationArchive, auxiliaryArchives),
                     serviceLoader.get().all(ProtocolArchiveProcessor.class)));
      }
   }

   private List<Archive<?>> loadAuxiliaryArchives(DeploymentDescription deployment) 
   {
      List<Archive<?>> archives = new ArrayList<Archive<?>>();

      // load based on the Containers ClassLoader
      Collection<AuxiliaryArchiveAppender> archiveAppenders = serviceLoader.get().all(AuxiliaryArchiveAppender.class);
      
      for(AuxiliaryArchiveAppender archiveAppender : archiveAppenders)
      {
         Archive<?> auxiliaryArchive = archiveAppender.createAuxiliaryArchive();
         if(auxiliaryArchive != null)
         {
            archives.add(auxiliaryArchive);
         }
      }
      return archives;
   }

   private void applyApplicationProcessors(Archive<?> applicationArchive, TestClass testClass)
   {
      Collection<ApplicationArchiveProcessor> processors = serviceLoader.get().all(ApplicationArchiveProcessor.class);
      for(ApplicationArchiveProcessor processor : processors)
      {
         processor.process(applicationArchive, testClass);
      }
   }
   
   private void applyAuxiliaryProcessors(List<Archive<?>> auxiliaryArchives)
   {
      Collection<AuxiliaryArchiveProcessor> processors = serviceLoader.get().all(AuxiliaryArchiveProcessor.class);
      for(AuxiliaryArchiveProcessor processor : processors)
      {
         for(Archive<?> auxiliaryArchive : auxiliaryArchives)
         {
            processor.process(auxiliaryArchive);
         }
      }
   }

   private void throwTargetNotFoundValidationException(ContainerRegistry conReg, TargetDescription target)
   {
      if (conReg.getContainers().size() == 0)
      {
         throwNoContainerFound(target);
      }
      throwNoMatchFound(conReg, target);
   }

   private void throwNoContainerFound(TargetDescription target)
   {
      throw new ValidationException("DeploymentScenario contains a target (" + target.getName()
            + ") not matching any defined Container in the registry.\n"
            + "Please include at least 1 Deployable Container on your Classpath.");
   }

   private void throwNoMatchFound(ContainerRegistry conReg, TargetDescription target)
   {
      throw new ValidationException("DeploymentScenario contains a target (" + target.getName()
            + ") not matching any defined Container in the registry.\n" + "Possible causes are: None of the "
            + conReg.getContainers().size() + " Containers are marked as default or you have defined a " + "@"
            + org.jboss.arquillian.container.test.api.Deployment.class.getSimpleName() + " with a @"
            + TargetsContainer.class.getSimpleName() + " of value (" + target.getName() + ") that "
            + "does not match any found/configured Containers (" + toString(conReg)
            + "), see arquillian.xml container@qualifier");
   }

   private String toString(ContainerRegistry reg)
   {
      StringBuilder sb = new StringBuilder();
      for (Container container : reg.getContainers())
      {
         sb.append(container.getName()).append(",");
      }
      if (sb.length() > 0)
      {
         sb.deleteCharAt(sb.length() - 1);
      }
      return sb.toString();
   }

}
