package com.yanzuoguang.util.helper;

import com.yanzuoguang.util.log.Log;
import com.yanzuoguang.util.thread.ThreadHelper;

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer;

/**
 * 超时监控
 *
 * @author 颜佐光
 */
public class YzgTimeout {

    public static final int TIME_OUT_DEFAULT = 15 * 1000;
    public static final int TIME_OUT_TIP = 10 * 1000;
    private static Queue<TimeInfo> queueInfos = null;


    /**
     * 超时监控
     *
     * @param cls      日志类
     * @param message  消息
     * @param runnable 运行函数
     */
    public static void timeOut(Class<?> cls, String message, Runnable runnable) {
        timeOut(cls, message, runnable, null);
    }

    /**
     * 超时监控
     *
     * @param cls      日志类
     * @param message  消息
     * @param runnable 运行函数
     */
    public static void timeOut(Class<?> cls, String message, Runnable runnable, Consumer<Long> consumer) {
        timeHeart(TIME_OUT_DEFAULT, TIME_OUT_TIP, runnable, (time) -> {
            Log.error(cls, "%s超时,已经执行%d豪秒,正在等待执行完成", message, time);
            if (consumer != null) {
                consumer.accept(time);
            }
        });
    }

    /**
     * 超时监控
     *
     * @param runnable 运行函数
     * @param heart    心跳函数
     */
    public static void timeHeart(Runnable runnable, YzgTimeoutHeart heart) {
        timeHeart(TIME_OUT_DEFAULT, TIME_OUT_TIP, runnable, heart);
    }

    /**
     * 超时监控
     *
     * @param tipOutDefault 默认超时时间
     * @param timeOutTip    超时心跳间隔
     * @param runnable      运行函数
     * @param heart         心跳函数
     */
    public static void timeHeart(int tipOutDefault, int timeOutTip,
                                 Runnable runnable, YzgTimeoutHeart heart) {
        TimeInfo timeInfo = getTimeInfo(tipOutDefault, timeOutTip, heart);
        try {
            runnable.run();
        } finally {
            timeInfo.setRun(true);
        }
    }

    private static TimeInfo getTimeInfo(int tipOutDefault, int timeOutTip, YzgTimeoutHeart heart) {
        init();
        TimeInfo timeInfo = new TimeInfo(tipOutDefault, timeOutTip, heart);
        synchronized (queueInfos) {
            queueInfos.add(timeInfo);
        }
        return timeInfo;
    }

    private static void init() {
        if (queueInfos != null) {
            return;
        }

        synchronized (YzgTimeout.class) {
            if (queueInfos != null) {
                return;
            }

            queueInfos = new ConcurrentLinkedQueue<>();
            ThreadHelper.runThread(() -> {
                while (true) {
                    try {
                        runItem();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                    ThreadHelper.sleep(200);
                }
            });
        }
    }

    private static void runItem() {
        int size;
        synchronized (queueInfos) {
            size = queueInfos.size();
        }
        for (int i = 0; i < size; i++) {
            TimeInfo poll;
            synchronized (queueInfos) {
                poll = queueInfos.poll();
            }
            if (poll == null) {
                return;
            }
            long end = System.currentTimeMillis();
            long time = end - poll.getStart();
            if (time > poll.getTimeMax()) {
                try {
                    poll.setTimeMax(poll.getTimeMax() + poll.getTimeOutTip());
                    ThreadHelper.runThread(() -> poll.getHeart().heart(time));
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (!poll.isRun()) {
                synchronized (queueInfos) {
                    queueInfos.add(poll);
                }
            }
        }
    }
}

class TimeInfo {

    private int timeOutDefault;
    private int timeOutTip;
    private YzgTimeoutHeart heart;
    private boolean run;
    private long start;
    private int timeMax;

    public TimeInfo(int timeOutDefault, int timeOutTip, YzgTimeoutHeart heart) {
        this.timeOutDefault = timeOutDefault;
        this.timeOutTip = timeOutTip;
        this.heart = heart;
        this.timeMax = timeOutDefault;
        this.start = System.currentTimeMillis();
    }

    public int getTimeOutDefault() {
        return timeOutDefault;
    }

    public void setTimeOutDefault(int timeOutDefault) {
        this.timeOutDefault = timeOutDefault;
    }

    public int getTimeOutTip() {
        return timeOutTip;
    }

    public void setTimeOutTip(int timeOutTip) {
        this.timeOutTip = timeOutTip;
    }

    public YzgTimeoutHeart getHeart() {
        return heart;
    }

    public void setHeart(YzgTimeoutHeart heart) {
        this.heart = heart;
    }

    public boolean isRun() {
        return run;
    }

    public void setRun(boolean run) {
        this.run = run;
    }

    public long getStart() {
        return start;
    }

    public void setStart(long start) {
        this.start = start;
    }

    public int getTimeMax() {
        return timeMax;
    }

    public void setTimeMax(int timeMax) {
        this.timeMax = timeMax;
    }
}