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

package com.hypixel.hytale.codec.util;

import java.util.logging.Level;
import io.sentry.Sentry;
import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import com.hypixel.hytale.codec.ExtraInfo;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.codec.Codec;
import org.bson.BsonDouble;
import org.bson.BsonBoolean;
import org.bson.BsonNull;
import org.bson.BsonString;
import org.bson.BsonValue;
import org.bson.BsonArray;
import org.bson.BsonDocument;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import ch.randelshofer.fastdoubleparser.JavaDoubleParser;
import sun.misc.Unsafe;
import com.hypixel.hytale.unsafe.UnsafeUtil;
import java.util.Arrays;
import java.io.IOException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Reader;

public class RawJsonReader implements AutoCloseable
{
    public static final ThreadLocal<char[]> READ_BUFFER;
    public static final int DEFAULT_CHAR_BUFFER_SIZE = 32768;
    public static final int MIN_CHAR_BUFFER_READ = 16384;
    public static final int BUFFER_GROWTH = 1048576;
    private static final int UNMARKED = -1;
    private int streamIndex;
    @Nullable
    private Reader in;
    @Nullable
    private char[] buffer;
    private int bufferIndex;
    private int bufferSize;
    private int markIndex;
    private int markLine;
    private int markLineStart;
    private StringBuilder tempSb;
    private int line;
    private int lineStart;
    public static final int ERROR_LINES_BUFFER = 10;
    
    public RawJsonReader(@Nonnull final char[] preFilledBuffer) {
        this.markIndex = -1;
        this.markLine = -1;
        this.markLineStart = -1;
        if (preFilledBuffer == null) {
            throw new IllegalArgumentException("buffer can't be null!");
        }
        this.in = null;
        this.buffer = preFilledBuffer;
        this.bufferIndex = 0;
        this.streamIndex = 0;
        this.bufferSize = preFilledBuffer.length;
    }
    
    public RawJsonReader(final Reader in, @Nonnull final char[] buffer) {
        this.markIndex = -1;
        this.markLine = -1;
        this.markLineStart = -1;
        if (buffer == null) {
            throw new IllegalArgumentException("buffer can't be null!");
        }
        this.in = in;
        this.buffer = buffer;
        this.bufferIndex = 0;
        this.streamIndex = 0;
        this.bufferSize = 0;
    }
    
    public char[] getBuffer() {
        return this.buffer;
    }
    
    public int getBufferIndex() {
        return this.bufferIndex;
    }
    
    public int getBufferSize() {
        return this.bufferSize;
    }
    
    public int getLine() {
        return this.line + 1;
    }
    
    public int getColumn() {
        return this.bufferIndex - this.lineStart + 1;
    }
    
    private boolean ensure() throws IOException {
        return this.ensure(1);
    }
    
    private boolean ensure(final int n) throws IOException {
        boolean filled = false;
        while (this.bufferIndex + n > this.bufferSize) {
            if (!this.fill()) {
                throw this.unexpectedEOF();
            }
            filled = true;
        }
        return filled;
    }
    
    private boolean fill() throws IOException {
        int dst;
        int len;
        if (this.markIndex <= -1) {
            this.streamIndex += this.bufferIndex;
            dst = 0;
            len = this.buffer.length;
        }
        else {
            final int spaceInBuffer = this.buffer.length - this.bufferIndex;
            if (spaceInBuffer > 16384) {
                dst = this.bufferIndex;
                len = spaceInBuffer;
            }
            else {
                final int delta = this.bufferIndex - this.markIndex;
                if (this.markIndex > 16384) {
                    System.arraycopy(this.buffer, this.markIndex, this.buffer, 0, delta);
                }
                else {
                    final int newSize = this.bufferIndex + 1048576;
                    System.err.println("Reallocate: " + this.buffer.length + " to " + newSize);
                    final char[] ncb = new char[newSize];
                    System.arraycopy(this.buffer, this.markIndex, ncb, 0, delta);
                    this.buffer = ncb;
                }
                this.streamIndex += this.markIndex;
                this.markIndex = 0;
                dst = delta;
                final int n2 = delta;
                this.bufferSize = n2;
                this.bufferIndex = n2;
                len = this.buffer.length - dst;
            }
        }
        if (this.in == null) {
            return false;
        }
        final int n = this.in.read(this.buffer, dst, len);
        if (n > 0) {
            this.bufferSize = dst + n;
            this.bufferIndex = dst;
            return true;
        }
        return false;
    }
    
    public int peek() throws IOException {
        return this.peek(0);
    }
    
    public int peek(final int n) throws IOException {
        if (this.bufferIndex + n >= this.bufferSize) {
            this.fill();
            if (this.bufferIndex + n >= this.bufferSize) {
                return -1;
            }
        }
        return this.buffer[this.bufferIndex + n];
    }
    
    public int read() throws IOException {
        if (this.bufferIndex >= this.bufferSize) {
            this.fill();
            if (this.bufferIndex >= this.bufferSize) {
                return -1;
            }
        }
        final char c = this.buffer[this.bufferIndex++];
        if (c == '\n') {
            ++this.line;
            this.lineStart = this.bufferIndex;
        }
        return c;
    }
    
