package com.nepxion.thunder.common.thread;

/**
 * <p>Title: Nepxion Thunder</p>
 * <p>Description: Nepxion Thunder For Distribution</p>
 * <p>Copyright: Copyright (c) 2017-2050</p>
 * <p>Company: Nepxion</p>
 * @author Haojun Ren
 * @version 1.0
 */

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Maps;
import com.nepxion.thunder.common.constant.ThunderConstant;
import com.nepxion.thunder.common.entity.ApplicationType;
import com.nepxion.thunder.common.entity.ThreadQueueType;
import com.nepxion.thunder.common.entity.ThreadRejectedPolicyType;
import com.nepxion.thunder.common.property.ThunderProperties;
import com.nepxion.thunder.common.util.ClassUtil;
import com.nepxion.thunder.common.util.StringUtil;

public class ThreadPoolFactory {
    private static final Logger LOG = LoggerFactory.getLogger(ThreadPoolFactory.class);

    private static ThunderProperties properties;

    private static ConcurrentMap<String, ThreadPoolExecutor> threadPoolServerExecutorMap = Maps.newConcurrentMap();
    private static ConcurrentMap<String, ThreadPoolExecutor> threadPoolClientExecutorMap = Maps.newConcurrentMap();

    public static void initialize(ThunderProperties properties) {
        ThreadPoolFactory.properties = properties;
    }

    public static ThreadPoolExecutor createThreadPoolDefaultExecutor(String url, String interfaze) {
        return createThreadPoolExecutor(url, interfaze,
                ThunderConstant.CPUS * 1,
                ThunderConstant.CPUS * 2,
                15 * 60 * 1000,
                false);
    }

    public static ThreadPoolExecutor createThreadPoolDefaultExecutor() {
        return createThreadPoolExecutor(ThunderConstant.CPUS * 1,
                ThunderConstant.CPUS * 2,
                15 * 60 * 1000,
                false);
    }

    public static ThreadPoolExecutor createThreadPoolServerExecutor(String url, String interfaze) {
        try {
            return createThreadPoolExecutor(threadPoolServerExecutorMap, url,
                    properties.getBoolean(ThunderConstant.THREAD_POOL_MULTI_MODE_ATTRIBUTE_NAME) ? interfaze : properties.getString(ThunderConstant.NAMESPACE_ELEMENT_NAME) + "-" + ApplicationType.SERVICE,
                    ThunderConstant.CPUS * properties.getInteger(ThunderConstant.THREAD_POOL_SERVER_CORE_POOL_SIZE_ATTRIBUTE_NAME),
                    ThunderConstant.CPUS * properties.getInteger(ThunderConstant.THREAD_POOL_SERVER_MAXIMUM_POOL_SIZE_ATTRIBUTE_NAME),
                    properties.getLong(ThunderConstant.THREAD_POOL_SERVER_KEEP_ALIVE_TIME_ATTRIBUTE_NAME),
                    properties.getBoolean(ThunderConstant.THREAD_POOL_SERVER_ALLOW_CORE_THREAD_TIMEOUT_ATTRIBUTE_NAME));
        } catch (Exception e) {
            throw new IllegalArgumentException("Properties maybe isn't initialized", e);
        }
    }

    public static ThreadPoolExecutor createThreadPoolClientExecutor(String url, String interfaze) {
        try {
            return createThreadPoolExecutor(threadPoolClientExecutorMap, url,
                    properties.getBoolean(ThunderConstant.THREAD_POOL_MULTI_MODE_ATTRIBUTE_NAME) ? interfaze : properties.getString(ThunderConstant.NAMESPACE_ELEMENT_NAME) + "-" + ApplicationType.REFERENCE,
                    ThunderConstant.CPUS * properties.getInteger(ThunderConstant.THREAD_POOL_CLIENT_CORE_POOL_SIZE_ATTRIBUTE_NAME),
                    ThunderConstant.CPUS * properties.getInteger(ThunderConstant.THREAD_POOL_CLIENT_MAXIMUM_POOL_SIZE_ATTRIBUTE_NAME),
                    properties.getLong(ThunderConstant.THREAD_POOL_CLIENT_KEEP_ALIVE_TIME_ATTRIBUTE_NAME),
                    properties.getBoolean(ThunderConstant.THREAD_POOL_CLIENT_ALLOW_CORE_THREAD_TIMEOUT_ATTRIBUTE_NAME));
        } catch (Exception e) {
            throw new IllegalArgumentException("Properties maybe isn't initialized");
        }
    }

