/*
 * Copyright (c) OSGi Alliance (2014). All Rights Reserved.
 * 
 * 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.osgi.util.promise;

import java.lang.reflect.InvocationTargetException;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import org.osgi.util.function.Function;
import org.osgi.util.function.Predicate;

/**
 * Promise implementation.
 * 
 * <p>
 * This class is not used directly by clients. Clients should use
 * {@link Deferred} to create a resolvable {@link Promise}.
 * 
 * @param <T> The result type associated with the Promise.
 * 
 * @ThreadSafe
 * @author $Id: ca2be5e80ac61cca849ae7373d42bde4fade7466 $
 */
final class PromiseImpl<T> implements Promise<T> {
	/**
	 * A ConcurrentLinkedQueue to hold the callbacks for this Promise, so no
	 * additional synchronization is required to write to or read from the
	 * queue.
	 */
	private final ConcurrentLinkedQueue<Runnable>	callbacks;
	/**
	 * A CountDownLatch to manage the resolved state of this Promise.
	 * 
	 * <p>
	 * This object is used as the synchronizing object to provide a critical
	 * section in {@link #resolve(Object, Throwable)} so that only a single
	 * thread can write the resolved state variables and open the latch.
	 * 
	 * <p>
	 * The resolved state variables, {@link #value} and {@link #fail}, must only
	 * be written when the latch is closed (getCount() != 0) and must only be
	 * read when the latch is open (getCount() == 0). The latch state must
	 * always be checked before writing or reading since the resolved state
	 * variables' memory consistency is guarded by the latch.
	 */
	private final CountDownLatch					resolved;
	/**
	 * The value of this Promise if successfully resolved.
	 * 
	 * @GuardedBy("resolved")
	 * @see #resolved
	 */
	private T										value;
	/**
	 * The failure of this Promise if resolved with a failure or {@code null} if
	 * successfully resolved.
	 * 
	 * @GuardedBy("resolved")
	 * @see #resolved
	 */
	private Throwable								fail;

	/**
	 * Initialize this Promise.
	 */
	PromiseImpl() {
		callbacks = new ConcurrentLinkedQueue<Runnable>();
		resolved = new CountDownLatch(1);
	}

	/**
	 * Initialize and resolve this Promise.
	 * 
	 * @param v The value of this resolved Promise.
	 * @param f The failure of this resolved Promise.
	 */
	PromiseImpl(T v, Throwable f) {
		value = v;
		fail = f;
		callbacks = new ConcurrentLinkedQueue<Runnable>();
		resolved = new CountDownLatch(0);
	}

	/**
	 * Resolve this Promise.
	 * 
	 * @param v The value of this Promise.
	 * @param f The failure of this Promise.
	 */
	void resolve(T v, Throwable f) {
		// critical section: only one resolver at a time
		synchronized (resolved) {
			if (resolved.getCount() == 0) {
				throw new IllegalStateException("Already resolved");
			}
			/*
			 * The resolved state variables must be set before opening the
			 * latch. This safely publishes them to be read by other threads
			 * that must verify the latch is open before reading.
			 */
			value = v;
			fail = f;
			resolved.countDown();
		}
		notifyCallbacks(); // call any registered callbacks
	}