    public long skip(final long skip) throws IOException {
        if (skip >= 0L) {
            long haveSkipped = 0L;
            while (haveSkipped < skip) {
                final long charsInBuffer = this.bufferSize - this.bufferIndex;
                final long charsToSkip = skip - haveSkipped;
                if (charsToSkip <= charsInBuffer) {
                    this.bufferIndex += (int)charsToSkip;
                    return skip;
                }
                haveSkipped += charsInBuffer;
                this.bufferIndex = this.bufferSize;
                this.fill();
                if (this.bufferIndex >= this.bufferSize) {
                    break;
                }
            }
            return haveSkipped;
        }
        final int negativeBufferIndex = -this.bufferIndex;
        if (skip < negativeBufferIndex) {
            this.bufferIndex = 0;
            return negativeBufferIndex;
        }
        this.bufferIndex += (int)skip;
        return skip;
    }
    
    public int findOffset(final char value) throws IOException {
        return this.findOffset(0, value);
    }
    
    public int findOffset(int start, final char value) throws IOException {
        while (true) {
            this.ensure();
            final char c = this.buffer[this.bufferIndex + start];
            if (c == value) {
                break;
            }
            ++start;
        }
        return start;
    }
    
    public void skipOrThrow(final long n) throws IOException {
        final long skipped = this.skip(n);
        if (skipped != n) {
            throw new IOException("Failed to skip " + n + " char's!");
        }
    }
    
    public boolean ready() throws IOException {
        return (this.buffer != null && this.bufferIndex < this.bufferSize) || this.in.ready();
    }
    
    public boolean markSupported() {
        return true;
    }
    
    public void mark(final int readAheadLimit) throws IOException {
        this.mark();
    }
    
    public boolean isMarked() {
        return this.markIndex >= 0;
    }
    
    public void mark() throws IOException {
        if (this.markIndex >= 0) {
            throw new IOException("mark can't be used while already marked!");
        }
        this.markIndex = this.bufferIndex;
        this.markLine = this.line;
        this.markLineStart = this.lineStart;
    }
    
    public void unmark() {
        this.markIndex = -1;
        this.markLine = -1;
        this.markLineStart = -1;
    }
    
    public int getMarkDistance() {
        return this.bufferIndex - this.markIndex;
    }
    
    public char[] cloneMark() {
        return Arrays.copyOfRange(this.buffer, this.markIndex, this.bufferIndex);
    }
    
    public void reset() throws IOException {
        if (this.markIndex < 0) {
            throw new IOException("Stream not marked");
        }
        this.bufferIndex = this.markIndex;
        this.markIndex = -1;
        this.line = this.markLine;
        this.lineStart = this.markLineStart;
        this.markLine = -1;
    }
    
    @Override
    public void close() throws IOException {
        if (this.buffer == null) {
            return;
        }
        try {
            if (this.in != null) {
                this.in.close();
            }
        }
        finally {
            this.in = null;
            this.buffer = null;
        }
    }
    
    public char[] closeAndTakeBuffer() throws IOException {
        final char[] buffer = this.buffer;
        this.close();
        return buffer;
    }
    
    public boolean peekFor(final char consume) throws IOException {
        this.ensure();
        return this.buffer[this.bufferIndex] == consume;
    }
    
    public boolean tryConsume(final char consume) throws IOException {
        this.ensure();
        if (this.buffer[this.bufferIndex] == consume) {
            ++this.bufferIndex;
            if (consume == '\n') {
                ++this.line;
                this.lineStart = this.bufferIndex;
            }
            return true;
        }
        return false;
    }
    
    public boolean tryConsumeString(@Nonnull final String str) throws IOException {
        this.mark();
        if (this.tryConsume('\"') && this.tryConsume(str) && this.tryConsume('\"')) {
            this.unmark();
            return true;
        }
        this.reset();
        return false;
    }
    
    public boolean tryConsume(@Nonnull final String str) throws IOException {
        return this.tryConsume(str, 0);
    }
    
    public boolean tryConsume(@Nonnull final String str, int start) throws IOException {
        while (start < str.length()) {
            this.ensure();
            while (start < str.length() && this.bufferIndex < this.bufferSize) {
                final char c = this.buffer[this.bufferIndex];
                if (c != str.charAt(start++)) {
                    return false;
                }
                ++this.bufferIndex;
                if (c != '\n') {
                    continue;
                }
                ++this.line;
                this.lineStart = this.bufferIndex;
            }
        }
        return true;
    }
    
    public int tryConsumeSome(@Nonnull final String str, int start) throws IOException {
        while (start < str.length()) {
            this.ensure();
            while (start < str.length() && this.bufferIndex < this.bufferSize) {
                final char c = this.buffer[this.bufferIndex];
                if (c != str.charAt(start)) {
                    return start;
                }
                ++start;
                ++this.bufferIndex;
                if (c != '\n') {
                    continue;
                }
                ++this.line;
                this.lineStart = this.bufferIndex;
            }
        }
        return start;
    }
    
