package com.ajjpj.afoundation.concurrent;

import com.ajjpj.afoundation.function.AFunction0NoThrow;
import com.ajjpj.afoundation.function.AFunction1NoThrow;
import com.ajjpj.afoundation.function.AStatement1NoThrow;
import com.ajjpj.afoundation.util.AUnchecker;
import sun.misc.Contended;
import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author arno
 */
@Contended
public class AThreadPoolImpl implements AThreadPoolWithAdmin {
    private static final int MAX_NUM_PRODUCER_AFFINITIES = 10_000;

    long p1, p2, p3, p4, p5, p6, p7;

    /**
     * This is a compile-time switch to completely remove all statistics gathering for the really paranoid
     */
    public static final boolean SHOULD_GATHER_STATISTICS = true;

    /**
     * This long is a bit set with the indexes of threads that are currently idling. All idling threads are guaranteed to be in this set,
     *  but some threads may be marked as idle though they are still settling down or otherwise not quite idle. All modifications are
     *  done as CAS via UNSAFE.<p>
     * This mechanism allows an optimization when threads are unparked: only threads marked as idle need to be unparked, and only one
     *  volatile read is required rather than one per worker thread.
     * TODO is this really faster, and if so, is the difference significant?
     * TODO this currently limits the number of threads to 64 --> generalize
     */
    @SuppressWarnings({"unused"})
    private volatile long idleThreads = 0;

    static final long MASK_IDLE_THREAD_SCANNING = Long.MIN_VALUE; // top-most bit reserved to signify 'scanning'

    private final ASharedQueue[] sharedQueues;
    final LocalQueue[] localQueues;

    private final Map<Integer, Integer> producerToQueueAffinity = new ConcurrentHashMap<> ();
    private final ASharedQueueAffinityStrategy sharedQueueAffinityStrategy;
    private final AWorkerThreadLifecycleCallback workerThreadLifecycleCallback;

    final AtomicBoolean shutdown = new AtomicBoolean (false);
    final boolean checkShutdownOnSubmission;

    long q1, q2, q3, q4, q5, q6, q7;

    public AThreadPoolImpl (boolean isDaemon, AFunction0NoThrow<String> threadNameFactory, AStatement1NoThrow<Throwable> exceptionHandler,
                            int numThreads, int localQueueSize, int numSharedQueues, boolean checkShutdownOnSubmission, AFunction1NoThrow<AThreadPoolImpl, ASharedQueue> sharedQueueFactory,
                            int ownLocalFifoInterval, int numPrefetchLocal, int skipLocalWorkInterval, int switchSharedQueueInterval, ASharedQueueAffinityStrategy sharedQueueAffinityStrategy,
                            AWorkerThreadLifecycleCallback workerThreadLifecycleCallback) {
        this.sharedQueueAffinityStrategy = sharedQueueAffinityStrategy;
        this.workerThreadLifecycleCallback = workerThreadLifecycleCallback;
        if (numPrefetchLocal >= localQueueSize - 2) {
            throw new IllegalArgumentException ("prefetch number must be smaller than local queue size");
        }

        this.checkShutdownOnSubmission = checkShutdownOnSubmission;
        sharedQueues = new ASharedQueue[numSharedQueues];
        for (int i=0; i<numSharedQueues; i++) {
            sharedQueues[i] = sharedQueueFactory.apply (this);
        }

        final Set<Integer> sharedQueuePrimes = primeFactors (numSharedQueues);

        localQueues = new LocalQueue[numThreads];
        for (int i=0; i<numThreads; i++) {
            localQueues[i] = new LocalQueue (this, localQueueSize);
            final WorkerThread thread = new WorkerThread (ownLocalFifoInterval, skipLocalWorkInterval, switchSharedQueueInterval, numPrefetchLocal, localQueues[i], sharedQueues, this, i, prime (i, sharedQueuePrimes), exceptionHandler);
            //TODO onCreatedThread callback --> core affinity etc. --> ThreadLifecycleCallback: onPostStart, onPreFinish
            thread.setDaemon (isDaemon);
            thread.setName (threadNameFactory.apply ());
            localQueues[i].init (thread);

            workerThreadLifecycleCallback.onPreStart (thread);
        }

        for (LocalQueue queue: localQueues) {
            //noinspection ConstantConditions
            queue.thread.start ();
            workerThreadLifecycleCallback.onPostStart (queue.thread);
        }
    }

    /**
     * This method returns an approximation of statistical data for all worker threads since the pool was started. Updates of the statistical data is done without synchronization,
     *  so some or all of the data may be stale, and some numbers may be pretty outdated while others are very current, even for the same thread. For long-running pools however
     *  the data may be useful in analyzing behavior in general and performance anomalies in particular. Your mileage may vary, you have been warned! ;-)
     */
    @Override public AThreadPoolStatistics getStatistics() {
        final AWorkerThreadStatistics[] workerStats = new AWorkerThreadStatistics[localQueues.length];
        for (int i=0; i<localQueues.length; i++) {
            //noinspection ConstantConditions
            workerStats[i] = localQueues[i].thread.getStatistics ();
        }

        final ASharedQueueStatistics[] sharedQueueStats = new ASharedQueueStatistics[sharedQueues.length];
        for (int i=0; i<sharedQueues.length; i++) {
            sharedQueueStats[i] = new ASharedQueueStatistics (sharedQueues[i].approximateSize());
        }

        return new AThreadPoolStatistics (workerStats, sharedQueueStats);
    }

    static Set<Integer> primeFactors (int n) {
        final Set<Integer> result = new HashSet<> ();

        for (int i=2; i<=n; i++) {
            if (isPrime (i) && n%i == 0) {
                result.add (i);
            }
        }

        return result;
    }

    static int prime (int number, Set<Integer> exceptions) {
        int numPrevPrimes = 0;

        for (int candidate=1; candidate<Integer.MAX_VALUE; candidate++) {
            if (isPrime (candidate) && !exceptions.contains (candidate)) {
                if (numPrevPrimes >= number) {
                    return candidate;
                }
                numPrevPrimes += 1;
            }
        }
        return 1; // this should never happen, but '1' is a safe fallback
    }

    static boolean isPrime (int n) {
        for (int i=2; i<=n/2; i++) {
            if (n%i == 0) return false;
        }
        return true;
    }

    @Override public void submit (Runnable code) {
        // check to reject submissions after shutdown, but only if this check is enabled - it does incur a volatile read on each submission, after all
        if (checkShutdownOnSubmission && shutdown.get ()) {
            throw new IllegalStateException ("pool is already shut down");
        }

        final WorkerThread wt;
        final Thread curThread = Thread.currentThread ();
        if (curThread.getClass () == WorkerThread.class && (wt = (WorkerThread) curThread).pool == this) {
            if (SHOULD_GATHER_STATISTICS) wt.stat_numLocalSubmits += 1;
            try {
                wt.localQueue.push (code);
            }
            catch (RejectedExecutionException e) {
                sharedQueues[getSharedQueueForCurrentThread ()].push (code);
            }
        }
        else {
            sharedQueues[getSharedQueueForCurrentThread ()].push (code);
        }
    }

    private int getSharedQueueForCurrentThread() {
        final int key = System.identityHashCode (Thread.currentThread ());

        Integer result = producerToQueueAffinity.get (key);
        if (result == null) {
            if (producerToQueueAffinity.size () > MAX_NUM_PRODUCER_AFFINITIES) {
                // in the unusual situation that producers are transient, discard affinity data if the map gets too large
                producerToQueueAffinity.clear ();
            }

            result = sharedQueueAffinityStrategy.getSharedQueueIndex (Thread.currentThread (), sharedQueues.length);
            producerToQueueAffinity.put (key, result);
        }

        return result;
    }

    @Override public State getState () {
        if (! shutdown.get ()) return State.Running;

        for (LocalQueue q: this.localQueues) {
            if (q.thread.isAlive ()) return State.ShuttingDown;
        }

        return State.Down;
    }

    /**
     * This method shuts down the thread pool. The method finishes immediately, returning a separate AFuture for every worker thread. In order to combine them into a single
     *  AFuture for all worker threads, use {@code AFuture.lift()} on the result.
     */
    @Override public List<AFuture<Void>> shutdown (ShutdownMode shutdownMode) {
        if (! shutdown.compareAndSet (false, true)) {
            throw new IllegalStateException ("pool can be shut down only once");
        }

        if (shutdownMode == ShutdownMode.SkipUnstarted || shutdownMode == ShutdownMode.InterruptRunning) {
            for (ASharedQueue sharedQueue: sharedQueues) {
                sharedQueue.clear ();
            }

            for (LocalQueue queue: localQueues) {
                //noinspection StatementWithEmptyBody
                while (queue.popFifo () != null) {
                    // do nothing, just drain the queue
                }
            }

            if (shutdownMode == ShutdownMode.InterruptRunning) {
                for (LocalQueue queue: localQueues) {
                    queue.thread.interrupt ();
                }
            }
        }

        final List<AFuture<Void>> result = new ArrayList<> ();

        for (LocalQueue localQueue : localQueues) {
            final ASettableFuture<Void> f = ASettableFuture.create ();
            sharedQueues[0].push (() -> {
                workerThreadLifecycleCallback.onPreDie (localQueue.thread);
                throw new PoolShutdown (f);
            });
            UNSAFE.unpark (localQueue.thread);
            result.add (f);

            f.onComplete (AThreadPool.SYNC_THREADPOOL, x -> workerThreadLifecycleCallback.onPostDie (localQueue.thread));
        }

        return result;
    }

    void onAvailableTask () {
        long idleBitMask = UNSAFE.getLongVolatile (this, OFFS_IDLE_THREADS);
        if ((idleBitMask & MASK_IDLE_THREAD_SCANNING) != 0L) {
            // some other thread is scanning, so there is no need to wake another thread
            return;
        }
        doWakeUpWorker (idleBitMask);
    }

    void wakeUpWorker () {
        long idleBitMask = UNSAFE.getLongVolatile (this, OFFS_IDLE_THREADS);
        doWakeUpWorker (idleBitMask);
    }

