/*
 * Copyright 2006-2010 the original author or authors.
 *
 * 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.springframework.batch.poller;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * A {@link Poller} that uses the callers thread to poll for a result as soon as
 * it is asked for. This is often appropriate if you expect a result relatively
 * quickly, or if there is only one such result expected (otherwise it is more
 * efficient to use a background thread to do the polling).
 * 
 * @author Dave Syer
 * 
 * @param <S> the type of the result
 */
public class DirectPoller<S> implements Poller<S> {

	private final long interval;

	public DirectPoller(long interval) {
		this.interval = interval;
	}

	/**
	 * Get a future for a non-null result from the callback. Only when the
	 * result is asked for (using {@link Future#get()} or
	 * {@link Future#get(long, TimeUnit)} will the polling actually start.
	 * 
	 * @see Poller#poll(Callable)
	 */
    @Override
	public Future<S> poll(Callable<S> callable) throws Exception {
		return new DirectPollingFuture<S>(interval, callable);
	}

	private static class DirectPollingFuture<S> implements Future<S> {

		private final long startTime = System.currentTimeMillis();

		private volatile boolean cancelled;

		private volatile S result = null;

		private final long interval;

		private final Callable<S> callable;

		public DirectPollingFuture(long interval, Callable<S> callable) {
			this.interval = interval;
			this.callable = callable;
		}

        @Override
		public boolean cancel(boolean mayInterruptIfRunning) {
			cancelled = true;
			return true;
		}

        @Override
		public S get() throws InterruptedException, ExecutionException {
			try {
				return get(-1, TimeUnit.MILLISECONDS);
			}
			catch (TimeoutException e) {
				throw new IllegalStateException("Unexpected timeout waiting for result", e);
			}
		}

        @Override
		public S get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {

			try {
				result = callable.call();
			}
			catch (Exception e) {
				throw new ExecutionException(e);
			}

			Long nextExecutionTime = startTime + interval;
			long currentTimeMillis = System.currentTimeMillis();
			long timeoutMillis = TimeUnit.MILLISECONDS.convert(timeout, unit);

			while (result == null && !cancelled) {

				long delta = nextExecutionTime - startTime;
				if (delta >= timeoutMillis && timeoutMillis > 0) {
					throw new TimeoutException("Timed out waiting for task to return non-null result");
				}

				if (nextExecutionTime > currentTimeMillis) {
					Thread.sleep(nextExecutionTime - currentTimeMillis);
				}

				currentTimeMillis = System.currentTimeMillis();
				nextExecutionTime = currentTimeMillis + interval;

				try {
					result = callable.call();
				}
				catch (Exception e) {
					throw new ExecutionException(e);
				}

			}

			return result;

		}

        @Override
		public boolean isCancelled() {
			return cancelled;
		}

        @Override
		public boolean isDone() {
			return cancelled || result != null;
		}

	}

}