    public void expect(final char expect) throws IOException {
        this.ensure();
        final char read = this.buffer[this.bufferIndex++];
        if (read != expect) {
            throw this.expecting(read, expect);
        }
        if (expect == '\n') {
            ++this.line;
            this.lineStart = this.bufferIndex;
        }
    }
    
    public void expect(@Nonnull final String str, int start) throws IOException {
        this.ensure(str.length() - start);
        while (start < str.length()) {
            final char c = this.buffer[this.bufferIndex];
            if (c != str.charAt(start++)) {
                throw this.expecting(c, str, start);
            }
            ++this.bufferIndex;
            if (c != '\n') {
                continue;
            }
            ++this.line;
            this.lineStart = this.bufferIndex;
        }
    }
    
    public boolean tryConsumeOrExpect(final char consume, final char expect) throws IOException {
        this.ensure();
        final char read = this.buffer[this.bufferIndex];
        if (read == consume) {
            ++this.bufferIndex;
            if (consume == '\n') {
                ++this.line;
                this.lineStart = this.bufferIndex;
            }
            return true;
        }
        if (read == expect) {
            ++this.bufferIndex;
            if (expect == '\n') {
                ++this.line;
                this.lineStart = this.bufferIndex;
            }
            return false;
        }
        throw this.expecting(read, expect);
    }
    
    public void consumeWhiteSpace() throws IOException {
        while (true) {
            if (this.bufferIndex >= this.bufferSize) {
                this.fill();
                if (this.bufferIndex >= this.bufferSize) {
                    return;
                }
            }
            while (this.bufferIndex < this.bufferSize) {
                final char ch = this.buffer[this.bufferIndex];
                switch (ch) {
                    case '\n': {
                        ++this.bufferIndex;
                        ++this.line;
                        this.lineStart = this.bufferIndex;
                        continue;
                    }
                    case '\t':
                    case '\r':
                    case ' ': {
                        ++this.bufferIndex;
                        continue;
                    }
                    default: {
                        if (Character.isWhitespace(ch)) {
                            ++this.bufferIndex;
                            continue;
                        }
                    }
                }
            }
        }
    }
    
    public void consumeIgnoreCase(@Nonnull final String str, int start) throws IOException {
        this.ensure(str.length() - start);
        while (start < str.length()) {
            final char c = this.buffer[this.bufferIndex];
            if (!equalsIgnoreCase(c, str.charAt(start++))) {
                throw this.expecting(c, str, start);
            }
            ++this.bufferIndex;
            if (c != '\n') {
                continue;
            }
            ++this.line;
            this.lineStart = this.bufferIndex;
        }
    }
    
    @Nonnull
    public String readString() throws IOException {
        this.expect('\"');
        return this.readRemainingString();
    }
    
    @Nonnull
    public String readRemainingString() throws IOException {
        if (this.tempSb == null) {
            this.tempSb = new StringBuilder(1024);
        }
        while (true) {
            this.ensure();
            while (this.bufferIndex < this.bufferSize) {
                char read = this.buffer[this.bufferIndex++];
                switch (read) {
                    case '\"': {
                        final String string = this.tempSb.toString();
                        this.tempSb.setLength();
                        return string;
                    }
                    case '\\': {
                        this.ensure();
                        read = this.buffer[this.bufferIndex++];
                        switch (read) {
                            case '\"':
                            case '/':
                            case '\\': {
                                this.tempSb.append(read);
                                continue;
                            }
                            case 'b': {
                                this.tempSb.append('\b');
                                continue;
                            }
                            case 'f': {
                                this.tempSb.append('\f');
                                continue;
                            }
                            case 'n': {
                                this.tempSb.append('\n');
                                continue;
                            }
                            case 'r': {
                                this.tempSb.append('\r');
                                continue;
                            }
                            case 't': {
                                this.tempSb.append('\t');
                                continue;
                            }
                            case 'u': {
                                this.ensure(4);
                                read = this.buffer[this.bufferIndex++];
                                int digit = Character.digit(read, 16);
                                if (digit == -1) {
                                    throw this.expectingWhile(read, "HEX Digit 0-F", "reading string");
                                }
                                int hex = digit << 12;
                                read = this.buffer[this.bufferIndex++];
                                digit = Character.digit(read, 16);
                                if (digit == -1) {
                                    throw this.expectingWhile(read, "HEX Digit 0-F", "reading string");
                                }
                                hex |= digit << 8;
                                read = this.buffer[this.bufferIndex++];
                                digit = Character.digit(read, 16);
                                if (digit == -1) {
                                    throw this.expectingWhile(read, "HEX Digit 0-F", "reading string");
                                }
                                hex |= digit << 4;
                                read = this.buffer[this.bufferIndex++];
                                digit = Character.digit(read, 16);
                                if (digit == -1) {
                                    throw this.expectingWhile(read, "HEX Digit 0-F", "reading string");
                                }
                                hex |= digit;
                                this.tempSb.appendCodePoint(hex);
                                continue;
                            }
                            default: {
                                throw this.expecting(read, "escape char");
                            }
                        }
                        break;
                    }
                    default: {
                        if (Character.isISOControl(read)) {
                            throw this.unexpectedChar(read);
                        }
                        this.tempSb.append(read);
                        continue;
                    }
                }
            }
        }
    }
    
