package nova;

import java.util.*;
import java.util.concurrent.*;

/**
 * Warranty & Liability
 * To the extent permitted by applicable law and unless explicitly
 * otherwise agreed upon, XLOG Technologies AG makes no warranties
 * regarding the provided information. XLOG Technologies AG assumes
 * no liability that any problems might be solved with the information
 * provided by XLOG Technologies AG.
 *
 * Rights & License
 * All industrial property rights regarding the information - copyright
 * and patent rights in particular - are the sole property of XLOG
 * Technologies AG. If the company was not the originator of some
 * excerpts, XLOG Technologies AG has at least obtained the right to
 * reproduce, change and translate the information.
 *
 * Reproduction is restricted to the whole unaltered document. Reproduction
 * of the information is only allowed for non-commercial uses. Selling,
 * giving away or letting of the execution of the library is prohibited.
 * The library can be distributed as part of your applications and libraries
 * for execution provided this comment remains unchanged.
 *
 * Restrictions
 * Only to be distributed with programs that add significant and primary
 * functionality to the library. Not to be distributed with additional
 * software intended to replace any components of the library.
 *
 * Trademarks
 * Jekejeke is a registered trademark of XLOG Technologies AG.
 */
public final class Handler {

    /**
     * make_special(), flags=MASK_PRED_SPECIAL
     */
    public static interface Builtin {
        public abstract Object run(Object[] args);
    }

    /**
     * make_check(), flags=MASK_PRED_TEST
     */
    public static interface Check {
        public abstract boolean run(Object[] args);
    }

    /**
     * make_arithmetic(), flags=MASK_PRED_ARITH
     * Use german spelling to distinguish from Function
     */
    public static interface Funktion {
        public abstract Number eval(Object[] obj);
    }

    /**
     * Choice point callback.
     */
    public static interface Callback {
        public abstract Object run(Object rope, int at, Machine.Choice choice);
    }

    /**
     * A runtime exception with a Prolog term.
     */
    public static class Problem extends RuntimeException {
        public Object term;

        public Problem(Object term) {
            this.term = term;
        }
    }

    /**
     * An array list which exposes removeRange.
     */
    public static class Rope extends ArrayList {

        public Rope() {
        }

        public Rope(Collection c) {
            super(c);
        }

        public void removeRange(int f, int t) {
            super.removeRange(f, t);
        }

    }

    /******************************************************************/
    /* Surrogate async/await                                          */
    /******************************************************************/

    public static Semaphore bouncer = new Semaphore(0, true);

    public static class Promise {
        Runnable proc;

        public Promise(Runnable proc) {
            this.proc = proc;
        }

        public void await() {
            bouncer.release();
            try {
                proc.run();
            } finally {
                bouncer.acquireUninterruptibly();
            }
        }

    }

    public static class Coroutine {
        Runnable proc;

        public Coroutine(Runnable proc) {
            this.proc = proc;
        }

        public void async() {
            new Thread(() -> {
                bouncer.acquireUninterruptibly();
                try {
                    proc.run();
                } finally {
                    bouncer.release();
                }
            }).start();
        }
    }

