package org.fungsi.concurrent;

import org.fungsi.Either;
import org.fungsi.function.UnsafeFunction;

import java.time.Duration;
import java.time.Instant;
import java.util.Deque;
import java.util.LinkedList;
import java.util.Optional;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;

final class PromiseImpl<T> implements Promise<T> {
	private Optional<Either<T, Throwable>> opt = Optional.empty();
	private final ReadWriteLock lock = new ReentrantReadWriteLock();
	private final Deque<Consumer<Either<T, Throwable>>> listeners = new LinkedList<>();

	@Override
	public void set(Either<T, Throwable> e) {
		lock.writeLock().lock();
		try {
			this.opt = Optional.of(e);
		} finally {
			lock.writeLock().unlock();
		}

		while (!listeners.isEmpty()) {
			listeners.removeFirst().accept(e);
		}
	}

	@Override
	public Optional<Either<T, Throwable>> poll() {
		lock.readLock().lock();
		try {
			return opt;
		} finally {
			lock.readLock().unlock();
		}
	}
	@Override
	public T get() {
		Either<T, Throwable> result;

		while (true) {
			Optional<Either<T, Throwable>> value = poll();

			if (value.isPresent()) {
				result = value.get();
				break;
			} else {
				try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					throw new Error(e);
				}
			}
		}

		return Either.unsafe(result);
	}

	@Override
	public T get(Duration timeout) {
		Instant deadline = Instant.now().plus(timeout);
		Either<T, Throwable> result;

		while (true) {
			Optional<Either<T, Throwable>> value = poll();

			if (value.isPresent()) {
				result = value.get();
				break;
			} else {
				if (Instant.now().isAfter(deadline)) {
					throw new TimeoutException();
				}
				try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					throw new Error(e);
				}
			}
		}

		return Either.unsafe(result);
	}

	@Override
	public Future<T> respond(Consumer<Either<T, Throwable>> fn) {
		listeners.addFirst(fn);
		return this;
	}

	@Override
	public <TT> Future<TT> bind(UnsafeFunction<T, Future<TT>> fn) {
		return new BoundFuture<>(this, fn);
	}
}

@@ -689,24 +689,56 @@
 wable%3E e) %7B%0A
+%09%09if (opt.isPresent()) return;%0A%0A
 %09%09lock.write
@@ -2169,11 +2169,222 @@
  fn);%0A%09%7D
+%0A%0A%09@Override%0A%09public String toString() %7B%0A%09%09return %22Promise(%22 +%0A%09%09%09%09(isDone()%0A%09%09%09%09%09? isSuccess()%0A%09%09%09%09%09%09? %22success%22%0A%09%09%09%09%09%09: %22failure%22%0A%09%09%09%09%09: %22pending%22) +%0A%09%09%09%09%22, %22 +%0A%09%09%09%09%22listeners=%22 + listeners.size() +%0A%09%09%09%22)%22;%0A%09%7D
 %0A%7D%0A

add String representation & ignore `set' when done