    public void skipString() throws IOException {
        this.expect('\"');
        this.skipRemainingString();
    }
    
    public void skipRemainingString() throws IOException {
        while (true) {
            this.ensure();
            while (this.bufferIndex < this.bufferSize) {
                char read = this.buffer[this.bufferIndex++];
                switch (read) {
                    case '\"': {
                        return;
                    }
                    case '\\': {
                        this.ensure();
                        read = this.buffer[this.bufferIndex++];
                        switch (read) {
                            case '\"':
                            case '/':
                            case '\\':
                            case 'b':
                            case 'f':
                            case 'n':
                            case 'r':
                            case 't': {
                                continue;
                            }
                            case 'u': {
                                this.ensure(4);
                                read = this.buffer[this.bufferIndex++];
                                int digit = Character.digit(read, 16);
                                if (digit == -1) {
                                    throw this.expectingWhile(read, "HEX Digit 0-F", "skipping string");
                                }
                                read = this.buffer[this.bufferIndex++];
                                digit = Character.digit(read, 16);
                                if (digit == -1) {
                                    throw this.expectingWhile(read, "HEX Digit 0-F", "skipping string");
                                }
                                read = this.buffer[this.bufferIndex++];
                                digit = Character.digit(read, 16);
                                if (digit == -1) {
                                    throw this.expectingWhile(read, "HEX Digit 0-F", "skipping string");
                                }
                                read = this.buffer[this.bufferIndex++];
                                digit = Character.digit(read, 16);
                                if (digit == -1) {
                                    throw this.expectingWhile(read, "HEX Digit 0-F", "skipping string");
                                }
                                continue;
                            }
                            default: {
                                throw this.expecting(read, "escape char");
                            }
                        }
                        break;
                    }
                    default: {
                        if (Character.isISOControl(read)) {
                            throw this.unexpectedChar(read);
                        }
                        continue;
                    }
                }
            }
        }
    }
    
    public long readStringPartAsLong(final int count) throws IOException {
        assert count > 0 && count <= 4;
        if (UnsafeUtil.UNSAFE != null && this.bufferIndex + count <= this.bufferSize) {
            return this.readStringPartAsLongUnsafe(count);
        }
        return this.readStringPartAsLongSlow(count);
    }
    
    protected long readStringPartAsLongSlow(final int count) throws IOException {
        this.ensure(count);
        final char c1 = this.buffer[this.bufferIndex++];
        if (count == 1) {
            return c1;
        }
        final char c2 = this.buffer[this.bufferIndex++];
        long value = (long)c1 | (long)c2 << 16;
        if (count == 2) {
            return value;
        }
        final char c3 = this.buffer[this.bufferIndex++];
        value |= (long)c3 << 32;
        if (count == 3) {
            return value;
        }
        final char c4 = this.buffer[this.bufferIndex++];
        return value | (long)c4 << 48;
    }
    
    protected long readStringPartAsLongUnsafe(final int count) throws IOException {
        this.ensure(count);
        final int offset = Unsafe.ARRAY_CHAR_BASE_OFFSET + Unsafe.ARRAY_CHAR_INDEX_SCALE * this.bufferIndex;
        final long value = UnsafeUtil.UNSAFE.getLong(this.buffer, offset);
        this.bufferIndex += count;
        final long mask = (count == 4) ? -1L : ((1L << count * 16) - 1L);
        return value & mask;
    }
    
    public boolean readBooleanValue() throws IOException {
        this.ensure(4);
        final char read = this.buffer[this.bufferIndex++];
        return switch (read) {
            case 'T',  't' -> {
                this.consumeIgnoreCase("true", 1);
                yield true;
            }
            case 'F',  'f' -> {
                this.consumeIgnoreCase("false", 1);
                yield false;
            }
            default -> throw this.expecting(read, "true' or 'false");
        };
    }
    
    public void skipBooleanValue() throws IOException {
        this.readBooleanValue();
    }
    
    @Nullable
    public Void readNullValue() throws IOException {
        this.consumeIgnoreCase("null", 0);
        return null;
    }
    
    public void skipNullValue() throws IOException {
        this.consumeIgnoreCase("null", 0);
    }
    
    public double readDoubleValue() throws IOException {
        final int start = this.bufferIndex;
        while (true) {
            if (this.bufferIndex >= this.bufferSize) {
                this.fill();
                if (this.bufferIndex >= this.bufferSize) {
                    return JavaDoubleParser.parseDouble(this.buffer, start, this.bufferIndex - start);
                }
            }
            while (this.bufferIndex < this.bufferSize) {
                final char read = this.buffer[this.bufferIndex];
                switch (read) {
                    case '+':
                    case '-':
                    case '.':
                    case 'E':
                    case 'e': {
                        ++this.bufferIndex;
                        continue;
                    }
                    default: {
                        if (Character.isDigit(read)) {
                            ++this.bufferIndex;
                            continue;
                        }
                        return JavaDoubleParser.parseDouble(this.buffer, start, this.bufferIndex - start);
                    }
                }
            }
        }
    }
    
