package com.example.jmacro.wjdr.util;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.cron.CronUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.*;

/**
 * 任务/定时任务工具
 *
 * @author wangbing
 * @version 0.0.1
 * @since 1.8
 */
public class TaskUtil extends CronUtil {

    private static ScheduledExecutorService service;

    public static ScheduledExecutorService getService() {
        return service;
    }

    public static void setService(ScheduledExecutorService service) {
        TaskUtil.service = service;
    }

    /**
     * 执行Runnable任务
     *
     * @param runnable Runnable任务
     * @param delay    延时(ms)
     */
    public static ScheduledFuture<?> schedule(Runnable runnable, long delay, TimeUnit unit) {
        if (service == null) {
            service = new ScheduledThreadPoolExecutor(1);
        }
        return service.schedule(runnable, delay, unit);
    }

    /**
     * 执行Runnable任务
     *
     * @param callable Callable任务
     * @param delay    延时(ms)
     */
    public static <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay) {
        if (service == null) {
            service = new ScheduledThreadPoolExecutor(1);
        }
        return service.schedule(callable, delay, TimeUnit.MILLISECONDS);
    }

    /**
     * 执行Runnable任务
     *
     * @param callable Callable任务
     * @param delay    延时
     * @param unit     时间单位
     */
    public static <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
        if (service == null) {
            service = new ScheduledThreadPoolExecutor(1);
        }
        return service.schedule(callable, delay, unit);
    }

    /**
     * 执行Runnable任务
     *
     * @param runnable     Runnable任务
     * @param initialDelay 延迟执行时间
     * @param period       循环周期（前任务开始-新任务开始）
     * @param unit         时间单位
     */
    public static ScheduledFuture<?> scheduleAtFixedRate(Runnable runnable, long initialDelay, long period, TimeUnit unit) {
        if (service == null) {
            service = new ScheduledThreadPoolExecutor(1);
        }
        return service.scheduleAtFixedRate(runnable, initialDelay, period, unit);
    }

    /**
     * 执行Runnable任务
     *
     * @param runnable     Runnable任务
     * @param initialDelay 延迟执行时间
     * @param period       循环周期（前任务开始-新任务开始）ms
     */
    public static ScheduledFuture<?> scheduleAtFixedRate(Runnable runnable, long initialDelay, long period) {
        if (service == null) {
            service = new ScheduledThreadPoolExecutor(1);
        }
        return service.scheduleAtFixedRate(runnable, initialDelay, period, TimeUnit.MILLISECONDS);
    }

    /**
     * 执行Runnable任务
     *
     * @param runnable     Runnable任务
     * @param initialDelay 延迟执行时间
     * @param delay        循环周期（前任务结束-新任务开始）
     * @param unit         时间单位
     */
    public static ScheduledFuture<?> scheduleWithFixedDelay(Runnable runnable, long initialDelay, long delay, TimeUnit unit) {
        if (service == null) {
            service = new ScheduledThreadPoolExecutor(1);
        }
        return service.scheduleAtFixedRate(runnable, initialDelay, delay, unit);
    }

    /**
     * 执行Runnable任务
     *
     * @param runnable     Runnable任务
     * @param initialDelay 延迟执行时间
     * @param delay        循环周期（前任务结束-新任务开始）ms
     */
    public static ScheduledFuture<?> scheduleWithFixedDelay(Runnable runnable, long initialDelay, long delay) {
        if (service == null) {
            service = new ScheduledThreadPoolExecutor(1);
        }
        return service.scheduleAtFixedRate(runnable, initialDelay, delay, TimeUnit.MILLISECONDS);
    }

    /**
     * 以获取结果为目的并支持重试的任务，通常为有失败风险的IO或异步操作
     *
     * @param runnable    任务
     * @param maxTryCount 最大重试次数
     * @param interval    重试时间间隔
     * @param <T>         结果
     */
    public static <T> T retryTask(Callable<T> runnable, int maxTryCount, long interval) {
        for (int i = 0; i < maxTryCount; i++) {
            T t = null;
            try {
                t = runnable.call();
                if (t != null) {
                    return t;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 控制重试时间间隔
                if (t == null && i < maxTryCount - 1) {
                    ThreadUtil.sleep(interval);
                }
            }
        }

        return null;
    }

    public static <T> T retryTask(Callable<T> runnable) {
        return retryTask(runnable, 3, 3000);
    }

    public static <T> T retryTask(Callable<T> runnable, int maxTryCount) {
        return retryTask(runnable, maxTryCount, 3000);
    }

    /**
     * 执限时任务
     *
     * @param callable 任务
     * @param time     毫秒
     */
    public static <T> T timeTask(Callable<T> callable, long time, TimeUnit unit) {
        try {
            CompletionService<T> completionService = ThreadUtil.newCompletionService();
            Future<T> submit = completionService.submit(callable);
            return submit.get(time, unit);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 执限时任务
     *
     * @param callable 任务
     * @param time     毫秒
     */
    public static void timeTask(Runnable callable, long time, TimeUnit unit) {
        try {
            CompletionService<Void> completionService = ThreadUtil.newCompletionService();
            Future<Void> submit = completionService.submit(callable, null);
            submit.get(time, unit);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 执行异步任务
     */
    public static void asyncTask(Runnable runnable) {
        ThreadUtil.execAsync(runnable);
    }

    /**
     * 执行异步任务
     */
    public static <T> Future<T> asyncTask(Callable<T> callables) {
        return ThreadUtil.execAsync(callables);
    }

    /**
     * 并行任务（多线程）
     */
    public static void parallelTasks(Runnable... runnables) {
        Assert.notEmpty(runnables, "callable can not empty");
        CompletionService<Object> completionService = ThreadUtil.newCompletionService();
        for (Runnable runnable : runnables) {
            completionService.submit(runnable, null);
        }
    }

    /**
     * 并行任务（多线程）
     * 可以保证结果也是按添加顺序返回
     */
    public static Object[] parallelTasks(Callable<Object>... callables) {
        Assert.notEmpty(callables, "callable can not empty");
        CompletionService<Object> completionService = ThreadUtil.newCompletionService();
        Object[] result = new Object[callables.length];
        List<Future> futures = new ArrayList<>();
        for (Callable<Object> callable : callables) {
            futures.add(completionService.submit(callable));
        }

        try {
            for (int i = 0; i < result.length; i++) {
                Future<Object> take = completionService.take();
                int indexOf = futures.indexOf(take);
                result[indexOf] = take.get();
                System.out.println("result=" + result[indexOf]);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 并行任务/任务流.
     * 可以依次运行任务，通过回调事件传递，能够保证任务中存在异步操作时也能按顺序执行
     */
    public static TaskStack flowTask(FlowTask task) {
        return new TaskStack(task);
    }

    /**
     * 并行任务/任务流任务栈
     */
    public static class TaskStack {
        private final Queue<FlowTask> taskFlowQueue = new ConcurrentLinkedQueue();
        private final TaskEvent event = new TaskEvent() {
            @Override
            public void next(Object[] arg) {
                FlowTask next = taskFlowQueue.poll();
                if (next == null) {
                    return;
                }
                next.onTask(event, arg);
            }
        };

        public TaskStack(FlowTask first) {
            this.taskFlowQueue.add(first);
        }

        public void start(Object... arg) {
            FlowTask first = this.taskFlowQueue.poll();
            if (first == null) {
                return;
            }
            first.onTask(event, arg);
        }

        public TaskStack then(FlowTask task) {
            this.taskFlowQueue.add(task);
            return this;
        }

    }

    /**
     * 并行任务/任务流事件
     */
    public interface TaskEvent {

        /**
         * 执行下一步
         *
         * @param arg 参数
         */
        void next(Object... arg);
    }

    /**
     * 任务
     */
    public interface FlowTask {

        /**
         * 公式任务
         *
         * @param flow 流
         * @param arg  参数
         */
        void onTask(TaskEvent flow, Object... arg);
    }
}
