package org.jboss.seam.test.integration;

import java.io.Serializable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.EntityManager;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.OverProtocol;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.seam.Component;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.Begin;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.core.Conversation;
import org.jboss.seam.mock.JUnitSeamTest;
import org.jboss.seam.persistence.ManagedEntityInterceptor;
import org.jboss.shrinkwrap.api.Archive;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * Verifies the work of the ManagedEntityInterceptor. Specifically that
 * collections containing entity instances are properly put into the session and
 * pulled back into the conversation-scoped component between requests (during a
 * potential period of passivation of the SFSB). The test also verifies that
 * when a component in a nested conversation calls a component in a parent
 * conversation, that the passivated state is kept with the parent conversation.
 * 
 * @author Norman Richards
 * @author Dan Allen
 */
@RunWith(Arquillian.class)
public class EntityPassivationTest extends JUnitSeamTest
{
   @Deployment(name="EntityPassivationTest")
   @OverProtocol("Servlet 3.0") 
   public static Archive<?> createDeployment()
   {
       return Deployments.defaultSeamDeployment()
       	.addClasses(SomeComponent.class, NestedComponent.class, UnversionedThing.class);
   }
    
   @Test
   public void testEntityList() throws Exception
   {
      String pid = new FacesRequest("/test.xhtml")
      {
         @Override
         protected void invokeApplication() throws Exception
         {
            // MEI is not installed by default, so we need to enable it
            Component.forName("entitytest.someComponent").addInterceptor(new ManagedEntityInterceptor());
            Component.forName("entitytest.nestedComponent").addInterceptor(new ManagedEntityInterceptor());
            
            Conversation.instance().begin(true, false);

            invokeAction("#{entitytest.someComponent.createSomeThings}");
            invokeAction("#{entitytest.someComponent.loadThings}");
         }

         @Override
         protected void renderResponse() throws Exception
         {
            Object thing = getValue("#{entitytest.someComponent.thing}");
            assert thing != null;

            List thingList = (List) getValue("#{entitytest.someComponent.thingsAsList}");
            assert thingList != null && !thingList.isEmpty();
            assert thingList.get(0) != null;

            Set thingSet = (Set) getValue("#{entitytest.someComponent.thingsAsSet}");
            assert thingSet != null && thingSet.size() > 0;
            assert thingSet.iterator().next() != null;

            Map thingMap = (Map) getValue("#{entitytest.someComponent.thingsAsMap}");
            assert thingMap != null && thingMap.size() > 0;
         }
      }.run();

      new FacesRequest("/test.xhtml", pid)
      {
         // the entities should be passivated
      }.run();

      new FacesRequest("/test.xhtml", pid)
      {
         // passivated a second time
      }.run();

      new FacesRequest("/test.xhtml", pid)
      {
         @Override
         protected void renderResponse() throws Exception
         {
            Object thing = getValue("#{entitytest.someComponent.thing}");
            assert thing != null;

            List thingList = (List) getValue("#{entitytest.someComponent.thingsAsList}");
            assert thingList != null && !thingList.isEmpty();
            assert thingList.get(0) != null;

            Set thingSet = (Set) getValue("#{entitytest.someComponent.thingsAsSet}");
            assert thingSet != null && thingSet.size() > 0;
            assert thingSet.iterator().next() != null;

            Map thingMap = (Map) getValue("#{entitytest.someComponent.thingsAsMap}");
            assert thingMap != null && thingMap.size() > 0;
         }

      }.run();

      // Start a nested conversation to verify that calls to a component in a parent conversation
      // will passivate that component's state in the parent conversation context and not in
      // the nested conversation. Thus, when the parent conversation is restored, its state
      // will be properly restored.
      String nid = new FacesRequest("/test.xhtml", pid)
      {
         @Override
         protected void invokeApplication() throws Exception
         {
            invokeMethod("#{entitytest.nestedComponent.nest}");
         }

         @Override
         protected void renderResponse() throws Exception
         {
            assert Conversation.instance().isNested();
         }

      }.run();
      
      new FacesRequest("/test.xhtml", nid)
      {
         @Override
         protected void invokeApplication() throws Exception
         {
            // invoke component in parent conversation from nested conversation
            invokeMethod("#{entitytest.someComponent.removeFirstThingFromList}");
         }
         
         @Override
         protected void renderResponse() throws Exception
         {
            // the nested conversation should not hold the serialized property of the component in the parent conversation
            assert !Arrays.asList(Contexts.getConversationContext().getNames()).contains("entitytest.someComponent.thingList");
            List thingList = (List) getValue("#{entitytest.someComponent.thingsAsList}");
            assert thingList.size() == 1;
         }   
      }.run();
      
      new FacesRequest("/test.xhtml", nid)
      {
         @Override
         protected void invokeApplication() throws Exception
         {
            invokeMethod("#{entitytest.nestedComponent.end}");
         }  
      }.run();
      
      new FacesRequest("/test.xhtml", pid)
      {
         @Override
         protected void renderResponse() throws Exception
         {
            // The state of the component in the parent conversation should be preserved.
            List thingList = (List) getValue("#{entitytest.someComponent.thingsAsList}");
            assert thingList.size() == 1;
         }  
      }.run();
   }
   
   @Name("entitytest.someComponent")
   @Scope(ScopeType.CONVERSATION)
   @AutoCreate
   public static class SomeComponent implements Serializable
   {
      @In
      EntityManager entityManager;

      Set<UnversionedThing> thingSet;
      List<UnversionedThing> thingList;
      Map<Long, UnversionedThing> thingMap;
      UnversionedThing thing;

      public void loadThings()
      {
         thingList = entityManager.createQuery("select t from UnversionedThing t").getResultList();
         thingSet = new HashSet<UnversionedThing>(thingList);

         thingMap = new HashMap<Long, UnversionedThing>();
         for (UnversionedThing thing : thingList)
         {
            thingMap.put(thing.getId(), thing);
         }

         thing = thingList.get(0);
      }

      public List<UnversionedThing> getThingsAsList()
      {
         return thingList;
      }

      public Set<UnversionedThing> getThingsAsSet()
      {
         return thingSet;
      }

      public Map<Long, UnversionedThing> getThingsAsMap()
      {
         return thingMap;
      }

      public UnversionedThing getThing()
      {
         return thing;
      }
      
      public void removeFirstThingFromList()
      {
         loadThings();
         thingList.remove(0);
      }

      public void createSomeThings()
      {
         UnversionedThing thing1 = new UnversionedThing();
         thing1.setName("thing one");
         entityManager.persist(thing1);
         
         UnversionedThing thing2 = new UnversionedThing();
         thing2.setName("thing two");
         entityManager.persist(thing2);
      }
   }
   
   @Name("entitytest.nestedComponent")
   @Scope(ScopeType.CONVERSATION)
   @AutoCreate
   public static class NestedComponent implements Serializable
   {
      @In
      EntityManager entityManager;

      @Begin(nested = true)
      public void nest()
      {
      }

      public void end()
      {
         Conversation.instance().end();
      }
   }
}
