/* jshint esversion: 6 */

var json = json || {};

json.TextReader = class {

    constructor(buffer) {
        this._buffer = buffer;
    }

    static create(buffer) {
        return new json.TextReader(buffer);
    }

    read() {
        const decoder = json.TextDecoder.create(this._buffer);
        const stack = [];
        this._decoder = decoder;
        this._escape = { '"': '"', '\\': '\\', '/': '/', b: '\b', f: '\f', n: '\n', r: '\r', t: '\t' };
        this._position = 0;
        this._char = decoder.decode();
        this._whitespace();
        let obj = undefined;
        let first = true;
        for (;;) {
            if (Array.isArray(obj)) {
                this._whitespace();
                let c = this._char;
                if (c === ']') {
                    this._next();
                    this._whitespace();
                    if (stack.length > 0) {
                        obj = stack.pop();
                        first = false;
                        continue;
                    }
                    if (this._char !== undefined) {
                        this._unexpected();
                    }
                    return obj;
                }
                if (!first) {
                    if (this._char !== ',') {
                        this._unexpected();
                    }
                    this._next();
                    this._whitespace();
                    c = this._char;
                }
                first = false;
                switch (c) {
                    case '{': {
                        this._next();
                        stack.push(obj);
                        const item = {};
                        obj.push(item);
                        obj = item;
                        first = true;
                        break;
                    }
                    case '[': {
                        this._next();
                        stack.push(obj);
                        const item = [];
                        obj.push(item);
                        obj = item;
                        first = true;
                        break;
                    }
                    default: {
                        obj.push(c === '"' ? this._string() : this._literal());
                        break;
                    }
                }
            }
            else if (obj instanceof Object) {
                this._whitespace();
                let c = this._char;
                if (c === '}') {
                    this._next();
                    this._whitespace();
                    if (stack.length > 0) {
                        obj = stack.pop();
                        first = false;
                        continue;
                    }
                    if (this._char !== undefined) {
                        this._unexpected();
                    }
                    return obj;
                }
                if (!first) {
                    if (this._char !== ',') {
                        this._unexpected();
                    }
                    this._next();
                    this._whitespace();
                    c = this._char;
                }
                first = false;
                if (c === '"') {
                    const key = this._string();
                    this._whitespace();
                    if (this._char !== ':') {
                        this._unexpected();
                    }
                    this._next();
                    this._whitespace();
                    c = this._char;
                    switch (c) {
                        case '{': {
                            this._next();
                            stack.push(obj);
                            const value = {};
                            obj[key] = value;
                            obj = value;
                            first = true;
                            break;
                        }
                        case '[': {
                            this._next();
                            stack.push(obj);
                            const value = [];
                            obj[key] = value;
                            obj = value;
                            first = true;
                            break;
                        }
                        default: {
                            obj[key] = c === '"' ? this._string() : this._literal();
                            break;
                        }
                    }
                    this._whitespace();
                    continue;
                }
                this._unexpected();
            }
            else {
                const c = this._char;
                switch (c) {
                    case '{': {
                        this._next();
                        obj = {};
                        first = true;
                        break;
                    }
                    case '[': {
                        this._next();
                        obj = [];
                        first = true;
                        break;
                    }
                    default: {
                        const value = c === '"' ? this._string() : c >= '0' && c <= '9' ? this._number() : this._literal();
                        if (this._char !== undefined) {
                            this._unexpected();
                        }
                        return value;
                    }
                }
                this._whitespace();
            }
        }
    }

    _next() {
        if (this._char === undefined) {
            this._unexpected();
        }
        this._position = this._decoder.position;
        this._char = this._decoder.decode();
    }

    _whitespace() {
        while (this._char === ' ' || this._char === '\n' || this._char === '\r' || this._char === '\t') {
            this._next();
        }
    }

    _literal() {
        const c = this._char;
        if (c >= '0' && c <= '9') {
            return this._number();
        }
        switch (c) {
            case 't': this._expect('true'); return true;
            case 'f': this._expect('false'); return false;
            case 'n': this._expect('null'); return null;
            case 'N': this._expect('NaN'); return NaN;
            case 'I': this._expect('Infinity'); return Infinity;
            case '-': return this._number();
        }
        this._unexpected();
    }

    _number() {
        let value = '';
        if (this._char === '-') {
            value = '-';
            this._next();
        }
        if (this._char === 'I') {
            this._expect('Infinity');
            return -Infinity;
        }
        const c = this._char;
        if (c < '0' || c > '9') {
            this._unexpected();
        }
        value += c;
        this._next();
        if (c === '0') {
            const n = this._char;
            if (n >= '0' && n <= '9') {
                this._unexpected();
            }
        }
        while (this._char >= '0' && this._char <= '9') {
            value += this._char;
            this._next();
        }
        if (this._char === '.') {
            value += '.';
            this._next();
            const n = this._char;
            if (n < '0' || n > '9') {
                this._unexpected();
            }
            while (this._char >= '0' && this._char <= '9') {
                value += this._char;
                this._next();
            }
        }
        if (this._char === 'e' || this._char === 'E') {
            value += this._char;
            this._next();
            const s = this._char;
            if (s === '-' || s === '+') {
                value += this._char;
                this._next();
            }
            const c = this._char;
            if (c < '0' || c > '9') {
                this._unexpected();
            }
            value += this._char;
            this._next();
            while (this._char >= '0' && this._char <= '9') {
                value += this._char;
                this._next();
            }
        }
        return +value;
    }

    _string() {
        let value = '';
        this._next();
        while (this._char != '"') {
            if (this._char === '\\') {
                this._next();
                if (this._char === 'u') {
                    this._next();
                    let uffff = 0;
                    for (let i = 0; i < 4; i ++) {
                        const hex = parseInt(this._char, 16);
                        if (!isFinite(hex)) {
                            this._unexpected();
                        }
                        this._next();
                        uffff = uffff * 16 + hex;
                    }
                    value += String.fromCharCode(uffff);
                }
                else if (this._escape[this._char]) {
                    value += this._escape[this._char];
                    this._next();
                }
                else {
                    this._unexpected();
                }
            }
            else if (this._char < ' ') {
                this._unexpected();
            }
            else {
                value += this._char;
                this._next();
            }
        }
        this._next();
        return value;
    }

    _expect(text) {
        for (let i = 0; i < text.length; i++) {
            if (text[i] !== this._char) {
                this._unexpected();
            }
            this._next();
        }
    }

    _unexpected() {
        let c = this._char;
        if (c === undefined) {
            throw new json.Error('Unexpected end of JSON input.');
        }
        else if (c === '"') {
            c = 'string';
        }
        else if ((c >= '0' && c <= '9') || c === '-') {
            c = 'number';
        }
        else {
            if (c < ' ' || c > '\x7F') {
                const name = Object.keys(this._escape).filter((key) => this._escape[key] === c);
                c = (name.length === 1) ? '\\' + name : '\\u' + ('000' + c.charCodeAt(0).toString(16)).slice(-4);
            }
            c = "token '" + c + "'";
        }
        throw new json.Error('Unexpected ' + c + this._location());
    }

    _location() {
        let line = 1;
        let column = 1;
        this._decoder.position = 0;
        let c;
        do {
            if (this._decoder.position === this.position) {
                return ' at ' + line.toString() + ':' + column.toString() + '.';
            }
            c = this._decoder.decode();
            if (c === '\n') {
                line++;
                column = 0;
            }
            else {
                column++;
            }
        }
        while (c !== undefined);
        return ' at ' + line.toString() + ':' + column.toString() + '.';
    }
};