    public static class ArrayMap<K, V> extends AbstractMap<K, V>
            implements Map<K, V>, RandomAccess {
        public static final int MIN_SIZE = 2;

        public Object[] kvs;
        public int size;

        public ArrayMap() {
            kvs = new Object[MIN_SIZE];
        }

        public ArrayMap(Map<K, V> map) {
            size = map.size();
            int len = MIN_SIZE;
            while (size > len)
                len = len * 2;
            kvs = new Object[2 * len];
            len = 0;
            Iterator<Entry<K, V>> it = map.entrySet().iterator();
            while (it.hasNext()) {
                Entry<K, V> entry = it.next();
                kvs[len++] = entry.getKey();
                kvs[len++] = entry.getValue();
            }
        }

        public int size() {
            return size;
        }

        public boolean isEmpty() {
            return size == 0;
        }

        public boolean containsKey(Object key) {
            return indexOfKey(key) >= 0;
        }

        public boolean containsValue(Object value) {
            return indexOfValue(value) >= 0;
        }

        public V get(Object key) {
            int i = indexOfKey(key);
            return (i >= 0 ? getValue(i) : null);
        }

        public V put(K key, V value) {
            int i = indexOfKey(key);
            if (i >= 0) {
                return setValue(i, value);
            } else {
                addLast(key, value);
                return null;
            }
        }

        public V remove(Object key) {
            int i = indexOfKey(key);
            if (i >= 0) {
                return removeAt(i);
            } else {
                return null;
            }
        }

        Set<Entry<K, V>> entrySet;

        public Set<Entry<K, V>> entrySet() {
            Set<Entry<K, V>> es = entrySet;
            if (es == null) {
                Set<Entry<K, V>> ses = new ArrayEntrySet();
                entrySet = ses;
                return ses;
            } else {
                return es;
            }
        }

        final class ArrayEntrySet extends AbstractSet<Entry<K, V>>
                implements Set<Entry<K, V>> {

            public final int size() {
                return size;
            }

            public final Iterator<Entry<K, V>> iterator() {
                return new ArrayEntryIterator();
            }

        }

        class ArrayEntryIterator implements Iterator<Entry<K, V>>, Entry<K, V> {
            int cursor;       // index of next element to return
            int lastRet = -1; // index of last element returned; -1 if no such

            public K getKey() {
                if (lastRet < 0)
                    throw new IllegalStateException();
                return (K) kvs[lastRet * 2];
            }

            public V getValue() {
                if (lastRet < 0)
                    throw new IllegalStateException();
                return (V) kvs[lastRet * 2 + 1];
            }

            public V setValue(V value) {
                if (lastRet < 0)
                    throw new IllegalStateException();
                V old = (V) kvs[lastRet * 2 + 1];
                kvs[lastRet * 2 + 1] = value;
                return old;
            }

            public boolean hasNext() {
                return cursor != size;
            }

            public Entry<K, V> next() {
                int i = cursor;
                if (i >= size)
                    throw new NoSuchElementException();
                lastRet = i;
                cursor = i + 1;
                return this;
            }

            public void remove() {
                if (lastRet < 0)
                    throw new IllegalStateException();
                removeAt(lastRet);
                cursor = lastRet;
                lastRet = -1;
            }

        }

        /***************************************************************/
        /* Helpers                                                     */
        /***************************************************************/

        /**
         * Retrieve a key.
         *
         * @param i The index.
         * @return The key.
         */
        public K getKey(int i) {
            return (K) kvs[2 * i];
        }

        /**
         * Retrieve a value.
         *
         * @param i The index.
         * @return The value.
         */
        public V getValue(int i) {
            return (V) kvs[2 * i + 1];
        }

        /**
         * Set a value.
         *
         * @param i     The index.
         * @param value The value.
         * @return The old value.
         */
        public V setValue(int i, V value) {
            V old = (V) kvs[2 * i + 1];
            kvs[2 * i + 1] = value;
            return old;
        }

        /**
         * Resize the pairs array to a new size.
         *
         * @param s The new size.
         */
        public void resize(int s) {
            Object[] newobs = new Object[2 * s];
            int k = Math.min(s, kvs.length / 2);
            System.arraycopy(kvs, 0, newobs, 0, 2 * k);
            kvs = newobs;
        }

        /**
         * Returns the first index of the key occurence.
         *
         * @param o The key.
         * @return The index, or -1.
         */
        public int indexOfKey(Object o) {
            for (int i = 0; i < size; i++)
                if (o != null ? o.equals(kvs[2 * i]) : null == kvs[2 * i])
                    return i;
            return -1;
        }

        /**
         * Returns the first index of the value occurence.
         *
         * @param o The value.
         * @return The index, or -1.
         */
        public int indexOfValue(Object o) {
            for (int i = 0; i < size; i++)
                if (o != null ? o.equals(kvs[2 * i + 1]) : null == kvs[2 * i + 1])
                    return i;
            return -1;
        }

        /**
         * Remove a key value pair at an index.
         *
         * @param i The index.
         * @return The old value at this index.
         */
        public V removeAt(int i) {
            V old = (V) kvs[2 * i + 1];
            int k = size - i - 1;
            if (k > 0)
                System.arraycopy(kvs, 2 * i + 2, kvs, 2 * i, 2 * k);
            --size;
            kvs[2 * size] = null;
            kvs[2 * size + 1] = null;
            return old;
        }

        /**
         * Add a key value at the end</p>
         *
         * @param key   The key.
         * @param value The value.
         */
        public void addLast(K key, V value) {
            if (size >= kvs.length / 2)
                resize(kvs.length);
            kvs[2 * size] = key;
            kvs[2 * size + 1] = value;
            size++;
        }

    }

}
