package cc.smtweb.framework.core.mvc.scheduler;

import cc.smtweb.framework.core.annotation.SwScheduling;
import cc.smtweb.framework.core.cache.redis.RedisManager;
import cc.smtweb.framework.core.db.DbEngine;
import cc.smtweb.framework.core.mvc.ISchedulerWakeup;
import cc.smtweb.framework.core.mvc.controller.IBeanContext;
import cc.smtweb.framework.core.mvc.controller.access.SchedulerMethodAccess;
import cc.smtweb.framework.core.mvc.scheduler.job.GroupJob;
import cc.smtweb.framework.core.mvc.scheduler.job.LocalJobExecutor;
import cc.smtweb.framework.core.mvc.scheduler.job.RedisJobExecutor;
import cc.smtweb.framework.core.mvc.scheduler.job.SimpleJob;
import com.serotonin.timer.RealTimeTimer;
import com.serotonin.timer.TimerTask;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class SchedulerTaskManager implements ISchedulerWakeup {
    private final ExecutorService executor;
    private final RealTimeTimer timer;
    private final Map<String, TimerTask> timerTaskMap = new HashMap<>();

    private SchedulerTaskManager() {
        int poolSize = Runtime.getRuntime().availableProcessors();
        this.executor = new ThreadPoolExecutor(2, poolSize * 2 + 2, 5, TimeUnit.MINUTES,
                new SynchronousQueue<>(),
                new BasicThreadFactory.Builder().namingPattern("sw-scheduler-").build());

        // 保证2个线程就够了，1个触发定时器，1个定时同步redis状态
        this.timer = new RealTimeTimer();
        this.timer.init(1, 4);
    }

    public static SchedulerTaskManager build(IBeanContext beanContext, List<SchedulerMethodAccess> tasks) {
        if (!tasks.isEmpty()) {
            SchedulerTaskManager result = new SchedulerTaskManager();

            DbEngine dbEngine = beanContext.getBean(DbEngine.class);
            RedisManager redisManager = beanContext.getBean(RedisManager.class);
            Long redisLockValue = dbEngine.nextId();

            LocalJobExecutor localJobExecutor = new LocalJobExecutor(beanContext, result.executor, redisLockValue, dbEngine);
            RedisJobExecutor redisJobExecutor = new RedisJobExecutor(beanContext, result.executor, redisLockValue, dbEngine, redisManager);

            for (SchedulerMethodAccess schedulerMethodAccess : tasks) {
                SwScheduling swScheduling = schedulerMethodAccess.getSwScheduling();

                AbstractJob job;
                String group = swScheduling.group();
                AbstractJobExecutor jobManager = swScheduling.multiServer() ? localJobExecutor: redisJobExecutor;

                if (StringUtils.isNotBlank(group)) {
                    job = new GroupJob(group, schedulerMethodAccess.getMethodAccess());
                } else {
                    job = new SimpleJob(schedulerMethodAccess.getMethodAccess());
                }

                AbstractJobQueue jobQueue = jobManager.initJobQueue(job);
                CronTimerTask task = new CronTimerTask(schedulerMethodAccess.getTimerTrigger(), jobQueue, job);

                result.timer.schedule(task);
                result.timerTaskMap.put(job.getName(), task);
            }

            int time = RedisJobExecutor.TIME_CHECK_SEC * 1000;
            result.timer.schedule(new FixedTimerTask(time, time, redisJobExecutor));

            return result;
        }

        return null;
    }

    public void shutdown() {
        this.timer.cancel();
        this.executor.shutdown();
    }

    @Override
    public boolean wakeup(Class<?> clazz, String methodName) {
        TimerTask task = timerTaskMap.get(clazz.getName() + "." + methodName);
        if (task != null) {
            task.run(System.currentTimeMillis());
            return true;
        }

        return false;
    }
}
