"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.NbtTagType = void 0;
const Utilities_1 = require("./../core/Utilities");
const Log_1 = require("./../core/Log");
const INbtTag_1 = require("./INbtTag");
var NbtTagType;
(function (NbtTagType) {
    NbtTagType[NbtTagType["end"] = 0] = "end";
    NbtTagType[NbtTagType["byte"] = 1] = "byte";
    NbtTagType[NbtTagType["short"] = 2] = "short";
    NbtTagType[NbtTagType["int"] = 3] = "int";
    NbtTagType[NbtTagType["long"] = 4] = "long";
    NbtTagType[NbtTagType["float"] = 5] = "float";
    NbtTagType[NbtTagType["double"] = 6] = "double";
    NbtTagType[NbtTagType["byteArray"] = 7] = "byteArray";
    NbtTagType[NbtTagType["string"] = 8] = "string";
    NbtTagType[NbtTagType["list"] = 9] = "list";
    NbtTagType[NbtTagType["compound"] = 10] = "compound";
    NbtTagType[NbtTagType["intArray"] = 11] = "intArray";
    NbtTagType[NbtTagType["longArray"] = 12] = "longArray";
    NbtTagType[NbtTagType["unknown"] = 99] = "unknown";
})(NbtTagType = exports.NbtTagType || (exports.NbtTagType = {}));
class NbtBinaryTag {
    get childrenWithEnd() {
        return this._children;
    }
    getTagChildren() {
        if (this._children.length === 0) {
            return [];
        }
        if (this.lastTagIsEnd) {
            return this._children.slice(0, -1);
        }
        return this._children;
    }
    getTagLength() {
        if (this.lastTagIsEnd) {
            return this._children.length - 1;
        }
        return this._children.length;
    }
    get lastTagIsEnd() {
        if (this._children.length === 0) {
            return false;
        }
        const lastTag = this._children[this._children.length - 1];
        if (lastTag.type === NbtTagType.end) {
            return true;
        }
        else if (this.childTagType === NbtTagType.end && lastTag._children.length === 0) {
            return true;
        }
        return false;
    }
    get valueAsString() {
        if (typeof this.value === "number") {
            return this.value.toString();
        }
        else if (typeof this.value === "string") {
            return this.value;
        }
        return "";
    }
    get valueAsJSONObject() {
        let obj = undefined;
        const str = this.valueAsString;
        if (str) {
            try {
                obj = JSON.parse(str);
            }
            catch (e) {
                obj = undefined;
            }
        }
        return obj;
    }
    get valueAsNumericArray() {
        const childTags = this.getTagChildren();
        const numarr = [];
        for (const childTag of childTags) {
            numarr.push(childTag.valueAsInt);
        }
        return numarr;
    }
    get valueAsStringArray() {
        const childTags = this.getTagChildren();
        const numarr = [];
        for (const childTag of childTags) {
            numarr.push(childTag.valueAsString);
        }
        return numarr;
    }
    get valueAsBoolean() {
        if (typeof this.value === "boolean") {
            return this.value;
        }
        else if (typeof this.value === "string") {
            if (this.value === "true" || this.value === "TRUE" || this.value === "1") {
                return true;
            }
            return false;
        }
        else if (typeof this.value === "number") {
            if (this.value <= 0) {
                return false;
            }
            return true;
        }
        return false;
    }
    get valueAsInt() {
        if (typeof this.value === "number") {
            return this.value;
        }
        else if (typeof this.value === "string") {
            return parseInt(this.value);
        }
        else if (typeof this.value === "boolean" && this.value === true) {
            return 1;
        }
        return 0;
    }
    get valueAsFloat() {
        if (typeof this.value === "number") {
            return this.value;
        }
        else if (typeof this.value === "string") {
            return parseFloat(this.value);
        }
        return 0;
    }
    get valueAsBigInt() {
        if (typeof this.value === "bigint") {
            return this.value;
        }
        const type = typeof this.value;
        if (this.value !== null && (type === "number" || type === "string" || type === "boolean" || type === "bigint")) {
            return BigInt(this.value);
        }
        return BigInt(0);
    }
    constructor(type, name, isListChild) {
        this.childTagType = NbtTagType.unknown;
        this.value = null;
        this._children = [];
        this.type = type;
        this.name = name;
        this.isListChild = isListChild;
    }
    setListFromArray(arr) {
        let children = this.getTagChildren();
        if (children.length > arr.length) {
            while (children.length > arr.length) {
                this.removeTagByIndex(children.length - 1);
                children = this.getTagChildren();
            }
        }
        else {
            while (children.length < arr.length) {
                const newTag = new NbtBinaryTag(NbtTagType.int, "", this.type === NbtTagType.list);
                this.pushTag(newTag);
                children = this.getTagChildren();
            }
        }
        for (let i = 0; i < children.length; i++) {
            children[i].value = arr[i];
        }
    }
    pushTag(tag) {
        // if this is a list we've got an end tag at the 0 slot, swap it out
        if (this.type === NbtTagType.list &&
            this.lastTagIsEnd &&
            this._children.length === 1 &&
            tag.type !== NbtTagType.end) {
            this._children[0] = tag;
            if ((this.childTagType === NbtTagType.unknown || this.childTagType === NbtTagType.end) &&
                tag.childTagType !== NbtTagType.end) {
                this.childTagType = tag.type;
            }
        }
        else if (this.type === NbtTagType.list) {
            if ((this.childTagType === NbtTagType.unknown || this.childTagType === NbtTagType.end) &&
                tag.childTagType !== NbtTagType.end) {
                this.childTagType = tag.type;
            }
            this._children.push(tag);
        }
        else if (this.type === NbtTagType.compound && this.lastTagIsEnd && tag.type !== NbtTagType.end) {
            // duplicate the End tag a the end
            this._children.push(this._children[this._children.length - 1]);
            this._children[this._children.length - 2] = tag;
        }
        else {
            this._children.push(tag);
        }
    }
    prepareForSave(includingChildren) {
        if (this.type === NbtTagType.list) {
            if (this._children.length === 0) {
                this.childTagType = NbtTagType.end;
                this.addTag(NbtTagType.end);
            }
            else if (this._children.length > 1 && this.lastTagIsEnd) {
                this._children = this._children.slice(0, -1);
            }
        }
        else if (this.type === NbtTagType.compound && !this.lastTagIsEnd) {
            this.addTag(NbtTagType.end);
        }
        if (includingChildren) {
            for (let i = 0; i < this._children.length; i++) {
                this._children[i].prepareForSave(includingChildren);
            }
        }
    }
    getByteSize() {
        this.prepareForSave(false);
        let byteSize = 0;
        if (!this.isListChild) {
            byteSize += 1; // one byte for type of NBT Tag
            if (this.type !== NbtTagType.end) {
                byteSize += 2 + this.name.length; // two bytes + name length for name.  assumes name is ASCII (1 byte per char)
            }
        }
        switch (this.type) {
            case NbtTagType.byte:
                byteSize += 1;
                break;
            case NbtTagType.short:
                byteSize += 2;
                break;
            case NbtTagType.int:
                byteSize += 4;
                break;
            case NbtTagType.long:
                byteSize += 8;
                break;
            case NbtTagType.float:
                byteSize += 4;
                break;
            case NbtTagType.double:
                byteSize += 8;
                break;
            case NbtTagType.string:
                byteSize += 2; // length of string is 2-byte short
                if (typeof this.value === "string") {
                    const bytes = Utilities_1.default.convertStringToBytes(this.value, "UTF-8");
                    if (bytes === undefined) {
                        throw new Error("Unexpected NBT conversion error in writing string.");
                    }
                    byteSize += bytes.length;
                }
                break;
            case NbtTagType.list:
                byteSize += 5; // one byte for type of items, + 4 bytes for length;
                break;
            case NbtTagType.intArray:
                byteSize += 4; // length of array
                byteSize += 4 * this.value.length;
                break;
            case NbtTagType.longArray:
                byteSize += 4; // length of array
                byteSize += 8 * this.value.length;
                break;
            default:
                break;
        }
        for (let i = 0; i < this._children.length; i++) {
            byteSize += this._children[i].getByteSize();
        }
        return byteSize;
    }
    getJsonString() {
        return JSON.stringify(this.getJson(), null, 2);
    }
    getJson() {
        this.prepareForSave(false);
        const tag = new INbtTag_1.default();
        if (this.name !== undefined && this.name !== "") {
            tag.name = this.name;
        }
        if (this.type !== NbtTagType.unknown) {
            tag.type = this.type;
        }
        if (this.value !== undefined) {
            tag.value = this.value;
        }
        if (this.childTagType !== NbtTagType.unknown) {
            tag.childTagType = this.childTagType;
        }
        if (this._children.length > 0) {
            tag.children = [];
            for (let i = 0; i < this._children.length; i++) {
                tag.children.push(this._children[i].getJson());
            }
        }
        return tag;
    }
    writeBytes(bytes, index, littleEndian) {
        this.prepareForSave(false);
        const byteLength = bytes.buffer.byteLength;
        const dv = new DataView(bytes.buffer, 0, byteLength);
        if (!this.isListChild) {
            // write out type.
            dv.setInt8(index, this.type);
            index += 1;
            if (this.type !== NbtTagType.end) {
                // write out name length;
                dv.setInt16(index, this.name.length, littleEndian);
                index += 2;
                // TODO: probably an incorrect assumption that name is always "ASCII"
                for (let j = 0; j < this.name.length; j++) {
                    bytes[index++] = this.name.charCodeAt(j);
                }
            }
        }
        switch (this.type) {
            case NbtTagType.byte:
                dv.setInt8(index, this.valueAsInt);
                index += 1;
                break;
            case NbtTagType.short:
                dv.setInt16(index, this.valueAsInt, littleEndian);
                index += 2;
                break;
            case NbtTagType.int:
                dv.setInt32(index, this.valueAsInt, littleEndian);
                index += 4;
                break;
            case NbtTagType.long:
                dv.setBigInt64(index, this.valueAsBigInt, littleEndian);
                index += 8;
                break;
            case NbtTagType.float:
                dv.setFloat32(index, this.valueAsFloat, littleEndian);
                index += 4;
                break;
            case NbtTagType.double:
                dv.setFloat64(index, this.valueAsFloat, littleEndian);
                index += 8;
                break;
            case NbtTagType.string:
                if (typeof this.value === "string") {
                    const bytes = Utilities_1.default.convertStringToBytes(this.value, "UTF-8");
                    if (bytes === undefined) {
                        throw new Error("Unexpected NBT conversion error in writing string.");
                    }
                    dv.setInt16(index, bytes.length, littleEndian);
                    index += 2;
                    index = Utilities_1.default.writeString(dv, index, this.value, "UTF-8");
                    if (index < 0 || index >= byteLength) {
                        throw new Error("Unexpected error writing string.");
                    }
                }
                else {
                    dv.setInt16(index, 0, littleEndian);
                    index += 2;
                }
                break;
            case NbtTagType.list:
                dv.setInt8(index, this.childTagType);
                index += 1;
                dv.setInt32(index, this.getTagLength(), littleEndian);
                index += 4;
                break;
            case NbtTagType.intArray:
                // array length
                dv.setInt32(index, this._children.length, littleEndian);
                index += 4;
                const nums = this.value;
                for (let i = 0; i < nums.length; i++) {
                    dv.setInt32(index, nums[i], littleEndian);
                    index += 4;
                }
                break;
            case NbtTagType.longArray:
                // array length
                dv.setInt32(index, this._children.length, littleEndian);
                index += 4;
                const bigints = this.value;
                for (let i = 0; i < bigints.length; i++) {
                    dv.setBigInt64(index, bigints[i], littleEndian);
                    index += 8;
                }
                break;
        }
        for (let i = 0; i < this._children.length; i++) {
            index = this._children[i].writeBytes(bytes, index, littleEndian);
        }
        return index;
    }
    removeTagByIndex(index) {
        const newChildren = [];
        let removedItem = false;
        for (let i = 0; i < this._children.length; i++) {
            if (i !== index) {
                newChildren.push(this._children[i]);
            }
            else {
                removedItem = true;
            }
        }
        this._children = newChildren;
        return removedItem;
    }
    removeTag(tagName) {
        const newChildren = [];
        let removedItem = false;
        for (let i = 0; i < this._children.length; i++) {
            if (this._children[i].name !== tagName) {
                newChildren.push(this._children[i]);
            }
            else {
                removedItem = true;
            }
        }
        this._children = newChildren;
        return removedItem;
    }
    ensureTag(tagName, tagType) {
        const tag = this.child(tagName);
        if (tag !== null) {
            Log_1.default.assert(tag.type === tagType, "Unexpected data type '" + tag.type + "' for an nbt tag '" + tagName + "'");
            return tag;
        }
        return this.addTag(tagType, tagName);
    }
    addTag(tagType, tagName) {
        if (this.type !== NbtTagType.compound && this.type !== NbtTagType.list) {
            throw new Error("Unsupported type for adding a tag");
        }
        if (tagName === undefined) {
            tagName = "";
        }
        const newTag = new NbtBinaryTag(tagType, tagName, this.type === NbtTagType.list);
        this.pushTag(newTag);
        return newTag;
    }
    getProperty(name) {
        for (let i = 0; i < this._children.length; i++) {
            if (this._children[i].name === name) {
                return this._children[i];
            }
        }
        return null;
    }
    child(name) {
        if (this.name === name) {
            return this;
        }
        for (let i = 0; i < this._children.length; i++) {
            if (this._children[i].name === name) {
                return this._children[i];
            }
        }
        return null;
    }
    find(name) {
        if (this.name === name) {
            return this;
        }
        for (let i = 0; i < this._children.length; i++) {
            if (this._children[i].name === name) {
                return this._children[i];
            }
        }
        for (let i = 0; i < this._children.length; i++) {
            const tagChild = this._children[i];
            if (tagChild.type === NbtTagType.compound || tagChild.type === NbtTagType.list) {
                const result = tagChild.find(name);
                if (result != null) {
                    return result;
                }
            }
        }
        return null;
    }
}
exports.default = NbtBinaryTag;

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