package cn.tee3.avd;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;

import java.util.LinkedList;
import java.util.Queue;

import javax.annotation.Nullable;

/**
 * Created by Bob.Liu on 2016/7/23.
 */
class ToolKit {
    @Nullable private static HandlerPoster mainPoster = null;

    @Nullable synchronized private static HandlerPoster getMainPoster() {
        if (null == mainPoster) {
            mainPoster = new HandlerPoster(Looper.getMainLooper(), 20);
        }
        return mainPoster;
    }

    /**
     * Asynchronously
     * The child thread asynchronous run relative to the main thread,
     * not blocking the child thread
     *
     * @param runnable Runnable Interface
     */
    public static void runOnMainThreadAsync(Runnable runnable) {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            runnable.run();
            return;
        }
        getMainPoster().async(runnable);
    }

    /**
     * Synchronously
     * The child thread relative thread synchronization operation,
     * blocking the child thread,
     * thread for the main thread to complete
     *
     * @param runnable Runnable Interface
     */
    public static void runOnMainThreadSync(Runnable runnable) {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            runnable.run();
            return;
        }
        SyncPost poster = new SyncPost(runnable);
        getMainPoster().sync(poster);
        poster.waitRun();
    }

    public static void dispose() {
        if (mainPoster != null) {
            mainPoster.dispose();
            mainPoster = null;
        }
    }

    static class SyncPost {
        boolean end = false;
        Runnable runnable;

        SyncPost(Runnable runnable) {
            this.runnable = runnable;
        }

        public void run() {
            synchronized (this) {
                runnable.run();
                end = true;
                try {
                    this.notifyAll();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        public void waitRun() {
            if (!end) {
                synchronized (this) {
                    if (!end) {
                        try {
                            this.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    };

    static class HandlerPoster extends Handler {
        private final int ASYNC = 0x1;
        private final int SYNC = 0x2;
        private final Queue<Runnable> asyncPool;
        private final Queue<SyncPost> syncPool;
        private final int maxMillisInsideHandleMessage;
        private boolean asyncActive;
        private boolean syncActive;

        HandlerPoster(Looper looper, int maxMillisInsideHandleMessage) {
            super(looper);
            this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
            asyncPool = new LinkedList<Runnable>();
            syncPool = new LinkedList<SyncPost>();
        }

        void dispose() {
            this.removeCallbacksAndMessages(null);
            this.asyncPool.clear();
            this.syncPool.clear();
        }

        void async(Runnable runnable) {
            synchronized (asyncPool) {
                asyncPool.offer(runnable);
                if (!asyncActive) {
                    asyncActive = true;
                    if (!sendMessage(obtainMessage(ASYNC))) {
                        throw new RuntimeException("Could not send handler message");
                    }
                }
            }
        }

        void sync(SyncPost post) {
            synchronized (syncPool) {
                syncPool.offer(post);
                if (!syncActive) {
                    syncActive = true;
                    if (!sendMessage(obtainMessage(SYNC))) {
                        throw new RuntimeException("Could not send handler message");
                    }
                }
            }
        }

        @Override
        public void handleMessage(Message msg) {
            if (msg.what == ASYNC) {
                boolean rescheduled = false;
                try {
                    long started = SystemClock.uptimeMillis();
                    while (true) {
                        Runnable runnable = asyncPool.poll();
                        if (runnable == null) {
                            synchronized (asyncPool) {
                                // Check again, this time in synchronized
                                runnable = asyncPool.poll();
                                if (runnable == null) {
                                    asyncActive = false;
                                    return;
                                }
                            }
                        }
                        runnable.run();
                        long timeInMethod = SystemClock.uptimeMillis() - started;
                        if (timeInMethod >= maxMillisInsideHandleMessage) {
                            if (!sendMessage(obtainMessage(ASYNC))) {
                                throw new RuntimeException("Could not send handler message");
                            }
                            rescheduled = true;
                            return;
                        }
                    }
                } finally {
                    asyncActive = rescheduled;
                }
            } else if (msg.what == SYNC) {
                boolean rescheduled = false;
                try {
                    long started = SystemClock.uptimeMillis();
                    while (true) {
                        SyncPost post = syncPool.poll();
                        if (post == null) {
                            synchronized (syncPool) {
                                // Check again, this time in synchronized
                                post = syncPool.poll();
                                if (post == null) {
                                    syncActive = false;
                                    return;
                                }
                            }
                        }
                        post.run();
                        long timeInMethod = SystemClock.uptimeMillis() - started;
                        if (timeInMethod >= maxMillisInsideHandleMessage) {
                            if (!sendMessage(obtainMessage(SYNC))) {
                                throw new RuntimeException("Could not send handler message");
                            }
                            rescheduled = true;
                            return;
                        }
                    }
                } finally {
                    syncActive = rescheduled;
                }
            } else
                super.handleMessage(msg);
        }
    };

}
