// 
// Decompiled by Procyon v0.6.0
// 

package it.unimi.dsi.fastutil.bytes;

import java.util.ListIterator;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.NoSuchElementException;
import it.unimi.dsi.fastutil.objects.ObjectListIterator;
import java.util.Collection;
import it.unimi.dsi.fastutil.BigArrays;
import it.unimi.dsi.fastutil.longs.LongArrays;
import java.util.Iterator;
import java.util.RandomAccess;
import java.io.Serializable;
import it.unimi.dsi.fastutil.objects.AbstractObjectList;

public class ByteArrayFrontCodedList extends AbstractObjectList<byte[]> implements Serializable, Cloneable, RandomAccess
{
    private static final long serialVersionUID = 1L;
    protected final int n;
    protected final int ratio;
    protected final byte[][] array;
    protected transient long[] p;
    
    public ByteArrayFrontCodedList(final Iterator<byte[]> arrays, final int ratio) {
        if (ratio < 1) {
            throw new IllegalArgumentException("Illegal ratio (" + ratio + ")");
        }
        byte[][] array = ByteBigArrays.EMPTY_BIG_ARRAY;
        long[] p = LongArrays.EMPTY_ARRAY;
        final byte[][] a = new byte[2][];
        long curSize = 0L;
        int n = 0;
        int b = 0;
        while (arrays.hasNext()) {
            a[b] = arrays.next();
            int length = a[b].length;
            if (n % ratio == 0) {
                p = LongArrays.grow(p, n / ratio + 1);
                p[n / ratio] = curSize;
                array = BigArrays.grow(array, curSize + count(length) + length, curSize);
                curSize += writeInt(array, length, curSize);
                BigArrays.copyToBig(a[b], 0, array, curSize, length);
                curSize += length;
            }
            else {
                int minLength;
                int common;
                for (minLength = Math.min(a[1 - b].length, length), common = 0; common < minLength && a[0][common] == a[1][common]; ++common) {}
                length -= common;
                array = BigArrays.grow(array, curSize + count(length) + count(common) + length, curSize);
                curSize += writeInt(array, length, curSize);
                curSize += writeInt(array, common, curSize);
                BigArrays.copyToBig(a[b], common, array, curSize, length);
                curSize += length;
            }
            b = 1 - b;
            ++n;
        }
        this.n = n;
        this.ratio = ratio;
        this.array = BigArrays.trim(array, curSize);
        this.p = LongArrays.trim(p, (n + ratio - 1) / ratio);
    }
    
    public ByteArrayFrontCodedList(final Collection<byte[]> c, final int ratio) {
        this(c.iterator(), ratio);
    }
    
    static int readInt(final byte[][] a, final long pos) {
        final byte b0 = BigArrays.get(a, pos);
        if (b0 >= 0) {
            return b0;
        }
        final byte b2 = BigArrays.get(a, pos + 1L);
        if (b2 >= 0) {
            return -b0 - 1 << 7 | b2;
        }
        final byte b3 = BigArrays.get(a, pos + 2L);
        if (b3 >= 0) {
            return -b0 - 1 << 14 | -b2 - 1 << 7 | b3;
        }
        final byte b4 = BigArrays.get(a, pos + 3L);
        if (b4 >= 0) {
            return -b0 - 1 << 21 | -b2 - 1 << 14 | -b3 - 1 << 7 | b4;
        }
        return -b0 - 1 << 28 | -b2 - 1 << 21 | -b3 - 1 << 14 | -b4 - 1 << 7 | BigArrays.get(a, pos + 4L);
    }
    
    static int count(final int length) {
        if (length < 128) {
            return 1;
        }
        if (length < 16384) {
            return 2;
        }
        if (length < 2097152) {
            return 3;
        }
        if (length < 268435456) {
            return 4;
        }
        return 5;
    }
    
    static int writeInt(final byte[][] a, int length, final long pos) {
        final int count = count(length);
        BigArrays.set(a, pos + count - 1L, (byte)(length & 0x7F));
        int i = count - 1;
        while (i-- != 0) {
            length >>>= 7;
            BigArrays.set(a, pos + i, (byte)(-(length & 0x7F) - 1));
        }
        return count;
    }
    
    public int ratio() {
        return this.ratio;
    }
    
    private int length(final int index) {
        final byte[][] array = this.array;
        final int delta = index % this.ratio;
        long pos = this.p[index / this.ratio];
        int length = readInt(array, pos);
        if (delta == 0) {
            return length;
        }
        pos += count(length) + length;
        length = readInt(array, pos);
        int common = readInt(array, pos + count(length));
        for (int i = 0; i < delta - 1; ++i) {
            pos += count(length) + count(common) + length;
            length = readInt(array, pos);
            common = readInt(array, pos + count(length));
        }
        return length + common;
    }
    
    public int arrayLength(final int index) {
        this.ensureRestrictedIndex(index);
        return this.length(index);
    }
    
    private int extract(final int index, final byte[] a, final int offset, final int length) {
        final int delta = index % this.ratio;
        final long startPos = this.p[index / this.ratio];
        long pos;
        int arrayLength = readInt(this.array, pos = startPos);
        int currLen = 0;
        if (delta == 0) {
            pos = this.p[index / this.ratio] + count(arrayLength);
            BigArrays.copyFromBig(this.array, pos, a, offset, Math.min(length, arrayLength));
            return arrayLength;
        }
        int common = 0;
        for (int i = 0; i < delta; ++i) {
            final long prevArrayPos = pos + count(arrayLength) + ((i != 0) ? count(common) : 0);
            pos = prevArrayPos + arrayLength;
            arrayLength = readInt(this.array, pos);
            common = readInt(this.array, pos + count(arrayLength));
            final int actualCommon = Math.min(common, length);
            if (actualCommon <= currLen) {
                currLen = actualCommon;
            }
            else {
                BigArrays.copyFromBig(this.array, prevArrayPos, a, currLen + offset, actualCommon - currLen);
                currLen = actualCommon;
            }
        }
        if (currLen < length) {
            BigArrays.copyFromBig(this.array, pos + count(arrayLength) + count(common), a, currLen + offset, Math.min(arrayLength, length - currLen));
        }
        return arrayLength + common;
    }
    
