/*
 * Copyright (c) 2020. ForteScarlet All rights reserved.
 * Project  simple-robot-core
 * File     TimeTaskManager.java
 *
 * You can contact the author through the following channels:
 * github https://github.com/ForteScarlet
 * gitee  https://gitee.com/ForteScarlet
 * email  [email protected]
 * QQ     1149159218
 *
 */

package com.forte.qqrobot.timetask;

import com.forte.lang.Language;
import com.forte.qqrobot.ResourceDispatchCenter;
import com.forte.qqrobot.anno.timetask.CronTask;
import com.forte.qqrobot.anno.timetask.FixedRateTask;
import com.forte.qqrobot.anno.timetask.TypeTask;
import com.forte.qqrobot.beans.types.TimeTaskTemplate;
import com.forte.qqrobot.beans.types.TimeType;
import com.forte.qqrobot.exception.TimeTaskException;
import com.forte.qqrobot.log.QQLog;
import com.forte.qqrobot.sender.MsgSender;
import com.forte.qqrobot.utils.AnnotationUtils;
import com.forte.qqrobot.utils.CQCodeUtil;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;

/**
 * 定时任务控制中心
 * 当一个类存在多个定时任务的注解的时候,将会按照类型全部加载
 * @author ForteScarlet <[163邮箱地址][email protected]>
 * @since JDK1.8
 **/
public class TimeTaskManager {

    /** 与定时任务有关的注解列表 */
    private static final Class<Annotation>[] timeTaskAnnos = new Class[]{
            CronTask.class, FixedRateTask.class, TypeTask.class
    };

    /**触发器名称前缀 */
    private static final String TRIGGER_NAME  = "trigger_";

    private static final String JOBDETAIL_NAME = "job_";

    /** 已经注册了的数量。初始量为0 */
    private int registerNum = 0;

    /** 尝试加载一个疑似是定时任务的类 */
    public void register(Class<? extends Job> timeTaskClass, MsgSender sender, StdSchedulerFactory factory){
        List<Annotation> timeTaskAnnos = isTimeTask(timeTaskClass);
        if(timeTaskAnnos.size() > 0){
            //遍历
            timeTaskAnnos.forEach(a -> register(timeTaskClass, a, sender, factory));
            registerNum += timeTaskAnnos.size();
        }
    }

    /** 尝试加载一个疑似是定时任务的类 */
    public void register(Class<? extends Job> timeTaskClass, Supplier<MsgSender> senderSupplier, StdSchedulerFactory factory){
        List<Annotation> timeTaskAnnos = isTimeTask(timeTaskClass);
        if(timeTaskAnnos.size() > 0){
            //遍历
            timeTaskAnnos.forEach(a -> register(timeTaskClass, a, senderSupplier.get(), factory));
            registerNum += timeTaskAnnos.size();
        }
    }

    /**
     * 启动定时任务
     */
    public void start(StdSchedulerFactory factory) throws SchedulerException {
        // 只有注册量在0以上的时候才初始化定时任务框架
        if(registerNum > 0){
            factory.getScheduler().start();
        }
    }


    /**
     * 判断一个Class对象是否为需要加载的定时任务
     */
    private static List<Annotation> isTimeTask(Class<? extends Job> clazz){
        //存在的定时任务注解
        List<Annotation> annoList = new ArrayList<>();
        //首先判断是否存在注解
        for(Class<Annotation> tc : timeTaskAnnos){
            Annotation an = AnnotationUtils.getAnnotation(clazz, tc);
            if(an != null){
                annoList.add(an);
            }
        }

        return annoList;
    }