    public void skipDoubleValue() throws IOException {
        while (true) {
            if (this.bufferIndex >= this.bufferSize) {
                this.fill();
                if (this.bufferIndex >= this.bufferSize) {
                    return;
                }
            }
            while (this.bufferIndex < this.bufferSize) {
                final char read = this.buffer[this.bufferIndex];
                switch (read) {
                    case '+':
                    case '-':
                    case '.':
                    case 'E':
                    case 'e': {
                        ++this.bufferIndex;
                        continue;
                    }
                    default: {
                        if (Character.isDigit(read)) {
                            ++this.bufferIndex;
                            continue;
                        }
                    }
                }
            }
        }
    }
    
    public float readFloatValue() throws IOException {
        return (float)this.readDoubleValue();
    }
    
    public void skipFloatValue() throws IOException {
        this.skipDoubleValue();
    }
    
    public long readLongValue() throws IOException {
        return this.readLongValue(10);
    }
    
    public long readLongValue(final int radix) throws IOException {
        if (this.tempSb == null) {
            this.tempSb = new StringBuilder(1024);
        }
        while (true) {
            if (this.bufferIndex >= this.bufferSize) {
                this.fill();
                if (this.bufferIndex >= this.bufferSize) {
                    final long value = Long.parseLong(this.tempSb, 0, this.tempSb.length(), radix);
                    this.tempSb.setLength();
                    return value;
                }
            }
            while (this.bufferIndex < this.bufferSize) {
                final char read = this.buffer[this.bufferIndex];
                switch (read) {
                    case '+':
                    case '-':
                    case '.':
                    case 'E':
                    case 'e': {
                        this.tempSb.append(read);
                        ++this.bufferIndex;
                        continue;
                    }
                    default: {
                        if (Character.digit(read, radix) >= 0) {
                            this.tempSb.append(read);
                            ++this.bufferIndex;
                            continue;
                        }
                        final long value2 = Long.parseLong(this.tempSb, 0, this.tempSb.length(), radix);
                        this.tempSb.setLength();
                        return value2;
                    }
                }
            }
        }
    }
    
    public void skipLongValue() throws IOException {
        this.skipLongValue(10);
    }
    
    public void skipLongValue(final int radix) throws IOException {
        while (true) {
            if (this.bufferIndex >= this.bufferSize) {
                this.fill();
                if (this.bufferIndex >= this.bufferSize) {
                    return;
                }
            }
            while (this.bufferIndex < this.bufferSize) {
                final char read = this.buffer[this.bufferIndex];
                switch (read) {
                    case '+':
                    case '-':
                    case '.':
                    case 'E':
                    case 'e': {
                        ++this.bufferIndex;
                        continue;
                    }
                    default: {
                        if (Character.digit(read, radix) >= 0) {
                            ++this.bufferIndex;
                            continue;
                        }
                    }
                }
            }
        }
    }
    
    public int readIntValue() throws IOException {
        return this.readIntValue(10);
    }
    
    public int readIntValue(final int radix) throws IOException {
        return (int)this.readLongValue(radix);
    }
    
    public byte readByteValue() throws IOException {
        return this.readByteValue(10);
    }
    
    public byte readByteValue(final int radix) throws IOException {
        return (byte)this.readLongValue(radix);
    }
    
    public void skipIntValue() throws IOException {
        this.skipLongValue();
    }
    
    public void skipIntValue(final int radix) throws IOException {
        this.skipLongValue(radix);
    }
    
    public void skipObject() throws IOException {
        this.expect('{');
        this.skipObjectContinued();
    }
    
    public void skipObjectContinued() throws IOException {
        int count = 1;
    Block_2:
        while (true) {
            this.ensure();
            while (this.bufferIndex < this.bufferSize) {
                final char read = this.buffer[this.bufferIndex++];
                switch (read) {
                    case '\n': {
                        ++this.line;
                        this.lineStart = this.bufferIndex;
                        continue;
                    }
                    case '{': {
                        ++count;
                        continue;
                    }
                    case '}': {
                        if (--count == 0) {
                            break Block_2;
                        }
                        continue;
                    }
                }
            }
        }
    }
    
    public void skipArray() throws IOException {
        this.expect('[');
        this.skipArrayContinued();
    }
    
    public void skipArrayContinued() throws IOException {
        int count = 1;
    Block_2:
        while (true) {
            this.ensure();
            while (this.bufferIndex < this.bufferSize) {
                final char read = this.buffer[this.bufferIndex++];
                switch (read) {
                    case '\n': {
                        ++this.line;
                        this.lineStart = this.bufferIndex;
                        continue;
                    }
                    case '[': {
                        ++count;
                        continue;
                    }
                    case ']': {
                        if (--count == 0) {
                            break Block_2;
                        }
                        continue;
                    }
                }
            }
        }
    }
    
