/******************************************************************************
 * Copyright (c) 2006, 2010 VMware Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution. 
 * The Eclipse Public License is available at 
 * http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0
 * is available at http://www.opensource.org/licenses/apache2.0.php.
 * You may elect to redistribute this code under either of these licenses. 
 * 
 * Contributors:
 *   VMware Inc.
 *****************************************************************************/

package org.eclipse.gemini.blueprint.internal.service.interceptor;

import junit.framework.TestCase;

import org.eclipse.gemini.blueprint.service.importer.support.internal.support.DefaultRetryCallback;
import org.eclipse.gemini.blueprint.service.importer.support.internal.support.RetryCallback;
import org.eclipse.gemini.blueprint.service.importer.support.internal.support.RetryTemplate;

/**
 * 
 * @author Costin Leau
 * 
 */
public class RetryTemplateTest extends TestCase {

	private static class EventRecorderRetryTemplate extends RetryTemplate {

		private long missingTarget = 0;
		private long missingTargetInvocation = 0;
		private long successfulStop = 0;
		private long failedStop = 0;


		/**
		 * Constructs a new <code>EventRecorderRetryTemplate</code> instance.
		 * 
		 * @param waitTime
		 * @param notificationLock
		 */
		public EventRecorderRetryTemplate(long waitTime, Object notificationLock) {
			super(waitTime, notificationLock);
		}

		/**
		 * Constructs a new <code>EventRecorderRetryTemplate</code> instance.
		 * 
		 * @param notificationLock
		 */
		public EventRecorderRetryTemplate(Object notificationLock) {
			super(notificationLock);
		}

		protected void callbackFailed(long stop) {
			setFailedStop(stop);
		}

		protected void callbackSucceeded(long stop) {
			setSuccessfulStop(stop);
		}

		protected synchronized void onMissingTarget() {
			missingTargetInvocation++;
			setMissingTarget(System.currentTimeMillis());
		}

		/**
		 * Returns the missingTarget.
		 * 
		 * @return Returns the missingTarget
		 */
		public synchronized long getMissingTarget() {
			return missingTarget;
		}

		public synchronized long getMissingTargetInvocation() {
			return missingTarget;
		}

		/**
		 * @param missingTarget The missingTarget to set.
		 */
		public synchronized void setMissingTarget(long missingTarget) {
			this.missingTarget = missingTarget;
		}

		/**
		 * Returns the successfulStop.
		 * 
		 * @return Returns the successfulStop
		 */
		public synchronized long getSuccessfulStop() {
			return successfulStop;
		}

		/**
		 * @param successfulStop The successfulStop to set.
		 */
		public synchronized void setSuccessfulStop(long successfulStop) {
			this.successfulStop = successfulStop;
		}

		/**
		 * Returns the failedStop.
		 * 
		 * @return Returns the failedStop
		 */
		public synchronized long getFailedStop() {
			return failedStop;
		}

		/**
		 * @param failedStop The failedStop to set.
		 */
		public synchronized void setFailedStop(long failedStop) {
			this.failedStop = failedStop;
		}
	}


	private EventRecorderRetryTemplate template;
	private RetryCallback callback;
	private Object monitor;


	private static class CountingCallback implements RetryCallback<Object> {

		private int count = 0;
		public final static int WAKES_THRESHOLD = 7;


		public synchronized Object doWithRetry() {
			count++;
			return null;
		}

		public boolean isComplete(Object result) {
			// postpone completion X times
			if (getCount() == WAKES_THRESHOLD)
				return true;
			return false;
		}

		public synchronized int getCount() {
			return count;
		}
	}

	private static class FailingCallback implements RetryCallback<Object> {

		private static Object VALUE = new Object();


		public Object doWithRetry() {
			return VALUE;
		}

		public boolean isComplete(Object result) {
			return false;
		}

	}


	protected void setUp() throws Exception {
		monitor = new Object();
		callback = new DefaultRetryCallback<Object>() {

			public Object doWithRetry() {
				return null;
			}
		};
	}

	protected void tearDown() throws Exception {
		template = null;
	}

