/*
 * JBoss, Home of Professional Open Source
 * Copyright 2009 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.execution;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.jboss.arquillian.container.spi.Container;
import org.jboss.arquillian.container.spi.client.deployment.DeploymentDescription;
import org.jboss.arquillian.container.spi.client.protocol.ProtocolDescription;
import org.jboss.arquillian.container.spi.client.protocol.metadata.ProtocolMetaData;
import org.jboss.arquillian.container.test.impl.domain.ProtocolDefinition;
import org.jboss.arquillian.container.test.impl.domain.ProtocolRegistry;
import org.jboss.arquillian.container.test.impl.execution.event.RemoteExecutionEvent;
import org.jboss.arquillian.container.test.spi.ContainerMethodExecutor;
import org.jboss.arquillian.container.test.spi.client.protocol.Protocol;
import org.jboss.arquillian.container.test.spi.client.protocol.ProtocolConfiguration;
import org.jboss.arquillian.container.test.spi.command.Command;
import org.jboss.arquillian.container.test.spi.command.CommandCallback;
import org.jboss.arquillian.container.test.test.AbstractContainerTestTestBase;
import org.jboss.arquillian.core.api.annotation.ApplicationScoped;
import org.jboss.arquillian.core.spi.context.Context;
import org.jboss.arquillian.core.test.context.ManagerTestContext;
import org.jboss.arquillian.core.test.context.ManagerTestContextImpl;
import org.jboss.arquillian.test.spi.TestMethodExecutor;
import org.jboss.arquillian.test.spi.TestResult;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;

/**
 * TestExecutorHandlerTestCase
 *
 * @author <a href="mailto:aknutsen@redhat.com">Aslak Knutsen</a>
 * @version $Revision: $
 */
@RunWith(MockitoJUnitRunner.class)
public class RemoteTestExecuterTestCase extends AbstractContainerTestTestBase
{
   @Mock
   private ContainerMethodExecutor executor;

   @Mock
   private Container container;

   @Mock
   private TestMethodExecutor testExecutor;

   @Mock
   private DeploymentDescription deploymentDescription;

   @Mock
   @SuppressWarnings("rawtypes")
   private Protocol protocol;

   @Mock
   private ProtocolDefinition protocolDefinition;

   @Mock
   private ProtocolRegistry protocolRegistry;

   @Mock
   private ProtocolMetaData protocolMetaData;

   @Override
   protected void addContexts(List<Class<? extends Context>> contexts) {
       super.addContexts(contexts);
       contexts.add(ManagerTestContextImpl.class);
   }

   @Override
   protected void addExtensions(List<Class<?>> extensions)
   {
      extensions.add(RemoteTestExecuter.class);
   }

   @SuppressWarnings("unchecked")
   @Before
   public void setup() throws Exception {
      bind(ApplicationScoped.class, Container.class, container);
      bind(ApplicationScoped.class, DeploymentDescription.class, deploymentDescription);
      bind(ApplicationScoped.class, ProtocolMetaData.class, protocolMetaData);
      bind(ApplicationScoped.class, ProtocolRegistry.class, protocolRegistry);

      Mockito.when(deploymentDescription.getProtocol()).thenReturn(new ProtocolDescription("TEST"));
      Mockito.when(protocolRegistry.getProtocol(Mockito.any(ProtocolDescription.class))).thenReturn(protocolDefinition);
      Mockito.when(protocolDefinition.getProtocol()).thenReturn(protocol);
      Mockito.when(protocol.getExecutor(
         Mockito.any(ProtocolConfiguration.class),
         Mockito.any(ProtocolMetaData.class),
         Mockito.any(CommandCallback.class))).thenAnswer(new Answer<ContainerMethodExecutor>() {
            @Override
            public ContainerMethodExecutor answer(InvocationOnMock invocation) throws Throwable {
               return new TestContainerMethodExecutor((CommandCallback)invocation.getArguments()[2]);
            }
         });

      Mockito.when(testExecutor.getInstance()).thenReturn(this);
      Mockito.when(testExecutor.getMethod()).thenReturn(
         getTestMethod("shouldReactivePreviousContextsOnRemoteEvents"));
   }

   @Test
   public void shouldReactivePreviousContextsOnRemoteEvents() throws Exception {
      getManager().getContext(ManagerTestContext.class).activate();
      fire(new RemoteExecutionEvent(testExecutor));
      assertEventFiredInContext(TestStringCommand.class, ManagerTestContext.class);
   }

   private Method getTestMethod(String name) throws Exception
   {
      return this.getClass().getMethod(name);
   }

   public class TestContainerMethodExecutor implements ContainerMethodExecutor {
       private CommandCallback callback;

       public TestContainerMethodExecutor(CommandCallback callback) {
           this.callback = callback;
       }

       @Override
       public TestResult invoke(TestMethodExecutor testMethodExecutor) {
          final CountDownLatch latch = new CountDownLatch(1);

          Thread remote = new Thread() {
             public void run() {
                callback.fired(new TestStringCommand());
                latch.countDown();
             };
          };
          remote.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
             @Override
             public void uncaughtException(Thread t, Throwable e) {
                throw new RuntimeException(e);
             }
          });
          remote.start();

          try {
             if(!latch.await(200, TimeUnit.MILLISECONDS)) {
                throw new RuntimeException("Latch never reached");
             }
          } catch (InterruptedException e) {
               throw new RuntimeException(e);
          }

          return TestResult.passed();
       }
   }

   public class TestStringCommand implements Command<String>, Serializable
   {
      private static final long serialVersionUID = 1L;

      private String result;
      private Throwable throwable;

      @Override
      public String getResult()
      {
         return result;
      }

      @Override
      public void setResult(String result)
      {
         this.result = result;
      }

      @Override
      public Throwable getThrowable()
      {
         return throwable;
      }

      @Override
      public void setThrowable(Throwable throwable)
      {
         this.throwable = throwable;
      }
   }
}