package modern.challenge;

import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Logger;

public final class CustomerAsyncs {

    private static final Logger logger = Logger.getLogger(CustomerAsyncs.class.getName());

    private CustomerAsyncs() {
        throw new AssertionError("Cannot be instantiated");
    }

    public static void printOrder()
            throws InterruptedException, ExecutionException {

        CompletableFuture<Void> cfPrintOrder = CompletableFuture.runAsync(() -> {
            try {
                logger.info(() -> "Order is printed by: " + Thread.currentThread().getName());
                Thread.sleep(500);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        cfPrintOrder.get(); // wait for order to be printed, this is blocking
        logger.info("Customer order was printed ...\n");
    }

    public static void fetchOrderSummary()
            throws InterruptedException, ExecutionException {

        CompletableFuture<String> cfOrderSummary = CompletableFuture.supplyAsync(() -> {
            try {
                logger.info(() -> "Fetch order summary by: " + Thread.currentThread().getName());
                Thread.sleep(500);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }

            return "Order Summary #93443";
        });

        String summary = cfOrderSummary.get(); // wait for summary to be available, this is blocking
        logger.info(() -> "Order summary: " + summary + "\n");
    }

    public static void fetchOrderSummaryExecutor()
            throws InterruptedException, ExecutionException {

        ExecutorService executor = Executors.newSingleThreadExecutor();

        CompletableFuture<String> cfOrderSummary = CompletableFuture.supplyAsync(() -> {
            try {
                logger.info(() -> "Fetch order summary by: " + Thread.currentThread().getName());
                Thread.sleep(500);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }

            return "Order Summary #91022";
        }, executor);

        String summary = cfOrderSummary.get(); // wait for summary to be available, this is blocking
        logger.info(() -> "Order summary: " + summary + "\n");

        executor.shutdownNow();
    }

    public static void fetchInvoiceTotalSign()
            throws InterruptedException, ExecutionException {

        CompletableFuture<String> cfFetchInvoice = CompletableFuture.supplyAsync(() -> {
            try {
                logger.info(() -> "Fetch invoice by: " + Thread.currentThread().getName());
                Thread.sleep(500);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }

            return "Invoice #3344";
        });

        CompletableFuture<String> cfTotalSign = cfFetchInvoice
                .thenApply(o -> o + " Total: $145")
                .thenApply(o -> o + " Signed");

        String result = cfTotalSign.get();
        logger.info(() -> "Invoice: " + result + "\n");
    }

    public static void fetchAndPrintOrder()
            throws InterruptedException, ExecutionException {

        CompletableFuture<Void> cfFetchAndPrintOrder = CompletableFuture.supplyAsync(() -> {
            try {
                logger.info(() -> "Fetch order by: " + Thread.currentThread().getName());
                Thread.sleep(500);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }

            return "Order #1024";
        }).thenAccept(
                o -> logger.info(() -> "Printing order "
                + o + " by: " + Thread.currentThread().getName()));

        cfFetchAndPrintOrder.get();
        logger.info("Order was fetched and printed \n");
    }

    public static void deliverOrderNotifyCustomer()
            throws InterruptedException, ExecutionException {

        CompletableFuture<Void> cfDeliverOrder = CompletableFuture.runAsync(() -> {
            try {
                logger.info(() -> "Order was delivered by: " + Thread.currentThread().getName());
                Thread.sleep(500);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        CompletableFuture<Void> cfNotifyCustomer = cfDeliverOrder.thenRun(() -> logger.info(()
                -> "Dear customer, your order has been delivered today by: "
                + Thread.currentThread().getName()));

        cfNotifyCustomer.get();
        logger.info(() -> "Order was delivered and customer was notified \n");
    }

    public static void fetchOrderTotalException()
            throws InterruptedException, ExecutionException {

        CompletableFuture<Integer> cfTotalOrder = CompletableFuture.supplyAsync(() -> {

            logger.info(() -> "Compute total: " + Thread.currentThread().getName());

            int surrogate = new Random().nextInt(1000);
            if (surrogate < 500) {
                throw new IllegalStateException("Invoice service is not responding");
            }

            return 1000;

        }).exceptionally(ex -> {
            logger.severe(() -> "Exception: " + ex
                    + " Thread: " + Thread.currentThread().getName());

            return 0;
        });

        int result = cfTotalOrder.get();
        logger.info(() -> "Total: " + result + "\n");
    }

    public static void fetchInvoiceTotalSignChainOfException()
            throws InterruptedException, ExecutionException {

        CompletableFuture<String> cfFetchInvoice = CompletableFuture.supplyAsync(() -> {

            logger.info(() -> "Fetch invoice by: " + Thread.currentThread().getName());

            int surrogate = new Random().nextInt(1000);
            if (surrogate < 500) {
                throw new IllegalStateException("Invoice service is not responding");
            }

            return "Invoice #3344";
        }).exceptionally(ex -> {
            logger.severe(() -> "Exception: " + ex
                    + " Thread: " + Thread.currentThread().getName());

            return "[Invoice-Exception]";
        }).thenApply(o -> {
            logger.info(() -> "Compute total by: " + Thread.currentThread().getName());

            int surrogate = new Random().nextInt(1000);
            if (surrogate < 500) {
                throw new IllegalStateException("Total service is not responding");
            }
            return o + " Total: $145";
        }).exceptionally(ex -> {
            logger.severe(() -> "Exception: " + ex
                    + " Thread: " + Thread.currentThread().getName());

            return "[Total-Exception]";
        }).thenApply(o -> {
            logger.info(() -> "Sign invoice by: " + Thread.currentThread().getName());

            int surrogate = new Random().nextInt(1000);
            if (surrogate < 500) {
                throw new IllegalStateException("Signing service is not responding");
            }

            return o + " Signed";
        }).exceptionally(ex -> {
            logger.severe(() -> "Exception: " + ex
                    + " Thread: " + Thread.currentThread().getName());

            return "[Sign-Exception]";
        });

        String result = cfFetchInvoice.get();
        logger.info(() -> "Result: " + result + "\n");
    }

    public static void fetchInvoiceTotalSignGlobalException()
            throws InterruptedException, ExecutionException {

        CompletableFuture<String> cfFetchInvoice = CompletableFuture.supplyAsync(() -> {

            logger.info(() -> "Fetch invoice by: " + Thread.currentThread().getName());

            int surrogate = new Random().nextInt(1000);
            if (surrogate < 500) {
                throw new IllegalStateException("Invoice service is not responding");
            }

            return "Invoice #3344";
        }).thenApply(o -> {
            logger.info(() -> "Compute total by: " + Thread.currentThread().getName());

            int surrogate = new Random().nextInt(1000);
            if (surrogate < 500) {
                throw new IllegalStateException("Total service is not responding");
            }
            return o + " Total: $145";
        }).thenApply(o -> {
            logger.info(() -> "Sign invoice by: " + Thread.currentThread().getName());

            int surrogate = new Random().nextInt(1000);
            if (surrogate < 500) {
                throw new IllegalStateException("Signing service is not responding");
            }

            return o + " Signed";
        }).exceptionally(ex -> {
            logger.severe(() -> "Exception: " + ex
                    + " Thread: " + Thread.currentThread().getName());

            return "[No-Invoice-Exception]";
        });

        String result = cfFetchInvoice.get();
        logger.info(() -> "Result: " + result + "\n");
    }

    public static void printInvoiceException()
            throws InterruptedException, ExecutionException {

        CompletableFuture<String> cfServicePrinterIp = CompletableFuture.supplyAsync(() -> {

            int surrogate = new Random().nextInt(1000);
            if (surrogate < 500) {
                throw new IllegalStateException("Printing service is not responding");
            }

            return "192.168.1.0";
        });

        CompletableFuture<String> cfBackupPrinterIp = CompletableFuture.supplyAsync(() -> {
            return "192.192.192.192";
        });

        CompletableFuture<Void> printInvoice = cfServicePrinterIp
                .exceptionallyCompose(th -> {
                    logger.severe(() -> "Exception: " + th
                            + " Thread: " + Thread.currentThread().getName());
                    return cfBackupPrinterIp;
                }).thenAccept((ip) -> logger.info(() -> "Printing at: " + ip));

        printInvoice.get();
        logger.info(() -> "Printing done ... \n");
    }

    public static void fetchOrderTotalExceptionAsync()
            throws InterruptedException, ExecutionException {

        ExecutorService executor = Executors.newSingleThreadExecutor();

        CompletableFuture<Integer> totalOrder = CompletableFuture.supplyAsync(() -> {

            logger.info(() -> "Compute total by: " + Thread.currentThread().getName());

            int surrogate = new Random().nextInt(1000);
            if (surrogate < 500) {
                throw new IllegalStateException("Computing service is not responding");
            }

            return 1000;

        }).exceptionallyAsync(ex -> {
            logger.severe(() -> "Exception: " + ex
                    + " Thread: " + Thread.currentThread().getName());

            return 0;
        }, executor);

        int result = totalOrder.get();
        logger.info(() -> "Total: " + result + "\n");

        executor.shutdownNow();
    }

    public static void fetchOrderTotalHandle()
            throws InterruptedException, ExecutionException {

        CompletableFuture<Integer> totalOrder = CompletableFuture.supplyAsync(() -> {

            logger.info(() -> "Compute total by: " + Thread.currentThread().getName());

            int surrogate = new Random().nextInt(1000);
            if (surrogate < 500) {
                throw new IllegalStateException("Computing service is not responding");
            }

            return 1000;

        }).handle((res, ex) -> {
            if (ex != null) {
                logger.severe(() -> "Exception: " + ex
                        + " Thread: " + Thread.currentThread().getName());

                return 0;
            }

            if (res != null) {
                int vat = res * 24 / 100;
                res += vat;
            }

            return res;
        });

        int result = totalOrder.get();
        logger.info(() -> "Total: " + result + "\n");
    }

    public static CompletableFuture<Integer> taxes() {
        CompletableFuture<Integer> completableFuture = new CompletableFuture<>();

        new Thread(() -> {
            try {
                int result = new Random().nextInt(100);
                Thread.sleep(10);
                completableFuture.complete(result);
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
        }).start();

        return completableFuture;
    }
}