package com.webchat.service.queue;

import com.webchat.common.util.JsonUtil;
import com.webchat.service.RedisService;
import com.webchat.service.queue.dto.BaseDelayNormalQueueDTO;
import com.webchat.service.queue.dto.BaseDelayQueueDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 抽象的redis队列实现方法
 *
 * @param <T>
 */
@Slf4j
public abstract class AbstractRedisDelayQueue<T extends BaseDelayQueueDTO> {

    @Autowired
    protected RedisService redisService;

    protected AbstractRedisQueue getRedisDelayNormalQueue() {

        return null;
    }

    private volatile boolean isInit = false;

    private volatile boolean isRun = true;

    private ThreadPoolExecutor poolExecutor;

    public void init() {
        if (isInit) {
            return;
        }
        log.info("开始初始化消息队列，参数如下: \n" +
                        "queue name: {} \n" +
                        "queue timeout: {}\n" +
                        "queue retry times: {}\n" +
                        "queue back queue times: {}\n" +
                        "queue pool size: {}",
                getQueueName(), getTimeout(), getRetryTimes(), getBackQueueTimes(), getPoolSize()
        );
        initThreadPool(getPoolSize());
        Long startSize = redisService.zSetSize(getQueueName());
        log.info("启动消息队列{}成功, 目前还有 {} 个元素没被消费", getQueueName(), startSize);
        isInit = true;
    }

    /**
     * 向消息队列加入消息
     *
     * @param data
     */
    public void submit(T data) {
        String jsonStr = JsonUtil.toJsonString(data);
        redisService.zadd(getQueueName(), jsonStr, data.getTime());
    }

    /**
     * 从消息队列redis读取数据的延迟
     *
     * @return
     */
    protected long getTimeout() {
        return 3000;
    }

    /**
     * 一个任务元素重试的次数
     *
     * @return 默认不重试
     */
    protected int getRetryTimes() {
        return 0;
    }

    /**
     * 一个任务最多被重返队列的次数
     *
     * @return
     */
    protected int getBackQueueTimes() {
        return 0;
    }

    /**
     * 获取最大执行任务数量
     *
     * @return
     */
    protected int getPoolSize() {
        return 1;
    }

    /**
     * 将字符串转化为对象
     *
     * @param s
     * @return
     */
    protected abstract T convert(String s);

    /**
     * 队列的名称
     *
     * @return
     */
    protected abstract String getQueueName();

    /**
     * 延迟队列消费
     *
     * @return
     */
    private Set<String> consumeMessage() {
        long currentTime = System.currentTimeMillis();
        // 按照score升序取消息任务
        Set<String> messages = redisService.zrangeByScore(getQueueName(), Long.MIN_VALUE, currentTime);
        if (CollectionUtils.isNotEmpty(messages)) {
            // 消费到了数据，需要删除队列中数据，否则会重复消费
            redisService.zremoveRangeByScore(getQueueName(), Long.MIN_VALUE, currentTime);
        }
        return messages;
    }

    /**
     * 负责调度的核心方法
     * 通过循环读取redis的阻塞队列，获取元素，并且转化为用户可直接操作的类型
     * 如果处理失败采用两种容错方式:
     * 1. 重试，可以通过设置retryTime来设置重试次数
     * 2. 重新归队，只有在重试全部失败后才回执行该逻辑，可以设置backQueueTimes设置归队的最大次数
     * 如果两种方式都失败，则进入用户自定义的容错方式中
     */
    public void schedule() {
        init();
        try {
            final Semaphore semaphore = new Semaphore(getPoolSize());
            while (isRun) {
                Thread.sleep(1000);
                // 判断是否目前达到了使用上限
                log.info("QueueName:{} 信号量剩余:{}", getQueueName(), semaphore.availablePermits());
                semaphore.acquire();
                // 消费消息
                final Set<String> messages = this.consumeMessage();
                if (CollectionUtils.isEmpty(messages)) {
                    log.info("QueueName:{} 目前没有元素", getQueueName());
                    semaphore.release();
                    continue;
                }
                BaseDelayNormalQueueDTO normalQueueDTO = BaseDelayNormalQueueDTO.of(messages);
                poolExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            AbstractRedisQueue redisQueue = getRedisDelayNormalQueue();
                            // 批量提交任务到普通队列
                            redisQueue.batchSubmit(normalQueueDTO);
                        } finally {
                            // 任务执行完释放信号量
                            semaphore.release();
                        }
                    }
                });
            }
        } catch (final Exception outerEx) {
            log.error("启动消息队列失败, 原因 ", outerEx);
        }
    }

    /**
     * 初始化数据库连接池
     *
     * @param poolSize
     */
    private void initThreadPool(int poolSize) {
        if (poolExecutor == null) {
            synchronized (this) {
                if (poolExecutor == null) {
                    poolExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(poolSize);
                }
            }
        }
    }
}
