/*
 * Copyright 2006 Takahiro Nakamura.
 *
 * 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 woolpack.ee;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.concurrent.Semaphore;

import junit.framework.TestCase;
import woolpack.LoadAllTests;
import woolpack.dom.Branch;
import woolpack.dom.DomConstants;
import woolpack.dom.DomContext;
import woolpack.dom.DomExpression;
import woolpack.dom.EvalEL;
import woolpack.dom.Serial;
import woolpack.el.PathEL;
import woolpack.test.DomExpressionRunnable;
import woolpack.test.RunnableDomExpression;
import woolpack.test.RunnableGate;
import woolpack.test.TestUtils;
import woolpack.test.RunnableGate.Context;
import woolpack.test.ee.EETestUtils;
import woolpack.utils.FixSwitch;
import woolpack.utils.SwitchBuilder;
import woolpack.utils.concurrent.Acquirable;
import woolpack.utils.concurrent.TrySemaphore;

public class DoAcquireTransactionTest extends TestCase {

	public void testConstructor(){
		try{
			new DoAcquireTransaction(
					null, 
					new FixSwitch<String,Acquirable>(null), 
					new ArrayList<String>(), 
					new ArrayList<String>(), 
					DomConstants.NULL, 
					DomConstants.NULL, 
					DomConstants.NULL);
			fail();
		}catch(final NullPointerException expected){
		}
		try{
			new DoAcquireTransaction(
					"", 
					new FixSwitch<String,Acquirable>(null), 
					new ArrayList<String>(), 
					new ArrayList<String>(), 
					DomConstants.NULL, 
					DomConstants.NULL, 
					DomConstants.NULL);
			fail();
		}catch(final StringIndexOutOfBoundsException expected){
		}
		try{
			new DoAcquireTransaction(
					"key0", 
					null, 
					new ArrayList<String>(), 
					new ArrayList<String>(), 
					DomConstants.NULL, 
					DomConstants.NULL, 
					DomConstants.NULL);
			fail();
		}catch(final NullPointerException expected){
		}
		try{
			new DoAcquireTransaction(
					"key0", 
					new FixSwitch<String,Acquirable>(null), 
					null, 
					new ArrayList<String>(), 
					DomConstants.NULL, 
					DomConstants.NULL, 
					DomConstants.NULL);
			fail();
		}catch(final NullPointerException expected){
		}
		try{
			new DoAcquireTransaction(
					"key0", 
					new FixSwitch<String,Acquirable>(null), 
					new ArrayList<String>(), 
					null, 
					DomConstants.NULL, 
					DomConstants.NULL, 
					DomConstants.NULL);
			fail();
		}catch(final NullPointerException expected){
		}
		try{
			new DoAcquireTransaction(
					"key0", 
					new FixSwitch<String,Acquirable>(null), 
					new ArrayList<String>(), 
					new ArrayList<String>(), 
					null, 
					DomConstants.NULL, 
					DomConstants.NULL);
			fail();
		}catch(final NullPointerException expected){
		}
		try{
			new DoAcquireTransaction(
					"key0", 
					new FixSwitch<String,Acquirable>(null), 
					new ArrayList<String>(), 
					new ArrayList<String>(), 
					DomConstants.NULL, 
					null, 
					DomConstants.NULL);
			fail();
		}catch(final NullPointerException expected){
		}
		try{
			new DoAcquireTransaction(
					"key0", 
					new FixSwitch<String,Acquirable>(null), 
					new ArrayList<String>(), 
					new ArrayList<String>(), 
					DomConstants.NULL, 
					DomConstants.NULL, 
					null);
			fail();
		}catch(final NullPointerException expected){
		}
		new DoAcquireTransaction(
				"key0", 
				new FixSwitch<String,Acquirable>(null), 
				new ArrayList<String>(), 
				new ArrayList<String>(), 
				DomConstants.NULL, 
				DomConstants.NULL, 
				DomConstants.NULL);
		new DoAcquireTransaction(
				new FixSwitch<String,Acquirable>(null), 
				new ArrayList<String>(), 
				new ArrayList<String>(), 
				DomConstants.NULL, 
				DomConstants.NULL, 
				DomConstants.NULL);
		new DoAcquireTransaction(
				new FixSwitch<String,Acquirable>(null), 
				new ArrayList<String>(), 
				new ArrayList<String>(), 
				DomConstants.NULL, 
				DomConstants.NULL);
	}
	
	public void testDuplicate(){
		try{
			new DoAcquireTransaction(
					new SwitchBuilder<String,Acquirable>()
					.put("ids0", new TrySemaphore(new Semaphore(1)))
					.put("ids1", new TrySemaphore(new Semaphore(1))).get(), 
					Arrays.asList("idm0", "ids1"),
					Arrays.asList("ide0", "ide1"), 
					DomConstants.NULL, 
					DomConstants.NULL, 
					DomConstants.NULL);
			fail();
		}catch(final IllegalArgumentException expected){
			assertEquals("duplicate id: [ids1]", expected.getMessage());
		}
		try{
			new DoAcquireTransaction(
					new SwitchBuilder<String,Acquirable>()
					.put("ids0", new TrySemaphore(new Semaphore(1)))
					.put("ids1", new TrySemaphore(new Semaphore(1))).get(), 
					Arrays.asList("idm0", "idm1"),
					Arrays.asList("ide0", "ids1"), 
					DomConstants.NULL, 
					DomConstants.NULL, 
					DomConstants.NULL);
			fail();
		}catch(final IllegalArgumentException expected){
			assertEquals("duplicate id: [ids1]", expected.getMessage());
		}
		try{
			new DoAcquireTransaction(
					new SwitchBuilder<String,Acquirable>()
					.put("ids0", new TrySemaphore(new Semaphore(1)))
					.put("ids1", new TrySemaphore(new Semaphore(1))).get(), 
					Arrays.asList("idm0", "idm1"),
					Arrays.asList("ide0", "idm1"), 
					DomConstants.NULL, 
					DomConstants.NULL, 
					DomConstants.NULL);
			fail();
		}catch(final IllegalArgumentException expected){
			assertEquals("duplicate id: [idm1]", expected.getMessage());
		}
	}
	
	public void testNormal() throws InterruptedException{
		final DomExpression thrower = new Branch<Object>(
				new EvalEL(null, new PathEL("context.request.empty")), 
				new SwitchBuilder<Object,DomExpression>().get());
		
		final RunnableGate gate = new RunnableGate(LoadAllTests.THREAD_POOL);
		final Semaphore semaphore = new Semaphore(1);
		final DomExpression expression = new DoAcquireTransaction(
				new SwitchBuilder<String,Acquirable>().put("id0", new TrySemaphore(semaphore)).get(), 
				Arrays.asList("id1"), 
				Arrays.asList("id2"), 
				new Serial(new RunnableDomExpression(gate.getGate("true")), thrower),
				new Serial(new RunnableDomExpression(gate.getGate("false")), thrower),
				new Serial(new RunnableDomExpression(gate.getGate("release")), thrower));
		
		final DomContext context = new DomContext();
		context.setRequest(new HashMap<String,Object>());
		context.setSession(EETestUtils.generateSession());
		{
			// 開始前に途中処理を実行した場合は falseExpression に委譲。
			context.setId("id1");
			final Context t = gate.execute(new DomExpressionRunnable(expression, context));
			assertTrue(TestUtils.equals(Arrays.asList("false"), t.getEndList()));
			assertEquals(1, semaphore.availablePermits());
		}
		{
			// 開始前に終了処理を実行した場合は falseExpression に委譲。
			context.setId("id2");
			final Context t = gate.execute(new DomExpressionRunnable(expression, context));
			assertTrue(TestUtils.equals(Arrays.asList("false"), t.getEndList()));
			assertEquals(1, semaphore.availablePermits());
		}
		{
			// 開始処理を実行した場合は trueExpression に委譲。
			context.setId("id0");
			final Context t = gate.execute(new DomExpressionRunnable(expression, context));
			assertTrue(TestUtils.equals(Arrays.asList("true"), t.getEndList()));
			assertEquals(0, semaphore.availablePermits());
		}
		{
			// 再び開始処理を実行した場合は releaseExpression trueExpression に委譲。
			context.setId("id0");
			final Context t = gate.execute(new DomExpressionRunnable(expression, context));
			assertTrue(TestUtils.equals(Arrays.asList("release", "true"), t.getEndList()));
			assertEquals(0, semaphore.availablePermits());
		}
		{
			// 開始後に途中処理を実行した場合は trueExpression に委譲。
			context.setId("id1");
			final Context t = gate.execute(new DomExpressionRunnable(expression, context));
			assertTrue(TestUtils.equals(Arrays.asList("true"), t.getEndList()));
			assertEquals(0, semaphore.availablePermits());
		}
		{
			// 終了前に開始処理を実行した場合は releaseExpression trueExpression に委譲。
			context.setId("id0");
			final Context t = gate.execute(new DomExpressionRunnable(expression, context));
			assertTrue(TestUtils.equals(Arrays.asList("release", "true"), t.getEndList()));
			assertEquals(0, semaphore.availablePermits());
		}
		{
			// 開始後に途中処理を実行した場合は trueExpression に委譲。
			context.setId("id1");
			final Context t = gate.execute(new DomExpressionRunnable(expression, context));
			assertTrue(TestUtils.equals(Arrays.asList("true"), t.getEndList()));
			assertEquals(0, semaphore.availablePermits());
		}
		{
			// 開始後に終了処理を実行した場合は trueExpression releaseExpression に委譲。
			context.setId("id2");
			final Context t = gate.execute(new DomExpressionRunnable(expression, context));
			assertTrue(TestUtils.equals(Arrays.asList("true", "release"), t.getEndList()));
			assertEquals(1, semaphore.availablePermits());
		}
		{
			// 開始処理を実行した場合は trueExpression に委譲。
			context.setId("id0");
			final Context t = gate.execute(new DomExpressionRunnable(expression, context));
			assertTrue(TestUtils.equals(Arrays.asList("true"), t.getEndList()));
			assertEquals(0, semaphore.availablePermits());
		}
		{
			// 開始後に終了処理を実行した場合は trueExpression releaseExpression に委譲。
			context.setId("id2");
			final Context t = gate.execute(new DomExpressionRunnable(expression, context));
			assertTrue(TestUtils.equals(Arrays.asList("true", "release"), t.getEndList()));
			assertEquals(1, semaphore.availablePermits());
		}
		{
			// 開始前に終了処理を実行した場合は falseExpression に委譲。
			context.setId("id2");
			final Context t = gate.execute(new DomExpressionRunnable(expression, context));
			assertTrue(TestUtils.equals(Arrays.asList("false"), t.getEndList()));
			assertEquals(1, semaphore.availablePermits());
		}
		{
			// 開始前に途中処理を実行した場合は falseExpression に委譲。
			context.setId("id1");
			final Context t = gate.execute(new DomExpressionRunnable(expression, context));
			assertTrue(TestUtils.equals(Arrays.asList("false"), t.getEndList()));
			assertEquals(1, semaphore.availablePermits());
		}
		semaphore.acquire();
		{
			// 別セッション実行中に開始処理を実行した場合は falseExpression に委譲。
			context.setId("id0");
			final Context t = gate.execute(new DomExpressionRunnable(expression, context));
			assertTrue(TestUtils.equals(Arrays.asList("false"), t.getEndList()));
			assertEquals(0, semaphore.availablePermits());
		}
		{
			// 別セッション実行中に途中処理を実行した場合は falseExpression に委譲。
			context.setId("id1");
			final Context t = gate.execute(new DomExpressionRunnable(expression, context));
			assertTrue(TestUtils.equals(Arrays.asList("false"), t.getEndList()));
			assertEquals(0, semaphore.availablePermits());
		}
		{
			// 別セッション実行中に終了処理を実行した場合は falseExpression に委譲。
			context.setId("id2");
			final Context t = gate.execute(new DomExpressionRunnable(expression, context));
			assertTrue(TestUtils.equals(Arrays.asList("false"), t.getEndList()));
			assertEquals(0, semaphore.availablePermits());
		}
		semaphore.release();
		{
			// 開始処理を実行した場合は trueExpression に委譲。
			context.setId("id0");
			final Context t = gate.execute(new DomExpressionRunnable(expression, context));
			assertTrue(TestUtils.equals(Arrays.asList("true"), t.getEndList()));
			assertEquals(0, semaphore.availablePermits());
		}
		context.setRequest(null);
		{
			// 終了処理の trueExpression で異常が発生した場合は releaseExpression に委譲。
			context.setId("id2");
			final Context t = gate.execute(new DomExpressionRunnable(expression, context));
			assertTrue(TestUtils.equals(Arrays.asList("true", "release"), t.getEndList()));
			assertEquals(1, semaphore.availablePermits());
		}
		context.setRequest(new HashMap<String,Object>());
		{
			// 開始処理を実行した場合は trueExpression に委譲。
			context.setId("id0");
			final Context t = gate.execute(new DomExpressionRunnable(expression, context));
			assertTrue(TestUtils.equals(Arrays.asList("true"), t.getEndList()));
			assertEquals(0, semaphore.availablePermits());
		}
		context.setRequest(null);
		{
			// 途中処理の trueExpression で異常が発生した場合は releaseExpression に委譲。
			context.setId("id1");
			final Context t = gate.execute(new DomExpressionRunnable(expression, context));
			assertTrue(TestUtils.equals(Arrays.asList("true", "release"), t.getEndList()));
			assertEquals(1, semaphore.availablePermits());
		}
		{
			// 開始処理の trueExpression で異常が発生した場合は releaseExpression に委譲。
			context.setId("id0");
			final Context t = gate.execute(new DomExpressionRunnable(expression, context));
			assertTrue(TestUtils.equals(Arrays.asList("true", "release"), t.getEndList()));
			assertEquals(1, semaphore.availablePermits());
		}
		context.setRequest(new HashMap<String,Object>());
		{
			// 開始処理を実行した場合は trueExpression に委譲。
			context.setId("id0");
			final Context t = gate.execute(new DomExpressionRunnable(expression, context));
			assertTrue(TestUtils.equals(Arrays.asList("true"), t.getEndList()));
			assertEquals(0, semaphore.availablePermits());
		}
		{
			// 開始後に無関係な処理を実行した場合は releaseExpression trueExpression に委譲。
			context.setId("id3");
			final Context t = gate.execute(new DomExpressionRunnable(expression, context));
			assertTrue(TestUtils.equals(Arrays.asList("release", "true"), t.getEndList()));
			assertEquals(1, semaphore.availablePermits());
		}
		{
			// 開始前に無関係な処理を実行した場合は trueExpression に委譲。
			context.setId("id3");
			final Context t = gate.execute(new DomExpressionRunnable(expression, context));
			assertTrue(TestUtils.equals(Arrays.asList("true"), t.getEndList()));
			assertEquals(1, semaphore.availablePermits());
		}
	}
}
