/**
 * OpenAtlasForAndroid Project
 * The MIT License (MIT) Copyright (OpenAtlasForAndroid) 2015 Bunny Blue,achellies
 * <p>
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 * and associated documentation files (the "Software"), to deal in the Software
 * without restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * <p>
 * The above copyright notice and this permission notice shall be included in all copies
 * or substantial portions of the Software.
 * <p>
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
 * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * @author BunnyBlue
 **/
package com.openatlas.android.task;

import android.os.AsyncTask;
import android.os.Build.VERSION;

import java.io.File;
import java.io.FileFilter;
import java.lang.Thread.State;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Iterator;
import java.util.concurrent.Executor;
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;
import java.util.concurrent.locks.LockSupport;
import java.util.regex.Pattern;

public class SaturativeExecutor extends ThreadPoolExecutor {
    private static final boolean DEBUG = false;
    static final Pattern PATTERN_CPU_ENTRIES;
    static final String TAG = "SatuExec";
    private static SaturationAwareBlockingQueue<Runnable> mQueue;
    private static final HashSet<Thread> mThreads;
    private static final ThreadFactory sThreadFactory;

    protected static class CountedTask implements Runnable {
        static final AtomicInteger mNumRunning;
        Runnable mRunnable;

        public CountedTask(Runnable runnable) {
            this.mRunnable = runnable;
        }

        @Override
        public void run() {
            mNumRunning.incrementAndGet();
            try {
                this.mRunnable.run();
            } finally {
                mNumRunning.decrementAndGet();
            }
        }

        static {
            mNumRunning = new AtomicInteger();
        }
    }

    protected static class SaturationAwareBlockingQueue<T> extends
            LinkedBlockingQueue<T> {
        private static final long serialVersionUID = 1;
        private SaturativeExecutor mExecutor;

        public SaturationAwareBlockingQueue(int i) {
            super(i);
        }

        void setExecutor(SaturativeExecutor saturativeExecutor) {
            this.mExecutor = saturativeExecutor;
        }

        @Override
        public boolean add(T t) {
            if (!this.mExecutor.isReallyUnsaturated()) {
                return super.add(t);
            }
            throw new IllegalStateException("Unsaturated");
        }

        @Override
        public boolean offer(T t) {
            return this.mExecutor.isReallyUnsaturated() ? DEBUG : super
                    .offer(t);
        }

        @Override
        public void put(T t) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean offer(T t, long j, TimeUnit timeUnit) {
            throw new UnsupportedOperationException();
        }
    }

    static {
        PATTERN_CPU_ENTRIES = Pattern.compile("cpu[0-9]+");
        sThreadFactory = new ThreadFactory() {


            private final AtomicInteger a = new AtomicInteger(1);


            @Override
            public Thread newThread(Runnable runnable) {
                Thread thread = new Thread(runnable, "SaturativeThread #"
                        + this.a.getAndIncrement());
                SaturativeExecutor.collectThread(thread);
                return thread;
            }

        };
        mThreads = new HashSet<Thread>();
    }

    @Override
    public void execute(Runnable runnable) {
        super.execute(new CountedTask(runnable));
    }

    public static final boolean installAsDefaultAsyncTaskExecutor(
            ThreadPoolExecutor threadPoolExecutor) {
        if (VERSION.SDK_INT >= 11) {
            try {
                Field declaredField = AsyncTask.class
                        .getDeclaredField("THREAD_POOL_EXECUTOR");
                declaredField.setAccessible(true);
                declaredField.set(null, threadPoolExecutor);
            } catch (Exception e) {
            }
        }
        try {
            Method method = AsyncTask.class.getMethod("setDefaultExecutor",
                    Executor.class);
            method.setAccessible(true);
            method.invoke(null, threadPoolExecutor);
            return true;
        } catch (Exception e2) {
            Field declaredField;
            try {
                declaredField = AsyncTask.class
                        .getDeclaredField("sDefaultExecutor");
                declaredField.setAccessible(true);
                declaredField.set(null, threadPoolExecutor);
                return true;
            } catch (Exception e3) {
                try {
                    declaredField = AsyncTask.class
                            .getDeclaredField("sExecutor");
                    declaredField.setAccessible(true);
                    declaredField.set(null, threadPoolExecutor);
                    return true;
                } catch (Exception e4) {
                    return false;
                }
            }
        }
    }

    public SaturativeExecutor() {
        this(determineBestMinPoolSize());
    }

    public SaturativeExecutor(int corePoolSize) {
        super(corePoolSize, 128, 1, TimeUnit.SECONDS, new SaturationAwareBlockingQueue(
                1024), sThreadFactory, new CallerRunsPolicy());
        // BlockingQueue saturationAwareBlockingQueue = new
        // SaturationAwareBlockingQueue(1024);
        // mQueue = saturationAwareBlockingQueue;
        mQueue = (SaturationAwareBlockingQueue<Runnable>) getQueue();

        ((SaturationAwareBlockingQueue<?>) getQueue()).setExecutor(this);
    }

    protected boolean isReallyUnsaturated() {
        if (isSaturated()) {
            return DEBUG;
        }
        LockSupport.parkNanos(10);
        return !isSaturated() ? true : DEBUG;
    }

    protected boolean isSaturated() {
        if (getPoolSize() <= 3) {
            return DEBUG;
        }
        int corePoolSize = getCorePoolSize();
        int i = CountedTask.mNumRunning.get();
        int size = mThreads.size();
        if (i < corePoolSize || i < size) {
            return true;
        }
        boolean z;
        synchronized (mThreads) {
            Iterator<Thread> it = mThreads.iterator();
            size = 0;
            while (it.hasNext()) {
                State state = it.next().getState();
                if (state == State.RUNNABLE || state == State.NEW) {
                    i = size + 1;
                } else {
                    if (state == State.TERMINATED) {
                        it.remove();
                    }
                    i = size;
                }
                size = i;
            }
        }
        z = size >= corePoolSize;
        return z;
    }

    public static void collectThread(Thread thread) {
        synchronized (mThreads) {
            mThreads.add(thread);
        }
    }

    private static int determineBestMinPoolSize() {
        int countCpuCores = countCpuCores();
        return countCpuCores > 0 ? countCpuCores : Runtime.getRuntime()
                .availableProcessors() * 2;
    }

    private static int countCpuCores() {
        try {
            return new File("/sys/devices/system/cpu/").listFiles(new FileFilter() {

                @Override
                public boolean accept(File file) {
                    return SaturativeExecutor.PATTERN_CPU_ENTRIES.matcher(file.getName())
                            .matches();
                }
            }).length;
        } catch (Exception e) {
            return 0;
        }
    }

}