/*
 * Copyright 2015 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.gradle.internal.operations;

import com.google.common.collect.Lists;
import com.google.common.util.concurrent.*;
import org.gradle.api.Action;
import org.gradle.internal.UncheckedException;

import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;

class DefaultOperationQueue<T> implements OperationQueue<T> {
    private final ListeningExecutorService executor;
    private final Action<? super T> worker;

    private final List<ListenableFuture> operations;
    private final List<Throwable> failures;
    private final AtomicBoolean cancelled;

    private boolean waitingForCompletion;

    DefaultOperationQueue(ExecutorService executor, Action<? super T> worker) {
        this.executor =  MoreExecutors.listeningDecorator(executor);
        this.worker = worker;
        this.operations = Lists.newLinkedList();
        this.failures = Lists.newCopyOnWriteArrayList();
        this.cancelled = new AtomicBoolean(false);
    }

    public void add(final T operation) {
        if (waitingForCompletion) {
            throw new IllegalStateException("OperationQueue cannot be reused once it has started completion.");
        }
        ListenableFuture<?> future = executor.submit(new Operation(operation));
        Futures.addCallback(future, new FailuresCallback());
        operations.add(future);
    }

    public void waitForCompletion() throws MultipleBuildOperationFailures {
        waitingForCompletion = true;

        CountDownLatch finished = new CountDownLatch(operations.size());
        for (ListenableFuture operation : operations) {
            Futures.addCallback(operation, new CompletionCallback(finished));
        }

        try {
            finished.await();
        } catch (InterruptedException e) {
            throw UncheckedException.throwAsUncheckedException(e);
        }

        // all operations are complete, check for errors
        if (!failures.isEmpty()) {
            throw new MultipleBuildOperationFailures(getFailureMessage(), failures);
        }
    }

    private String getFailureMessage() {
        if (failures.size()==1) {
            return "A build operation failed; see the error output for details.";
        }
        return "Multiple build operations failed; see the error output for details.";
    }

    private class FailuresCallback implements FutureCallback {
        public void onSuccess(Object result) {
            // don't care
        }

        public void onFailure(Throwable t) {
            if (!ignoredException(t)) {
                failures.add(t);
            }
            // TODO: Provide eager cancellation version of this too
            // For now, we'll keep continuing when we encounter a failure
            // cancel other operations ASAP
            // cancelled.set(true);
        }

        private boolean ignoredException(Throwable t) {
            return t instanceof CancellationException;
        }
    }

    private static class CompletionCallback implements FutureCallback {
        private final CountDownLatch finished;

        private CompletionCallback(CountDownLatch finished) {
            this.finished = finished;
        }

        public void onSuccess(Object result) {
            finished.countDown();
        }

        public void onFailure(Throwable t) {
            finished.countDown();
        }
    }

    class Operation implements Runnable {
        private final T operation;

        Operation(T operation) {
            this.operation = operation;
        }

        public void run() {
            if (!cancelled.get()) {
                worker.execute(operation);
            } else {
                throw new CancellationException("Cancelled by some other operation failure");
            }
        }

        public String toString() {
            return String.format("Worker %s for operation %s", worker, operation);
        }
    }
}

@@ -840,59 +840,8 @@
 st;%0A
-import java.util.concurrent.CancellationException;%0A
 impo
@@ -928,58 +928,8 @@
 ice;
-%0Aimport java.util.concurrent.atomic.AtomicBoolean;
 %0A%0Acl
@@ -1142,95 +1142,8 @@
 ons;
-%0A    private final List%3CThrowable%3E failures;%0A    private final AtomicBoolean cancelled;
 %0A%0A  
@@ -1415,116 +1415,8 @@
 ();%0A
-        this.failures = Lists.newCopyOnWriteArrayList();%0A        this.cancelled = new AtomicBoolean(false);%0A
     
@@ -1701,69 +1701,8 @@
 ));%0A
-        Futures.addCallback(future, new FailuresCallback());%0A
     
@@ -1919,24 +1919,93 @@
 ns.size());%0A
+        List%3CThrowable%3E failures = Lists.newCopyOnWriteArrayList();%0A%0A
         for 
@@ -2122,16 +2122,26 @@
 finished
+, failures
 ));%0A    
@@ -2475,16 +2475,24 @@
 Message(
+failures
 ), failu
@@ -2551,16 +2551,40 @@
 Message(
+List%3CThrowable%3E failures
 ) %7B%0A    
@@ -2814,22 +2814,31 @@
 ate 
-class Failures
+static class Completion
 Call
@@ -2883,576 +2883,99 @@
    p
-ublic void onSuccess(Object result) %7B%0A            // don't care%0A        %7D%0A%0A        public void onFailure(Throwable t) %7B%0A            if (!ignoredException(t)) %7B%0A                failures.add(t);%0A            %7D%0A            // TODO: Provide eager cancellation version of this too%0A            // For now, we'll keep continuing when we encounter a failure%0A            // cancel other operations ASAP%0A            // cancelled.set(true);%0A        %7D%0A%0A        private boolean ignoredException(Throwable t) %7B%0A            return t instanceof CancellationException;%0A        %7D
+rivate final CountDownLatch finished;%0A        private final List%3CThrowable%3E failures;%0A
 %0A    
-%7D%0A%0A
     
@@ -2978,37 +2978,24 @@
     private 
-static class 
 CompletionCa
@@ -3004,134 +3004,89 @@
 back
- implements FutureCallback %7B%0A        private final CountDownLatch finished;%0A%0A        private CompletionCallback(CountDownLatch
+(CountDownLatch finished, List%3CThrowable%3E failures) %7B%0A            this.finished =
  fin
@@ -3090,19 +3090,17 @@
 finished
-) %7B
+;
 %0A       
@@ -3110,34 +3110,34 @@
   this.f
-inished = finished
+ailures = failures
 ;%0A      
@@ -3304,32 +3304,61 @@
 ed.countDown();%0A
+            failures.add(t);%0A
         %7D%0A    %7D%0A
@@ -3564,203 +3564,34 @@
     
-if (!cancelled.get()) %7B%0A                worker.execute(operation);%0A            %7D else %7B%0A                throw new CancellationException(%22Cancelled by some other operation failure%22);%0A            %7D
+worker.execute(operation);
 %0A   

Remove leftover code for eager cancellation in DefaultOperationQueue