package at.yawk.numaec;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ConcurrentModificationException;
import org.eclipse.collections.api.LazyLongIterable;
import org.eclipse.collections.api.LongIterable;
import org.eclipse.collections.api.block.function.primitive.ObjectLongIntToObjectFunction;
import org.eclipse.collections.api.block.function.primitive.ObjectLongToObjectFunction;
import org.eclipse.collections.api.block.function.primitive.LongToObjectFunction;
import org.eclipse.collections.api.block.predicate.primitive.LongPredicate;
import org.eclipse.collections.api.block.procedure.primitive.LongIntProcedure;
import org.eclipse.collections.api.block.procedure.primitive.LongProcedure;
import org.eclipse.collections.api.iterator.MutableLongIterator;
import org.eclipse.collections.api.iterator.LongIterator;
import org.eclipse.collections.api.list.ListIterable;
import org.eclipse.collections.api.list.MutableList;
import org.eclipse.collections.api.list.primitive.ImmutableLongList;
import org.eclipse.collections.api.list.primitive.MutableLongList;
import org.eclipse.collections.api.list.primitive.LongList;
import org.eclipse.collections.impl.lazy.primitive.ReverseLongIterable;
import org.eclipse.collections.impl.list.mutable.primitive.SynchronizedLongList;
import org.eclipse.collections.impl.list.mutable.primitive.UnmodifiableLongList;
import org.eclipse.collections.impl.primitive.AbstractLongIterable;

import java.util.Spliterator;

class LongBufferListImpl extends AbstractLongIterable implements LongBufferList {
    private static final int INITIAL_CAPACITY = 16;

    final LargeByteBufferAllocator allocator;
    LargeByteBuffer buffer;
    int size;

    LongBufferListImpl(LargeByteBufferAllocator allocator) {
        this.allocator = allocator;
        buffer = LargeByteBuffer.EMPTY;
    }

    LongBufferListImpl(LargeByteBufferAllocator allocator, int initialCapacity) {
        this.allocator = allocator;
        buffer = allocator.allocate(scale(initialCapacity));
    }

    @Override
    public void close() {
        buffer.close();
        buffer = null;
    }

    protected long scale(int index) {
        return ((long) index) * Long.BYTES;
    }

    @Override
    public LongIterator longIterator() {
        return new Itr();
    }

    @Override
    public long[] toArray() {
        long[] array = new long[size];
        for (int i = 0; i < size; i++) {
            array[i] = get(i);
        }
        return array;
    }

