package net.openmob.mobileimsdk.android.utils;

import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
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;

public class MBThreadPoolExecutor {
    private static final String TAG = MBThreadPoolExecutor.class.getSimpleName();

    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();

    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));

    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;

    private static final long KEEP_ALIVE_TIME = 30L;

    private static final int WAIT_COUNT = 128;

    private static ThreadPoolExecutor pool = createThreadPoolExecutor();

    private static ThreadPoolExecutor createThreadPoolExecutor() {
        if (pool == null)
                pool = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, 30L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(128), new CThreadFactory("MBThreadPool", 3), new CHandlerException());
        return pool;
    }

    public static class CThreadFactory implements ThreadFactory {
        private AtomicInteger counter = new AtomicInteger(1);

        private String prefix = "";

        private int priority = 5;

        public CThreadFactory(String prefix, int priority) {
            this.prefix = prefix;
            this.priority = priority;
        }

        public CThreadFactory(String prefix) {
            this.prefix = prefix;
        }

        public Thread newThread(Runnable r) {
            Thread executor = new Thread(r, this.prefix + " #" + this.counter.getAndIncrement());
            executor.setDaemon(true);
            executor.setPriority(this.priority);
            return executor;
        }
    }

    private static class CHandlerException extends ThreadPoolExecutor.AbortPolicy {
        private CHandlerException() {
        }

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            Log.i(MBThreadPoolExecutor.TAG, "rejectedExecution:" + r);
            Log.i(MBThreadPoolExecutor.TAG, MBThreadPoolExecutor.logAllThreadStackTrace().toString());
            if (!MBThreadPoolExecutor.pool.isShutdown()) {
                MBThreadPoolExecutor.pool.shutdown();
                MBThreadPoolExecutor.pool = null;
            }
            MBThreadPoolExecutor.pool = MBThreadPoolExecutor.createThreadPoolExecutor();
        }
    }

    private static ExecutorService jobsForUI = Executors.newFixedThreadPool(CORE_POOL_SIZE, new CThreadFactory("MBJobsForUI", 4));

    private static Thread mainThread;

    private static Handler mainHandler;

    public static void runInBackground(Runnable runnable) {
        if (pool == null)
            createThreadPoolExecutor();
        pool.execute(runnable);
    }

    static {
        Looper mainLooper = Looper.getMainLooper();
        mainThread = mainLooper.getThread();
        mainHandler = new Handler(mainLooper);
    }

    public static boolean isOnMainThread() {
        return (mainThread == Thread.currentThread());
    }

    public static void runOnMainThread(Runnable r) {
        if (isOnMainThread()) {
            r.run();
        } else {
            mainHandler.post(r);
        }
    }

    public static void runOnMainThread(Runnable r, long delayMillis) {
        if (delayMillis <= 0L) {
            runOnMainThread(r);
        } else {
            mainHandler.postDelayed(r, delayMillis);
        }
    }

    private static HashMap<Runnable, Runnable> mapToMainHandler = new HashMap<>();

    public static void runInBackground(Runnable runnable, long delayMillis) {
        if (delayMillis <= 0L) {
            runInBackground(runnable);
        } else {
            Runnable mainRunnable = () -> {
                mapToMainHandler.remove(runnable);
                pool.execute(runnable);
            };
            if (mapToMainHandler.containsKey(runnable)) {
                Log.d(TAG, "+ runnable + ");
                removeCallbackInBackground(runnable);
            }
            mapToMainHandler.put(runnable, mainRunnable);
            mainHandler.postDelayed(mainRunnable, delayMillis);
        }
    }

    public static void removeCallbackOnMainThread(Runnable r) {
        mainHandler.removeCallbacks(r);
    }

    public static void removeCallbackInBackground(Runnable runnable) {
        Runnable mainRunnable = mapToMainHandler.get(runnable);
        if (mainRunnable != null) {
            mainHandler.removeCallbacks(mainRunnable);
            pool.remove(mainRunnable);
        } else {
            pool.remove(runnable);
        }
    }

    public static void logStatus() {
        StringBuilder sb = new StringBuilder();
        sb.append("getActiveCount=");
        sb.append(pool.getActiveCount());
        sb.append("\ngetTaskCount=");
        sb.append(pool.getTaskCount());
        sb.append("\ngetCompletedTaskCount=");
        sb.append(pool.getCompletedTaskCount());
        Log.d(TAG, sb.toString());
    }

    public static StringBuilder logAllThreadStackTrace() {
        StringBuilder builder = new StringBuilder();
        Map<Thread, StackTraceElement[]> liveThreads = Thread.getAllStackTraces();
        for (Iterator<Thread> i = liveThreads.keySet().iterator(); i.hasNext(); ) {
            Thread key = i.next();
            builder.append("Thread ").append(key.getName()).append("\n");
            StackTraceElement[] trace = liveThreads.get(key);
            for (int j = 0; j < trace.length; j++)
                builder.append("\tat ").append(trace[j]).append("\n");
        }
        return builder;
    }
}