    @Override
    public byte[] get(final int index) {
        return this.getArray(index);
    }
    
    public byte[] getArray(final int index) {
        this.ensureRestrictedIndex(index);
        final int length = this.length(index);
        final byte[] a = new byte[length];
        this.extract(index, a, 0, length);
        return a;
    }
    
    public int get(final int index, final byte[] a, final int offset, final int length) {
        this.ensureRestrictedIndex(index);
        ByteArrays.ensureOffsetLength(a, offset, length);
        final int arrayLength = this.extract(index, a, offset, length);
        if (length >= arrayLength) {
            return arrayLength;
        }
        return length - arrayLength;
    }
    
    public int get(final int index, final byte[] a) {
        return this.get(index, a, 0, a.length);
    }
    
    @Override
    public int size() {
        return this.n;
    }
    
    @Override
    public ObjectListIterator<byte[]> listIterator(final int start) {
        this.ensureIndex(start);
        return new ObjectListIterator<byte[]>() {
            byte[] s = ByteArrays.EMPTY_ARRAY;
            int i = start;
            long pos = 0L;
            boolean inSync;
            
            {
                this.i = 0;
                if (start != 0) {
                    if (start != ByteArrayFrontCodedList.this.n) {
                        this.pos = ByteArrayFrontCodedList.this.p[start / ByteArrayFrontCodedList.this.ratio];
                        int j = start % ByteArrayFrontCodedList.this.ratio;
                        this.i = start - j;
                        while (j-- != 0) {
                            this.next();
                        }
                    }
                }
            }
            
            @Override
            public boolean hasNext() {
                return this.i < ByteArrayFrontCodedList.this.n;
            }
            
            @Override
            public boolean hasPrevious() {
                return this.i > 0;
            }
            
            @Override
            public int previousIndex() {
                return this.i - 1;
            }
            
            @Override
            public int nextIndex() {
                return this.i;
            }
            
            @Override
            public byte[] next() {
                if (!this.hasNext()) {
                    throw new NoSuchElementException();
                }
                int length;
                if (this.i % ByteArrayFrontCodedList.this.ratio == 0) {
                    this.pos = ByteArrayFrontCodedList.this.p[this.i / ByteArrayFrontCodedList.this.ratio];
                    length = ByteArrayFrontCodedList.readInt(ByteArrayFrontCodedList.this.array, this.pos);
                    this.s = ByteArrays.ensureCapacity(this.s, length, 0);
                    BigArrays.copyFromBig(ByteArrayFrontCodedList.this.array, this.pos + ByteArrayFrontCodedList.count(length), this.s, 0, length);
                    this.pos += length + ByteArrayFrontCodedList.count(length);
                    this.inSync = true;
                }
                else if (this.inSync) {
                    length = ByteArrayFrontCodedList.readInt(ByteArrayFrontCodedList.this.array, this.pos);
                    final int common = ByteArrayFrontCodedList.readInt(ByteArrayFrontCodedList.this.array, this.pos + ByteArrayFrontCodedList.count(length));
                    this.s = ByteArrays.ensureCapacity(this.s, length + common, common);
                    BigArrays.copyFromBig(ByteArrayFrontCodedList.this.array, this.pos + ByteArrayFrontCodedList.count(length) + ByteArrayFrontCodedList.count(common), this.s, common, length);
                    this.pos += ByteArrayFrontCodedList.count(length) + ByteArrayFrontCodedList.count(common) + length;
                    length += common;
                }
                else {
                    this.s = ByteArrays.ensureCapacity(this.s, length = ByteArrayFrontCodedList.this.length(this.i), 0);
                    ByteArrayFrontCodedList.this.extract(this.i, this.s, 0, length);
                }
                ++this.i;
                return ByteArrays.copy(this.s, 0, length);
            }
            
            @Override
            public byte[] previous() {
                if (!this.hasPrevious()) {
                    throw new NoSuchElementException();
                }
                this.inSync = false;
                final ByteArrayFrontCodedList this$0 = ByteArrayFrontCodedList.this;
                final int n = this.i - 1;
                this.i = n;
                return this$0.getArray(n);
            }
        };
    }
    
    public ByteArrayFrontCodedList clone() {
        return this;
    }
    
    @Override
    public String toString() {
        final StringBuffer s = new StringBuffer();
        s.append("[");
        for (int i = 0; i < this.n; ++i) {
            if (i != 0) {
                s.append(", ");
            }
            s.append(ByteArrayList.wrap(this.getArray(i)).toString());
        }
        s.append("]");
        return s.toString();
    }
    
    protected long[] rebuildPointerArray() {
        final long[] p = new long[(this.n + this.ratio - 1) / this.ratio];
        final byte[][] a = this.array;
        long pos = 0L;
        int i = 0;
        int j = 0;
        int skip = this.ratio - 1;
        while (i < this.n) {
            final int length = readInt(a, pos);
            final int count = count(length);
            if (++skip == this.ratio) {
                skip = 0;
                p[j++] = pos;
                pos += count + length;
            }
            else {
                pos += count + count(readInt(a, pos + count)) + length;
            }
            ++i;
        }
        return p;
    }
    
    private void readObject(final ObjectInputStream s) throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        this.p = this.rebuildPointerArray();
    }
}
