package com.netease.im.uikit.common.framework;

import android.annotation.TargetApi;
import android.os.Build;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 *  NimTaskExecutor taskExecutor = new NimTaskExecutor("NIM_HTTP_TASK_EXECUTOR", new NimTaskExecutor.Config(1, 3, 10 * 1000, true));
 */
public class NimTaskExecutor implements Executor {

    private final static int QUEUE_INIT_CAPACITY = 20;

    public static final Executor IMMEDIATE_EXECUTOR = new Executor() {
        @Override
        public void execute(Runnable command) {
            command.run();
        }
    };

    public static class Config {
        public int core;
        public int max;
        public int timeout;
        public boolean allowCoreTimeOut;

        public Config(int core, int max, int timeout, boolean allowCoreTimeOut) {
            this.core = core;
            this.max = max;
            this.timeout = timeout;
            this.allowCoreTimeOut = allowCoreTimeOut;
        }
    }

    private final String name;

    private final Config config;

    private ExecutorService service;

    public NimTaskExecutor(String name, Config config) {
        this(name, config, true);
    }

    public NimTaskExecutor(String name, Config config, boolean startup) {
        this.name = name;
        this.config = config;

        if (startup) {
            startup();
        }
    }

    public void startup() {
        synchronized (this) {
            // has startup
            if (service != null && !service.isShutdown()) {
                return;
            }

            // create
            service = createExecutor(config);
        }
    }

    public void shutdown() {
        ExecutorService executor = null;

        synchronized (this) {
            // swap
            if (service != null) {
                executor = service;
                service = null;
            }
        }

        if (executor != null) {
            // shutdown
            if (!executor.isShutdown()) {
                executor.shutdown();
            }

            // recycle
            executor = null;
        }
    }

    @Override
    public void execute(Runnable runnable) {
        synchronized (this) {
            // has shutdown, reject
            if (service == null || service.isShutdown()) {
                return;
            }

            // execute
            service.execute(runnable);
        }
    }

    public Future<?> submit(Runnable runnable) {
        synchronized (this) {
            if (service == null || service.isShutdown()) {
                return null;
            }
            return service.submit(runnable);
        }
    }

    private ExecutorService createExecutor(Config config) {
        ThreadPoolExecutor service = new ThreadPoolExecutor(config.core, config.max, config.timeout,
                TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(QUEUE_INIT_CAPACITY),
                new TaskThreadFactory(name), new ThreadPoolExecutor.DiscardPolicy());

        allowCoreThreadTimeOut(service, config.allowCoreTimeOut);

        return service;
    }

    static class TaskThreadFactory implements ThreadFactory {
        private final ThreadGroup mThreadGroup;

        private final AtomicInteger mThreadNumber = new AtomicInteger(1);

        private final String mNamePrefix;

        TaskThreadFactory(String name) {
            SecurityManager s = System.getSecurityManager();

            mThreadGroup = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();

            mNamePrefix = name + "#";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(mThreadGroup, r, mNamePrefix + mThreadNumber.getAndIncrement(), 0);

            // no daemon
            if (t.isDaemon())
                t.setDaemon(false);

            // normal priority
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);

            return t;
        }
    }

    private static final void allowCoreThreadTimeOut(ThreadPoolExecutor service, boolean value) {
        if (Build.VERSION.SDK_INT >= 9) {
            allowCoreThreadTimeOut9(service, value);
        }
    }

    @TargetApi(9)
    private static final void allowCoreThreadTimeOut9(ThreadPoolExecutor service, boolean value) {
        service.allowCoreThreadTimeOut(value);
    }
}