"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
Object.defineProperty(exports, "__esModule", { value: true });
const NbtBinaryTag_1 = require("./NbtBinaryTag");
const Utilities_1 = require("./../core/Utilities");
const Log_1 = require("../core/Log");
const DataUtilities_1 = require("../core/DataUtilities");
class NbtBinary {
    roots = null;
    context;
    isInErrorState;
    errorMessages;
    get singleRoot() {
        if (this.roots === null) {
            return null;
        }
        if (this.roots.length === 0) {
            return null;
        }
        if (this.roots.length !== 1) {
            this._pushError("Unexpectedly did not find a single root (" + this.roots.length + ")");
        }
        return this.roots[0];
    }
    _pushError(message, contextIn) {
        this.isInErrorState = true;
        if (this.errorMessages === undefined) {
            this.errorMessages = [];
        }
        let contextOut = undefined;
        if (contextIn) {
            contextOut = this.context ? this.context + "-" + contextIn : contextIn;
        }
        else {
            contextOut = this.context;
        }
        Log_1.default.error(message + (contextOut ? " " + contextOut : ""));
        this.errorMessages.push({
            message: message,
            context: contextOut,
        });
    }
    _getVarInt(data, index) {
        let bytesRead = 0;
        let result = 0;
        let currentByte = 0;
        do {
            currentByte = data[index];
            const currentByteVal = currentByte & 0b01111111;
            result |= currentByteVal << (7 * bytesRead);
            bytesRead++;
            if (bytesRead > 5) {
                throw new Error("VarInt is unexpectedly large");
            }
        } while ((currentByte & 0b10000000) !== 0);
        return {
            value: result,
            bytesRead: bytesRead,
        };
    }
    getJsonString() {
        return JSON.stringify(this.getJson());
    }
    getJson() {
        if (this.roots === null) {
            return {};
        }
        if (this.roots.length === 1) {
            return this.roots[0].getJson();
        }
        throw new Error("Unexpected multiple roots.");
    }
    toBinary() {
        if (this.roots === undefined || this.roots === null) {
            return undefined;
        }
        let byteSize = 0;
        for (let i = 0; i < this.roots.length; i++) {
            byteSize += this.roots[i].getByteSize();
        }
        const ab = new ArrayBuffer(byteSize);
        const bytes = new Uint8Array(ab);
        let bytesWrittenAll = 0;
        for (let i = 0; i < this.roots.length; i++) {
            bytesWrittenAll += this.roots[i].writeBytes(bytes, bytesWrittenAll, true);
        }
        Log_1.default.assert(bytesWrittenAll === byteSize, "Unexpectedly did not write full NBT.");
        return bytes;
    }
    ensureSingleRoot() {
        if (this.roots) {
            if (this.roots.length !== 1) {
                throw new Error("Unexpected root count.");
            }
            return this.roots[0];
        }
        this.roots = [];
        this.roots.push(new NbtBinaryTag_1.default(NbtBinaryTag_1.NbtTagType.compound, "", false));
        return this.roots[0];
    }
    fromBinary(data, littleEndian, isVarint, skipBytes, stringsAreASCII, processAsList) {
        const tagStack = [];
        const listCountStack = [];
        const listTypeStack = [];
        this.roots = [];
        let i = 0;
        if (skipBytes !== undefined) {
            i = skipBytes;
        }
        if (!stringsAreASCII) {
            stringsAreASCII = false;
        }
        while (i < data.length) {
            let tagType = NbtBinaryTag_1.NbtTagType.unknown;
            let name = "";
            let isListChild = false;
            if (tagStack.length === 0 || tagStack[tagStack.length - 1].type !== NbtBinaryTag_1.NbtTagType.list) {
                tagType = data[i++];
                if (tagType > 13 && tagType !== 99) {
                    this._pushError("Unexpected NBT tag type: " + tagType);
                }
                if (tagType !== NbtBinaryTag_1.NbtTagType.end) {
                    let nameLength = 0;
                    if (isVarint) {
                        const result = this._getVarInt(data, i);
                        nameLength = result.value;
                        i += result.bytesRead;
                    }
                    else {
                        nameLength = data[i++];
                        nameLength += data[i++] * 256;
                    }
                    for (let j = 0; j < nameLength; j++) {
                        name += String.fromCharCode(data[i++]);
                    }
                }
                else if (tagStack.length === 0) {
                    break;
                }
            } // we're in list sub tag parsing mode
            else {
                tagType = listTypeStack[tagStack.length - 1];
                isListChild = true;
            }
            const activeTag = new NbtBinaryTag_1.default(tagType, name, isListChild);
            if (tagStack.length === 0) {
                this.roots.push(activeTag);
            } // if (activeTag.type !== NbtTagType.end)
            else {
                const parentTag = tagStack[tagStack.length - 1];
                if (parentTag.type === NbtBinaryTag_1.NbtTagType.list &&
                    (parentTag.childTagType === undefined || parentTag.childTagType === NbtBinaryTag_1.NbtTagType.unknown)) {
                    parentTag.childTagType = activeTag.type;
                }
                parentTag.childrenWithEnd.push(activeTag);
            }
            if (tagStack.length > 0 && tagStack[tagStack.length - 1].type === NbtBinaryTag_1.NbtTagType.list) {
                listCountStack[tagStack.length - 1]--;
                if (listCountStack[tagStack.length - 1] === 0) {
                    tagStack.pop();
                }
            }
            if (activeTag.type === NbtBinaryTag_1.NbtTagType.compound) {
                tagStack.push(activeTag);
            }
            else if (activeTag.type === NbtBinaryTag_1.NbtTagType.end) {
                tagStack.pop();
                if (tagStack.length === 0 && !processAsList) {
                    break;
                }
            }
            else if (activeTag.type === NbtBinaryTag_1.NbtTagType.byte) {
                activeTag.value = DataUtilities_1.default.getSignedByte(data[i++]);
            }
            else if (activeTag.type === NbtBinaryTag_1.NbtTagType.byteArray) {
                const arrayLength = DataUtilities_1.default.getSignedInteger(data[i++], data[i++], data[i++], data[i++], littleEndian);
                const numberArray = [];
                for (let j = 0; j < arrayLength; j++) {
                    numberArray.push(DataUtilities_1.default.getSignedByte(data[i++]));
                }
                activeTag.value = numberArray;
            }
            else if (activeTag.type === NbtBinaryTag_1.NbtTagType.list) {
                activeTag.type = NbtBinaryTag_1.NbtTagType.list;
                tagStack.push(activeTag);
                listTypeStack[tagStack.length - 1] = data[i++];
                listCountStack[tagStack.length - 1] = DataUtilities_1.default.getSignedInteger(data[i++], data[i++], data[i++], data[i++], littleEndian);
            }
            else if (activeTag.type === NbtBinaryTag_1.NbtTagType.intArray) {
                const arrayLength = DataUtilities_1.default.getSignedInteger(data[i++], data[i++], data[i++], data[i++], littleEndian);
                const numberArray = [];
                for (let j = 0; j < arrayLength; j++) {
                    numberArray.push(DataUtilities_1.default.getSignedInteger(data[i++], data[i++], data[i++], data[i++], littleEndian));
                }
                activeTag.value = numberArray;
            }
            else if (activeTag.type === NbtBinaryTag_1.NbtTagType.longArray) {
                const arrayLength = DataUtilities_1.default.getSignedInteger(data[i++], data[i++], data[i++], data[i++], littleEndian);
                const numberArray = [];
                for (let j = 0; j < arrayLength; j++) {
                    numberArray.push(DataUtilities_1.default.getSignedLong(data[i++], data[i++], data[i++], data[i++], data[i++], data[i++], data[i++], data[i++], littleEndian));
                }
                activeTag.value = numberArray;
            }
            else if (activeTag.type === NbtBinaryTag_1.NbtTagType.short) {
                activeTag.value = DataUtilities_1.default.getSignedShort(data[i++], data[i++], littleEndian);
            }
            else if (activeTag.type === NbtBinaryTag_1.NbtTagType.int) {
                activeTag.value = DataUtilities_1.default.getSignedInteger(data[i++], data[i++], data[i++], data[i++], littleEndian);
            }
            else if (activeTag.type === NbtBinaryTag_1.NbtTagType.float) {
                activeTag.value = DataUtilities_1.default.getFloat(data[i++], data[i++], data[i++], data[i++], littleEndian);
            }
            else if (activeTag.type === NbtBinaryTag_1.NbtTagType.double) {
                activeTag.value = DataUtilities_1.default.getSignedDouble(data[i++], data[i++], data[i++], data[i++], data[i++], data[i++], data[i++], data[i++], littleEndian);
            }
            else if (activeTag.type === NbtBinaryTag_1.NbtTagType.long) {
                activeTag.value = DataUtilities_1.default.getSignedLong(data[i++], data[i++], data[i++], data[i++], data[i++], data[i++], data[i++], data[i++], littleEndian);
            }
            else if (activeTag.type === NbtBinaryTag_1.NbtTagType.string) {
                let stringLength = 0;
                if (isVarint) {
                    const result = this._getVarInt(data, i);
                    stringLength = result.value;
                    i += result.bytesRead;
                }
                else {
                    stringLength = DataUtilities_1.default.getUnsignedShort(data[i++], data[i++], littleEndian);
                }
                const view = new DataView(data.buffer);
                let str;
                if (stringsAreASCII) {
                    str = Utilities_1.default.readStringASCIIBuffer(data, i, stringLength);
                }
                else {
                    str = Utilities_1.default.getString(view, i, stringLength, "UTF8");
                }
                if (str === undefined) {
                    throw new Error("Unexpectedly could not read a string.");
                }
                activeTag.value = str;
                i += stringLength;
                Log_1.default.assert(i <= data.length, "NBTFB");
                /*
                        let valueString = "";
        
                        for (let j=0; j<stringLength; j++)
                        {
                            valueString += String.fromCharCode(data[i++]);
                        }
        
                        activeTag.value = valueString;*/
            }
            else {
                Log_1.default.unexpectedError("Unsupported NBT tag type '" + activeTag.type + "'");
            }
        }
        let bytesRead = i;
        if (skipBytes !== undefined) {
            bytesRead -= skipBytes;
        }
        return bytesRead;
    }
}
exports.default = NbtBinary;

//# sourceMappingURL=../maps/minecraft/NbtBinary.js.map