json.TextDecoder = class {

    static create(buffer) {
        const length = buffer.length;
        if (typeof buffer === 'string') {
            return new json.TextDecoder.String(buffer);
        }
        if (length >= 3 && buffer[0] === 0xef && buffer[1] === 0xbb && buffer[2] === 0xbf) {
            return new json.TextDecoder.Utf8(buffer, 3);
        }
        if (length >= 2 && buffer[0] === 0xff && buffer[1] === 0xfe) {
            return new json.TextDecoder.Utf16LE(buffer, 2);
        }
        if (length >= 2 && buffer[0] === 0xfe && buffer[1] === 0xff) {
            return new json.TextDecoder.Utf16BE(buffer, 2);
        }
        if (length >= 4 && buffer[0] === 0x00 && buffer[1] === 0x00 && buffer[2] === 0xfe && buffer[3] === 0xff) {
            throw new json.Error("Unsupported UTF-32 big-endian encoding.");
        }
        if (length >= 4 && buffer[0] === 0xff && buffer[1] === 0xfe && buffer[2] === 0x00 && buffer[3] === 0x00) {
            throw new json.Error("Unsupported UTF-32 little-endian encoding.");
        }
        if (length >= 5 && buffer[0] === 0x2B && buffer[1] === 0x2F && buffer[2] === 0x76 && buffer[3] === 0x38 && buffer[4] === 0x2D) {
            throw new json.Error("Unsupported UTF-7 encoding.");
        }
        if (length >= 4 && buffer[0] === 0x2B && buffer[1] === 0x2F && buffer[2] === 0x76 && (buffer[3] === 0x38 || buffer[3] === 0x39 || buffer[3] === 0x2B || buffer[3] === 0x2F)) {
            throw new json.Error("Unsupported UTF-7 encoding.");
        }
        if (length >= 4 && buffer[0] === 0x84 && buffer[1] === 0x31 && buffer[2] === 0x95 && buffer[3] === 0x33) {
            throw new json.Error("Unsupported GB-18030 encoding.");
        }
        if (length > 4 && (length % 2) == 0 && (buffer[0] === 0x00 || buffer[1] === 0x00 || buffer[2] === 0x00 || buffer[3] === 0x00)) {
            const lo = new Uint32Array(256);
            const hi = new Uint32Array(256);
            for (let i = 0; i < length; i += 2) {
                lo[buffer[i]]++;
                hi[buffer[i + 1]]++;
            }
            if (lo[0x00] === 0 && (hi[0x00] / (length >> 1)) > 0.5) {
                return new json.TextDecoder.Utf16LE(buffer, 0);
            }
            if (hi[0x00] === 0 && (lo[0x00] / (length >> 1)) > 0.5) {
                return new json.TextDecoder.Utf16BE(buffer, 0);
            }
        }
        return new json.TextDecoder.Utf8(buffer, 0);
    }
};