    public void skipValue() throws IOException {
        this.ensure();
        final char read = this.buffer[this.bufferIndex];
        switch (read) {
            case '\"': {
                this.skipString();
                break;
            }
            case 'N':
            case 'n': {
                this.skipNullValue();
                break;
            }
            case 'T':
            case 't': {
                this.consumeIgnoreCase("true", 0);
                break;
            }
            case 'F':
            case 'f': {
                this.consumeIgnoreCase("false", 0);
                break;
            }
            case '{': {
                this.skipObject();
                break;
            }
            case '[': {
                this.skipArray();
                break;
            }
            case '+':
            case '-': {
                this.skipDoubleValue();
                break;
            }
            default: {
                if (Character.isDigit(read)) {
                    this.skipDoubleValue();
                    break;
                }
                throw this.unexpectedChar(read);
            }
        }
    }
    
    @Nonnull
    private IOException unexpectedEOF() {
        return new IOException("Unexpected EOF!");
    }
    
    @Nonnull
    private IOException unexpectedChar(final char read) {
        return new IOException("Unexpected character: " + Integer.toHexString(read) + ", '" + read + "'!");
    }
    
    @Nonnull
    private IOException expecting(final char read, final char expect) {
        return new IOException("Unexpected character: " + Integer.toHexString(read) + ", '" + read + "' expected '" + expect + "'!");
    }
    
    @Nonnull
    private IOException expecting(final char read, final String expected) {
        return new IOException("Unexpected character: " + Integer.toHexString(read) + ", '" + read + "' expected '" + expected + "'!");
    }
    
    @Nonnull
    private IOException expectingWhile(final char read, final String expected, final String reason) {
        return new IOException("Unexpected character: " + Integer.toHexString(read) + ", '" + read + "' expected '" + expected + "' while " + reason);
    }
    
    @Nonnull
    private IOException expecting(final char read, @Nonnull final String expected, final int index) {
        return new IOException("Unexpected character: " + Integer.toHexString(read) + ", '" + read + "' when consuming string '" + expected + "' expected '" + expected.substring(index - 1) + "'!");
    }
    
    @Nonnull
    @Override
    public String toString() {
        if (this.buffer == null) {
            return "Closed RawJsonReader";
        }
        final StringBuilder s = new StringBuilder("Index: ").append(this.streamIndex + this.bufferIndex).append(", StreamIndex: ").append(this.streamIndex).append(", BufferIndex: ").append(this.bufferIndex).append(", BufferSize: ").append(this.bufferSize).append(", Line: ").append(this.line).append(", MarkIndex: ").append(this.markIndex).append(", MarkLine: ").append(this.markLine).append('\n');
        int lineStart;
        int lineNumber;
        for (lineStart = this.findLineStart(this.bufferIndex), lineNumber = this.line; lineStart > 0 && lineNumber > this.line - 10; lineStart = this.findLineStart(lineStart), --lineNumber) {}
        while (lineNumber < this.line) {
            lineStart = this.appendLine(s, lineStart, lineNumber);
            ++lineNumber;
        }
        for (lineStart = this.appendProblemLine(s, lineStart, this.line); lineStart < this.bufferSize && lineNumber < this.line + 10; lineStart = this.appendLine(s, lineStart, lineNumber), ++lineNumber) {}
        if (this.in == null) {
            return "Buffer RawJsonReader: " + String.valueOf(s);
        }
        return "Streamed RawJsonReader: " + String.valueOf(s);
    }
    
    private int findLineStart(int index) {
        --index;
        while (index > 0 && this.buffer[index] != '\n') {
            --index;
        }
        return index;
    }
    
    private int appendLine(@Nonnull final StringBuilder sb, int index, final int lineNumber) {
        final int lineStart = index + 1;
        ++index;
        while (index < this.bufferSize && this.buffer[index] != '\n') {
            ++index;
        }
        sb.append("L").append(String.format("%3s", lineNumber)).append('|').append(this.buffer, lineStart, index - lineStart).append('\n');
        return index;
    }
    
    private int appendProblemLine(@Nonnull final StringBuilder sb, int index, final int lineNumber) {
        int lineStart;
        for (lineStart = ++index; index < this.bufferSize && this.buffer[index] != '\n'; ++index) {}
        sb.append("L").append(String.format("%3s", lineNumber)).append('>').append(this.buffer, lineStart, index - lineStart).append('\n');
        sb.append("    |");
        sb.append("-".repeat(Math.max(0, this.bufferIndex - lineStart - 1)));
        sb.append('^').append('\n');
        return index;
    }
    
    @Nonnull
    public static RawJsonReader fromRawString(final String str) {
        return fromJsonString("\"" + str);
    }
    
    @Nonnull
    public static RawJsonReader fromJsonString(@Nonnull final String str) {
        return fromBuffer(str.toCharArray());
    }
    
    @Nonnull
    public static RawJsonReader fromPath(@Nonnull final Path path, @Nonnull final char[] buffer) throws IOException {
        return new RawJsonReader(new InputStreamReader(Files.newInputStream(path, new OpenOption[0]), StandardCharsets.UTF_8), buffer);
    }
    
    @Nonnull
    public static RawJsonReader fromBuffer(@Nonnull final char[] buffer) {
        return new RawJsonReader(buffer);
    }
    
    public static boolean equalsIgnoreCase(final char c1, final char c2) {
        if (c1 == c2) {
            return true;
        }
        final char u1 = Character.toUpperCase(c1);
        final char u2 = Character.toUpperCase(c2);
        return u1 == u2 || Character.toLowerCase(u1) == Character.toLowerCase(u2);
    }
    