    private void doWakeUpWorker (long idleBitMask) {
        if ((idleBitMask & ~MASK_IDLE_THREAD_SCANNING) == 0L) {
            // all threads are busy already
            return;
        }

        for (LocalQueue localQueue : localQueues) {
            if ((idleBitMask & 1L) != 0) {
                //noinspection ConstantConditions
                if (markWorkerAsBusyAndScanning (localQueue.thread)) {
                    // wake up the worker only if no-one else woke up the thread in the meantime
                    UNSAFE.unpark (localQueue.thread);
                }
                // even if someone else woke up the thread in the meantime, at least one thread is scanning --> we can safely abort here
                break;
            }
            idleBitMask = idleBitMask >> 1;
        }
    }

    void markWorkerAsIdle (long mask) {
        long prev, after;
        do {
            prev = UNSAFE.getLongVolatile (this, OFFS_IDLE_THREADS);
            after = prev | mask;

            // A 'scanning' thread (i.e. a thread that was triggered by 'onAvailableTask') going to sleep means that scanning is finished, so we
            //  can clear the flag. A thread going to sleep after doing some work also triggers the flag to be cleared, but that is safe and
            //  incurs little additional overhead - clearing the flag in this place is basically for free.
//            after = after & ~MASK_IDLE_THREAD_SCANNING;
        }
        while (! UNSAFE.compareAndSwapLong (this, OFFS_IDLE_THREADS, prev, after));
    }

    boolean markWorkerAsBusy (long mask) {
        long prev, after;
        do {
            prev = UNSAFE.getLongVolatile (this, OFFS_IDLE_THREADS);
            if ((prev & mask) == 0) {
                // someone else woke up the thread in the meantime
                return false;
            }

            after = prev & ~mask;
        }
        while (! UNSAFE.compareAndSwapLong (this, OFFS_IDLE_THREADS, prev, after));

        return true;
    }

    boolean markWorkerAsBusyAndScanning (WorkerThread worker) {
        final long mask = worker.idleThreadMask;
        long prev, after;
        do {
            prev = UNSAFE.getLongVolatile (this, OFFS_IDLE_THREADS);
            if ((prev & mask) == 0L) {
                // someone else woke up the thread concurrently --> it is scanning now, and there is no need to wake it up or change the 'idle' mask
                return false;
            }

            after = prev & ~mask;
            after = after | MASK_IDLE_THREAD_SCANNING;
        }
        while (! UNSAFE.compareAndSwapLong (this, OFFS_IDLE_THREADS, prev, after));
        return true;
    }

    void unmarkScanning() {
        long prev, after;
        do {
            prev = UNSAFE.getLongVolatile (this, OFFS_IDLE_THREADS);
            after = prev & ~MASK_IDLE_THREAD_SCANNING;
            if (prev == after) {
                return;
            }
        }
        while (! UNSAFE.compareAndSwapLong (this, OFFS_IDLE_THREADS, prev, after));
    }

    //------------------ Unsafe stuff
    private static final Unsafe UNSAFE;

    private static final long OFFS_IDLE_THREADS;

    static {
        try {
            final Field f = Unsafe.class.getDeclaredField ("theUnsafe");
            f.setAccessible (true);
            UNSAFE = (Unsafe) f.get (null);

            OFFS_IDLE_THREADS = UNSAFE.objectFieldOffset (AThreadPoolImpl.class.getDeclaredField ("idleThreads"));
        }
        catch (Exception e) {
            AUnchecker.throwUnchecked (e);
            throw new RuntimeException(); // for the compiler
        }
    }
}


package com.ajjpj.afoundation.collection.immutable;

import com.ajjpj.afoundation.collection.ACollectionHelper;
import com.ajjpj.afoundation.collection.AEquality;
import com.ajjpj.afoundation.function.AFunction1;
import com.ajjpj.afoundation.function.APartialFunction;
import com.ajjpj.afoundation.function.APredicate;

import java.io.*;
import java.lang.reflect.Array;
import java.util.*;

/**
 * This is an immutable linked list implementation. It provides "mutators" that return copies of the list without
 *  affecting the original ("copy on write").

 *
 * The API is based on terminology of functional languages. NIL is the empty list, head is the list's
 *  first element, tail is the list without its first element, and cons() is the
 *  operation that prepends an element to an existing list.
 *
 * @author arno
 */
abstract public class AList<T> extends AbstractACollection<T, AList<T>> implements Serializable {
    private final int size;

    protected AList(int size) {
        this.size = size;
    }

    /**
     * Returns the empty list. All calls to this method are guaranteed to return the same instance.
     */
    @SuppressWarnings("unchecked")
    public static <T> AList<T> nil() {
        return (AList<T>) Nil.INSTANCE;
    }

    public static final AList nil = Nil.INSTANCE;

    /**
     * Creates an AList based on the contents of an existing java.util.Iterable, copying its contents.
     */
    public static <T> AList<T> create(Iterable<T> elements) {
        if (elements instanceof AList) {
            return (AList<T>) elements;
        }
        if(elements instanceof List) {
            return create((List<T>) elements);
        }
        AList<T> result = nil();

        for(T el: elements) {
            result = result.cons(el);
        }
        return result.reverse();
    }

    /**
     * Creates an AList based on the contents of an existing java.util.List, copying its content.
     */
    public static <T> AList<T> create(List<T> elements) {
        AList<T> result = nil();

        for(int i=elements.size()-1; i>=0; i--) {
            result = result.cons(elements.get(i));
        }
        return result;
    }

    /**
     * Creates an AList from a given list of elements.
     */
    @SafeVarargs
    public static <T> AList<T> create(T... elements) {
        return create(Arrays.asList(elements));
    }

    /**
     * Returns a read-only java.util.List  view of this AList.
     */
    public java.util.List<T> asJavaUtilList() {
        return new JuListWrapper<>(this);
    }

    @Override public Collection<T> asJavaUtilCollection () {
        return asJavaUtilList ();
    }

    @Override protected AList<T> createInternal (Collection<T> elements) {
        return AList.create (elements);
    }

    @Override protected AEquality equalityForEquals() {
        return AEquality.EQUALS;
    }

    /**
     * Returns this AList's head, if any.
     */
    public abstract AOption<T> optHead();

    /**
     * Returns the list's head, i.e. its first element. If called on the empty list, it throws a NoSuchElementException.
     */
    public abstract T head();

    /**
     * Returns the list's tail, i.e. the list without its first element. If called on the empty list, it throws a
     *  NoSuchElementException.
     */
    public abstract AList<T> tail();

    /**
     * Returns a new AList with the new element prepended. This is the only operation to 'add' elements to an AList.
     */
    public AList<T> cons(T el) {
        return new AHead<>(el, this);
    }

    /**
     * Returns a copy of this AList with elements in reversed order.
     */
    public AList<T> reverse() {
        AList<T> remaining = this;
        AList<T> result = nil();

        while(! remaining.isEmpty()) {
            result = result.cons(remaining.head());
            remaining = remaining.tail();
        }

        return result;
    }

    public int size() {
        return size;
    }

    @Override public AList<T> toList() {
        // override as an optimization
        return this;
    }

    @Override public <X,E extends Throwable> AList<X> map (AFunction1<? super T, ? extends X, E> f) throws E {
        return create (ACollectionHelper.<T,X,E>map (this, f));
    }

    @Override public <X, E extends Throwable> AList<X> flatMap(AFunction1<? super T, ? extends Iterable<X>, E> f) throws E {
        return create(ACollectionHelper.<T,X,E>flatMap (this, f));
    }

    @SuppressWarnings("unchecked")
    @Override public <X> AList<X> flatten() {
        return (AList<X>) create(ACollectionHelper.flatten((Iterable<? extends Iterable<Object>>) this));
    }

    @Override public <X, E extends Throwable> AList<X> collect (APartialFunction<? super T, ? extends X, E> pf) throws E {
        return create (ACollectionHelper.<T,X,E>collect (this, pf));
    }

