package at.yawk.numaec;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ConcurrentModificationException;
import org.eclipse.collections.api.LazyFloatIterable;
import org.eclipse.collections.api.FloatIterable;
import org.eclipse.collections.api.block.function.primitive.ObjectFloatIntToObjectFunction;
import org.eclipse.collections.api.block.function.primitive.ObjectFloatToObjectFunction;
import org.eclipse.collections.api.block.function.primitive.FloatToObjectFunction;
import org.eclipse.collections.api.block.predicate.primitive.FloatPredicate;
import org.eclipse.collections.api.block.procedure.primitive.FloatIntProcedure;
import org.eclipse.collections.api.block.procedure.primitive.FloatProcedure;
import org.eclipse.collections.api.iterator.MutableFloatIterator;
import org.eclipse.collections.api.iterator.FloatIterator;
import org.eclipse.collections.api.list.ListIterable;
import org.eclipse.collections.api.list.MutableList;
import org.eclipse.collections.api.list.primitive.ImmutableFloatList;
import org.eclipse.collections.api.list.primitive.MutableFloatList;
import org.eclipse.collections.api.list.primitive.FloatList;
import org.eclipse.collections.impl.lazy.primitive.ReverseFloatIterable;
import org.eclipse.collections.impl.list.mutable.primitive.SynchronizedFloatList;
import org.eclipse.collections.impl.list.mutable.primitive.UnmodifiableFloatList;
import org.eclipse.collections.impl.primitive.AbstractFloatIterable;


class FloatBufferListImpl extends AbstractFloatIterable implements FloatBufferList {
    private static final int INITIAL_CAPACITY = 16;

    final LargeByteBufferAllocator allocator;
    LargeByteBuffer buffer;
    int size;

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

    FloatBufferListImpl(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) * Float.BYTES;
    }

    @Override
    public FloatIterator floatIterator() {
        return new Itr();
    }

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

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

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

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

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

    @Override
    public double
 dotProduct(FloatList list) {
        double
 sum = 0;
        int i = 0;
        FloatIterator itr = list.floatIterator();
        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(float value) {
        int low = 0;
        int high = size - 1;
        while (low <= high) {
            int mid = (low + high) / 2; // no overflow because low + high <= size
            float 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(float value) {
        for (int i = 0; i < size; i++) {
            if (get(i) == value) {
                return i;
            }
        }
        return -1;
    }

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

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

    @Override
    public LazyFloatIterable asReversed() {
        return ReverseFloatIterable.adapt(this);
    }

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

    @Override
    public FloatList select(FloatPredicate predicate) {
        throw new UnsupportedOperationException();
    }

    @Override
    public FloatList reject(FloatPredicate predicate) {
        throw new UnsupportedOperationException();
    }

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

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

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

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

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

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

    @Override
    public <T> T injectInto(T injectedValue, ObjectFloatToObjectFunction<? 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, ObjectFloatIntToObjectFunction<? super T, ? extends T> function) {
        for (int i = 0; i < size; i++) {
            injectedValue = function.valueOf(injectedValue, get(i), i);
        }
        return injectedValue;
    }

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

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

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

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

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

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

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

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

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


    @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 FloatList) {
            FloatList other = (FloatList) o;
            int expectedSize = other.size();
            if (expectedSize == this.size()) {
                FloatIterator j = other.floatIterator();
                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 FloatIterator {
        int i;

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

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

    public static class Mutable extends FloatBufferListImpl implements MutableFloatBufferList {
        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, float 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.setFloat(scale(index), element);
                size++;
            }
        }

        @Override
        public boolean addAllAtIndex(int index, float... 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.setFloat(scale(index + i), source[i]);
                }
                size += source.length;
                return true;
            }
        }

        @Override
        public boolean addAllAtIndex(int index, FloatIterable 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));
                FloatIterator itr = source.floatIterator();
                for (int i = 0; i < expectedSize; i++) {
                    if (!itr.hasNext()) { throw new ConcurrentModificationException(); }
                    buffer.setFloat(scale(index + i), itr.next());
                }
                if (itr.hasNext()) { throw new ConcurrentModificationException(); }
                size += expectedSize;
                return true;
            }
        }

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

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

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

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

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

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

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

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

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

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

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

        @Override
        public MutableFloatList with(float element) {
            add(element);
            return this;
        }

        @Override
        public MutableFloatList without(float element) {
            remove(element);
            return this;
        }

        @Override
        public MutableFloatList withAll(FloatIterable elements) {
            addAll(elements);
            return this;
        }

        @Override
        public MutableFloatList withoutAll(FloatIterable elements) {
            removeAll(elements);
            return this;
        }

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

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

        @Override
        public MutableFloatList asUnmodifiable() {
            return new UnmodifiableFloatList(this);
        }

        @Override
        public MutableFloatList asSynchronized() {
            return new SynchronizedFloatList(this);
        }

        @Override
        public MutableFloatIterator floatIterator() {
            return new Itr();
        }

        @Override
        public MutableFloatList select(FloatPredicate predicate) {
            throw new UnsupportedOperationException();
        }

        @Override
        public MutableFloatList reject(FloatPredicate predicate) {
            throw new UnsupportedOperationException();
        }

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

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

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

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

        class Itr extends FloatBufferListImpl.Itr implements MutableFloatIterator {
            int removalIndex = -1;

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

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