    @Deprecated
    public static BsonDocument readBsonDocument(@Nonnull final RawJsonReader reader) throws IOException {
        reader.expect('{');
        final StringBuilder sb = new StringBuilder("{");
        readBsonDocument0(reader, sb);
        return BsonDocument.parse(sb.toString());
    }
    
    private static void readBsonDocument0(@Nonnull final RawJsonReader reader, @Nonnull final StringBuilder sb) throws IOException {
        int count = 1;
        int read;
        while ((read = reader.read()) != -1) {
            sb.append((char)read);
            switch (read) {
                case 123: {
                    ++count;
                    continue;
                }
                case 125: {
                    if (--count == 0) {
                        return;
                    }
                    continue;
                }
                case 91: {
                    readBsonArray0(reader, sb);
                    continue;
                }
                case 10: {
                    ++reader.line;
                    reader.lineStart = reader.bufferIndex;
                    continue;
                }
            }
        }
        throw reader.unexpectedEOF();
    }
    
    @Deprecated
    public static BsonArray readBsonArray(@Nonnull final RawJsonReader reader) throws IOException {
        reader.expect('[');
        final StringBuilder sb = new StringBuilder("[");
        readBsonArray0(reader, sb);
        return BsonArray.parse(sb.toString());
    }
    
    private static void readBsonArray0(@Nonnull final RawJsonReader reader, @Nonnull final StringBuilder sb) throws IOException {
        int count = 1;
        int read;
        while ((read = reader.read()) != -1) {
            sb.append((char)read);
            switch (read) {
                case 91: {
                    ++count;
                    continue;
                }
                case 93: {
                    if (--count == 0) {
                        return;
                    }
                    continue;
                }
                case 123: {
                    readBsonDocument0(reader, sb);
                    continue;
                }
                case 10: {
                    ++reader.line;
                    reader.lineStart = reader.bufferIndex;
                    continue;
                }
            }
        }
        throw reader.unexpectedEOF();
    }
    
    @Deprecated
    public static BsonValue readBsonValue(@Nonnull final RawJsonReader reader) throws IOException {
        final int read = reader.peek();
        if (read == -1) {
            throw reader.unexpectedEOF();
        }
        return switch (read) {
            case 34 -> new BsonString(reader.readString());
            case 78,  110 -> {
                reader.skipNullValue();
                yield BsonNull.VALUE;
            }
            case 70,  84,  102,  116 -> reader.readBooleanValue() ? BsonBoolean.TRUE : BsonBoolean.FALSE;
            case 123 -> readBsonDocument(reader);
            case 91 -> readBsonArray(reader);
            case 43,  45 -> new BsonDouble(reader.readDoubleValue());
            default -> {
                if (Character.isDigit(read)) {
                    yield new BsonDouble(reader.readDoubleValue());
                }
                throw reader.unexpectedChar((char)read);
            }
        };
    }
    
    public static boolean seekToKey(@Nonnull final RawJsonReader reader, @Nonnull final String search) throws IOException {
        reader.consumeWhiteSpace();
        reader.expect('{');
        reader.consumeWhiteSpace();
        if (reader.tryConsume('}')) {
            return false;
        }
        while (true) {
            reader.expect('\"');
            if (reader.tryConsume(search) && reader.tryConsume('\"')) {
                reader.consumeWhiteSpace();
                reader.expect(':');
                reader.consumeWhiteSpace();
                return true;
            }
            reader.skipRemainingString();
            reader.consumeWhiteSpace();
            reader.expect(':');
            reader.consumeWhiteSpace();
            reader.skipValue();
            reader.consumeWhiteSpace();
            if (reader.tryConsumeOrExpect('}', ',')) {
                return false;
            }
            reader.consumeWhiteSpace();
        }
    }
    
    @Nullable
    public static String seekToKeyFromObjectStart(@Nonnull final RawJsonReader reader, @Nonnull final String search1, @Nonnull final String search2) throws IOException {
        reader.consumeWhiteSpace();
        reader.expect('{');
        reader.consumeWhiteSpace();
        if (reader.tryConsume('}')) {
            return null;
        }
        while (true) {
            reader.expect('\"');
            final int search1Index = reader.tryConsumeSome(search1, 0);
            if (search1Index == search1.length() && reader.tryConsume('\"')) {
                reader.consumeWhiteSpace();
                reader.expect(':');
                reader.consumeWhiteSpace();
                return search1;
            }
            if (reader.tryConsume(search2, search1Index) && reader.tryConsume('\"')) {
                reader.consumeWhiteSpace();
                reader.expect(':');
                reader.consumeWhiteSpace();
                return search2;
            }
            reader.skipRemainingString();
            reader.consumeWhiteSpace();
            reader.expect(':');
            reader.consumeWhiteSpace();
            reader.skipValue();
            reader.consumeWhiteSpace();
            if (reader.tryConsumeOrExpect('}', ',')) {
                return null;
            }
            reader.consumeWhiteSpace();
        }
    }
    
