package at.yawk.numaec;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.ConcurrentModificationException;
import org.eclipse.collections.api.LazyCharIterable;
import org.eclipse.collections.api.CharIterable;
import org.eclipse.collections.api.block.function.primitive.ObjectCharIntToObjectFunction;
import org.eclipse.collections.api.block.function.primitive.ObjectCharToObjectFunction;
import org.eclipse.collections.api.block.function.primitive.CharToObjectFunction;
import org.eclipse.collections.api.block.predicate.primitive.CharPredicate;
import org.eclipse.collections.api.block.procedure.primitive.CharIntProcedure;
import org.eclipse.collections.api.block.procedure.primitive.CharProcedure;
import org.eclipse.collections.api.iterator.MutableCharIterator;
import org.eclipse.collections.api.iterator.CharIterator;
import org.eclipse.collections.api.list.ListIterable;
import org.eclipse.collections.api.list.MutableList;
import org.eclipse.collections.api.list.primitive.ImmutableCharList;
import org.eclipse.collections.api.list.primitive.MutableCharList;
import org.eclipse.collections.api.list.primitive.CharList;
import org.eclipse.collections.impl.lazy.primitive.ReverseCharIterable;
import org.eclipse.collections.impl.list.mutable.primitive.SynchronizedCharList;
import org.eclipse.collections.impl.list.mutable.primitive.UnmodifiableCharList;
import org.eclipse.collections.impl.primitive.AbstractCharIterable;


class CharBufferListImpl extends AbstractCharIterable implements CharBufferList {
    private static final int INITIAL_CAPACITY = 16;

    final LargeByteBufferAllocator allocator;
    LargeByteBuffer buffer;
    int size;

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

    CharBufferListImpl(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) * Character.BYTES;
    }

    @Override
    public CharIterator charIterator() {
        return new Itr();
    }

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

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

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

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

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

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

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

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

    @Override
    public LazyCharIterable asReversed() {
        return ReverseCharIterable.adapt(this);
    }

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

    @Override
    public CharList select(CharPredicate predicate) {
        throw new UnsupportedOperationException();
    }

    @Override
    public CharList reject(CharPredicate predicate) {
        throw new UnsupportedOperationException();
    }

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

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

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

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

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

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

    @Override
    public <T> T injectInto(T injectedValue, ObjectCharToObjectFunction<? 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, ObjectCharIntToObjectFunction<? 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 char max() {
        char max = get(0);
        for (int i = 1; i < size; i++) {
            char item = get(i);
            if (item > max) {
                return item;
            }
        }
        return max;
    }

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

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

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

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

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

    @Override
    public CharList 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 CharList) {
            CharList other = (CharList) o;
            int expectedSize = other.size();
            if (expectedSize == this.size()) {
                CharIterator j = other.charIterator();
                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 CharIterator {
        int i;

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

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

    public static class Mutable extends CharBufferListImpl implements MutableCharBufferList {
        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, char 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.setChar(scale(index), element);
                size++;
            }
        }

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

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

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

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

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

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

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

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

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

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

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

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

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

        @Override
        public MutableCharList with(char element) {
            add(element);
            return this;
        }

        @Override
        public MutableCharList without(char element) {
            remove(element);
            return this;
        }

        @Override
        public MutableCharList withAll(CharIterable elements) {
            addAll(elements);
            return this;
        }

        @Override
        public MutableCharList withoutAll(CharIterable elements) {
            removeAll(elements);
            return this;
        }

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

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

        @Override
        public MutableCharList asUnmodifiable() {
            return new UnmodifiableCharList(this);
        }

        @Override
        public MutableCharList asSynchronized() {
            return new SynchronizedCharList(this);
        }

        @Override
        public MutableCharIterator charIterator() {
            return new Itr();
        }

        @Override
        public MutableCharList select(CharPredicate predicate) {
            throw new UnsupportedOperationException();
        }

        @Override
        public MutableCharList reject(CharPredicate predicate) {
            throw new UnsupportedOperationException();
        }

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

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

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

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

        class Itr extends CharBufferListImpl.Itr implements MutableCharIterator {
            int removalIndex = -1;

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

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