package at.yawk.numaec;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.eclipse.collections.api.LazyShortIterable;
import org.eclipse.collections.api.RichIterable;
import org.eclipse.collections.api.bag.Bag;
import org.eclipse.collections.api.bag.primitive.DoubleBag;
import org.eclipse.collections.api.block.function.primitive.DoubleToObjectFunction;
import org.eclipse.collections.api.block.function.primitive.ObjectDoubleToObjectFunction;
import org.eclipse.collections.api.block.predicate.primitive.DoublePredicate;
import org.eclipse.collections.api.block.predicate.primitive.ShortDoublePredicate;
import org.eclipse.collections.api.block.procedure.Procedure;
import org.eclipse.collections.api.block.procedure.primitive.DoubleProcedure;
import org.eclipse.collections.api.block.procedure.primitive.ShortDoubleProcedure;
import org.eclipse.collections.api.block.procedure.primitive.ShortProcedure;
import org.eclipse.collections.api.collection.primitive.MutableDoubleCollection;
import org.eclipse.collections.api.iterator.DoubleIterator;
import org.eclipse.collections.api.iterator.MutableDoubleIterator;
import org.eclipse.collections.api.iterator.ShortIterator;
import org.eclipse.collections.api.map.primitive.DoubleShortMap;
import org.eclipse.collections.api.map.primitive.ImmutableShortDoubleMap;
import org.eclipse.collections.api.map.primitive.ShortDoubleMap;
import org.eclipse.collections.api.set.primitive.MutableShortSet;
import org.eclipse.collections.api.tuple.primitive.ShortDoublePair;
import org.eclipse.collections.impl.lazy.AbstractLazyIterable;
import org.eclipse.collections.impl.lazy.primitive.AbstractLazyShortIterable;
import org.eclipse.collections.impl.primitive.AbstractDoubleIterable;
import org.eclipse.collections.impl.tuple.primitive.PrimitiveTuples;

abstract class BaseShortDoubleMap extends AbstractDoubleIterable implements ShortDoubleMap {
    static final long KEY_MASK = -1L >>> (64 - (Short.BYTES * 8));
    static final long VALUE_MASK = -1L >>> (64 - (Double.BYTES * 8));

    static long toKey(short key) {
        return key & KEY_MASK;
    }

    static long toValue(double value) {
        return Double.doubleToRawLongBits(value) & VALUE_MASK;
    }

    @SuppressWarnings("ConstantConditions")
    static short fromKey(long key) {
        if (KEY_MASK != -1) { if (key < 0 || key > KEY_MASK) { throw new IllegalArgumentException(); } }
        return (short) key;
    }

    @SuppressWarnings("ConstantConditions")
    static double fromValue(long value) {
        if (VALUE_MASK != -1) { if (value < 0 || value > VALUE_MASK) { throw new IllegalArgumentException(); } }
        return (double) Double.longBitsToDouble(value);
    }

    protected abstract MapStoreCursor iterationCursor();

    protected abstract MapStoreCursor keyCursor(short key);

    @DoNotMutate
    void checkInvariants() {
        int count = count(x -> true);
        if (count != size()) { throw new AssertionError(); }
    }

    @Override
    public double getIfAbsent(short key, double ifAbsent) {
        try (MapStoreCursor cursor = keyCursor(key)) {
            if (cursor.elementFound()) {
                return fromValue(cursor.getValue());
            } else {
                return ifAbsent;
            }
        }
    }

    @Override
    public double getOrThrow(short key) {
        try (MapStoreCursor cursor = keyCursor(key)) {
            if (cursor.elementFound()) {
                return fromValue(cursor.getValue());
            } else {
                throw new IllegalStateException("Key " + key + " not present");
            }
        }
    }

    @Override
    public boolean containsKey(short key) {
        try (MapStoreCursor cursor = keyCursor(key)) {
            return cursor.elementFound();
        }
    }

    @Override
    public void forEachKey(ShortProcedure procedure) {
        try (MapStoreCursor cursor = iterationCursor()) {
            while (cursor.next()) {
                procedure.value(fromKey(cursor.getKey()));
            }
        }
    }

    @Override
    public void forEachKeyValue(ShortDoubleProcedure procedure) {
        try (MapStoreCursor cursor = iterationCursor()) {
            while (cursor.next()) {
                procedure.value(fromKey(cursor.getKey()),
                                fromValue(cursor.getValue()));
            }
        }
    }

