package com.github.blemale.fuse;

import org.agrona.concurrent.*;

import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;

public class CircuitBreaker implements AutoCloseable {
    public static class CircuitBreakerOpenException extends RuntimeException {}

    private final StateMachine stateMachine;
    private final QueuedPipe<CallStatus> queuedPipe;
    private final AgentRunner agentRunner;

    public CircuitBreaker(Condition condition, Duration cooldown) {
        stateMachine = new StateMachine(condition, cooldown);
        queuedPipe = new ManyToOneConcurrentArrayQueue<>(1024);
        agentRunner =
                new AgentRunner(
                        new YieldingIdleStrategy(),
                        t -> {},
                        null,
                        new PipeConsumerAgent(queuedPipe, stateMachine)
                );
        AgentRunner.startOnThread(agentRunner);
    }

    public <T> CompletableFuture<T> executeAsync(Supplier<CompletableFuture<T>> action) {
        try {
            if (stateMachine.isExecutionAllowed()) {
                return action.get().whenCompleteAsync(this::reportResult);
            } else {
                report(CallStatus.OPEN);
                CompletableFuture<T> f = new CompletableFuture<>();
                f.completeExceptionally(new CircuitBreakerOpenException());
                return f;
            }
        } catch (Throwable ex) {
            report(CallStatus.FAILURE);
            CompletableFuture<T> f = new CompletableFuture<>();
            f.completeExceptionally(ex);
            return f;
        }
    }

    public <T> T execute(Supplier<T> action) {
        try {
            if (stateMachine.isExecutionAllowed()) {
                return action.get();
            } else {
                report(CallStatus.OPEN);
                throw new CircuitBreakerOpenException();
            }
        } catch (Throwable ex) {
            report(CallStatus.FAILURE);
            throw ex;
        }
    }

    public void close() {
        agentRunner.close();
    }


    private <T> void reportResult(T t, Throwable ex) {
        if (ex != null) {
            report(CallStatus.FAILURE);
        } else {
            report(CallStatus.SUCCESS);
        }
    }

    private void report(CallStatus callStatus) {
        while (!queuedPipe.offer(callStatus));
    }
}

@@ -304,17 +304,120 @@
 eption %7B
-%7D
+%0A        private static CircuitBreakerOpenException INSTANCE = new CircuitBreakerOpenException();%0A    %7D%0A
 %0A%0A    pr
@@ -1464,20 +1464,16 @@
 ionally(
-new 
 CircuitB
@@ -1491,18 +1491,25 @@
 xception
-()
+.INSTANCE
 );%0A     
@@ -1995,20 +1995,16 @@
   throw 
-new 
 CircuitB
@@ -2018,26 +2018,33 @@
 penException
-()
+.INSTANCE
 ;%0A          

Use a static instance of CircuitBreakerOpenException to reduce allocation