	/**
	 * Call any registered callbacks if this Promise is resolved.
	 */
	private void notifyCallbacks() {
		if (resolved.getCount() != 0) {
			return; // return if not resolved
		}

		/*
		 * Note: multiple threads can be in this method removing callbacks from
		 * the queue and calling them, so the order in which callbacks are
		 * called cannot be specified.
		 */
		for (Runnable callback = callbacks.poll(); callback != null; callback = callbacks.poll()) {
			try {
				callback.run();
			} catch (Throwable t) {
				Logger.logCallbackException(t);
			}
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public boolean isDone() {
		return resolved.getCount() == 0;
	}

	/**
	 * {@inheritDoc}
	 */
	public T getValue() throws InvocationTargetException, InterruptedException {
		resolved.await();
		if (fail == null) {
			return value;
		}
		throw new InvocationTargetException(fail);
	}

	/**
	 * {@inheritDoc}
	 */
	public Throwable getFailure() throws InterruptedException {
		resolved.await();
		return fail;
	}

	/**
	 * {@inheritDoc}
	 */
	public Promise<T> onResolve(Runnable callback) {
		callbacks.offer(callback);
		notifyCallbacks(); // call any registered callbacks
		return this;
	}

	/**
	 * {@inheritDoc}
	 */
	public <R> Promise<R> then(Success<? super T, ? extends R> success, Failure failure) {
		PromiseImpl<R> chained = new PromiseImpl<R>();
		onResolve(new Then<R>(chained, success, failure));
		return chained;
	}

	/**
	 * {@inheritDoc}
	 */
	public <R> Promise<R> then(Success<? super T, ? extends R> success) {
		return then(success, null);
	}

	/**
	 * A callback used to chain promises for the {@link #then(Success, Failure)}
	 * method.
	 * 
	 * @Immutable
	 */
	private final class Then<R> implements Runnable {
		private final PromiseImpl<R>			chained;
		private final Success<T, ? extends R>	success;
		private final Failure					failure;

		@SuppressWarnings("unchecked")
		Then(PromiseImpl<R> chained, Success<? super T, ? extends R> success, Failure failure) {
			this.chained = chained;
			this.success = (Success<T, ? extends R>) success;
			this.failure = failure;
		}

		public void run() {
			Throwable f;
			final boolean interrupted = Thread.interrupted();
			try {
				f = getFailure();
			} catch (Throwable e) {
				f = e; // propagate new exception
			} finally {
				if (interrupted) { // restore interrupt status
					Thread.currentThread().interrupt();
				}
			}
			if (f != null) {
				if (failure != null) {
					try {
						failure.fail(PromiseImpl.this);
					} catch (Throwable e) {
						f = e; // propagate new exception
					}
				}
				// fail chained
				chained.resolve(null, f);
				return;
			}
			Promise<? extends R> returned = null;
			if (success != null) {
				try {
					returned = success.call(PromiseImpl.this);
				} catch (Throwable e) {
					chained.resolve(null, e);
					return;
				}
			}
			if (returned == null) {
				// resolve chained with null value
				chained.resolve(null, null);
			} else {
				// resolve chained when returned promise is resolved
				returned.onResolve(new Chain<R>(chained, returned));
			}
		}
	}

	/**
	 * A callback used to resolve the chained Promise when the Promise promise
	 * is resolved.
	 * 
	 * @Immutable
	 */
	private final static class Chain<R> implements Runnable {
		private final PromiseImpl<R>		chained;
		private final Promise<? extends R>	promise;
		private final Throwable				failure;

		Chain(PromiseImpl<R> chained, Promise<? extends R> promise) {
			this.chained = chained;
			this.promise = promise;
			this.failure = null;
		}

		Chain(PromiseImpl<R> chained, Promise<? extends R> promise, Throwable failure) {
			this.chained = chained;
			this.promise = promise;
			this.failure = failure;
		}

		public void run() {
			R value = null;
			Throwable f;
			final boolean interrupted = Thread.interrupted();
			try {
				f = promise.getFailure();
				if (f == null) {
					value = promise.getValue();
				} else if (failure != null) {
					f = failure;
				}
			} catch (Throwable e) {
				f = e; // propagate new exception
			} finally {
				if (interrupted) { // restore interrupt status
					Thread.currentThread().interrupt();
				}
			}
			chained.resolve(value, f);
		}
	}

	/**
	 * Resolve this Promise with the specified Promise.
	 * 
	 * <p>
	 * If the specified Promise is successfully resolved, this Promise is
	 * resolved with the value of the specified Promise. If the specified
	 * Promise is resolved with a failure, this Promise is resolved with the
	 * failure of the specified Promise.
	 * 
	 * @param with A Promise whose value or failure must be used to resolve this
	 *        Promise. Must not be {@code null}.
	 * @return A Promise that is resolved only when this Promise is resolved by
	 *         the specified Promise. The returned Promise must be successfully
	 *         resolved with the value {@code null}, if this Promise was
	 *         resolved by the specified Promise. The returned Promise must be
	 *         resolved with a failure of {@link IllegalStateException}, if this
	 *         Promise was already resolved when the specified Promise was
	 *         resolved.
	 */
	Promise<Void> resolveWith(Promise<? extends T> with) {
		PromiseImpl<Void> chained = new PromiseImpl<Void>();
		ResolveWith resolveWith = new ResolveWith(chained);
		with.then(resolveWith, resolveWith);
		return chained;
	}

	/**
	 * A callback used to resolve this Promise with another Promise for the
	 * {@link PromiseImpl#resolveWith(Promise)} method.
	 * 
	 * @Immutable
	 */
	private final class ResolveWith implements Success<T, Void>, Failure {
		private final PromiseImpl<Void>	chained;

		ResolveWith(PromiseImpl<Void> chained) {
			this.chained = chained;
		}

		public Promise<Void> call(Promise<T> with) throws Exception {
			try {
				resolve(with.getValue(), null);
			} catch (Throwable e) {
				chained.resolve(null, e);
				return null;
			}
			chained.resolve(null, null);
			return null;
		}

		public void fail(Promise<?> with) throws Exception {
			try {
				resolve(null, with.getFailure());
			} catch (Throwable e) {
				chained.resolve(null, e);
				return;
			}
			chained.resolve(null, null);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public Promise<T> filter(Predicate<? super T> predicate) {
		return then(new Filter<T>(predicate));
	}

	/**
	 * A callback used by the {@link PromiseImpl#filter(Predicate)} method.
	 * 
	 * @Immutable
	 */
	private static final class Filter<T> implements Success<T, T> {
		private final Predicate<? super T>	predicate;

		Filter(Predicate<? super T> predicate) {
			this.predicate = requireNonNull(predicate);
		}

		public Promise<T> call(Promise<T> resolved) throws Exception {
			if (predicate.test(resolved.getValue())) {
				return resolved;
			}
			throw new NoSuchElementException();
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public <R> Promise<R> map(Function<? super T, ? extends R> mapper) {
		return then(new Map<T, R>(mapper));
	}

	/**
	 * A callback used by the {@link PromiseImpl#map(Function)} method.
	 * 
	 * @Immutable
	 */
	private static final class Map<T, R> implements Success<T, R> {
		private final Function<? super T, ? extends R>	mapper;

		Map(Function<? super T, ? extends R> mapper) {
			this.mapper = requireNonNull(mapper);
		}

		public Promise<R> call(Promise<T> resolved) throws Exception {
			return new PromiseImpl<R>(mapper.apply(resolved.getValue()), null);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public <R> Promise<R> flatMap(Function<? super T, Promise<? extends R>> mapper) {
		return then(new FlatMap<T, R>(mapper));
	}

	/**
	 * A callback used by the {@link PromiseImpl#flatMap(Function)} method.
	 * 
	 * @Immutable
	 */
	private static final class FlatMap<T, R> implements Success<T, R> {
		private final Function<? super T, Promise<? extends R>>	mapper;

		FlatMap(Function<? super T, Promise<? extends R>> mapper) {
			this.mapper = requireNonNull(mapper);
		}

		@SuppressWarnings("unchecked")
		public Promise<R> call(Promise<T> resolved) throws Exception {
			return (Promise<R>) mapper.apply(resolved.getValue());
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public Promise<T> recover(Function<Promise<?>, ? extends T> recovery) {
		PromiseImpl<T> chained = new PromiseImpl<T>();
		Recover<T> recover = new Recover<T>(chained, recovery);
		then(recover, recover);
		return chained;
	}

	/**
	 * A callback used by the {@link PromiseImpl#recover(Function)} method.
	 * 
	 * @Immutable
	 */
	private static final class Recover<T> implements Success<T, Void>, Failure {
		private final PromiseImpl<T>					chained;
		private final Function<Promise<?>, ? extends T>	recovery;

		Recover(PromiseImpl<T> chained, Function<Promise<?>, ? extends T> recovery) {
			this.chained = chained;
			this.recovery = requireNonNull(recovery);
		}

		public Promise<Void> call(Promise<T> resolved) throws Exception {
			T value;
			try {
				value = resolved.getValue();
			} catch (Throwable e) {
				chained.resolve(null, e);
				return null;
			}
			chained.resolve(value, null);
			return null;
		}

		public void fail(Promise<?> resolved) throws Exception {
			T recovered;
			Throwable failure;
			try {
				recovered = recovery.apply(resolved);
				failure = resolved.getFailure();
			} catch (Throwable e) {
				chained.resolve(null, e);
				return;
			}
			if (recovered == null) {
				chained.resolve(null, failure);
			} else {
				chained.resolve(recovered, null);
			}
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public Promise<T> recoverWith(Function<Promise<?>, Promise<? extends T>> recovery) {
		PromiseImpl<T> chained = new PromiseImpl<T>();
		RecoverWith<T> recoverWith = new RecoverWith<T>(chained, recovery);
		then(recoverWith, recoverWith);
		return chained;
	}

	/**
	 * A callback used by the {@link PromiseImpl#recoverWith(Function)} method.
	 * 
	 * @Immutable
	 */
	private static final class RecoverWith<T> implements Success<T, Void>, Failure {
		private final PromiseImpl<T>								chained;
		private final Function<Promise<?>, Promise<? extends T>>	recovery;

		RecoverWith(PromiseImpl<T> chained, Function<Promise<?>, Promise<? extends T>> recovery) {
			this.chained = chained;
			this.recovery = requireNonNull(recovery);
		}

		public Promise<Void> call(Promise<T> resolved) throws Exception {
			T value;
			try {
				value = resolved.getValue();
			} catch (Throwable e) {
				chained.resolve(null, e);
				return null;
			}
			chained.resolve(value, null);
			return null;
		}

		public void fail(Promise<?> resolved) throws Exception {
			Promise<? extends T> recovered;
			Throwable failure;
			try {
				recovered = recovery.apply(resolved);
				failure = resolved.getFailure();
			} catch (Throwable e) {
				chained.resolve(null, e);
				return;
			}
			if (recovered == null) {
				chained.resolve(null, failure);
			} else {
				recovered.onResolve(new Chain<T>(chained, recovered));
			}
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public Promise<T> fallbackTo(Promise<? extends T> fallback) {
		PromiseImpl<T> chained = new PromiseImpl<T>();
		FallbackTo<T> fallbackTo = new FallbackTo<T>(chained, fallback);
		then(fallbackTo, fallbackTo);
		return chained;
	}

	/**
	 * A callback used by the {@link PromiseImpl#fallbackTo(Promise)} method.
	 * 
	 * @Immutable
	 */
	private static final class FallbackTo<T> implements Success<T, Void>, Failure {
		private final PromiseImpl<T>		chained;
		private final Promise<? extends T>	fallback;

		FallbackTo(PromiseImpl<T> chained, Promise<? extends T> fallback) {
			this.chained = chained;
			this.fallback = requireNonNull(fallback);
		}

		public Promise<Void> call(Promise<T> resolved) throws Exception {
			T value;
			try {
				value = resolved.getValue();
			} catch (Throwable e) {
				chained.resolve(null, e);
				return null;
			}
			chained.resolve(value, null);
			return null;
		}

		public void fail(Promise<?> resolved) throws Exception {
			Throwable failure;
			try {
				failure = resolved.getFailure();
			} catch (Throwable e) {
				chained.resolve(null, e);
				return;
			}
			fallback.onResolve(new Chain<T>(chained, fallback, failure));
		}
	}

	static <V> V requireNonNull(V value) {
		if (value != null) {
			return value;
		}
		throw new NullPointerException();
	}

	/**
	 * Use the lazy initialization holder class idiom to delay creating a Logger
	 * until we actually need it.
	 */
	private static final class Logger {
		private final static java.util.logging.Logger	LOGGER;
		static {
			LOGGER = java.util.logging.Logger.getLogger(PromiseImpl.class.getName());
		}

		static void logCallbackException(Throwable t) {
			LOGGER.log(java.util.logging.Level.WARNING, "Exception from Promise callback", t);
		}
	}
}