	// reset test - a separate thread reset a template that waits for a long time
	public void testTemplateReset() throws Exception {
		long initialWaitTime = 20 * 1000;
		template = new EventRecorderRetryTemplate(initialWaitTime, monitor);

		long start = System.currentTimeMillis();

		Runnable shutdownTask = new Runnable() {

			public void run() {
				// wait a bit

				try {
					Thread.sleep(3 * 1000);
				}
				catch (InterruptedException e) {
					throw new RuntimeException(e);
				}
				System.out.println("About to reset template...");
				template.reset(0);
				System.out.println("Resetted template...");
			}
		};

		Thread th = new Thread(shutdownTask, "shutdown-thread");
		th.start();
		assertNull(template.execute(callback));
		long stop = System.currentTimeMillis();

		long waitingTime = stop - start;
		assertTrue("Template not stopped in time", waitingTime < initialWaitTime);
	}

	// simple test that keeps waking up the template for a number of times
	// the callback counts the invocations and then returns nicely
	public void testSpuriousWakeup() throws Exception {
		// wait 20s
		long initialWaitTime = 20 * 1000;
		final CountingCallback callback = new CountingCallback();
		template = new EventRecorderRetryTemplate(initialWaitTime, monitor);

		Runnable spuriousTask = new Runnable() {

			public void run() {
				try {
					// start sending notifications to the monitor
					do {
						// sleep for a while
						Thread.sleep(50);

						synchronized (monitor) {
							monitor.notifyAll();
						}
					} while (!callback.isComplete(null));
				}
				catch (InterruptedException e) {
					throw new RuntimeException(e);
				}
			}
		};

		Thread th = new Thread(spuriousTask, "wake-up thread");
		th.start();

		long start = System.currentTimeMillis();
		template.execute(callback);
		long stop = System.currentTimeMillis();
		long waited = stop - start;

		assertTrue(template.getMissingTarget() >= start);
		assertEquals(1, template.missingTargetInvocation);
		assertEquals("successful callback does not end in failure", 0, template.failedStop);

		assertEquals(CountingCallback.WAKES_THRESHOLD, callback.getCount());
		assertTrue(waited < initialWaitTime);
	}

	// test that checks the template keeps waiting until the waiting period elapses
	// if the callback returns falls even if there are (plenty) of wakeups
	public void testFailingCallbackWithSpuriousWakeups() throws Exception {
		// wait 10 secs
		long initialWaitTime = 10 * 1000;
		template = new EventRecorderRetryTemplate(initialWaitTime, monitor);

		Runnable spuriousTask = new Runnable() {

			public void run() {
				try {
					// start sending notifications to the monitor
					do {
						// sleep for a while
						Thread.sleep(50);

						synchronized (monitor) {
							monitor.notifyAll();
						}
					} while (!callback.isComplete(null));
				}
				catch (InterruptedException e) {
					throw new RuntimeException(e);
				}
			}
		};

		Thread th = new Thread(spuriousTask, "wake-up thread");
		th.start();

		callback = new FailingCallback();
		long start = System.currentTimeMillis();
		assertNull("failing callback should always return null", template.execute(callback));
		long stop = System.currentTimeMillis();

		assertEquals("failed callback does not end succesful", 0, template.successfulStop);
		long waited = stop - start;

		assertTrue(waited >= template.getFailedStop());

		assertTrue(template.getMissingTarget() >= start);
		assertEquals(1, template.missingTargetInvocation);

		assertTrue(waited >= initialWaitTime - 3);
	}

	// test the retry with a thread that keeps waking up the template
	// then does a reset
	// the test checks the event method
	public void testSpuriousWakeupWithReset() throws Exception {

		long initialWaitTime = 30 * 1000;
		template = new EventRecorderRetryTemplate(initialWaitTime, monitor);

		Runnable spuriousAndResetTask = new Runnable() {

			public void run() {

				try {

					// wait a bit
					int count = 0;
					// start sending notifications to the monitor
					do {
						// sleep for a while
						Thread.sleep(50);

						synchronized (monitor) {
							monitor.notifyAll();
						}
						count++;
					} while (count > 100);

					// sent enough, 
					System.out.println("About to reset template...");
					template.reset(0);
					System.out.println("Resetted template...");
				}
				catch (InterruptedException e) {
					throw new RuntimeException(e);
				}
			}
		};

		Thread th = new Thread(spuriousAndResetTask, "spurious_reset-thread");
		th.start();
		long start = System.currentTimeMillis();
		assertNull(template.execute(callback));
		long stop = System.currentTimeMillis();

		long waitingTime = stop - start;
		assertTrue("Template not stopped in time", waitingTime < initialWaitTime);

		assertTrue(template.getMissingTarget() >= start);
		assertEquals(1, template.missingTargetInvocation);
		assertEquals("failed callback does not end succesful", 0, template.successfulStop);
		assertTrue(waitingTime >= template.getFailedStop());
	}
}