package com.qmth.boot.redis.schedule;

import com.qmth.boot.redis.constant.RedisConstant;
import org.redisson.api.CronSchedule;
import org.redisson.api.RScheduledExecutorService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.PeriodicTrigger;
import org.springframework.scheduling.support.ScheduledMethodRunnable;

import java.util.Date;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 * 使用Redisson分布式任务调度器注册定时任务
 */
public class RedisTaskScheduler implements TaskScheduler, RedisConstant {

    private static final Logger log = LoggerFactory.getLogger(RedisTaskScheduler.class);

    public static ApplicationContext applicationContext;

    private RScheduledExecutorService scheduledExecutorService;

    public RedisTaskScheduler(RScheduledExecutorService scheduledExecutorService,
            ApplicationContext applicationContext) {
        this.scheduledExecutorService = scheduledExecutorService;
        this.applicationContext = applicationContext;
        log.info("Redis task scheduler inited.");
    }

    @Override
    public ScheduledFuture<?> schedule(Runnable runnable, Trigger trigger) {
        if (trigger instanceof CronTrigger) {
            return scheduledExecutorService
                    .schedule(runnable, CronSchedule.of(((CronTrigger) trigger).getExpression()));
        } else if (trigger instanceof PeriodicTrigger) {
            PeriodicTrigger pt = (PeriodicTrigger) trigger;
            if (pt.isFixedRate()) {
                return scheduledExecutorService
                        .scheduleAtFixedRate(runnable, pt.getInitialDelay(), pt.getPeriod(), pt.getTimeUnit());
            } else {
                return scheduledExecutorService
                        .scheduleWithFixedDelay(runnable, pt.getInitialDelay(), pt.getPeriod(), pt.getTimeUnit());
            }
        } else {
            throw new RuntimeException("Unsupported trigger type: " + trigger.getClass());
        }
    }

    @Override
    public ScheduledFuture<?> schedule(Runnable runnable, Date startTime) {
        long initialDelay = startTime.getTime() - System.currentTimeMillis();
        return scheduledExecutorService.schedule(createRunnable(runnable), initialDelay, TimeUnit.MILLISECONDS);
    }

    @Override
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable runnable, Date startTime, long period) {
        long initialDelay = startTime.getTime() - System.currentTimeMillis();
        return scheduledExecutorService
                .scheduleAtFixedRate(createRunnable(runnable), initialDelay, period, TimeUnit.MILLISECONDS);
    }

    @Override
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable runnable, long period) {
        return scheduledExecutorService.scheduleAtFixedRate(createRunnable(runnable), 0, period, TimeUnit.MILLISECONDS);
    }

    @Override
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable runnable, Date startTime, long period) {
        long initialDelay = startTime.getTime() - System.currentTimeMillis();
        return scheduledExecutorService
                .scheduleWithFixedDelay(createRunnable(runnable), initialDelay, period, TimeUnit.MILLISECONDS);
    }

    @Override
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable runnable, long period) {
        return scheduledExecutorService
                .scheduleWithFixedDelay(createRunnable(runnable), 0, period, TimeUnit.MILLISECONDS);
    }

    private Runnable createRunnable(Runnable runnable) {
        if (runnable instanceof ScheduledMethodRunnable) {
            ScheduledMethodRunnable methodRunnable = (ScheduledMethodRunnable) runnable;
            SpringTaskRunnable taskRunnable = new SpringTaskRunnable();
            taskRunnable.setBeanType(methodRunnable.getTarget().getClass());
            taskRunnable.setMethodName(methodRunnable.getMethod().getName());
            runnable = taskRunnable;
        }
        return runnable;
    }
}