    @Override
    public boolean contains(long value) {
        for (int i = 0; i < size; i++) {
            if (get(i) == value) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void forEach(LongProcedure procedure) {
        for (int i = 0; i < size; i++) {
            procedure.value(get(i));
        }
    }

    @Override
    public void each(LongProcedure procedure) {
        forEach(procedure);
    }

    @Override
    public long get(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException();
        } else {
            return buffer.getLong(scale(index));
        }
    }

    @Override
    public long
 dotProduct(LongList list) {
        long
 sum = 0;
        int i = 0;
        LongIterator itr = list.longIterator();
        while (i < size && itr.hasNext()) {
            sum += get(i++) * itr.next();
        }
        if (itr.hasNext() || i < size) {
            throw new IllegalArgumentException("Size mismatch");
        }
        return sum;
    }

    @Override
    public int binarySearch(long value) {
        int low = 0;
        int high = size - 1;
        while (low <= high) {
            int mid = (low + high) / 2; // no overflow because low + high <= size
            long pivot = get(mid);
            if (pivot < value) {
                low = mid + 1;
            } else if (pivot > value) {
                high = mid - 1;
            } else {
                return mid;
            }
        }
        return ~low;
    }

    @Override
    public int indexOf(long value) {
        for (int i = 0; i < size; i++) {
            if (get(i) == value) {
                return i;
            }
        }
        return -1;
    }

    @Override
    public int lastIndexOf(long value) {
        for (int i = size - 1; i >= 0; i--) {
            if (get(i) == value) {
                return i;
            }
        }
        return -1;
    }

    @Override
    public long getLast() {
        return get(size - 1);
    }

    @Override
    public LazyLongIterable asReversed() {
        return ReverseLongIterable.adapt(this);
    }

    @Override
    public long getFirst() {
        return get(0);
    }

    @Override
    public LongList select(LongPredicate predicate) {
        throw new UnsupportedOperationException();
    }

    @Override
    public LongList reject(LongPredicate predicate) {
        throw new UnsupportedOperationException();
    }

    @Override
    public <V> ListIterable<V> collect(LongToObjectFunction<? extends V> function) {
        throw new UnsupportedOperationException();
    }

    @Override
    public long detectIfNone(LongPredicate predicate, long ifNone) {
        for (int i = 0; i < size; i++) {
            long element = get(i);
            if (predicate.accept(element)) {
                return element;
            }
        }
        return ifNone;
    }

    @Override
    public int count(LongPredicate predicate) {
        int count = 0;
        for (int i = 0; i < size; i++) {
            if (predicate.accept(get(i))) {
                count++;
            }
        }
        return count;
    }

    @Override
    public boolean anySatisfy(LongPredicate predicate) {
        for (int i = 0; i < size; i++) {
            long element = get(i);
            if (predicate.accept(element)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean allSatisfy(LongPredicate predicate) {
        for (int i = 0; i < size; i++) {
            long element = get(i);
            if (!predicate.accept(element)) {
                return false;
            }
        }
        return true;
    }

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

    @Override
    public <T> T injectInto(T injectedValue, ObjectLongToObjectFunction<? super T, ? extends T> function) {
        for (int i = 0; i < size; i++) {
            injectedValue = function.valueOf(injectedValue, get(i));
        }
        return injectedValue;
    }

    @Override
    public <T> T injectIntoWithIndex(T injectedValue, ObjectLongIntToObjectFunction<? super T, ? extends T> function) {
        for (int i = 0; i < size; i++) {
            injectedValue = function.valueOf(injectedValue, get(i), i);
        }
        return injectedValue;
    }

    @Override
    public long
 sum() {
        long
 sum = 0;
        for (int i = 0; i < size; i++) {
            sum += get(i);
        }
        return sum;
    }

    @Override
    public long max() {
        long max = get(0);
        for (int i = 1; i < size; i++) {
            long item = get(i);
            if (item > max) {
                return item;
            }
        }
        return max;
    }

    @Override
    public long min() {
        long min = get(0);
        for (int i = 1; i < size; i++) {
            long item = get(i);
            if (item < min) {
                return item;
            }
        }
        return min;
    }

    @Override
    public ImmutableLongList toImmutable() {
        throw new UnsupportedOperationException();
    }

    @Override
    public LongList distinct() {
        throw new UnsupportedOperationException();
    }

    @Override
    public void forEachWithIndex(LongIntProcedure procedure) {
        for (int i = 0; i < size; i++) {
            procedure.value(get(i), i);
        }
    }

    @Override
    public LongList toReversed() {
        throw new UnsupportedOperationException();
    }

    @Override
    public LongList subList(int fromIndex, int toIndex) {
        throw new UnsupportedOperationException();
    }

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

    @Override
    public Spliterator.OfLong spliterator() {
        throw new UnsupportedOperationException();
    }

    @Override
    public void appendString(Appendable appendable, String start, String separator, String end) {
        try {
            appendable.append(start);
            for (int i = 0; i < size; i++) {
                if (i != 0) {
                    appendable.append(separator);
                }
                appendable.append(String.valueOf(get(i)));
            }
            appendable.append(end);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof LongList) {
            LongList other = (LongList) o;
            int expectedSize = other.size();
            if (expectedSize == this.size()) {
                LongIterator j = other.longIterator();
                int i = 0;
                while (i < expectedSize) {
                    if (!j.hasNext()) { throw new ConcurrentModificationException(); }
                    if (j.next() != get(i)) { return false; }
                    i++;
                }
                if (j.hasNext()) { throw new ConcurrentModificationException(); }
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        int hashCode = 1;
        for (int i = 0; i < size(); i++) {
            hashCode *= 31;
            hashCode += get(i);
        }
        return hashCode;
    }

    class Itr implements LongIterator {
        int i;

        @Override
        public long next() {
            return get(i++);
        }

        @Override
        public boolean hasNext() {
            return i < size;
        }
    }

    public static class Mutable extends LongBufferListImpl implements MutableLongBufferList {
        Mutable(LargeByteBufferAllocator allocator) {
            super(allocator);
        }

        Mutable(LargeByteBufferAllocator allocator, int initialCapacity) {
            super(allocator, initialCapacity);
        }

        private void ensureCapacity(int capacity) {
            long requiredCapacity = scale(capacity);
            long currentCapacity = buffer.size();
            if (requiredCapacity > currentCapacity) {
                long newCapacity = currentCapacity == 0 ? scale(INITIAL_CAPACITY) : currentCapacity;
                while (requiredCapacity > newCapacity) {
                    newCapacity += newCapacity >> 1; // *= 1.5
                }
                LargeByteBuffer reallocated = buffer.reallocate(newCapacity);
                if (reallocated != null) {
                    this.buffer = reallocated;
                } else {
                    @SuppressWarnings("resource")
                    LargeByteBuffer swap = allocator.allocate(newCapacity);
                    try {
                        swap.copyFrom(buffer, 0, 0, scale(size));

                        LargeByteBuffer tmp = swap;
                        swap = this.buffer; // old buffer will be closed
                        this.buffer = tmp;
                    } finally {
                        swap.close();
                    }
                }
            }
        }

        @Override
        public void addAtIndex(int index, long element) {
            if (index == size) {
                add(element);
            } else if (index < 0 || index > size) {
                throw new IndexOutOfBoundsException();
            } else {
                // we may do a redundant copy here, but that's not too bad.
                ensureCapacity(size + 1);
                buffer.copyFrom(buffer, scale(index), scale(index + 1), scale(size - index));
                buffer.setLong(scale(index), element);
                size++;
            }
        }

        @Override
        public boolean addAllAtIndex(int index, long... source) {
            if (index == size) {
                return addAll(source);
            } else if (index < 0 || index > size) {
                throw new IndexOutOfBoundsException();
            } else if (source.length == 0) {
                return false;
            } else {
                // we may do a redundant copy here, but that's not too bad.
                ensureCapacity(size + source.length);
                buffer.copyFrom(buffer, scale(index), scale(index + source.length), scale(size - index));
                for (int i = 0; i < source.length; i++) {
                    buffer.setLong(scale(index + i), source[i]);
                }
                size += source.length;
                return true;
            }
        }

        @Override
        public boolean addAllAtIndex(int index, LongIterable source) {
            if (index == size) {
                return addAll(source);
            } else if (index < 0 || index > size) {
                throw new IndexOutOfBoundsException();
            } else if (source.isEmpty()) {
                return false;
            } else {
                // we may do a redundant copy here, but that's not too bad.
                int expectedSize = source.size();
                ensureCapacity(size + expectedSize);
                buffer.copyFrom(buffer, scale(index), scale(index + expectedSize), scale(size - index));
                LongIterator itr = source.longIterator();
                for (int i = 0; i < expectedSize; i++) {
                    if (!itr.hasNext()) { throw new ConcurrentModificationException(); }
                    buffer.setLong(scale(index + i), itr.next());
                }
                if (itr.hasNext()) { throw new ConcurrentModificationException(); }
                size += expectedSize;
                return true;
            }
        }

        @Override
        public long removeAtIndex(int index) {
            if (index < 0 || index >= size) {
                throw new IndexOutOfBoundsException();
            } else {
                long value = buffer.getLong(scale(index));
                if (size > 1) {
                    buffer.copyFrom(buffer, scale(index + 1), scale(index), scale(size - index - 1));
                }
                size--;
                return value;
            }
        }

        @Override
        public long set(int index, long element) {
            if (index < 0 || index >= size) {
                throw new IndexOutOfBoundsException();
            } else {
                long old = buffer.getLong(scale(index));
                buffer.setLong(scale(index), element);
                return old;
            }
        }

        @Override
        public boolean add(long element) {
            ensureCapacity(size + 1);
            buffer.setLong(scale(size), element);
            size++;
            return true;
        }

        @Override
        public boolean addAll(long... source) {
            ensureCapacity(size + source.length);
            for (int i = 0; i < source.length; i++) {
                buffer.setLong(scale(i + size), source[i]);
            }
            size += source.length;
            return source.length > 0;
        }

        @Override
        public boolean addAll(LongIterable source) {
            int expectedSize = source.size();
            ensureCapacity(size + expectedSize);
            LongIterator itr = source.longIterator();
            int i = 0;
            while (itr.hasNext()) {
                if (i > expectedSize) {
                    throw new ConcurrentModificationException();
                }
                buffer.setLong(scale(i + size), itr.next());
                i++;
            }
            size += i;
            return i > 0;
        }

        @Override
        public boolean remove(long value) {
            int ix = indexOf(value);
            if (ix == -1) {
                return false;
            } else {
                removeAtIndex(ix);
                return true;
            }
        }

        @Override
        public boolean removeAll(LongIterable source) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean removeAll(long... source) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean retainAll(LongIterable elements) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean retainAll(long... source) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void clear() {
            size = 0;
        }

        @Override
        public MutableLongList with(long element) {
            add(element);
            return this;
        }

        @Override
        public MutableLongList without(long element) {
            remove(element);
            return this;
        }

        @Override
        public MutableLongList withAll(LongIterable elements) {
            addAll(elements);
            return this;
        }

        @Override
        public MutableLongList withoutAll(LongIterable elements) {
            removeAll(elements);
            return this;
        }

        @Override
        public MutableLongList reverseThis() {
            throw new UnsupportedOperationException();
        }

        @Override
        public MutableLongList sortThis() {
            throw new UnsupportedOperationException();
        }

        @Override
        public MutableLongList asUnmodifiable() {
            return new UnmodifiableLongList(this);
        }

        @Override
        public MutableLongList asSynchronized() {
            return new SynchronizedLongList(this);
        }

        @Override
        public MutableLongIterator longIterator() {
            return new Itr();
        }

        @Override
        public MutableLongList select(LongPredicate predicate) {
            throw new UnsupportedOperationException();
        }

        @Override
        public MutableLongList reject(LongPredicate predicate) {
            throw new UnsupportedOperationException();
        }

        @Override
        public <V> MutableList<V> collect(LongToObjectFunction<? extends V> function) {
            throw new UnsupportedOperationException();
        }

        @Override
        public MutableLongList toReversed() {
            throw new UnsupportedOperationException();
        }

        @Override
        public MutableLongList distinct() {
            throw new UnsupportedOperationException();
        }

        @Override
        public MutableLongList subList(int fromIndex, int toIndex) {
            throw new UnsupportedOperationException();
        }

        class Itr extends LongBufferListImpl.Itr implements MutableLongIterator {
            int removalIndex = -1;

            @Override
            public long next() {
                removalIndex = i;
                return super.next();
            }

            @Override
            public void remove() {
                if (removalIndex == -1) { throw new IllegalStateException(); }
                removeAtIndex(removalIndex);
                i--;
                removalIndex = -1;
            }
        }
    }
}