    @Override
    public LazyShortIterable keysView() {
        return new AbstractLazyShortIterable() {
            @Override
            public ShortIterator shortIterator() {
                return new KeyIterator(iterationCursor());
            }

            @Override
            public void each(ShortProcedure procedure) {
                forEachKey(procedure);
            }
        };
    }

    @Override
    public RichIterable<ShortDoublePair> keyValuesView() {
        return new AbstractLazyIterable<ShortDoublePair>() {
            @Override
            public Iterator<ShortDoublePair> iterator() {
                return new KeyValueIterator(iterationCursor());
            }

            @Override
            public void each(Procedure<? super ShortDoublePair> procedure) {
                iterator().forEachRemaining(procedure);
            }
        };
    }

    @Override
    public boolean containsValue(double value) {
        long needle = toValue(value);
        try (MapStoreCursor cursor = iterationCursor()) {
            while (cursor.next()) {
                if (cursor.getValue() == needle) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public void forEachValue(DoubleProcedure procedure) {
        try (MapStoreCursor cursor = iterationCursor()) {
            while (cursor.next()) {
                procedure.value(fromValue(cursor.getValue()));
            }
        }
    }

    @Override
    public MutableDoubleIterator doubleIterator() {
        return new ValueIterator(iterationCursor());
    }

    @Override
    public double[] toArray() {
        double[] data = new double[size()];
        try (MapStoreCursor cursor = iterationCursor()) {
            int i = 0;
            while (cursor.next()) {
                if (i >= data.length) { throw new ConcurrentModificationException(); }
                data[i++] = fromValue(cursor.getValue());
            }
            if (i < data.length) { throw new ConcurrentModificationException(); }
            return data;
        }
    }

    @Override
    public double detectIfNone(DoublePredicate predicate, double ifNone) {
        try (MapStoreCursor cursor = iterationCursor()) {
            while (cursor.next()) {
                double value = fromValue(cursor.getValue());
                if (predicate.accept(value)) {
                    return value;
                }
            }
        }
        return ifNone;
    }

    @Override
    public int count(DoublePredicate predicate) {
        int count = 0;
        try (MapStoreCursor cursor = iterationCursor()) {
            while (cursor.next()) {
                double value = fromValue(cursor.getValue());
                if (predicate.accept(value)) {
                    count++;
                }
            }
        }
        return count;
    }

    @Override
    public boolean anySatisfy(DoublePredicate predicate) {
        try (MapStoreCursor cursor = iterationCursor()) {
            while (cursor.next()) {
                double value = fromValue(cursor.getValue());
                if (predicate.accept(value)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public boolean allSatisfy(DoublePredicate predicate) {
        try (MapStoreCursor cursor = iterationCursor()) {
            while (cursor.next()) {
                double value = fromValue(cursor.getValue());
                if (!predicate.accept(value)) {
                    return false;
                }
            }
        }
        return true;
    }

    @Override
    public <T> T injectInto(T injectedValue, ObjectDoubleToObjectFunction<? super T, ? extends T> function) {
        try (MapStoreCursor cursor = iterationCursor()) {
            while (cursor.next()) {
                injectedValue = function.valueOf(injectedValue, fromValue(cursor.getValue()));
            }
        }
        return injectedValue;
    }

    @Override
    public double
 sum() {
        double
 sum = 0;
        try (MapStoreCursor cursor = iterationCursor()) {
            while (cursor.next()) {
                sum += fromValue(cursor.getValue());
            }
        }
        return sum;
    }

    @Override
    public double max() {
        try (MapStoreCursor cursor = iterationCursor()) {
            if (!cursor.next()) { throw new NoSuchElementException(); }
            double max = fromValue(cursor.getValue());
            while (cursor.next()) {
                max = (double) Math.max(max, fromValue(cursor.getValue()));
            }
            return max;
        }
    }

    @Override
    public double min() {
        try (MapStoreCursor cursor = iterationCursor()) {
            if (!cursor.next()) { throw new NoSuchElementException(); }
            double min = fromValue(cursor.getValue());
            while (cursor.next()) {
                min = (double) Math.min(min, fromValue(cursor.getValue()));
            }
            return min;
        }
    }

    @Override
    public void appendString(Appendable appendable, String start, String separator, String end) {
        try (MapStoreCursor cursor = iterationCursor()) {
            appendable.append(start);
            boolean first = true;
            while (cursor.next()) {
                if (first) {
                    first = false;
                } else {
                    appendable.append(separator);
                }
                appendable.append(String.valueOf(fromValue(cursor.getValue())));
            }
            appendable.append(end);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder("{");
        try (MapStoreCursor cursor = iterationCursor()) {
            boolean first = true;
            while (cursor.next()) {
                if (first) {
                    first = false;
                } else {
                    builder.append(", ");
                }
                builder.append(fromKey(cursor.getKey()))
                        .append('=')
                        .append(fromValue(cursor.getValue()));
            }
            return builder.append('}').toString();
        }
    }

    @Override
    public double get(short key) {
        return getIfAbsent(key, (double) 0);
    }

    @Override
    public DoubleShortMap flipUniqueValues() {
        throw new UnsupportedOperationException("ShortDoubleBufferMap.flipUniqueValues not implemented yet");
    }

    @Override
    public ShortDoubleMap select(ShortDoublePredicate predicate) {
        throw new UnsupportedOperationException("ShortDoubleBufferMap.select not implemented yet");
    }

    @Override
    public ShortDoubleMap reject(ShortDoublePredicate predicate) {
        throw new UnsupportedOperationException("ShortDoubleBufferMap.reject not implemented yet");
    }

    @Override
    public ImmutableShortDoubleMap toImmutable() {
        throw new UnsupportedOperationException("ShortDoubleBufferMap.toImmutable not implemented yet");
    }

    @Override
    public MutableShortSet keySet() {
        throw new UnsupportedOperationException("ShortDoubleBufferMap.keySet not implemented yet");
    }

    @Override
    public MutableDoubleCollection values() {
        throw new UnsupportedOperationException("ShortDoubleBufferMap.values not implemented yet");
    }

    @Override
    public DoubleBag select(DoublePredicate predicate) {
        throw new UnsupportedOperationException("ShortDoubleBufferMap.select not implemented yet");
    }

    @Override
    public DoubleBag reject(DoublePredicate predicate) {
        throw new UnsupportedOperationException("ShortDoubleBufferMap.reject not implemented yet");
    }

    @Override
    public <V> Bag<V> collect(DoubleToObjectFunction<? extends V> function) {
        throw new UnsupportedOperationException("ShortDoubleBufferMap.collect not implemented yet");
    }

    @Override
    public boolean contains(double value) {
        return containsValue(value);
    }

    @Override
    public void forEach(DoubleProcedure procedure) {
        forEachValue(procedure);
    }

    @Override
    public void each(DoubleProcedure procedure) {
        forEachValue(procedure);
    }

    @Override
    public boolean noneSatisfy(DoublePredicate predicate) {
        return !anySatisfy(predicate);
    }

    private static abstract class BaseIterator {
        
this cursor is never closed, but since all MapStoreCursor.close() does is make it available for reuse, that's not too bad.
/** * this cursor is never closed, but since all {@link MapStoreCursor#close()} does is make it available for * reuse, that's not too bad. */
protected final MapStoreCursor cursor; private boolean peeked; BaseIterator(MapStoreCursor cursor) { this.cursor = cursor; } protected void next0() { if (!hasNext()) { throw new NoSuchElementException(); } peeked = false; } public boolean hasNext() { if (peeked) { return true; } else { return peeked = cursor.next(); } } } private static class KeyIterator extends BaseIterator implements ShortIterator { KeyIterator(MapStoreCursor cursor) { super(cursor); } @Override public short next() { next0(); return fromKey(cursor.getKey()); } } private static class KeyValueIterator extends BaseIterator implements Iterator<ShortDoublePair> { KeyValueIterator(MapStoreCursor cursor) { super(cursor); } @Override public ShortDoublePair next() { next0(); return PrimitiveTuples.pair(fromKey(cursor.getKey()), fromValue(cursor.getValue())); } } protected static class ValueIterator extends BaseIterator implements DoubleIterator, MutableDoubleIterator { ValueIterator(MapStoreCursor cursor) { super(cursor); } @Override public double next() { next0(); return fromValue(cursor.getValue()); } @Override public void remove() { throw new UnsupportedOperationException("ValueIterator.remove not implemented yet"); } } }