package org.jboss.seam.test.integration;

import static org.jboss.seam.ScopeType.APPLICATION;

import java.io.Serializable;
import java.util.concurrent.atomic.AtomicInteger;

import org.jboss.seam.annotations.Create;
import org.jboss.seam.annotations.Factory;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.core.Expressions;
import org.jboss.seam.mock.JUnitSeamTest;
import org.junit.Test;
import org.junit.After;

// JBSEAM-4669
public class ConcurrentFactoryTest 
    extends JUnitSeamTest 
{
    private volatile boolean exceptionOccured = false;
    static AtomicInteger testSequence = new AtomicInteger(0);

    private void concurrentFactoryCallTest() throws Exception {
       new ComponentTest() {
          @Override
          protected void testComponents() throws Exception {
             int myTestSequence;
             myTestSequence = testSequence.getAndIncrement();
             if (myTestSequence == 0) {
                assert "TestString".equals(getValue("#{concurrentFactoryTest.LockHoldingComponent.string}"));
             } else {
                try {
                   Thread.sleep(500);
                } catch (InterruptedException e) {
                   e.printStackTrace();
                }
                assert "TestString".equals(getValue("#{concurrentFactoryTest.dependentString}"));
             }
             System.out.println(myTestSequence);

          }
      }.run();
    }
    
    private class ConcurrentFactoryCallTestThread extends Thread
    {
        public void run() {
            try
            {
                ConcurrentFactoryTest.this.concurrentFactoryCallTest();
            }
            catch (Throwable e)
            {
                e.printStackTrace();
                ConcurrentFactoryTest.this.exceptionOccured = true;
            }
        };
    }
   
    @Test(timeout=10000)
    public void concurrentFactoryCall() 
        throws Exception 
    {
       Thread thread1 = new ConcurrentFactoryCallTestThread();
       Thread thread2 = new ConcurrentFactoryCallTestThread();

       thread1.start();
       thread2.start();
    
       thread1.join();
       thread2.join();
       
       assert !exceptionOccured;
    }
    
    @Name("concurrentFactoryTest.LockHoldingComponent")
    @Scope(APPLICATION)
    static public class LockHoldingComponent implements Serializable {
       @In(value = "concurrentFactoryTest.slowlyCreatedComponent", create = true) SlowlyCreatedComponent slowlyCreatedComponent;
       
       public String getString() {
          return (String) Expressions.instance().createValueExpression("#{concurrentFactoryTest.plainFactoryGeneratedString}").getValue();
       }
    }
    
    @Name("concurrentFactoryTest.slowlyCreatedComponent")
    static public class SlowlyCreatedComponent {
       @Create
       public void slowlyCreate() {
          try {
             Thread.sleep(1000);
          } catch (InterruptedException e) {
             e.printStackTrace();
          }
       }
    }
      
    @Name("concurrentFactoryTest.dependentFactory")
    static public class DependentFactory {
        @Factory(value = "concurrentFactoryTest.dependentString", scope = APPLICATION, autoCreate = true)
        public String createString() {
           return (String) Expressions.instance().createValueExpression("#{concurrentFactoryTest.LockHoldingComponent.string}").getValue();
        }
    }
    
    @Name("concurrentFactoryTest.plainFactory")
    static public class PlainFactory {
        @Factory(value = "concurrentFactoryTest.plainFactoryGeneratedString", scope = APPLICATION, autoCreate = true)
        public String createPlainString() {
           return "TestString";
        }
    }
    
    @After
    @Override
    public void end()
    {
       // don't attempt to endSession, as it will block in the deadlocked org.jboss.seam.Component.getInstanceFromFactory lock
    }
}