    @Nullable
    public static String seekToKeyFromObjectContinued(@Nonnull final RawJsonReader reader, @Nonnull final String search1, @Nonnull final String search2) throws IOException {
        reader.consumeWhiteSpace();
        if (reader.tryConsumeOrExpect('}', ',')) {
            return null;
        }
        reader.consumeWhiteSpace();
        while (true) {
            reader.expect('\"');
            final int search1Index = reader.tryConsumeSome(search1, 0);
            if (search1Index == search1.length() && reader.tryConsume('\"')) {
                reader.consumeWhiteSpace();
                reader.expect(':');
                reader.consumeWhiteSpace();
                return search1;
            }
            if (reader.tryConsume(search2, search1Index) && reader.tryConsume('\"')) {
                reader.consumeWhiteSpace();
                reader.expect(':');
                reader.consumeWhiteSpace();
                return search2;
            }
            reader.skipRemainingString();
            reader.consumeWhiteSpace();
            reader.expect(':');
            reader.consumeWhiteSpace();
            reader.skipValue();
            reader.consumeWhiteSpace();
            if (reader.tryConsumeOrExpect('}', ',')) {
                return null;
            }
            reader.consumeWhiteSpace();
        }
    }
    
    public static void validateBsonDocument(@Nonnull final RawJsonReader reader) throws IOException {
        reader.expect('{');
        reader.consumeWhiteSpace();
        if (reader.tryConsume('}')) {
            return;
        }
        while (true) {
            reader.skipString();
            reader.consumeWhiteSpace();
            reader.expect(':');
            reader.consumeWhiteSpace();
            validateBsonValue(reader);
            reader.consumeWhiteSpace();
            if (reader.tryConsumeOrExpect('}', ',')) {
                break;
            }
            reader.consumeWhiteSpace();
        }
    }
    
    public static void validateBsonArray(@Nonnull final RawJsonReader reader) throws IOException {
        reader.expect('[');
        reader.consumeWhiteSpace();
        if (reader.tryConsume(']')) {
            return;
        }
        while (true) {
            validateBsonValue(reader);
            reader.consumeWhiteSpace();
            if (reader.tryConsumeOrExpect(']', ',')) {
                break;
            }
            reader.consumeWhiteSpace();
        }
    }
    
    public static void validateBsonValue(@Nonnull final RawJsonReader reader) throws IOException {
        final int read = reader.peek();
        if (read == -1) {
            throw reader.unexpectedEOF();
        }
        switch (read) {
            case 34: {
                reader.skipString();
                break;
            }
            case 78:
            case 110: {
                reader.skipNullValue();
                break;
            }
            case 70:
            case 84:
            case 102:
            case 116: {
                reader.readBooleanValue();
                break;
            }
            case 123: {
                validateBsonDocument(reader);
                break;
            }
            case 91: {
                validateBsonArray(reader);
                break;
            }
            case 43:
            case 45: {
                reader.readDoubleValue();
                break;
            }
            default: {
                if (Character.isDigit(read)) {
                    reader.readDoubleValue();
                    return;
                }
                throw reader.unexpectedChar((char)read);
            }
        }
    }
    
    @Nullable
    public static <T> T readSync(@Nonnull final Path path, @Nonnull final Codec<T> codec, @Nonnull final HytaleLogger logger) throws IOException {
        final char[] buffer = RawJsonReader.READ_BUFFER.get();
        final RawJsonReader reader = fromPath(path, buffer);
        try {
            final ExtraInfo extraInfo = ExtraInfo.THREAD_LOCAL.get();
            final T value = codec.decodeJson(reader, extraInfo);
            extraInfo.getValidationResults().logOrThrowValidatorExceptions(logger);
            return value;
        }
        finally {
            final char[] newBuffer = reader.closeAndTakeBuffer();
            if (newBuffer.length > buffer.length) {
                RawJsonReader.READ_BUFFER.set(newBuffer);
            }
        }
    }
    
    @Nullable
    public static <T> T readSyncWithBak(@Nonnull final Path path, @Nonnull final Codec<T> codec, @Nonnull final HytaleLogger logger) {
        try {
            return (T)readSync(path, (Codec<Object>)codec, logger);
        }
        catch (final IOException e) {
            final Path backupPath = path.resolveSibling(String.valueOf(path.getFileName()) + ".bak");
            if (e instanceof NoSuchFileException && !Files.exists(backupPath, new LinkOption[0])) {
                return null;
            }
            if (Sentry.isEnabled()) {
                Sentry.captureException(e);
            }
            logger.at(Level.SEVERE).withCause(e).log("Failed to load from primary file %s, trying backup file", path);
            try {
                final T value = (T)readSync(backupPath, (Codec<Object>)codec, logger);
                logger.at(Level.WARNING).log("Loaded from backup file %s after primary file %s failed to load", backupPath, path);
                return value;
            }
            catch (final NoSuchFileException e2) {
                return null;
            }
            catch (final IOException e3) {
                logger.at(Level.WARNING).withCause(e).log("Failed to load from both %s and backup file %s", path, backupPath);
                return null;
            }
        }
    }
    
    static {
        READ_BUFFER = ThreadLocal.withInitial(() -> new char[131072]);
    }
}