    /**
     * Returns a java.util.Iterator over this AList's elements, allowing ALists to be used with Java's
     *  for(...: list) syntax introduced in version 1.5.
     */
    @Override
    public Iterator<T> iterator() {
        return new Iterator<T>() {
            AList<T> pos = AList.this;

            @Override
            public boolean hasNext() {
                return pos.nonEmpty();
            }

            @Override
            public T next() {
                final T result = pos.head();
                pos = pos.tail();
                return result;
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }

    static final class AHead<T> extends AList<T> {
        private final T head;
        private final AList<T> tail;

        AHead(T head, AList<T> tail) {
            super(tail.size() + 1);
            this.head = head;
            this.tail = tail;
        }

        @Override public AOption<T> optHead() {
            return AOption.some(head);
        }

        @Override public T head() {
            return head;
        }

        @Override public AList<T> tail() {
            return tail;
        }

        @Override public boolean contains (T el) {
            return AEquality.EQUALS.equals (el, head) || tail.contains (el);
        }

        @Override public ACollection<T> clear () {
            return nil;
        }
    }

    static final class Nil extends AList<Object> {
        public Nil() {
            super(0);
        }

        public static final Nil INSTANCE = new Nil();

        private Object readResolve() {
            return INSTANCE;
        }

        @Override public AOption<Object> optHead() {
            return AOption.none();
        }

        @Override public Object head() {
            throw new NoSuchElementException("no 'head' for an empty list.");
        }

        @Override public AList<Object> tail() {
            throw new NoSuchElementException("no 'tail' for an empty list.");
        }

        @Override public ACollection<Object> clear () {
            return this;
        }

        @Override public <E extends Throwable> boolean forAll(APredicate<? super Object, E> pred) throws E {
            return true;
        }

        @Override public <E extends Throwable> boolean exists(APredicate<? super Object, E> pred) throws E {
            return false;
        }

        @Override public boolean contains (Object el) {
            return false;
        }
    }

// ------------------------------- java.com.ajjpj.abase.util wrappers

    static class JuListIteratorWrapper<T> implements java.util.ListIterator<T> {
        private AList<T> inner;
        private int idx;

        JuListIteratorWrapper(AList<T> inner, int idx) {
            this.inner = inner;
            this.idx = idx;
        }

        @Override
        public boolean hasNext() {
            return ! inner.isEmpty();
        }

        @Override
        public T next() {
            final T result = inner.head();
            inner = inner.tail();
            idx += 1;
            return result;
        }

        @Override
        public boolean hasPrevious() {
            return false;
        }

        @Override
        public T previous() {
            return null;
        }

        @Override
        public int nextIndex() {
            return idx+1;
        }

        @Override
        public int previousIndex() {
            return idx-1;
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }

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

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

    static class JuListWrapper<T> implements java.util.List<T> {
        private final AList<T> inner;

        JuListWrapper(AList<T> inner) {
            this.inner = inner;
        }

        @Override
        public int size() {
            return inner.size();
        }

        @Override
        public boolean isEmpty() {
            return inner.isEmpty();
        }

        @Override
        public boolean contains(Object o) {
            return indexOf(o) >= 0;
        }

        @Override
        public boolean containsAll(Collection<?> c) {
            for(Object o: c) {
                if(! contains(o)) {
                    return false;
                }
            }
            return true;
        }

        @Override
        public T get(int index) {
            AList<T> l = inner;

            for(int i=0; i<index; i++) {
                l = l.tail();
            }

            return l.head();
        }

        @Override
        public int indexOf(Object o) {
            int idx = 0;
            AList<T> l = inner;

            while(! l.isEmpty()) {
                if(AEquality.EQUALS.equals(l.head(), o)) {
                    return idx;
                }

                idx += 1;
                l = l.tail();
            }
            return -1;
        }

        @Override
        public int lastIndexOf(Object o) {
            int idx = 0;
            AList<T> l = inner;
            int result = -1;

            while(! l.isEmpty()) {
                if(AEquality.EQUALS.equals(l.head(), o)) {
                    result = idx;
                }

                idx += 1;
                l = l.tail();
            }
            return result;
        }

        @Override
        public Iterator<T> iterator() {
            return listIterator();
        }

        @Override
        public Object[] toArray() {
            final Object[] result = new Object[inner.size()];
            int idx = 0;

            for(Object o: this) {
                result[idx] = o;
                idx += 1;
            }
            return result;
        }

        @SuppressWarnings("unchecked")
        @Override
        public <T1> T1[] toArray(T1[] a) {
            if(a.length < size()) {
                a = (T1[]) Array.newInstance(a.getClass().getComponentType(), size());
            }

            if(a.length >= size()) {
                int idx = 0;

                for(Object o: this) {
                    a[idx] = (T1) o;
                    idx += 1;
                }

                if(a.length > size()) {
                    a[size()] = null;
                }
            }

            return a;
        }

        @Override
        public ListIterator<T> listIterator() {
            return new JuListIteratorWrapper<>(inner, 0);
        }

        @Override
        public ListIterator<T> listIterator(int index) {
            AList<T> l = inner;
            for(int i=0; i<index; i++) {
                l = l.tail();
            }
            return new JuListIteratorWrapper<>(l, index);
        }

        @Override
        public List<T> subList(int fromIndex, int toIndex) {
            throw new UnsupportedOperationException();
        }

        // ------------------- mutators

        @Override
        public T set(int index, T element) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void add(int index, T element) {
            throw new UnsupportedOperationException();
        }

        @Override
        public T remove(int index) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean add(T t) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean remove(Object o) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean addAll(Collection<? extends T> c) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean addAll(int index, Collection<? extends T> c) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean removeAll(Collection<?> c) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean retainAll(Collection<?> c) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void clear() {
            throw new UnsupportedOperationException();
        }
    }
}


package com.ajjpj.afoundation.collection;

import com.ajjpj.afoundation.collection.immutable.ACollection;
import com.ajjpj.afoundation.collection.immutable.AOption;
import com.ajjpj.afoundation.collection.immutable.ASet;
import com.ajjpj.afoundation.collection.immutable.AbstractACollection;
import com.ajjpj.afoundation.function.AFunction1;
import com.ajjpj.afoundation.function.AFunction2;
import com.ajjpj.afoundation.function.APartialFunction;
import com.ajjpj.afoundation.function.APredicate;

import java.lang.reflect.Array;
import java.util.*;

/**
 * This class consists of a number of useful methods that operate on a variety of collection types.
 *
 * @author arno
 */
public class ACollectionHelper {
    /**
     * Create a {@link java.util.Set} from an {@link Iterator}.
     */
    public static <T> Set<T> createSet (Iterator<T> elements) {
        final Set<T> result = new HashSet<> ();
        while (elements.hasNext ()) result.add (elements.next ());
        return result;
    }

    /**
     * Create a {@link java.util.Set} from an {@link java.lang.Iterable}.
     */
    public static <T> Set<T> createSet (Iterable<T> elements) {
        return createSet (elements.iterator ());
    }

    /**
     * Create a {@link java.util.Set} from an {@link Iterator}, transforming each element.
     */
    public static <T,U,E extends Throwable> Set createSet (Iterator<T> elements, AFunction1<T,U,E> f) throws E {
        final Set result = new HashSet<> ();
        while (elements.hasNext ()) result.add (f.apply (elements.next ()));
        return result;
    }

    /**
     * Create a {@link java.util.Set} from an {@link Iterable}, transforming each element.
     */
    public static <T,U,E extends Throwable> Set createSet (Iterable<T> elements, AFunction1<T,U,E> f) throws E {
        return createSet (elements.iterator (), f);
    }

    /**
     * Returns a string representation of a collection, separating elements with a comma.
     */
    public static String mkString(Iterable<?> iterable) {
        return mkString (iterable, ", ");
    }

    /**
     * Returns a string representation of a collection, separating elements with a separator.
     */
    public static String mkString(Iterable<?> iterable, String separator) {
        return mkString(iterable, "", separator, "");
    }

    /**
     * Returns a string representation of a collection, separating elements with a separator and putting
     *  prefix before the first and a suffix after the last element.
     */
    public static String mkString(Iterable<?> iterable, String prefix, String separator, String suffix) {
        final StringBuilder result = new StringBuilder(prefix);

        boolean first = true;
        for(Object o: iterable) {
            if(first) {
                first = false;
            }
            else {
                result.append(separator);
            }
            result.append(o);
        }

        result.append(suffix);
        return result.toString ();
    }

    /**
     * Returns an element of a collection that matches a predicate, if any, or AOption.none() if there is no match.
     */
    public static <T, E extends Throwable> AOption<T> find(Iterable<T> coll, APredicate<? super T, E> pred) throws E {
        for(T o: coll) {
            if(pred.apply(o)) {
                return AOption.some(o);
            }
        }
        return AOption.none ();
    }

    /**
     * Matches a predicate against collection elements, and returns true iff it matches them all.
     */
    public static <T, E extends Throwable> boolean forAll(Iterable<T> coll, APredicate<? super T, E> pred) throws E {
        for(T o: coll) {
            if(!pred.apply(o)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Matches a predicate against collection elements, and returns true iff it matches at least one of them.
     */
    public static <T, E extends Throwable> boolean exists(Iterable<T> coll, APredicate<? super T, E> pred) throws E {
        for(T o: coll) {
            if(pred.apply(o)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Applies a transformation function to all elements of a collection, creating a new collection from the results.
     */
    public static <T, X, E extends Throwable> Collection<X> map(Iterable<T> coll, AFunction1<? super T, ? extends X, E> f) throws E {
        final List<X> result = new ArrayList<>();

        for(T o: coll) {
            result.add(f.apply(o));
        }

        return result;
    }

    /**
     * Applies a transformation function to all elements of a collection, creating a new collection from the results.
     */
    public static <T, X, E extends Throwable> List<X> map(List<T> coll, AFunction1<? super T, ? extends X, E> f) throws E {
        final List<X> result = createEmptyListOfType (coll, true);

        for(T o: coll) {
            result.add(f.apply(o));
        }

        return result;
    }

    /**
     * Applies a transformation function to all elements of a collection, creating a new collection from the results.
     */
    public static <T, X, E extends Throwable> Set<X> map (Set<T> coll, AFunction1<? super T, ? extends X, E> f) throws E {
        final Set<X> result = createEmptySetOfType(coll, true);

        for(T o: coll) {
            result.add(f.apply(o));
        }

        return result;
    }

    @SuppressWarnings ("unchecked")
    private static <T> List<T> createEmptyListOfType (List<?> original, boolean sameSize) {
        if (original instanceof ArrayList) {
            return sameSize ? new ArrayList<T> (original.size ()) : new ArrayList<T> ();
        }

        try {
            return original.getClass ().newInstance ();
        }
        catch (Exception e) {
            return sameSize ? new ArrayList<T> (original.size ()) : new ArrayList<T> ();
        }
    }

    @SuppressWarnings ("unchecked")
    private static <T> Set<T> createEmptySetOfType (Set<?> original, boolean sameSize) {
        if (original instanceof HashSet) {
            return sameSize ? new HashSet<T> (original.size ()) : new HashSet<T> ();
        }

        try {
            return original.getClass ().newInstance ();
        }
        catch (Exception e) {
            return sameSize ? new HashSet<T> (original.size ()) : new HashSet<T> ();
        }
    }

    /**
     * Same as map(), except that the transformation function returns collections and all the results are
     *  flattened into a single collection.
     */
    public static <T, X, E extends Throwable> Collection<X> flatMap(Iterable<T> coll, AFunction1<? super T, ? extends Iterable<X>, E> f) throws E {
        final List<X> result = new ArrayList<>();

        for(T o: coll) {
            for(X el: f.apply(o)) {
                result.add(el);
            }
        }

        return result;
    }

    /**
     * Same as map(), except that the transformation function returns collections and all the results are
     *  flattened into a single collection.
     */
    public static <T, X, E extends Throwable> List<X> flatMapList(Iterable<T> coll, AFunction1<? super T, ? extends Iterable<X>, E> f) throws E {
        final List<X> result = new ArrayList<>();

        for(T o: coll) {
            for(X el: f.apply(o)) {
                result.add(el);
            }
        }

        return result;
    }

    /**
     * Same as map(), except that the transformation function returns collections and all the results are
     *  flattened into a single collection.
     */
    public static <T, X, E extends Throwable> Set<X> flatMapSet(Iterable<T> coll, AFunction1<? super T, ? extends Iterable<X>, E> f) throws E {
        final Set<X> result = new HashSet<>();

        for(T o: coll) {
            for(X el: f.apply(o)) {
                result.add(el);
            }
        }

        return result;
    }

    /**
     * Takes a collection of collections and creates a new collection from the elements, leaving out the innermost
     *  level of collection.
     */
    public static <T> Collection<T> flatten(Iterable<? extends Iterable<T>> coll) {
        final List<T> result = new ArrayList<>();
        for(Iterable<T> o: coll) {
            for(T el: o) {
                result.add(el);
            }
        }
        return result;
    }

    /**
     * Takes a collection of collections and creates a new collection from the elements, leaving out the innermost
     *  level of collection.
     */
    public static <T> List<T> flattenList(Iterable<? extends Iterable<T>> coll) {
        final List<T> result = new ArrayList<>();
        for(Iterable<T> o: coll) {
            for(T el: o) {
                result.add(el);
            }
        }
        return result;
    }

    /**
     * Takes a collection of collections and creates a new collection from the elements, leaving out the innermost
     *  level of collection.
     */
    public static <T> Set<T> flattenSet(Iterable<? extends Iterable<T>> coll) {
        final Set<T> result = new HashSet<>();
        for(Iterable<T> o: coll) {
            for(T el: o) {
                result.add(el);
            }
        }
        return result;
    }

    /**
     * Applies a transformation function to all elements of a collection, where the partial function is defined for. Creates a new collection
     *   of the transformed elements only. So the number of result elements may be less than the number of elements in the source collection.
     */
    public static <T, X, E extends Throwable> Collection<X> collect (Iterable<T> coll, APartialFunction<? super T, ? extends X, E> pf) throws E {
        final List<X> result = new ArrayList<>();

        for (T o: coll) {
            if (pf.isDefinedAt (o)) {
                result.add (pf.apply (o));
            }
        }

        return result;
    }

    /**
     * Applies a transformation function to all elements of a collection, where the partial function is defined for. Creates a new collection
     *   of the transformed elements only. So the number of result elements may be less than the number of elements in the source collection.
     */
    public static <T, X, E extends Throwable> List<X> collect (List<T> coll, APartialFunction<? super T, ? extends X, E> pf) throws E {
        final List<X> result = createEmptyListOfType (coll, true);

        for (T o: coll) {
            if (pf.isDefinedAt (o)) {
                result.add (pf.apply (o));
            }
        }

        return result;
    }

    /**
     * Applies a transformation function to all elements of a collection, where the partial function is defined for. Creates a new collection
     *   of the transformed elements only. So the number of result elements may be less than the number of elements in the source collection.
     */
    public static <T, X, E extends Throwable> Set<X> collect (Set<T> coll, APartialFunction<? super T, ? extends X, E> pf) throws E {
        final Set<X> result = createEmptySetOfType(coll, true);

        for(T o: coll) {
            if (pf.isDefinedAt (o)) {
                result.add (pf.apply (o));
            }
        }

        return result;
    }

    /**
     * Matches all elements of a collection against a predicate, creating a new collection from those that match.
     */
    public static <T, E extends Throwable> Collection<T> filter(Iterable<T> coll, APredicate<? super T, E> pred) throws E {
        final List<T> result = new ArrayList<>();
        for(T o: coll) {
            if(pred.apply(o)) {
                result.add(o);
            }
        }
        return result;
    }

    /**
     * Matches all elements of a collection against a predicate, creating a new collection from those that match.
     */
    public static <T, E extends Throwable> List<T> filter(List<T> coll, APredicate<? super T, E> pred) throws E {
        final List<T> result = createEmptyListOfType (coll, false);
        for(T o: coll) {
            if(pred.apply(o)) {
                result.add(o);
            }
        }
        return result;
    }

    /**
     * Matches all elements of a collection against a predicate, creating a new collection from those that match.
     */
    public static <T, E extends Throwable> Set<T> filter(Set<T> coll, APredicate<? super T, E> pred) throws E {
        final Set<T> result = createEmptySetOfType(coll, false);
        for(T o: coll) {
            if(pred.apply(o)) {
                result.add(o);
            }
        }
        return result;
    }

    /**
     * Matches all elements of a collection against a predicate, creating a new collection from those that do not match.
     */
    public static <T, E extends Throwable> Collection<T> filterNot (Iterable<T> coll, APredicate<? super T, E> pred) throws E {
        final List<T> result = new ArrayList<>();
        for(T o: coll) {
            if(! pred.apply(o)) {
                result.add(o);
            }
        }
        return result;
    }

    /**
     * Matches all elements of a collection against a predicate, creating a new collection from those that do not match.
     */
    public static <T, E extends Throwable> List<T> filterNot (List<T> coll, APredicate<? super T, E> pred) throws E {
        final List<T> result = createEmptyListOfType (coll, false);
        for(T o: coll) {
            if (! pred.apply(o)) {
                result.add(o);
            }
        }
        return result;
    }

    /**
     * Matches all elements of a collection against a predicate, creating a new collection from those that do not match.
     */
    public static <T, E extends Throwable> Set<T> filterNot (Set<T> coll, APredicate<? super T, E> pred) throws E {
        final Set<T> result = createEmptySetOfType(coll, false);
        for(T o: coll) {
            if (! pred.apply(o)) {
                result.add(o);
            }
        }
        return result;
    }

    /**
     * Creates a Map from a collection. Each element's key is determined by applying a function to the element. All
     *  elements with the same key are stored as that key's value in the returned Map.
     */
    public static <T, X, E extends Throwable> Map<X, Collection<T>> groupBy (Iterable<T> coll, AFunction1<? super T, ? extends X, E> f) throws E {
        final Map<X, Collection<T>> result = new HashMap<>();
        for(T o: coll) {
            final X key = f.apply(o);
            Collection<T> perKey = result.get(key);
            if(perKey == null) {
                perKey = new ArrayList<>();
                result.put(key, perKey);
            }
            perKey.add(o);
        }
        return result;
    }

    /**
     * Creates a Map from a collection. Each element's key is determined by applying a function to the element. All
     *  elements with the same key are stored as that key's value in the returned Map.
     */
    public static <T, X, E extends Throwable> Map<X, List<T>> groupBy (List<T> coll, AFunction1<? super T, ? extends X, E> f) throws E {
        final Map<X, List<T>> result = new HashMap<>();
        for(T o: coll) {
            final X key = f.apply(o);
            List<T> perKey = result.get(key);
            if(perKey == null) {
                perKey = createEmptyListOfType (coll, false);
                result.put(key, perKey);
            }
            perKey.add(o);
        }
        return result;
    }

    /**
     * Creates a Map from a collection. Each element's key is determined by applying a function to the element. All
     *  elements with the same key are stored as that key's value in the returned Map.
     */
    public static <T, X, E extends Throwable> Map<X, Set<T>> groupBy (Set<T> coll, AFunction1<? super T, ? extends X, E> f) throws E {
        final Map<X, Set<T>> result = new HashMap<>();
        for(T o: coll) {
            final X key = f.apply(o);
            Set<T> perKey = result.get(key);
            if(perKey == null) {
                perKey = createEmptySetOfType(coll, false);
                result.put(key, perKey);
            }
            perKey.add(o);
        }
        return result;
    }

    /**
     * Creates a Map from a collection. Each element's key is determined by applying a function to the element. All
     *  elements with the same key are stored as that key's value in the returned Map.

     *
     * This method gives control over the equalityForEquals strategy used to determine if two keys are 'equal'. To accomodate that,
     *  the keys are wrapped in AEqualsWrapper.

     *
     * This method is rather technical in nature, and it is probably more useful as a foundation for generic code than
     *  for direct use by applications.
     */
    public static <T, X, E extends Throwable> Map<AEqualsWrapper<X>, Collection<T>> groupBy(Iterable<T> coll, AFunction1<? super T, ? extends X, E> f, AEquality keyEquality) throws E {
        final Map<AEqualsWrapper<X>, Collection<T>> result = new HashMap<>();
        for(T o: coll) {
            final AEqualsWrapper<X> key = new AEqualsWrapper<>(keyEquality, f.apply(o));
            Collection<T> perKey = result.get(key);
            if(perKey == null) {
                perKey = new ArrayList<>();
                result.put (key, perKey);
            }
            perKey.add(o);
        }
        return result;
    }

    /**
     * Creates a Map from a collection. Each element's key is determined by applying a function to the element. All
     *  elements with the same key are stored as that key's value in the returned Map.

     *
     * This method gives control over the equalityForEquals strategy used to determine if two keys are 'equal'. To accomodate that,
     *  the keys are wrapped in AEqualsWrapper.

     *
     * This method is rather technical in nature, and it is probably more useful as a foundation for generic code than
     *  for direct use by applications.
     */
    public static <T, X, E extends Throwable> Map<AEqualsWrapper<X>, List<T>> groupBy(List<T> coll, AFunction1<? super T, ? extends X, E> f, AEquality keyEquality) throws E {
        final Map<AEqualsWrapper<X>, List<T>> result = new HashMap<>();
        for(T o: coll) {
            final AEqualsWrapper<X> key = new AEqualsWrapper<>(keyEquality, f.apply(o));
            List<T> perKey = result.get(key);
            if(perKey == null) {
                perKey = createEmptyListOfType (coll, false);
                result.put (key, perKey);
            }
            perKey.add(o);
        }
        return result;
    }

    /**
     * Creates a Map from a collection. Each element's key is determined by applying a function to the element. All
     *  elements with the same key are stored as that key's value in the returned Map.

     *
     * This method gives control over the equalityForEquals strategy used to determine if two keys are 'equal'. To accomodate that,
     *  the keys are wrapped in AEqualsWrapper.

     *
     * This method is rather technical in nature, and it is probably more useful as a foundation for generic code than
     *  for direct use by applications.
     */
    public static <T, X, E extends Throwable> Map<AEqualsWrapper<X>, Set<T>> groupBy (Set<T> coll, AFunction1<? super T, ? extends X, E> f, AEquality keyEquality) throws E {
        final Map<AEqualsWrapper<X>, Set<T>> result = new HashMap<>();
        for(T o: coll) {
            final AEqualsWrapper<X> key = new AEqualsWrapper<>(keyEquality, f.apply(o));
            Set<T> perKey = result.get(key);
            if(perKey == null) {
                perKey = createEmptySetOfType(coll, false);
                result.put(key, perKey);
            }
            perKey.add(o);
        }
        return result;
    }

    /**
     * Applies a binary operator to a start value and all elements of this sequence, going left to right.
     *
     * @param <T> element type of the collection
     * @param <R> result type
     */
    public static <T, R, E extends Throwable> R foldLeft (Iterable<T> coll, R startValue, AFunction2<R, ? super T, R, E> f) throws E {
        R result = startValue;

        for (T e: coll) {
            result = f.apply (result, e);
        }

        return result;
    }

    /**
     * Applies a binary operator to a start value and all elements of this list, going left to right.
     *
     * @param <T> element type of the collection
     * @param <R> result type
     *
     */
    public static <T, R, E extends Throwable> R foldRight (List<T> coll, R startValue, AFunction2<R, ? super T, R, E> f) throws E {
        R result = startValue;

        ListIterator<T> i = coll.listIterator(coll.size());
        while ( i.hasPrevious() ) {
            result = f.apply (result, i.previous());
        }

        return result;
    }

    /**
     * Copies the content of an Iterable into an (immutable) ACollection instance. Subsequent
     *  changes to the underlying collection have no effect on the returned ACollection instance.

     *
     * The returned collection has list semantics with regard to map() and other modifying methods;
     *  duplicate values are allowed.
     */
    public static <T> ACollectionWrapper<T> asACollectionCopy(Collection<T> c) {
        return asACollectionView(new ArrayList<>(c));
    }

    /**
     * Wraps the content of a java.util.Collection in an ACollection instance. While the returned
     *  instance itself has no mutator methods, changes to the underlying collection are reflected in the wrapping
     *  ACollection instance.

     *
     * The returned collection has list semantics with regard to map() and other modifying methods; duplicate
     *  values are allowed.
     */
    @SuppressWarnings("unchecked")
    public static <T> ACollectionWrapper<T> asACollectionView(Collection<T> c) {
        return new ACollectionWrapper(c);
    }

    /**
     * Wraps the content of a java.util.Set in an ASet instance. While the returned
     *  instance itself has no mutator methods, changes to the underlying collection are reflected in the wrapping
     *  ASet instance.

     *
     * The returned collection has set semantics with regard to map() and other modifying methods; duplicate
     *  values are removed.
     */
    public static <T> ASetWrapper<T> asASetView (Collection<T> c) {
        if (c instanceof Set) {
            return new ASetWrapper<> ((Set<T>) c);
        }

        return new ASetWrapper<> (new HashSet<> (c));
    }

    /**
     * Copies the content of an array into an (immutable) ACollection instance. Subsequent
     *  changes to the underlying array have no effect on the returned ACollection instance.

     *
     * The returned collection has list semantics with regard to map() and other modifying methods;
     *  duplicate values are allowed.
     */
    @SuppressWarnings("unchecked")
    public static <T> AArrayWrapper<T> asArrayCopy(T[] c) {
        final T[] newArray = (T[]) Array.newInstance(c.getClass().getComponentType(), c.length);
        System.arraycopy(c, 0, newArray, 0, c.length);
        return new AArrayWrapper<>(newArray);
    }

    /**
     * Wraps the content of an array in an ACollection instance. While the returned
     *  instance itself has no mutator methods, changes to the underlying array are reflected in the wrapping
     *  ACollection instance.

     *
     * The returned collection has list semantics with regard to map() and other modifying methods; duplicate
     *  values are allowed.
     */
    public static <T> AArrayWrapper<T> asArrayView(T[] c) {
        return new AArrayWrapper<>(c);
    }

    public static class ACollectionWrapper<T> extends AbstractACollection<T, ACollectionWrapper<T>> {
        private final Collection<T> inner;

        private ACollectionWrapper(Collection<T> inner) {
            this.inner = inner;
        }

        @Override protected ACollectionWrapper<T> createInternal(Collection<T> elements) {
            return new ACollectionWrapper<>(elements);
        }

        @Override protected AEquality equalityForEquals() {
            return AEquality.EQUALS;
        }

        @Override public ACollection<T> clear () {
            throw new UnsupportedOperationException ();
        }

        @Override public boolean contains (T el) {
            for (T e: inner) {
                if (Objects.equals (e, el)) {
                    return true;
                }
            }
            return false;
        }

        @Override public int size() {
            return inner.size();
        }

        @Override public <X, E extends Throwable> ACollection<X> map(AFunction1<? super T, ? extends X, E> f) throws E {
            return new ACollectionWrapper<>(ACollectionHelper.<T,X,E>map(inner, f));
        }

        @Override public <X, E extends Throwable> ACollection<X> flatMap(AFunction1<? super T, ? extends Iterable<X>, E> f) throws E {
            return new ACollectionWrapper<>(ACollectionHelper.<T,X,E>flatMap (inner, f));
        }

        @SuppressWarnings("unchecked")
        @Override public <X> ACollection<X> flatten() {
            return new ACollectionWrapper<>(ACollectionHelper.flatten ((Iterable<? extends Iterable<X>>) inner));
        }

        @Override public <X, E extends Throwable> ACollection<X> collect (APartialFunction<? super T, ? extends X, E> pf) throws E {
            return new ACollectionWrapper<>(ACollectionHelper.<T,X,E>collect (inner, pf));
        }

        @Override public <R, E extends Throwable> R foldLeft (R startValue, AFunction2<R, ? super T, R, E> f) throws E {
            return ACollectionHelper.foldLeft (inner, startValue, f);
        }

        @SuppressWarnings("NullableProblems")
        @Override public Iterator<T> iterator() {
            return inner.iterator();
        }
    }

    public static class ASetWrapper<T> extends AbstractACollection<T, ASetWrapper<T>> implements ASet<T> {
        private final Set<T> inner;

        public ASetWrapper (Set<T> inner) {
            this.inner = inner;
        }

        @Override public Set<T> asJavaUtilSet () {
            return inner;
        }

        @Override protected ASetWrapper<T> createInternal (Collection<T> elements) {
            return new ASetWrapper<> (new HashSet <> (elements));
        }
        @Override public ACollection<T> clear () {
            return createInternal (new HashSet<T> ());
        }
        @Override public int size () {
            return inner.size ();
        }
        @Override public boolean contains (T el) {
            return inner.contains (el);
        }
        @Override public Iterator<T> iterator () {
            return inner.iterator ();
        }

        @Override public AEquality equalityForEquals () {
            return AEquality.EQUALS;
        }
        @Override public ASet<T> with (T el) {
            throw new UnsupportedOperationException ("adding and removing elements is not supported for views - create a full-blown AHashSet instead");
        }

        @Override public ASet<T> without (T el) {
            throw new UnsupportedOperationException ("adding and removing elements is not supported for views - create a full-blown AHashSet instead");
        }

        @Override public <X, E extends Throwable> ASet<X> map (AFunction1<? super T, ? extends X, E> f) throws E {
            return new ASetWrapper<> (ACollectionHelper.<T,X,E>map (inner, f));
        }

        @Override public <X, E extends Throwable> ASet<X> flatMap (AFunction1<? super T, ? extends Iterable<X>, E> f) throws E {
            return new ASetWrapper<> (new HashSet<> (ACollectionHelper.<T,X,E>flatMap (inner, f)));
        }

        @Override public <X, E extends Throwable> ASet<X> collect (APartialFunction<? super T, ? extends X, E> pf) throws E {
            return new ASetWrapper<> (ACollectionHelper.<T,X,E>collect (inner, pf));
        }

        @SuppressWarnings ("unchecked")
        @Override public <X> ASet<X> flatten () {
            return new ASetWrapper<>(new HashSet<> (ACollectionHelper.flatten ((Iterable<? extends Iterable<X>>) inner)));
        }

        @Override public <E extends Throwable> ASetWrapper<T> filter (APredicate<? super T, E> pred) throws E {
            return new ASetWrapper<> (ACollectionHelper.<T,E>filter (inner, pred));
        }

        @Override public <E extends Throwable> ASetWrapper<T> filterNot (APredicate<? super T, E> pred) throws E {
            return new ASetWrapper<> (ACollectionHelper.<T,E>filterNot (inner, pred));
        }
    }

    public static class AArrayWrapper<T> extends AbstractACollection<T, AArrayWrapper<T>> {
        private final T[] inner;

        private AArrayWrapper(T[] inner) {
            this.inner = inner;
        }

        @SuppressWarnings("unchecked")
        @Override protected AArrayWrapper<T> createInternal(Collection<T> elements) {
            final T[] result = (T[]) Array.newInstance(inner.getClass().getComponentType(), elements.size());
            int idx = 0;
            for(T o: elements) {
                result[idx++] = o;
            }
            return new AArrayWrapper<>(result);
        }

        @Override public ACollection<T> clear () {
            throw new UnsupportedOperationException ();
        }

        @Override public boolean contains (T el) {
            for (T e: inner) {
                if (Objects.equals (e, el)) {
                    return true;
                }
            }
            return false;
        }

        @Override protected AEquality equalityForEquals() {
            return AEquality.EQUALS;
        }

        @Override public int size() {
            return inner.length;
        }

        /**
         * Returns ACollectionWrapper instead of AArrayWrapper because Java can not instantiate an array for a component type that is available only as a generic parameter.
         */
        @SuppressWarnings("unchecked")
        @Override public <X> ACollectionWrapper<X> flatten() {
            return new ACollectionWrapper<>(ACollectionHelper.flatten(Arrays.asList((Iterable<X>[]) inner)));
        }

        /**
         * Returns ACollectionWrapper instead of AArrayWrapper because Java can not instantiate an array for a component type that is available only as a generic parameter.
         */
        @Override public <X, E extends Throwable> ACollectionWrapper<X> map(AFunction1<? super T, ? extends X, E> f) throws E {
            return new ACollectionWrapper<>(ACollectionHelper.<T,X,E>map (Arrays.asList (inner), f));
        }

        /**
         * Returns ACollectionWrapper instead of AArrayWrapper because Java can not instantiate an array for a component type that is available only as a generic parameter.
         */
        @Override public <X, E extends Throwable> ACollectionWrapper<X> flatMap(AFunction1<? super T, ? extends Iterable<X>, E> f) throws E {
            return new ACollectionWrapper<>(ACollectionHelper.<T,X,E>flatMap (Arrays.asList (inner), f));
        }

        @Override public <X, E extends Throwable> ACollection<X> collect (APartialFunction<? super T, ? extends X, E> pf) throws E {
            return new ACollectionWrapper<> (ACollectionHelper.<T,X,E>collect (Arrays.asList (inner), pf));
        }

        @Override public <R, E extends Throwable> R foldLeft (R startValue, AFunction2<R, ? super T, R, E> f) throws E {
            return ACollectionHelper.foldLeft (Arrays.asList (inner), startValue, f);
        }

        @SuppressWarnings("NullableProblems")
        @Override
        public Iterator<T> iterator() {
            return new Iterator<T>() {
                int idx = 0;

                @Override public boolean hasNext() {
                    return idx < inner.length;
                }

                @Override public T next() {
                    final T result = inner[idx];
                    idx += 1;
                    return result;
                }

                @Override
                public void remove() {
                    throw new UnsupportedOperationException();
                }
            };
        }
    }

    /**
     * Returns a Collection with the exact same elements as an Iterable, copying only if the parameter is not a collection.
     */
    public static <T> List<T> asJavaUtilList(Iterable<T> c) {
        if(c instanceof List) {
            return (List<T>) c;
        }

        return asJavaUtilCollection(c.iterator());

    }
    /**
     * Returns a Collection with the exact same elements as an Iterable, copying only if the parameter is not a collection.
     */
    public static <T> Collection<T> asJavaUtilCollection(Iterable<T> c) {
        if(c instanceof Collection) {
            return (Collection<T>) c;
        }

        return asJavaUtilCollection(c.iterator());
    }

    /**
     * Copies the elements from an Iterator into a Collection.
     */
    public static <T> List<T> asJavaUtilCollection(Iterator<T> c) {
        final List<T> result = new ArrayList<>();

        while(c.hasNext()) {
            result.add(c.next());
        }

        return result;
    }
}


package com.ajjpj.afoundation.collection.immutable;

import com.ajjpj.afoundation.collection.AEquality;
import com.ajjpj.afoundation.function.AFunction1;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.Map;

/**
 * This is an immutable hash map based on 32-way hash tries. Its implementation is optimized to minimize copying when
 *  the map is modified.

 *
 * The code in this class is essentially a port of the HashMap class from the Scala standard library. Thank you for
 *  the excellent code, Scala team!
 *
 * @author arno
 */
public class AHashMap<K, V> extends AbstractAMap<K,V> {
    private static final int LEVEL_INCREMENT = 5;
    private static final AEquality DEFAULT_EQUALITY = AEquality.EQUALS;

    private static final AHashMap<Object, Object> emptyEquals = new AHashMap<>(AEquality.EQUALS);
    private static final AHashMap<Object, Object> emptyIdentity = new AHashMap<>(AEquality.IDENTITY);

    final AEquality equality;

    /**
     * Returns an empty AHashMap instance with default (i.e. equals-based) equalityForEquals. Calling this factory method instead
     *  of the constructor allows internal reuse of empty map instances since they are immutable.
     */
    public static <K,V> AHashMap<K,V> empty() {
        return empty(DEFAULT_EQUALITY);
    }
    /**
     * Returns an empty AHashMap instance with the given equalityForEquals strategy. Calling this factory method instead of
     *  the constructor allows internal reuse of empty map instances since they are immutable.
     */
    @SuppressWarnings("unchecked")
    public static <K,V> AHashMap<K,V> empty(AEquality equality) {
        // for typical equalityForEquals implementations, return pre-instantiated objects
        if(equality == AEquality.EQUALS) return (AHashMap<K, V>) emptyEquals;
        if(equality == AEquality.IDENTITY) return (AHashMap<K, V>) emptyIdentity;
        return new AHashMap<>(equality);
    }

    /**
     * Returns an AHashMap instance with default (i.e. equals-based) equalityForEquals, initializing it from the contents of
     *  a given java.util.Map.
     */
    @SuppressWarnings("unused")
    public static <K,V> AHashMap<K,V> fromJavaUtilMap(Map<K,V> map) {
        return fromJavaUtilMap(DEFAULT_EQUALITY, map);
    }
    /**
     * Returns an AHashMap instance for a given equalityForEquals, initializing it from the contents of a given
     *  java.util.Map.
     */
    public static <K,V> AHashMap<K,V> fromJavaUtilMap(AEquality equality, Map<K,V> map) {
        AHashMap<K,V> result = empty(equality);

        for(Map.Entry<K,V> entry: map.entrySet()) {
            result = result.updated(entry.getKey(), entry.getValue());
        }

        return result;
    }

    /**
     * Returns an AHashMap instance with default (i.e. equals-based) equalityForEquals, initializing it from separate 'keys'
     *  and 'values' collections. Both collections are iterated exactly once, and are expected to have the same size.
     */
    public static <K,V> AHashMap<K,V> fromKeysAndValues(Iterable<K> keys, Iterable<V> values) {
        return fromKeysAndValues(DEFAULT_EQUALITY, keys, values);
    }
    /**
     * Returns an AHashMap instance with a given equalityForEquals, initializing it from separate 'keys'
     *  and 'values' collections. Both collections are iterated exactly once, and are expected to have the same size.
     */
    public static <K,V> AHashMap<K,V> fromKeysAndValues(AEquality equality, Iterable<K> keys, Iterable<V> values) {
        final Iterator<K> ki = keys.iterator();
        final Iterator<V> vi = values.iterator();

        AHashMap<K,V> result = empty(equality);

        while(ki.hasNext()) {
            final K key = ki.next();
            final V value = vi.next();

            result = result.updated(key, value);
        }
        return result;
    }

    /**
     * Returns an AHashMap instance with default (i.e. equals-based) equalityForEquals, initializing it from a collection of
     *  keys and a function. For each element of the keys collection, the function is called once to
     *  determine the corresponding value, and the pair is then stored in the map.
     */
    @SuppressWarnings("unused")
    public static <K,V, E extends Throwable> AHashMap<K,V> fromKeysAndFunction(Iterable<K> keys, AFunction1<? super K, ? extends V, E> f) throws E {
        return fromKeysAndFunction(DEFAULT_EQUALITY, keys, f);
    }
    /**
     * Returns an AHashMap instance with a given equalityForEquals, initializing it from a collection of
     *  keys and a function. For each element of the keys collection, the function is called once to
     *  determine the corresponding value, and the pair is then stored in the map.
     */
    public static <K,V, E extends Throwable> AHashMap<K,V> fromKeysAndFunction(AEquality equality, Iterable<K> keys, AFunction1<? super K, ? extends V, E> f) throws E {
        final Iterator<K> ki = keys.iterator();

        AHashMap<K,V> result = AHashMap.empty(equality);

        while(ki.hasNext()) {
            final K key = ki.next();
            final V value = f.apply(key);

            result = result.updated(key, value);
        }
        return result;
    }

    private AHashMap(AEquality equality) {
        this.equality = equality;
    }

    @Override public AEquality keyEquality () {
        return equality;
    }

    @Override public AMap<K, V> clear () {
        return empty (equality);
    }

    @Override public int size() {
        return 0;
    }

    @Override public AOption<V> get(K key) {
        return doGet (key, computeHash (key, equality), 0);
    }

    @Override public AHashMap<K,V> updated(K key, V value) {
        return doUpdated(key, computeHash(key, equality), 0, value);
    }

    @Override public AHashMap<K,V> removed(K key) {
        return doRemoved(key, computeHash(key, equality), 0);
    }

    @Override public Iterator<AMapEntry<K, V>> iterator() {
        return new HashMapIterator<> (this);
    }

    static class HashMapIterator<K,V> implements Iterator<AMapEntry<K,V>> {
        private final Deque<Object> stack = new ArrayDeque<> ();

        public HashMapIterator (AHashMap<K,V> root) {
            if (! root.isEmpty ()) {
                if (root instanceof HashMapCollision1) {
                    pushCollision ((HashMapCollision1<K, V>) root);
                }
                else {
                    stack.push (root);
                }
            }
        }

        private void pushCollision (HashMapCollision1<K,V> collision) {
            for (AMapEntry entry: collision.kvs) {
                stack.push (entry);
            }
        }

        @Override public boolean hasNext () {
            return ! stack.isEmpty ();
        }

        @SuppressWarnings ("unchecked")
        @Override public AMapEntry<K, V> next () {
            Object next;

            while (true) {
                next = stack.pop ();

                if (next.getClass () == HashMap1.class || next.getClass () == AListMap.Node.class) {
                    return (AMapEntry<K, V>) next;
                }
                if (next.getClass () == HashTrieMap.class) {
                    final HashTrieMap trie = (HashTrieMap) next;

                    // push elements in reverse order to provide iteration in 'ascending hash order', simplifying testing
                    for (int i=trie.elems.length-1; i>=0; i--) {
                        stack.push (trie.elems [i]);
                    }
                }
                else if (next.getClass () == HashMapCollision1.class) {
                    pushCollision ((HashMapCollision1<K, V>) next);
                }
            }
        }

        @Override public void remove () {
            throw new UnsupportedOperationException ();
        }
    }

    @Override public ASet<K> keys() {
        return AHashSet.fromMap (this);
    }

    /**
     * @param level number of least significant bits of the hash to discard for local hash lookup. This mechanism
     *              is used to create a 32-way hash trie - level increases by 5 at each level
     */
    AOption<V> doGet(K key, int hash, int level) {
        return AOption.none();
    }

    AHashMap<K,V> doUpdated(K key, int hash, int level, V value) {
        return new HashMap1<> (key, hash, value, equality);
    }

    AHashMap<K,V> doRemoved(K key, int hash, int level) {
        return this;
    }

    private static int computeHash(Object key, AEquality equality) {
        int h = equality.hashCode(key);
        h = h + ~(h << 9);
        h = h ^ (h >>> 14);
        h = h + (h << 4);
        return h ^ (h >>> 10);
    }

    @SuppressWarnings("unchecked")
    private static <K,V> AHashMap<K,V>[] createArray(int size) {
        return new AHashMap[size];
    }

    /**
     * very internal method. It assumes hash0 != hash1.
     */
    private static<K,V> HashTrieMap<K,V> mergeLeafMaps(int hash0, AHashMap<K,V> elem0, int hash1, AHashMap<K,V> elem1, int level, int size, AEquality equality) {
        final int index0 = (hash0 >>> level) & 0x1f;
        final int index1 = (hash1 >>> level) & 0x1f;
        if(index0 != index1) {
            final int bitmap = (1 << index0) | (1 << index1);
            final AHashMap<K,V>[] elems = createArray(2);
            if(index0 < index1) {
                elems[0] = elem0;
                elems[1] = elem1;
            }
            else {
                elems[0] = elem1;
                elems[1] = elem0;
            }
            return new HashTrieMap<>(bitmap, elems, size, equality);
        }
        else {
            final AHashMap<K,V>[] elems = createArray(1);
            final int bitmap = (1 << index0);
            // try again, based on the
            elems[0] = mergeLeafMaps(hash0, elem0, hash1, elem1, level + LEVEL_INCREMENT, size, equality);
            return new HashTrieMap<>(bitmap, elems, size, equality);
        }
    }

    static class HashMap1<K,V> extends AHashMap<K,V> implements AMapEntry<K,V> {
        private final K key;
        private final int hash;
        private final V value;

        HashMap1(K key, int hash, V value, AEquality equality) {
            super(equality);

            this.key = key;
            this.hash = hash;
            this.value = value;
        }

        @Override public K getKey () {
            return key;
        }
        @Override public V getValue () {
            return value;
        }
        @Override public int size() {
            return 1;
        }

        @Override AOption<V> doGet(K key, int hash, int level) {
            if(equality.equals(this.key, key)) {
                return AOption.some(value);
            }
            return AOption.none();
        }

        @Override AHashMap<K,V> doUpdated(K key, int hash, int level, V value) {
            if (hash == this.hash && equality.equals(key, this.key)) {
                if(this.value == value) {
                    return this;
                }
                else {
                    return new HashMap1<>(key, hash, value, equality);
                }
            }
            else {
                if (hash != this.hash) {
                    // they have different hashes, but may collide at this level - find a level at which they don't
                    final AHashMap<K,V> that = new HashMap1<>(key, hash, value, equality);
                    return mergeLeafMaps(this.hash, this, hash, that, level, 2, equality);
                }
                else {
                    // hash collision --> store all elements in the same bin
                    return new HashMapCollision1<> (hash, AListMap.<K,V>empty(equality).updated(this.key,this.value).updated(key,value));
                }
            }
        }

        @Override AHashMap<K,V> doRemoved(K key, int hash, int level) {
            if (hash == this.hash && equality.equals(key, this.key)) {
                return empty(equality);
            }
            else {
                return this;
            }
        }
    }

    static class HashMapCollision1<K,V> extends AHashMap<K,V> {
        private final int hash;
        private final AListMap<K,V> kvs;

        HashMapCollision1(int hash, AListMap<K, V> kvs) {
            super(kvs.equality);

            this.hash = hash;
            this.kvs = kvs;
        }

        @Override
        public int size() {
            return kvs.size();
        }

        @Override
        AOption<V> doGet(K key, int hash, int level) {
            if (hash == this.hash) {
                return kvs.get(key);
            }
            else {
                return AOption.none ();
            }
        }

        @Override
        AHashMap<K,V> doUpdated(K key, int hash, int level, V value) {
            if (hash == this.hash) {
                return new HashMapCollision1<>(hash, kvs.updated(key, value));
            }
            else {
                final HashMap1<K,V> that = new HashMap1<>(key, hash, value, equality);
                return mergeLeafMaps(this.hash, this, hash, that, level, size() + 1, equality);
            }
        }

        @Override
        AHashMap<K,V> doRemoved(K key, int hash, int level) {
            if (hash == this.hash) {
                final AListMap<K,V> kvs1 = kvs.removed(key);
                if (kvs1.isEmpty()) {
                    return AHashMap.empty(equality);
                }
                else if(kvs1.tail().isEmpty()) {
                    return new HashMap1<>(kvs1.getKey (), computeHash (kvs1.getKey (), equality), kvs1.getValue (), equality);
                }
                else {
                    return new HashMapCollision1<>(hash, kvs1);
                }
            }
            else {
                return this;
            }
        }
    }

    static class HashTrieMap<K,V> extends AHashMap<K,V> {
        final int bitmap;
        final AHashMap<K,V>[] elems;
        final int size;

        HashTrieMap(int bitmap, AHashMap<K, V>[] elems, int size, AEquality equality) {
            super(equality);

            this.bitmap = bitmap;
            this.elems = elems;
            this.size = size;
        }

        @Override public int size() {
            return size;
        }

        @Override
        AOption<V> doGet(K key, int hash, int level) {
            final int index = (hash >>> level) & 0x1f;
            final int mask = 1 << index;

            if (bitmap == - 1) {
                return elems[index & 0x1f].doGet(key, hash, level + LEVEL_INCREMENT);
            }
            else if ((bitmap & mask) != 0) {
                final int offset = Integer.bitCount(bitmap & (mask - 1));
                return elems[offset].doGet(key, hash, level + LEVEL_INCREMENT);
            }
            else {
                return AOption.none();
            }
        }

        @Override
        AHashMap<K,V>  doUpdated(K key, int hash, int level, V value) {
            final int index = (hash >>> level) & 0x1f;
            final int mask = (1 << index);
            final int offset = Integer.bitCount(bitmap & (mask - 1));
            if ((bitmap & mask) != 0) {
                final AHashMap<K,V> sub = elems[offset];

                final AHashMap<K,V> subNew = sub.doUpdated(key, hash, level + LEVEL_INCREMENT, value);
                if(subNew == sub) {
                    return this;
                }
                else {
                    final AHashMap<K,V>[] elemsNew = createArray(elems.length);
                    System.arraycopy(elems, 0, elemsNew, 0, elems.length);
                    elemsNew[offset] = subNew;
                    return new HashTrieMap<> (bitmap, elemsNew, size + (subNew.size() - sub.size()), equality);
                }
            }
            else {
                final AHashMap<K,V>[] elemsNew = createArray(elems.length + 1);
                System.arraycopy(elems, 0, elemsNew, 0, offset);
                elemsNew[offset] = new HashMap1<>(key, hash, value, equality);
                System.arraycopy(elems, offset, elemsNew, offset + 1, elems.length - offset);
                return new HashTrieMap<>(bitmap | mask, elemsNew, size + 1, equality);
            }
        }

        @Override
        AHashMap<K,V> doRemoved(K key, int hash, int level) {
            final int index = (hash >>> level) & 0x1f;
            final int mask = (1 << index);
            final int  offset = Integer.bitCount(bitmap & (mask - 1));

            if ((bitmap & mask) != 0) {
                final AHashMap<K,V> sub = elems[offset];
                final AHashMap<K,V> subNew = sub.doRemoved(key, hash, level + LEVEL_INCREMENT);

                if (subNew == sub) {
                    return this;
                }
                else if (subNew.isEmpty()) {
                    final int  bitmapNew = bitmap ^ mask;
                    if (bitmapNew != 0) {
                        final AHashMap<K,V>[] elemsNew = createArray(elems.length - 1);
                        System.arraycopy(elems, 0, elemsNew, 0, offset);
                        System.arraycopy(elems, offset + 1, elemsNew, offset, elems.length - offset - 1);
                        final int sizeNew = size - sub.size();
                        if (elemsNew.length == 1 && ! (elemsNew[0] instanceof HashTrieMap)) {
                            return elemsNew[0];
                        }
                        else {
                            return new HashTrieMap<>(bitmapNew, elemsNew, sizeNew, equality);
                        }
                    }
                    else {
                        return AHashMap.empty(equality);
                    }
                }
                else if(elems.length == 1 && ! (subNew instanceof HashTrieMap)) {
                    return subNew;
                }
                else {
                    final AHashMap<K,V>[] elemsNew = createArray(elems.length);
                    System.arraycopy(elems, 0, elemsNew, 0, elems.length);
                    elemsNew[offset] = subNew;
                    final int sizeNew = size + (subNew.size() - sub.size());
                    return new HashTrieMap<>(bitmap, elemsNew, sizeNew, equality);
                }
            } else {
                return this;
            }
        }
    }

    private Object readResolve() {
        // rebuild the map in case hashCodes of entries were changed by serialization

        AHashMap<K,V> result = AHashMap.empty (equality);

        for (AMapEntry<K,V> entry: this) {
            result = result.updated (entry.getKey (), entry.getValue ());
        }

        return result;
    }
}


package com.ajjpj.afoundation.concurrent;

import com.ajjpj.afoundation.util.AUnchecker;
import sun.misc.Contended;
import sun.misc.Unsafe;

import java.lang.reflect.Field;

/**
 * @author arno
 */
@Contended
class SharedQueueBlockPushBlockPopImpl implements ASharedQueue {
    long p1, p2, p3, p4, p5, p6, p7;

    private final int prefetchBatchSize;
    private final Object PUSH_LOCK = new Object ();

    /**
     * an array holding all currently submitted tasks.
     */
    private final Runnable[] tasks;

    /**
     * a bit mask to project an offset into the valid range of offsets for the tasks array
     */
    private final int mask;

    private final AThreadPoolImpl pool;

    private long base = 0;
    private long top = 0;

    long q1, q2, q3, q4, q5, q6, q7;

    /**
     * @param prefetchBatchSize the number of tasks a worker thread should attempt to fetch when its local queue has run empty, to the degree they are available in this
     *                          queue. A value of 1 denotes no prefetch, i.e. a worker thread just fetches one item and is done with. A value of 2 means a worker thread
     *                          attempts to prefetch one task in addition to the task it gets for immediate consumption etc.
     */
    SharedQueueBlockPushBlockPopImpl (int prefetchBatchSize, AThreadPoolImpl pool, int size) {
        if (prefetchBatchSize < 1) throw new IllegalArgumentException ("worker threads must (attempt to) fetch a minimum of 1 task");
        this.prefetchBatchSize = prefetchBatchSize;
        this.pool = pool;

        if (1 != Integer.bitCount (size)) throw new IllegalArgumentException ("size must be a power of 2");
        if (size < 8 || size > 1024*1024) throw new IllegalArgumentException ("size must be in the range from 8 to " + (1024*1024));

        this.tasks = new Runnable[size];
        this.mask = size-1;
    }

    /**
     * @return an approximation of the queue's current size. The value may be stale and is not synchronized in any way, and it is intended for debugging and statistics
     *  purposes only.
     */
    @Override public int approximateSize () {
        return (int) (
                UNSAFE.getLongVolatile (this, OFFS_TOP) -
                UNSAFE.getLongVolatile (this, OFFS_BASE)
        );
    }

    /**
     * Add a new task to the top of the shared queue, incrementing 'top'.
     */
    @Override public void push (Runnable task) {
        final long _base;
        final long _top;

        synchronized (PUSH_LOCK) {
            _base = base;
            _top = top;

            if (_top == _base + mask) {
                throw new RejectedExecutionExceptionWithoutStacktrace ("Shared queue overflow");
            }

            tasks[asArrayIndex (_top)] = task;

            // volatile put for atomicity and to ensure ordering wrt. storing the task
            UNSAFE.putLongVolatile (this, OFFS_TOP, _top+1);
        }

        pool.onAvailableTask ();
    }

    int asArrayIndex (long l) {
        return (int) (l & mask);
    }

    @Override public synchronized Runnable popFifo (LocalQueue localQueue) {
        final long _base = base;
        final long _top = top;

        final long size = _top-_base;

        if (size == 0) {
            // Terminate the loop: the queue is empty.
            //TODO verify that Hotspot optimizes this kind of return-from-the-middle well
            return null;
        }

        final Runnable result = fetchTask (_base);
        if (result == null) {
            System.err.println ("null @ " + _base);
            return null; //TODO why is this necessary?
        }

        int idx;

        long newLocalTop = localQueue.top;
        for (idx=1; idx < size && idx < prefetchBatchSize; idx++) {
            final Runnable task = fetchTask (_base+idx);
            if (task == null) {
                System.err.println ("************************ fetched task[base+" + idx + "] is null although it really couldn't *******************************");
            }

            localQueue.tasks [localQueue.asArrayindex (newLocalTop++)] = task;
        }

        // volatile put for atomicity and to ensure ordering wrt. nulling the task --> read operations do not hold the same monitor
        if (idx > 1) {
            UNSAFE.putLongVolatile (localQueue, LocalQueue.OFFS_TOP, newLocalTop);
        }

        // volatile put for atomicity and to ensure ordering wrt. nulling the task --> read operations do not hold the same monitor
        UNSAFE.putLongVolatile (this, OFFS_BASE, _base + idx);

        return result;
    }

    private Runnable fetchTask (long idx) {
        final int arrIdx = asArrayIndex (idx);
        final Runnable result = tasks [arrIdx];
        if (result != null) tasks[arrIdx] = null; //TODO remove the check
        return result;
    }

    @Override public synchronized void clear () {
        final long _top = top;
        for (long _base=base; _base < _top; _base++) {
            tasks[asArrayIndex (_base)] = null;
        }

        UNSAFE.putLongVolatile (this, OFFS_BASE, _top);
    }

    //------------- Unsafe stuff
    static final Unsafe UNSAFE;

    static final long OFFS_BASE;
    static final long OFFS_TOP;

    static {
        try {
            final Field f = Unsafe.class.getDeclaredField ("theUnsafe");
            f.setAccessible (true);
            UNSAFE = (Unsafe) f.get (null);

            OFFS_BASE = UNSAFE.objectFieldOffset (SharedQueueBlockPushBlockPopImpl.class.getDeclaredField ("base"));
            OFFS_TOP  = UNSAFE.objectFieldOffset (SharedQueueBlockPushBlockPopImpl.class.getDeclaredField ("top"));
        }
        catch (Exception e) {
            AUnchecker.throwUnchecked (e);
            throw new RuntimeException(); // for the compiler
        }
    }
}


package com.ajjpj.afoundation.concurrent;

import com.ajjpj.afoundation.util.AUnchecker;
import sun.misc.Contended;
import sun.misc.Unsafe;

import java.lang.reflect.Field;

/**
 * @author arno
 */
@Contended
class LocalQueue {
    long p1, p2, p3, p4, p5, p6, p7;

    /**
     * an array holding all currently submitted tasks.
     */
    final Runnable[] tasks;

    /**
     * The thread is really final and not-null. Because of circular references during initialization, it is technically not final and initialized not in the constructor but in
     *  a separate call to method {@code init()}.
     */
    WorkerThread thread;

    /**
     * a bit mask to project an offset into the valid range of offsets for the tasks array
     */
    private final int mask;

    private final AThreadPoolImpl pool;

    @SuppressWarnings ("unused")
    long base = 0;
    long top = 0;

    long q1, q2, q3, q4, q5, q6, q7;

    LocalQueue (AThreadPoolImpl pool, int size) {
        this.pool = pool;

        if (1 != Integer.bitCount (size)) throw new IllegalArgumentException ("size must be a power of 2");
        if (size < 8 || size > 1024*1024) throw new IllegalArgumentException ("size must be in the range from 8 to " + (1024*1024));

        this.tasks = new Runnable[size];
        this.mask = size-1;
    }

    void init(WorkerThread thread) {
        this.thread = thread;
    }

    /**
     * @return an approximation of the queue's current size, useful only for statistics purposes.
     */
    int approximateSize() {
        return (int) (
                UNSAFE.getLongVolatile (this, OFFS_TOP) -
                UNSAFE.getLongVolatile (this, OFFS_BASE)
        );
    }

    /**
     * Add a new task to the top of the localQueue, incrementing 'top'. This is only ever called from the owning thread.
     */
    void push (Runnable task) {
        final long _base = UNSAFE.getLongVolatile (this, OFFS_BASE); // read base first (and only once)
        final long _top = top;
        if (_top == _base + mask) {
            throw new RejectedExecutionExceptionWithoutStacktrace ("local queue overflow");
        }

        tasks[asArrayindex (_top)] = task;
        // 'top' is only ever modified by the owning thread, so we need no CAS here. Storing 'top' with volatile semantics publishes the task and ensures that changes to the task
        //  can never overtake changes to 'top' wrt visibility.
        UNSAFE.putLongVolatile (this, OFFS_TOP, _top+1);

        // Notify pool only for the first added item per queue.
        if (_top - _base <= 1) {
            pool.onAvailableTask();
        }
    }

    /**
     * Fetch (and remove) a task from the top of the queue, i.e. LIFO semantics. This is only ever called from the owning thread, removing (or
     *  at least reducing) contention at the top of the queue: No other thread operates there.
     */
    Runnable popLifo () {
        final long _top = top;
        final Runnable result = tasks[asArrayindex (_top-1)];
        if (result == null) {
            // The queue is empty. It is possible for the queue to be empty even if the previous unprotected read does not return null, but
            //  it will only ever return null if the queue really is empty: New entries are only added by the owning thread, and this method
            //  'popLifo()' is also only ever called by the owning thread.
            return null;
        }

        if (! UNSAFE.compareAndSwapObject (tasks, taskOffset (_top-1), result, null)) {
            // The CAS operation failing means that another thread pulled the top-most item from the queue, so the queue is now definitely
            //  empty. It also null'ed out the task in the array if it was previously available, allowing to to be GC'ed when processing is
            //  finished.
            return null;
        }

        // Since 'result' is not null, and was not previously consumed by another thread, we can safely consume it --> decrement 'top'
        UNSAFE.putOrderedLong (this, OFFS_TOP, _top-1);
        return result;
    }

    /**
     * Fetch (and remove) a task from the bottom of the queue, i.e. FIFO semantics. This method can be called by any thread.
     */
    Runnable popFifo () {
        long _base, _top;

        while (true) {
            // reading 'base' with volatile semantics emits the necessary barriers to ensure visibility of 'top'
            _base = UNSAFE.getLongVolatile (this, OFFS_BASE);
            _top = top;

            if (_base == _top) {
                // Terminate the loop: the queue is empty.
                //TODO verify that Hotspot optimizes this kind of return-from-the-middle well
                return null;
            }

            // a regular read is OK here: 'push()' emits a store barrier after storing the task, 'popLifo()' modifies it with CAS, and 'popFifo()' does
            //  a volatile read of 'base' before reading the task
            final Runnable result = tasks[asArrayindex (_base)];

            // result == null means that another thread concurrently fetched the task from under our nose.
            // checking _base against a re-read 'base' with volatile semantics avoids wrap-around race - 'base' could have incremented by a multiple of the queue's size between
            //   our first reading it and fetching the task at that offset, which would cause the increment inside the following if block to significantly decrement it and
            //   wreak havoc.
            // CAS ensures that only one thread gets the task, and allows GC when processing is finished
            if (result != null && _base == UNSAFE.getLongVolatile(this, OFFS_BASE) && UNSAFE.compareAndSwapObject (tasks, taskOffset (_base), result, null)) {
                UNSAFE.putLongVolatile (this, OFFS_BASE, _base+1); //TODO is 'putOrdered' sufficient?
                return result;
            }
        }
    }

    private long taskOffset (long l) {
        return OFFS_TASKS + SCALE_TASKS * (l & mask);
    }

    int asArrayindex (long l) {
        return (int) (l & mask);
    }

    //------------- Unsafe stuff
    private static final Unsafe UNSAFE;

    private static final long OFFS_TASKS;
    private static final long SCALE_TASKS;

    private static final long OFFS_BASE;
    static final long OFFS_TOP;

    static {
        try {
            final Field f = Unsafe.class.getDeclaredField ("theUnsafe");
            f.setAccessible (true);
            UNSAFE = (Unsafe) f.get (null);

            OFFS_TASKS = UNSAFE.arrayBaseOffset (Runnable[].class);
            SCALE_TASKS = UNSAFE.arrayIndexScale (Runnable[].class);

            OFFS_BASE = UNSAFE.objectFieldOffset (LocalQueue.class.getDeclaredField ("base"));
            OFFS_TOP = UNSAFE.objectFieldOffset (LocalQueue.class.getDeclaredField ("top"));
        }
        catch (Exception e) {
            AUnchecker.throwUnchecked (e);
            throw new RuntimeException(); // for the compiler
        }
    }
}