/**
 * 
 */
package com.github.phantomthief.failover.util;

import static com.google.common.base.Throwables.propagate;
import static com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly;
import static java.util.concurrent.TimeUnit.MILLISECONDS;

import java.util.HashSet;
import java.util.Set;
import java.util.function.Predicate;

import com.github.phantomthief.failover.Failover;
import com.github.phantomthief.failover.exception.NoAvailableResourceException;
import com.github.phantomthief.util.ThrowableConsumer;
import com.github.phantomthief.util.ThrowableFunction;

/**
 * @author w.vela
 */
public class FailoverUtils {

    private FailoverUtils() {
        throw new UnsupportedOperationException();
    }

    public static <T, R, X extends Throwable> R supplyWithRetry(int maxRetryTimes,
            long sleepBetweenRetryMs, Failover<T> failover, ThrowableFunction<T, R, X> func,
            Predicate<Throwable> failChecker) throws X {
        Set<T> failed = new HashSet<>();
        Throwable lastError = null;
        for (int i = 0; i < maxRetryTimes; i++) {
            T oneAvailable = failover.getOneAvailableExclude(failed);
            if (oneAvailable != null) {
                try {
                    R result = func.apply(oneAvailable);
                    failover.success(oneAvailable);
                    return result;
                } catch (Throwable e) {
                    if (failChecker == null || failChecker.test(e)) {
                        failover.fail(oneAvailable);
                        failed.add(oneAvailable);
                        if (sleepBetweenRetryMs > 0) {
                            sleepUninterruptibly(sleepBetweenRetryMs, MILLISECONDS);
                        }
                        lastError = e;
                        continue;
                    } else {
                        failover.success(oneAvailable);
                    }
                    throw e;
                }
            } else {
                throw new NoAvailableResourceException();
            }
        }
        throw propagate(lastError);
    }

    public static <T, R, X extends Throwable> R supply(Failover<T> failover,
            ThrowableFunction<T, R, X> func, Predicate<Throwable> failChecker) throws X {
        T oneAvailable = failover.getOneAvailable();
        if (oneAvailable != null) {
            try {
                R result = func.apply(oneAvailable);
                failover.success(oneAvailable);
                return result;
            } catch (Throwable e) {
                if (failChecker == null || failChecker.test(e)) {
                    failover.fail(oneAvailable);
                } else {
                    failover.success(oneAvailable);
                }
                throw e;
            }
        } else {
            throw new NoAvailableResourceException();
        }
    }

    public static <T, X extends Throwable> void runWithRetry(int maxRetryTimes,
            long sleepBetweenRetryMs, Failover<T> failover, ThrowableConsumer<T, X> func,
            Predicate<Throwable> failChecker) throws X {
        supplyWithRetry(maxRetryTimes, sleepBetweenRetryMs, failover, t -> {
            func.accept(t);
            return null;
        }, failChecker);
    }

    public static <T, X extends Throwable> void run(Failover<T> failover,
            ThrowableConsumer<T, X> func, Predicate<Throwable> failChecker) throws X {
        supply(failover, t -> {
            func.accept(t);
            return null;
        }, failChecker);
    }
}

@@ -227,66 +227,263 @@
 til.
-concurrent.TimeUnit.MILLISECONDS;%0A%0Aimport java.util.HashSe
+Collections.emptyList;%0Aimport static java.util.Collections.emptySet;%0Aimport static java.util.Collections.singletonList;%0Aimport static java.util.concurrent.TimeUnit.MILLISECONDS;%0A%0Aimport java.util.Collections;%0Aimport java.util.HashSet;%0Aimport java.util.Lis
 t;%0Ai
@@ -3758,11 +3758,1009 @@
 );%0A    %7D
+%0A%0A    public static %3CT%3E Failover%3CT%3E singleton(Failover%3CT%3E parent, T o) %7B%0A        return new Failover%3CT%3E() %7B%0A%0A            @Override%0A            public List%3CT%3E getAll() %7B%0A                return singletonList(o);%0A            %7D%0A%0A            @Override%0A            public void fail(T object) %7B%0A                parent.fail(object);%0A            %7D%0A%0A            @Override%0A            public List%3CT%3E getAvailable() %7B%0A                List%3CT%3E parentAvailable = parent.getAvailable();%0A                if (parentAvailable.contains(o)) %7B%0A                    return singletonList(o);%0A                %7D else %7B%0A                    return emptyList();%0A                %7D%0A            %7D%0A%0A            @Override%0A            public Set%3CT%3E getFailed() %7B%0A                Set%3CT%3E parentFailed = parent.getFailed();%0A                if (parentFailed.contains(o)) %7B%0A                    return Collections.singleton(o);%0A                %7D else %7B%0A                    return emptySet();%0A                %7D%0A            %7D%0A        %7D;%0A    %7D
 %0A%7D%0A

add singleton for failover.