    public static ThreadPoolExecutor createThreadPoolExecutor(final ConcurrentMap<String, ThreadPoolExecutor> threadPoolExecutorMap, final String url, final String interfaze, final int corePoolSize, final int maximumPoolSize, final long keepAliveTime, final boolean allowCoreThreadTimeOut) {
        ThreadPoolExecutor threadPoolExecutor = threadPoolExecutorMap.get(interfaze);
        if (threadPoolExecutor == null) {
            ThreadPoolExecutor newThreadPoolExecutor = createThreadPoolExecutor(url, interfaze, corePoolSize, maximumPoolSize, keepAliveTime, allowCoreThreadTimeOut);
            threadPoolExecutor = threadPoolExecutorMap.putIfAbsent(interfaze, newThreadPoolExecutor);
            if (threadPoolExecutor == null) {
                threadPoolExecutor = newThreadPoolExecutor;
            }
        }

        return threadPoolExecutor;
    }

    public static ThreadPoolExecutor createThreadPoolExecutor(final String url, final String interfaze, final int corePoolSize, final int maximumPoolSize, final long keepAliveTime, final boolean allowCoreThreadTimeOut) {
        final String threadName = StringUtil.firstLetterToUpper(ClassUtil.convertBeanName(interfaze)) + "-" + (StringUtils.isNotEmpty(url) ? url + "-" : "") + "thread";

        LOG.info("Thread pool executor is created, threadName={}, corePoolSize={}, maximumPoolSize={}, keepAliveTime={}, allowCoreThreadTimeOut={}", threadName, corePoolSize, maximumPoolSize, keepAliveTime, allowCoreThreadTimeOut);

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                TimeUnit.MILLISECONDS,
                createBlockingQueue(),
                new ThreadFactory() {
                    private AtomicInteger number = new AtomicInteger(0);

                    @Override
                    public Thread newThread(Runnable runnable) {
                        return new Thread(runnable, threadName + "-" + number.getAndIncrement());
                    }
                },
                createRejectedPolicy());
        threadPoolExecutor.allowCoreThreadTimeOut(allowCoreThreadTimeOut);

        return threadPoolExecutor;
    }

    public static ThreadPoolExecutor createThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, boolean allowCoreThreadTimeOut) {
        LOG.info("Thread pool executor is created, corePoolSize={}, maximumPoolSize={}, keepAliveTime={}, allowCoreThreadTimeOut={}", corePoolSize, maximumPoolSize, keepAliveTime, allowCoreThreadTimeOut);

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                TimeUnit.MILLISECONDS,
                createBlockingQueue(),
                createRejectedPolicy());
        threadPoolExecutor.allowCoreThreadTimeOut(allowCoreThreadTimeOut);

        return threadPoolExecutor;
    }

    private static BlockingQueue<Runnable> createBlockingQueue() {
        String queue = properties.getString(ThunderConstant.THREAD_POOL_QUEUE_ATTRIBUTE_NAME);
        ThreadQueueType queueType = ThreadQueueType.fromString(queue);

        int queueCapacity = ThunderConstant.CPUS * properties.getInteger(ThunderConstant.THREAD_POOL_QUEUE_CAPACITY_ATTRIBUTE_NAME);

        switch (queueType) {
            case LINKED_BLOCKING_QUEUE:
                return new LinkedBlockingQueue<Runnable>(queueCapacity);
            case ARRAY_BLOCKING_QUEUE:
                return new ArrayBlockingQueue<Runnable>(queueCapacity);
            case SYNCHRONOUS_QUEUE:
                return new SynchronousQueue<Runnable>();
        }

        return null;
    }

    private static RejectedExecutionHandler createRejectedPolicy() {
        String rejectedPolicy = properties.getString(ThunderConstant.THREAD_POOL_REJECTED_POLICY_ATTRIBUTE_NAME);
        ThreadRejectedPolicyType rejectedPolicyType = ThreadRejectedPolicyType.fromString(rejectedPolicy);

        switch (rejectedPolicyType) {
            case BLOCKING_POLICY_WITH_REPORT:
                return new BlockingPolicyWithReport();
            case CALLER_RUNS_POLICY_WITH_REPORT:
                return new CallerRunsPolicyWithReport();
            case ABORT_POLICY_WITH_REPORT:
                return new AbortPolicyWithReport();
            case REJECTED_POLICY_WITH_REPORT:
                return new RejectedPolicyWithReport();
            case DISCARDED_POLICY_WITH_REPORT:
                return new DiscardedPolicyWithReport();
        }

        return null;
    }
}