    /**
     * 注册至定时任务调度器
     */
    private static void register(Class<? extends Job> clazz, Annotation annotation, MsgSender sender, StdSchedulerFactory factory){
        //判断注解类型
        Scheduler scheduler;
        try {
            scheduler = factory.getScheduler();
            scheduler.start();
        } catch (SchedulerException e) {
            throw new TimeTaskException("schedulerInitFailed", e);
        }

        //创建一个jobDetail的实例,将该实例与HelloJob Class绑定
        JobDetail jobDetail = null;
        Trigger trigger = null;
        String name = null;

        //创建可以提供的参数集合


        if(annotation instanceof CronTask){
            //如果是cron定时任务
            CronTask cronTask = (CronTask)annotation;
            name = cronTask.id();
            name = name.trim().length() <= 0 ? clazz.getSimpleName() : name;
            //创建定时任务触发器与Job实例
            jobDetail = getJobDetail(clazz, name);
            //创建定时触发器
            trigger = getTrigger(cronTask, name);
        }else if(annotation instanceof FixedRateTask){
            //如果是时间周期定时任务
            FixedRateTask fixedRateTask = (FixedRateTask)annotation;
            name = fixedRateTask.id();
            name = name.trim().length() <= 0 ? clazz.getSimpleName() : name;
            //创建定时任务触发器与Job实例
            jobDetail = getJobDetail(clazz, name);
            //创建定时触发器
            trigger = getTrigger(fixedRateTask, name);

        }else if(annotation instanceof TypeTask){
            //如果是模板定时任务
            TypeTask typeTask = (TypeTask)annotation;
            name = typeTask.id();
            name = name.trim().length() <= 0 ? clazz.getSimpleName() : name;
            //创建定时任务触发器与Job实例
            jobDetail = getJobDetail(clazz, name);
            //创建定时触发器
            trigger = getTrigger(typeTask, name);
        }

        if(jobDetail != null && trigger != null){
            //注册定时任务
            try {
                //向Job中注入参数
                JobDataMap jobDataMap = jobDetail.getJobDataMap();
                //保存一个送信器实例
                TimeTaskContext.giveMsgSender(jobDataMap, sender);
                //保存一个cq工具类实例
                TimeTaskContext.giveCQCodeUtil(jobDataMap, CQCodeUtil.build());
                //保存一个依赖工厂,以实现定时任务内的依赖注入。
                TimeTaskContext.giveDependCenter(jobDataMap, ResourceDispatchCenter.getDependCenter());


                scheduler.scheduleJob(jobDetail, trigger);
                QQLog.info("run.timeTask.loadSuccess", name);
            } catch (SchedulerException e) {
                throw new TimeTaskException("registerFailed", e, name);
            }
        }else{
            String why = jobDetail == null ?
                    Language.format("exception.dependResource.initFailed", JobDetail.class)
                    :
                    Language.format("exception.dependResource.initFailed", Trigger.class);
            throw new TimeTaskException("registerWhyFailed", name, why);
        }

    }

    //**************** - ****************//


    /** 获取定时任务实例 */
    private static JobDetail getJobDetail(Class<? extends Job> clazz, String name){
        return JobBuilder.newJob(clazz).withIdentity(getJobName(name)).build();
    }

    /** 获取触发器 */
    private static Trigger getTrigger(CronTask cronTask, String name){
        String cron = cronTask.value();
        String identity = getTriggerName(name);
        return TriggerBuilder.newTrigger().withIdentity(identity).withSchedule(CronScheduleBuilder.cronSchedule(cron)).build();
    }
    /** 获取触发器 */
    private static Trigger getTrigger(FixedRateTask fixedRateTask, String name){
        long time = fixedRateTask.value();
        TimeType timeType = fixedRateTask.timeType();
        int repeatCount = fixedRateTask.RepeatCount();
        repeatCount = Math.max(repeatCount, -1);
        String identity = getTriggerName(name);

        //获取Builder
        SimpleScheduleBuilder simpleScheduleBuilder = timeType.getSimpleScheduleBuilder(time).withRepeatCount(repeatCount);
        //返回结果
        return TriggerBuilder.newTrigger().withIdentity(identity).withSchedule(simpleScheduleBuilder).build();
    }
    /** 获取触发器 */
    private static Trigger getTrigger(TypeTask typeTask, String name){
        TimeTaskTemplate value = typeTask.value();
        return value.getTrigger(getTriggerName(name));
    }

    private static String getTriggerName(String name){
        return TRIGGER_NAME + name;
    }

    private static String getJobName(String name){
        return JOBDETAIL_NAME + name;
    }

}