json.TextDecoder.String = class {

    constructor(buffer) {
        this.buffer = buffer;
        this.position = 0;
        this.length = buffer.length;
    }

    decode() {
        return this.position < this.length ? this.buffer[this.position++] : undefined;
    }
};

json.TextDecoder.Utf8 = class {

    constructor(buffer, position) {
        this.position = position || 0;
        this.buffer = buffer;
    }

    decode() {
        const c = this.buffer[this.position];
        if (c === undefined) {
            return c;
        }
        this.position++;
        if (c < 0x80) {
            return String.fromCodePoint(c);
        }
        if (c >= 0xC2 && c <= 0xDF) {
            if (this.buffer[this.position] !== undefined) {
                const c2 = this.buffer[this.position];
                this.position++;
                return String.fromCharCode(((c & 0x1F) << 6) | (c2 & 0x3F));
            }
        }
        if (c >= 0xE0 && c <= 0xEF) {
            if (this.buffer[this.position + 1] !== undefined) {
                const c2 = this.buffer[this.position];
                if ((c !== 0xE0 || c2 >= 0xA0) && (c !== 0xED || c2 <= 0x9f)) {
                    const c3 = this.buffer[this.position + 1];
                    if (c3 >= 0x80 && c3 < 0xFB) {
                        this.position += 2;
                        return String.fromCharCode(((c & 0x0F) << 12) | ((c2 & 0x3F) << 6) | ((c3 & 0x3F) << 0));
                    }
                }
            }
        }
        if (c >= 0xF0 && c <= 0xF4) {
            if (this.buffer[this.position + 2] !== undefined) {
                const c2 = this.buffer[this.position];
                if ((c !== 0xF0 || c2 >= 0x90) && (c !== 0xF4 || c2 <= 0x8f)) {
                    const c3 = this.buffer[this.position + 1];
                    if (c3 >= 0x80 && c3 < 0xFB) {
                        const c4 = this.buffer[this.position + 2];
                        this.position += 3;
                        return String.fromCodePoint(((c & 0x07) << 18) | ((c2 & 0x3F) << 12) | ((c3 & 0x3F) << 6) | (c4 & 0x3F));
                    }
                }
            }
        }
        return String.fromCharCode(0xfffd);
    }
};

json.TextDecoder.Utf16LE = class {

    constructor(buffer, position) {
        this.buffer = buffer;
        this.position = position || 0;
        this.length = buffer.length;
    }

    decode() {
        if (this.position + 1 < this.length) {
            const c = this.buffer[this.position++] | (this.buffer[this.position++] << 8);
            if ((c & 0xF800) !== 0xD800) {
                return String.fromCharCode(c);
            }
            if ((c & 0xFC00) !== 0xD800) {
                if (this._position + 1 < this._length) {
                    const c2 = this._buffer[this._position++] | (this._buffer[this._position++] << 8);
                    if ((c2 & 0xFC00) === 0xDC00) {
                        return String.fromCodePoint(((c & 0x3ff) << 10) + (c2 & 0x3ff) + 0x10000);
                    }
                }
            }
            return String.fromCharCode(0xfffd);
        }
        return undefined;
    }
};

json.TextDecoder.Utf16BE = class {

    constructor(buffer, position) {
        this.buffer = buffer;
        this.position = position || 0;
        this.length = buffer.length;
    }

    decode() {
        if (this.position + 1 < this.length) {
            const c = (this.buffer[this.position++] << 8) | this.buffer[this.position++];
            if ((c & 0xF800) !== 0xD800) {
                return String.fromCharCode(c);
            }
            if ((c & 0xFC00) !== 0xD800) {
                if (this._position + 1 < this._length) {
                    const c2 = (this._buffer[this._position++] << 8) | this._buffer[this._position++];
                    if ((c2 & 0xFC00) === 0xDC00) {
                        return String.fromCodePoint(((c & 0x3ff) << 10) + (c2 & 0x3ff) + 0x10000);
                    }
                }
            }
            return String.fromCharCode(0xfffd);
        }
        return undefined;
    }
};

json.Error = class extends Error {

    constructor(message) {
        super(message);
        this.name = 'JSON Error';
    }
};

if (typeof module !== 'undefined' && typeof module.exports === 'object') {
    module.exports.TextReader = json.TextReader;
    module.exports.TextDecoder = json.TextDecoder;
}