package graphql.execution;

import graphql.Directives;
import graphql.ExecutionResult;
import graphql.ExecutionResultImpl;
import graphql.GraphQLError;
import graphql.Internal;
import graphql.language.Field;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;

import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;

/**
 * This provides support for @defer directives on fields that mean that results will be sent AFTER
 * the main result is sent via a Publisher stream.
 */
@Internal
public class DeferSupport implements Publisher<ExecutionResult> {

    private final Deque<Supplier<CompletableFuture<ExecutionResult>>> deferredCalls = new ConcurrentLinkedDeque<>();
    private final List<GraphQLError> errorsEncountered = new ArrayList<>();
    private final AtomicBoolean deferDetected = new AtomicBoolean(false);

    public boolean checkForDeferDirective(List<Field> currentField) {
        for (Field field : currentField) {
            if (field.getDirective(Directives.DeferDirective.getName()) != null) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void subscribe(Subscriber<? super ExecutionResult> subscriber) {
        subscriber.onSubscribe(emptySubscription());
        boolean ok = drainDeferredCalls(subscriber);
        if (ok) {
            subscriber.onComplete();
        }
    }

    private boolean drainDeferredCalls(Subscriber<? super ExecutionResult> subscriber) {
        while (!deferredCalls.isEmpty()) {
            Supplier<CompletableFuture<ExecutionResult>> deferredExecution = deferredCalls.pop();
            try {
                CompletableFuture<ExecutionResult> future = deferredExecution.get();
                //
                // this becomes a blocking call on the deferred resolveField value.  This and the use of the queue
                // means we will resolve them in "encountered" order.  We could do it async and hence in any order
                //
                ExecutionResult executionResult = future.get();
                executionResult = addAnyErrorsEncountered(executionResult);
                subscriber.onNext(executionResult);
            } catch (Exception e) {
                subscriber.onError(e);
                return false;
            }
        }
        return true;
    }

    private ExecutionResult addAnyErrorsEncountered(ExecutionResult executionResult) {
        synchronized (errorsEncountered) {
            ExecutionResultImpl sourceResult = (ExecutionResultImpl) executionResult;
            ExecutionResultImpl.Builder builder = ExecutionResultImpl.newExecutionResult().from(sourceResult);

            builder.addErrors(errorsEncountered);
            errorsEncountered.clear();

            return builder.build();
        }
    }


    public void onFetcherError(GraphQLError error) {
        synchronized (errorsEncountered) {
            errorsEncountered.add(error);
        }
    }

    public void enqueue(Supplier<CompletableFuture<ExecutionResult>> deferredCall) {
        deferDetected.set(true);
        deferredCalls.offer(deferredCall);
    }

    public boolean isDeferDetected() {
        return deferDetected.get();
    }

    public Publisher<ExecutionResult> getPublisher() {
        return this;
    }

    private Subscription emptySubscription() {
        return new Subscription() {
            @Override
            public void request(long n) {
            }

            @Override
            public void cancel() {
            }
        };
    }
}

@@ -1515,21 +1515,8 @@
     
- boolean ok =
  dra
@@ -1548,73 +1548,8 @@
 r);%0A
-        if (ok) %7B%0A            subscriber.onComplete();%0A        %7D%0A
     
@@ -1563,23 +1563,20 @@
 private 
-boolean
+void
  drainDe
@@ -1649,16 +1649,12 @@
     
-while
+if
  (
-!
 defe
@@ -1672,32 +1672,75 @@
 sEmpty()) %7B%0A    
+        subscriber.onComplete();%0A        %7D%0A
         Supplier
@@ -1793,16 +1793,24 @@
 xecution
+Supplier
  = defer
@@ -1829,34 +1829,8 @@
 ();%0A
-            try %7B%0A        
     
@@ -1894,16 +1894,24 @@
 xecution
+Supplier
 .get();%0A
@@ -1922,248 +1922,145 @@
     
-        //%0A                // this becomes a blocking call on the deferred resolveField value.  This and the use of the queue%0A                // means we will resolve them in %22encountered%22 order.  We could do it async and hence in any order
+future.whenComplete((executionResult, exception) -%3E %7B%0A            if (exception != null) %7B%0A                subscriber.onError(exception);
 %0A   
@@ -2076,10 +2076,15 @@
     
-//
+return;
 %0A   
@@ -2096,64 +2096,10 @@
     
-    ExecutionResult executionResult = future.get();%0A    
+%7D%0A
     
@@ -2170,36 +2170,32 @@
 t);%0A            
-    
 subscriber.onNex
@@ -2230,144 +2230,50 @@
     
-%7D catch (Exception e) %7B%0A                subscriber.onError(e);%0A                return false;%0A            %7D%0A        %7D%0A        return true
+drainDeferredCalls(subscriber);%0A        %7D)
 ;%0A  

make drainDeferredCalls non blocking