package ru.yandex.webmaster3.core.util.trie;

/**
 * @author avhaliullin
 */
public class TrieBufferReader {
    final byte[] buffer;
    int offset = 0;

    public TrieBufferReader(byte[] buffer) {this.buffer = buffer;}

    public int getOffset() {
        return offset;
    }

    public void skip(int bytes) {
        offset += bytes;
    }

    public void setOffset(int offset) {
        this.offset = offset;
    }

    public byte readByte() {
        return buffer[offset++];
    }

    public void readBytes(byte[] dest, int destOffset, int len) {
        System.arraycopy(buffer, offset, dest, destOffset, len);
        offset += len;
    }

    public byte[] readBytes(int len) {
        byte[] res = new byte[len];
        System.arraycopy(buffer, offset, res, 0, len);
        offset += len;
        return res;
    }

    public int readUInt16() {
        int result = ((buffer[offset] & 0xff) << 8) |
                (buffer[offset + 1] & 0xff);
        offset += 2;
        return result;
    }

    public int readInt() {
        int result = (((buffer[offset] & 0xff) << 24) |
                ((buffer[offset + 1] & 0xff) << 16) |
                ((buffer[offset + 2] & 0xff) << 8) |
                (buffer[offset + 3] & 0xff));
        offset += 4;
        return result;
    }

    public long readLong() {
        long result =
                ((((long) buffer[offset] & 0xffL) << 56) |
                        (((long) buffer[offset + 1] & 0xffL) << 48) |
                        (((long) buffer[offset + 2] & 0xffL) << 40) |
                        (((long) buffer[offset + 3] & 0xffL) << 32) |
                        (((long) buffer[offset + 4] & 0xffL) << 24) |
                        (((long) buffer[offset + 5] & 0xffL) << 16) |
                        (((long) buffer[offset + 6] & 0xffL) << 8) |
                        ((long) buffer[offset + 7] & 0xffL));
        offset += 8;
        return result;
    }

    public int readRawVarUInt32() {
        fastpath:
        {
            int pos = offset;

            if (offset == pos) {
                break fastpath;
            }

            final byte[] buffer = this.buffer;
            int x;
            if ((x = buffer[pos++]) >= 0) {
                offset = pos;
                return x;
            } else if (offset - pos < 9) {
                break fastpath;
            } else if ((x ^= (buffer[pos++] << 7)) < 0L) {
                x ^= (~0L << 7);
            } else if ((x ^= (buffer[pos++] << 14)) >= 0L) {
                x ^= (~0L << 7) ^ (~0L << 14);
            } else if ((x ^= (buffer[pos++] << 21)) < 0L) {
                x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21);
            } else {
                int y = buffer[pos++];
                x ^= y << 28;
                x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28);
                if (y < 0 &&
                        buffer[pos++] < 0 &&
                        buffer[pos++] < 0 &&
                        buffer[pos++] < 0 &&
                        buffer[pos++] < 0 &&
                        buffer[pos++] < 0) {
                    break fastpath;
                }
            }
            offset = pos;
            return x;
        }
        return (int) readRawVarint64SlowPath();
    }

    private long readRawVarint64SlowPath() {
        long result = 0;
        for (int shift = 0; shift < 64; shift += 7) {
            final byte b = readByte();
            result |= (long) (b & 0x7F) << shift;
            if ((b & 0x80) == 0) {
                return result;
            }
        }
        throw new RuntimeException("Malformed varint");
    }

    public int compare(byte[] key, int keyOffset, int len) {
        int diff = 0;
        for (int i = 0; i < len; i++) {
            diff = key[keyOffset + i] - buffer[offset + i];
            if (diff != 0) {
                break;
            }
        }
        offset += len;
        return diff;
    }
}
