"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.SubChunkFormatType = void 0;
const Log_1 = require("../core/Log");
const BlockPalette_1 = require("./BlockPalette");
const DataUtilities_1 = require("../core/DataUtilities");
const BlockCube_1 = require("../minecraft/BlockCube");
const Block_1 = require("./Block");
const NbtBinary_1 = require("./NbtBinary");
const GenericBlockActor_1 = require("./blockActors/GenericBlockActor");
const BlockActorFactory_1 = require("./blockActors/BlockActorFactory");
const Database_1 = require("./Database");
const CHUNK_X_SIZE = 16;
const CHUNK_Z_SIZE = 16;
const SUBCHUNK_Y_SIZE = 16;
const MAX_LEGACY_Y = 128;
var SubChunkFormatType;
(function (SubChunkFormatType) {
    SubChunkFormatType[SubChunkFormatType["paletteFrom1dot2dot13"] = 0] = "paletteFrom1dot2dot13";
    SubChunkFormatType[SubChunkFormatType["subChunk1dot0"] = 1] = "subChunk1dot0";
})(SubChunkFormatType = exports.SubChunkFormatType || (exports.SubChunkFormatType = {}));
class WorldChunk {
    get absoluteMinY() {
        return this.absoluteZeroY;
    }
    get minY() {
        return this.absoluteZeroY + this.minSubChunkIndex * 16;
    }
    get maxY() {
        return this.absoluteZeroY + (this.maxSubChunkIndex + 1) * 16;
    }
    get absoluteMaxY() {
        return this.absoluteZeroY + this.subChunks.length * 16;
    }
    get blockActors() {
        if (!this.blockActorsEnsured) {
            this.ensureBlockActors();
        }
        return this._blockActors;
    }
    constructor(world, inX, inZ) {
        this.blockActorKeys = [];
        this._blockActorsRelLoc = [];
        this._blockActors = [];
        this.blockActorsEnsured = false;
        this.absoluteZeroY = -512;
        this.chunkMinY = 0;
        this.x = 0;
        this.z = 0;
        this.maxSubChunkIndex = -512; // set it very low
        this.minSubChunkIndex = 512; // set it very high
        this.world = world;
        this.subChunks = [];
        this.subChunkFormatType = [];
        this.blockPalettes = [];
        this.bitsPerBlock = [];
        this.blockDataStart = [];
        this.auxBlockPalettes = [];
        this.auxBitsPerBlock = [];
        this.auxBlockDataStart = [];
        this.pendingSubChunksToProcess = [];
        this.actorDigests = [];
        for (let i = 0; i < 64; i++) {
            this.pendingSubChunksToProcess[i] = false;
        }
        this.x = inX;
        this.z = inZ;
    }
    clearCachedData() {
        for (let i = 0; i < 64; i++) {
            if (this.subChunks[i] !== undefined) {
                this.blockDataStart[i] = -1;
                this.bitsPerBlock[i] = -1;
                this.blockPalettes[i] = undefined;
                this.pendingSubChunksToProcess[i] = true;
            }
        }
        this.blockTops = undefined;
        this.blockActorsEnsured = false;
        this._blockActorsRelLoc = [];
        this._blockActors = [];
    }
    addActorDigest(digest) {
        this.actorDigests.push(digest);
    }
    translateSubChunkIndex(storageSubChunk) {
        Log_1.default.assert((storageSubChunk >= 0 && storageSubChunk <= 31) || (storageSubChunk >= 224 && storageSubChunk <= 255), "Unexpected subchunk index (" + storageSubChunk + ")");
        if (storageSubChunk >= 224) {
            return storageSubChunk - 224;
        }
        return storageSubChunk + 32;
    }
    processSubChunk(index) {
        if (this.pendingSubChunksToProcess[index] === true) {
            this.pendingSubChunksToProcess[index] = false;
            this.parseSubChunk(index);
        }
    }
    addKeyValue(keyValue) {
        let keyBytes = keyValue.keyBytes;
        if (keyBytes) {
            const dimExtensionBytes = keyBytes.length >= 13 ? 4 : 0;
            const val = keyBytes[8 + dimExtensionBytes];
            // disabling the "duplicate unexpected versions" since this assumption is violated in C&C R17 world
            switch (val) {
                case 43:
                    Log_1.default.assert(keyValue.value !== undefined && keyValue.value.length > 512, "Unexpected length for a type 43 recrod.");
                    if (keyValue.value) {
                        this.blockTops = [];
                        for (let i = 0; i < 16; i++) {
                            const arr = [];
                            for (let j = 0; j < 16; j++) {
                                let val = keyValue.value[j * 32 + i * 2] + keyValue.value[j * 32 + (i * 2 + 1)] * 256;
                                arr.push(val - 65); // subtracting - 65 is arbitrary here
                            }
                            this.blockTops.push(arr);
                        }
                    }
                    break;
                case 115: // not sure what chunk #115 is, or if this is a parsing bug. observed to be one byte with a value of 0
                    // Log.assert(false, "Unexpected type 115 record.");
                    break;
                case 118: // 118 = legacy version
                    Log_1.default.assert(keyValue.value !== undefined && (keyValue.value.length === 0 || keyValue.value.length === 1), "Unexpected type 118 record.");
                    if (keyValue.value && keyValue.value.length === 1) {
                        this.legacyVersion = keyValue.value[0];
                    }
                    else if (keyValue.value && keyValue.value.length === 0) {
                        this.legacyVersion = undefined;
                    }
                    break;
                case 44: // version
                    this.chunkVersion = keyValue;
                    break;
                case 45: // data2d
                    this.biomesAndElevation = keyValue;
                    break;
                case 46: // data2d legacy
                    // Log.assert(false, "Data 2D legacy (NYI).");
                    break;
                case 47: // subchunk prefix
                    let subChunkIndex = this.translateSubChunkIndex(keyBytes[9 + dimExtensionBytes]);
                    if (subChunkIndex < 0) {
                        Log_1.default.fail("Unexpected sub chunk index.");
                        return;
                    }
                    if (this.subChunks[subChunkIndex] !== undefined) {
                        // Log.fail("Unexpected subchunk already defined.");
                    }
                    if (!keyValue.value || keyValue.value.length <= 0) {
                        Log_1.default.assert(this.subChunks[subChunkIndex] === undefined, "Empty subchunk defined.");
                        return;
                    }
                    this.subChunks[subChunkIndex] = keyValue;
                    this.maxSubChunkIndex = Math.max(this.maxSubChunkIndex, subChunkIndex);
                    this.minSubChunkIndex = Math.min(this.minSubChunkIndex, subChunkIndex);
                    this.pendingSubChunksToProcess[subChunkIndex] = true;
                    break;
                case 48: // legacy terrain
                    const bytes = keyValue.value;
                    if (bytes && bytes.length > 0) {
                        Log_1.default.assert(bytes.length === 83200, "LegacyTerrain record should be 83,200 bytes");
                        this.legacyTerrainBytes = bytes;
                    }
                    break;
                case 49: // block entity
                    this.blockActorKeys.push(keyValue);
                    this.blockActorsEnsured = false;
                    break;
                case 50: // entity
                    //       Log.assert(!this.entity, "Unexpected multiple entities.");
                    this.entity = keyValue;
                    break;
                case 51: // pending ticks
                    //Log.assert(!this.pendingTicks, "Unexpected multiple pending ticks.");
                    this.pendingTicks = keyValue;
                    break;
                case 52: // legacy block extra data
                    // Log.assert(false, "Legacy block extra data - NYI");
                    break;
                case 53: // biome state
                    //Log.assert(!this.biomeState, "Unexpected multiple biome states.");
                    this.biomeState = keyValue;
                    break;
                case 54: // finalized state
                    //  Log.assert(!this.finalizedState, "Unexpected multiple states.");
                    this.finalizedState = keyValue;
                    break;
                case 55: // conversion data. data that the converter provides, that are used at runtime for things like blending. no longer used?
                    break;
                case 56: // EDU border blocks?
                    break;
                case 57: // spawn areas (hard coded spawners)
                    break;
                case 58: // random tick
                    break;
                case 59: // check sums
                    // Log.assert(!this.checksumKey, "Unexpected multiple states.");
                    this.checksumKey = keyValue;
                    break;
                case 60: // generation seed
                    break;
                case 61: // generated pre caves and cliffs blending (unused)
                    break;
                case 62: // blending biome height (unused)
                    break;
                case 63: // metadata hash
                    break;
                case 64: // blending data
                    break;
                case 65: // actor digest version
                    break;
                case 119: // ??
                    //Log.assert(false, "Unexpected type 119 data.");
                    break;
                default:
                    Log_1.default.debugAlert("Unsupported chunk type: " + val);
            }
        }
    }
    clearKeyValue(keyBytes) {
        if (keyBytes) {
            const dimExtensionBytes = keyBytes.length > 18 || keyBytes.length === 13 || keyBytes.length === 14 ? 4 : 0;
            const val = keyBytes.charCodeAt(8 + dimExtensionBytes);
            // disabling the "duplicate unexpected versions" since this assumption is violated in C&C R17 world
            switch (val) {
                case 43: // not sure what chunk #43 is, or if this is a parsing bug. observed to be 578 bytes. "data3d"
                    break;
                case 115: // not sure what chunk #61 is, or if this is a parsing bug. observed to be one byte with a value of 0
                    break;
                case 118: // 118 = legacy version
                case 44: // version
                    //    Log.assert(!this.chunkVersion, "Unexpected multiple chunk versions.");
                    this.chunkVersion = undefined;
                    break;
                case 45: // data2d
                    //  Log.assert(!this.biomesAndElevation, "Unexpected multiple biomes and elevations.");
                    this.biomesAndElevation = undefined;
                    break;
                case 46: // data2d legacy
                    break;
                case 47: // subchunk prefix
                    break;
                case 48: // legacy terrain
                    this.legacyTerrainBytes = undefined;
                    break;
                case 49: // block entity
                    break;
                case 50: // entity
                    //       Log.assert(!this.entity, "Unexpected multiple entities.");
                    this.entity = undefined;
                    break;
                case 51: // pending ticks
                    //Log.assert(!this.pendingTicks, "Unexpected multiple pending ticks.");
                    this.pendingTicks = undefined;
                    break;
                case 52: // legacy block extra data
                    break;
                case 53: // biome state
                    //Log.assert(!this.biomeState, "Unexpected multiple biome states.");
                    this.biomeState = undefined;
                    break;
                case 54: // finalized state
                    //  Log.assert(!this.finalizedState, "Unexpected multiple states.");
                    this.finalizedState = undefined;
                    break;
                case 55: // conversion data. data that the converter provides, that are used at runtime for things like blending. no longer used?
                    break;
                case 56: // EDU border blocks?
                    break;
                case 57: // spawn areas (hard coded spawners)
                    break;
                case 58: // random tick
                    break;
                case 59: // check sums
                    // Log.assert(!this.checksumKey, "Unexpected multiple states.");
                    this.checksumKey = undefined;
                    break;
                case 60: // generation seed
                    break;
                case 61: // generated pre caves and cliffs blending (unused)
                    break;
                case 62: // blending biome height (unused)
                    break;
                case 63: // metadata hash
                    break;
                case 64: // blending data
                    break;
                case 65: // actor digest version
                    break;
                case 72: // actor digest version
                    break;
                case 119: // ??
                    break;
                default:
                    throw new Error("Unsupported chunk type: " + val);
            }
        }
    }
    ensureBlockActors() {
        if (!this._blockActorsRelLoc || this.blockActorsEnsured) {
            return;
        }
        this._blockActorsRelLoc = [];
        this._blockActors = [];
        for (const keyValue of this.blockActorKeys) {
            if (keyValue.value && keyValue.value.length > 0) {
                const tag = new NbtBinary_1.default();
                tag.context = this.world.name + " chunk at x:" + this.x * 16 + " z:" + this.z * 16;
                try {
                    tag.fromBinary(keyValue.value, true, false, 0, true, true);
                }
                catch (e) {
                    Log_1.default.error("Could not parse a block actor.");
                }
                if (tag.roots) {
                    for (let i = 0; i < tag.roots.length; i++) {
                        const ba = new GenericBlockActor_1.default(tag.roots[i]);
                        if (ba.x !== undefined &&
                            ba.z !== undefined &&
                            ba.y !== undefined &&
                            ba.x >= this.x * 16 &&
                            ba.x < (this.x + 1) * 16 &&
                            ba.z >= this.z * 16 &&
                            ba.z < (this.z + 1) * 16) {
                            let actorRelX = ba.x % 16;
                            let actorRelZ = ba.z % 16;
                            if (actorRelX < 0) {
                                actorRelX = 16 + actorRelX;
                            }
                            if (actorRelZ < 0) {
                                actorRelZ = 16 + actorRelZ;
                            }
                            if (ba.id) {
                                const specificBa = BlockActorFactory_1.default.create(ba.id, tag.roots[i]);
                                if (specificBa) {
                                    specificBa.load();
                                    if (!this._blockActorsRelLoc[actorRelX]) {
                                        this._blockActorsRelLoc[actorRelX] = [];
                                    }
                                    if (!this._blockActorsRelLoc[actorRelX][ba.y]) {
                                        this._blockActorsRelLoc[actorRelX][ba.y] = [];
                                    }
                                    this._blockActorsRelLoc[actorRelX][ba.y][actorRelZ] = specificBa;
                                    if (specificBa.x !== undefined && specificBa.y !== undefined && specificBa.z !== undefined) {
                                        //       this.removeBlockActorAtLoc(specificBa.x, specificBa.y, specificBa.z);
                                    }
                                    this._blockActors.push(specificBa);
                                }
                                Log_1.default.assert(specificBa !== undefined, "Could not find an actor implementation for '" + ba.id + "'");
                            }
                        }
                    }
                }
            }
        }
        this.blockActorsEnsured = true;
    }
    removeBlockActorAtLoc(x, y, z) {
        const newBlockActors = [];
        for (const blockActor of this._blockActors) {
            if (blockActor.x !== x || blockActor.y !== y || blockActor.z !== z) {
                newBlockActors.push(blockActor);
            }
        }
        this._blockActors = newBlockActors;
    }
    getSubChunkCube(subChunkId) {
        const bc = new BlockCube_1.default();
        bc.maxX = CHUNK_X_SIZE;
        bc.maxY = SUBCHUNK_Y_SIZE;
        bc.maxZ = CHUNK_Z_SIZE;
        this.fillCube(bc, 0, 0, 0, 16, 16, 16, 0, subChunkId * SUBCHUNK_Y_SIZE, 0);
        return bc;
    }
    fillCubeLegacy(cube, cubeX, cubeY, cubeZ, maxCubeX, maxCubeY, maxCubeZ, internalOffsetX, internalOffsetY, internalOffsetZ) {
        Log_1.default.assert(cubeX >= 0 &&
            cubeY >= 0 &&
            cubeZ >= 0 &&
            maxCubeX > cubeX &&
            maxCubeY > cubeY &&
            maxCubeZ > cubeZ &&
            internalOffsetX < CHUNK_X_SIZE &&
            internalOffsetZ < CHUNK_Z_SIZE &&
            this.legacyTerrainBytes !== undefined, "Fill cube legacy not within bounds.");
        if (!this.legacyTerrainBytes) {
            return;
        }
        for (let iX = cubeX; iX < maxCubeX && iX - cubeX + internalOffsetX < CHUNK_X_SIZE; iX++) {
            const inChunkX = iX - cubeX + internalOffsetX;
            const plane = cube.x(iX);
            for (let iY = cubeY; iY < maxCubeY && iY - cubeY + internalOffsetY < 128; iY++) {
                const blockLine = plane.y(iY);
                const inChunkY = iY - cubeY + internalOffsetY;
                for (let iZ = cubeZ; iZ < maxCubeZ && iZ - cubeZ + internalOffsetZ < CHUNK_Z_SIZE; iZ++) {
                    const inChunkZ = iZ - cubeZ + internalOffsetZ;
                    const byte = this.legacyTerrainBytes[inChunkX * 128 * 16 + inChunkZ * 128 + inChunkY];
                    if (byte) {
                        blockLine.z(iZ).copyFrom(Block_1.default.fromLegacyId(byte));
                    }
                }
            }
        }
    }
    fillCube(cube, cubeX, cubeY, cubeZ, maxCubeX, maxCubeY, maxCubeZ, internalOffsetX, internalOffsetY, internalOffsetZ) {
        if (this.legacyTerrainBytes) {
            this.fillCubeLegacy(cube, cubeX, cubeY, cubeZ, maxCubeX, maxCubeY, maxCubeZ, internalOffsetX, internalOffsetY, internalOffsetZ);
            return;
        }
        Log_1.default.assert(cubeX >= 0 &&
            cubeY >= 0 &&
            cubeZ >= 0 &&
            maxCubeX > cubeX &&
            maxCubeY > cubeY &&
            maxCubeZ > cubeZ &&
            internalOffsetX < CHUNK_X_SIZE &&
            internalOffsetZ < CHUNK_Z_SIZE, "Fill cube not within bounds.");
        const zHeight = maxCubeY - cubeY;
        const initialChunkId = this.getSubChunkIndexFromY(internalOffsetY);
        const finalChunkId = this.getSubChunkIndexFromY(internalOffsetY + zHeight);
        Log_1.default.assert(initialChunkId >= 0 && finalChunkId >= initialChunkId, "WCFC");
        for (let i = 0; i <= finalChunkId - initialChunkId; i++) {
            const subChunkId = initialChunkId + i;
            const subChunk = this.subChunks[subChunkId];
            if (subChunk) {
                const subChunkYExtent = this.getStartYFromSubChunkIndex(subChunkId + 1);
                let cubeYStartForThisSubChunk = 0;
                if (i >= 1) {
                    cubeYStartForThisSubChunk = 16 - ((Math.abs(this.absoluteZeroY) + internalOffsetY) % 16);
                }
                if (i >= 2) {
                    cubeYStartForThisSubChunk += (i - 1) * 16;
                }
                if (this.subChunkFormatType[subChunkId] === SubChunkFormatType.subChunk1dot0) {
                    const blockTemplates = [];
                    const bytes = subChunk.value;
                    if (bytes) {
                        Log_1.default.assert(bytes.length === 10251 || bytes.length === 10241 || bytes.length === 6145, "Expected 6145 or 10241 bytes for a legacy subchunk. (" + bytes.length + ")");
                        for (let i = 0; i < 4096; i++) {
                            let blockTypeIndex = bytes[1 + i];
                            let blockAuxIndex = bytes[4097 + i];
                            let templateIndex = blockTypeIndex * 256 + blockAuxIndex;
                            if (!blockTemplates[templateIndex]) {
                                const blockType = Database_1.default.getBlockTypeByLegacyId(blockTypeIndex);
                                if (!blockType || !blockType.typeId) {
                                    throw new Error("Expected a block type for index " + blockTypeIndex);
                                }
                                const block = new Block_1.default("minecraft:" + blockType.typeId);
                                block.data = blockAuxIndex;
                                blockTemplates[templateIndex] = block;
                            }
                            cube
                                .x(i % 16)
                                .y(Math.floor(i / 256))
                                .z(Math.floor(i / 16))
                                .copyFrom(blockTemplates[templateIndex]);
                        }
                    }
                }
                else {
                    const subChunkBitsPerBlock = this.bitsPerBlock[subChunkId];
                    const bpw = Math.floor(32 / subChunkBitsPerBlock);
                    const subChunkBlockDataStart = this.blockDataStart[subChunkId];
                    const bytes = subChunk.value;
                    const blockPalette = this.blockPalettes[subChunkId];
                    if (bytes && blockPalette) {
                        for (let iX = cubeX; iX < maxCubeX && iX - cubeX + internalOffsetX < CHUNK_X_SIZE; iX++) {
                            const inChunkX = iX - cubeX + internalOffsetX;
                            const plane = cube.x(iX);
                            const blockIndexXStart = inChunkX * 256;
                            for (let iY = cubeY + cubeYStartForThisSubChunk; iY < maxCubeY && iY - cubeY + internalOffsetY < subChunkYExtent; iY++) {
                                const inSubChunkY = (Math.abs(this.absoluteZeroY) + (iY - cubeY + internalOffsetY)) % 16;
                                Log_1.default.assert(inSubChunkY >= 0, "WCFCA");
                                const blockLine = plane.y(iY);
                                for (let iZ = cubeZ; iZ < maxCubeZ && iZ - cubeZ + internalOffsetZ < CHUNK_Z_SIZE; iZ++) {
                                    const inChunkZ = iZ - cubeZ + internalOffsetZ;
                                    const blockWordByteStart = subChunkBlockDataStart + Math.floor((blockIndexXStart + inChunkZ * 16 + inSubChunkY) / bpw) * 4;
                                    const blocksIn = (blockIndexXStart + inChunkZ * 16 + inSubChunkY) % bpw;
                                    let word = DataUtilities_1.default.getUnsignedInteger(bytes[blockWordByteStart], bytes[blockWordByteStart + 1], bytes[blockWordByteStart + 2], bytes[blockWordByteStart + 3], true);
                                    word >>>= subChunkBitsPerBlock * blocksIn;
                                    let value = 0;
                                    for (let i = 0; i < subChunkBitsPerBlock; i++) {
                                        let inc = word % 2;
                                        inc <<= i;
                                        value += inc;
                                        word >>>= 1;
                                    }
                                    if (blockPalette.blocks.length > 0) {
                                        Log_1.default.assert(value < blockPalette.blocks.length, "Unexpected block index.");
                                        const block = blockPalette.blocks[value];
                                        if (block) {
                                            blockLine.z(iZ).copyFrom(block);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    getTopBlockY(x, z) {
        if (!this.blockTops) {
            this.determineBlockTops();
        }
        if (!this.blockTops) {
            throw new Error("Unexpected block top error.");
        }
        return this.blockTops[x][z];
    }
    getTopBlock(x, z) {
        if (!this.blockTops) {
            this.determineBlockTops();
        }
        if (!this.blockTops) {
            throw new Error("Unexpected block top error.");
        }
        return this.getBlock(x, this.blockTops[x][z], z);
    }
    _getBlockLegacy(x, y, z) {
        if (!this.legacyTerrainBytes || z < 0 || x < 0 || y < 0) {
            throw new Error();
        }
        const byte = this.legacyTerrainBytes[x * 128 * 16 + z * 128 + y];
        return Block_1.default.fromLegacyId(byte);
    }
    _getBlockLegacyList() {
        if (!this.legacyTerrainBytes) {
            throw new Error();
        }
        const blocks = [];
        for (let y = 0; y < MAX_LEGACY_Y; y++) {
            for (let z = 0; z < 16; z++) {
                for (let x = 0; x < 16; x++) {
                    const byte = this.legacyTerrainBytes[x * 128 * 16 + z * 128 + y];
                    blocks.push(Block_1.default.fromLegacyId(byte));
                }
            }
        }
        return blocks;
    }
    doesBlockPaletteExist(y) {
        if (this.legacyTerrainBytes) {
            return true;
        }
        const subChunkId = this.getSubChunkIndexFromY(y);
        const blockPalettes = this.blockPalettes[subChunkId];
        if (blockPalettes) {
            return true;
        }
        return false;
    }
    // x and z should be between 0 and 15
    getBlock(x, y, z) {
        if (y < this.absoluteZeroY) {
            return undefined;
        }
        Log_1.default.assert(x >= 0 && x < 16 && z >= 0 && z < 16, "Retrieving an out-of-range block from a chunk.");
        if (this.legacyTerrainBytes) {
            return this._getBlockLegacy(x, y, z);
        }
        const subChunkId = this.getSubChunkIndexFromY(y);
        if (this.pendingSubChunksToProcess[subChunkId] === true) {
            this.processSubChunk(subChunkId);
        }
        // legacy subchunk format 1.0 -> 1.2.13
        if (this.subChunkFormatType[subChunkId] === SubChunkFormatType.subChunk1dot0) {
            const subChunk = this.subChunks[subChunkId];
            if (subChunk === undefined) {
                return undefined;
            }
            const bytes = subChunk.value;
            if (bytes) {
                const inSubChunkY = y - this.getStartYFromSubChunkIndex(subChunkId);
                Log_1.default.assert(inSubChunkY >= 0 && inSubChunkY < 16, "Unexpected Y for a sub chunk (" + inSubChunkY + ")");
                Log_1.default.assert(bytes.length === 10251 || bytes.length === 10241 || bytes.length === 6145, "1.00 subchunk format should be 6145 or 10241 bytes. (" + bytes.length + ")");
                const blockTypeIndex = bytes[1 + (inSubChunkY + z * 16 + x * 256)];
                const blockAuxIndex = bytes[4097 + (inSubChunkY + z * 16 + x * 256)];
                const baseType = Database_1.default.getBlockTypeByLegacyId(blockTypeIndex);
                Log_1.default.assertDefined(baseType.typeId);
                const block = new Block_1.default("minecraft:" + baseType.typeId);
                block.data = blockAuxIndex;
                return block;
            }
            return undefined;
        }
        const index = this.getBlockPaletteIndex(x, y, z);
        if (index === undefined) {
            return undefined;
        }
        const blockPalettes = this.blockPalettes[subChunkId];
        if (!blockPalettes) {
            return undefined;
        }
        const blocks = blockPalettes.blocks;
        Log_1.default.assert(index < blocks.length, "Unexpected block index");
        return blocks[index];
    }
    getBlockList() {
        if (this.legacyTerrainBytes) {
            return this._getBlockLegacyList();
        }
        const blocks = [];
        for (let subChunkId = this.minSubChunkIndex; subChunkId < this.maxSubChunkIndex; subChunkId++) {
            const subChunk = this.subChunks[subChunkId];
            if (subChunk !== undefined) {
                if (this.pendingSubChunksToProcess[subChunkId] === true) {
                    this.processSubChunk(subChunkId);
                }
                if (this.subChunkFormatType[subChunkId] === SubChunkFormatType.subChunk1dot0) {
                    const blockTemplates = [];
                    const bytes = subChunk.value;
                    if (bytes) {
                        Log_1.default.assert(bytes.length === 10251 || bytes.length === 10241 || bytes.length === 6145 || bytes.length === 6155, "Expected 6145 or 10241 bytes for a legacy subchunk in getblock. (" + bytes.length + ")");
                        // 6145 bytes if the light information is omitted;
                        // 10241 bytes if there is 2kb + 2kb of light information
                        for (let i = 0; i < 4096; i++) {
                            let blockTypeIndex = bytes[1 + i];
                            let blockAuxIndex = bytes[4097 + i];
                            let templateIndex = blockTypeIndex * 256 + blockAuxIndex;
                            if (!blockTemplates[templateIndex]) {
                                const blockType = Database_1.default.getBlockTypeByLegacyId(blockTypeIndex);
                                if (!blockType || !blockType.typeId) {
                                    throw new Error("Expected a block type for index " + blockTypeIndex);
                                }
                                const block = new Block_1.default("minecraft:" + blockType.typeId);
                                block.data = blockAuxIndex;
                                blockTemplates[templateIndex] = block;
                            }
                            blocks.push(blockTemplates[templateIndex]);
                        }
                    }
                }
                else {
                    let indices = this.getBlockPaletteIndexList(subChunkId);
                    if (indices) {
                        const blockPalettes = this.blockPalettes[subChunkId];
                        if (blockPalettes) {
                            const blockTemplates = blockPalettes.blocks;
                            for (let i = 0; i < indices.length; i++) {
                                blocks.push(blockTemplates[indices[i]]);
                            }
                        }
                    }
                }
            }
        }
        return blocks;
    }
    _determineBlockTopsLegacy() {
        if (!this.legacyTerrainBytes || !this.blockTops) {
            return;
        }
        for (let iX = 0; iX < 16; iX++) {
            const iXByte = iX * 128 * 16;
            for (let iZ = 0; iZ < 16; iZ++) {
                const iZByte = iZ * 128;
                for (let iY = 127; iY >= 0; iY--) {
                    const byte = this.legacyTerrainBytes[iXByte + iZByte + iY];
                    if (byte !== 0) {
                        this.blockTops[iX][iZ] = iY;
                        iY = -1;
                    }
                }
            }
        }
    }
    determineBlockTops() {
        this.blockTops = [];
        for (let i = 0; i < 16; i++) {
            const arr = [];
            for (let j = 0; j < 16; j++) {
                arr.push(-32768);
            }
            this.blockTops.push(arr);
        }
        if (this.legacyTerrainBytes) {
            this._determineBlockTopsLegacy();
            return;
        }
        let matchCount = 0;
        for (let subChunkId = this.maxSubChunkIndex; subChunkId >= 0; subChunkId--) {
            if (this.pendingSubChunksToProcess[subChunkId] === true) {
                this.parseSubChunk(subChunkId);
            }
            const subChunk = this.subChunks[subChunkId];
            if (subChunk !== undefined) {
                const bytes = subChunk.value;
                if (bytes !== undefined) {
                    if (this.subChunkFormatType[subChunkId] === SubChunkFormatType.subChunk1dot0) {
                        for (let iY = 15; iY >= 0; iY--) {
                            for (let iZ = 0; iZ < 16; iZ++) {
                                for (let iX = 0; iX < 16; iX++) {
                                    let blockTypeId = bytes[iX * 256 + iZ * CHUNK_Z_SIZE + iY];
                                    if (blockTypeId !== 0 /* air */ &&
                                        blockTypeId !== 37 /* flower */ &&
                                        blockTypeId !== 31 /* tallgrass*/ &&
                                        this.blockTops[iX][iZ] < -1024) {
                                        const yIndex = iY + this.getStartYFromSubChunkIndex(subChunkId);
                                        this.blockTops[iX][iZ] = yIndex - 1;
                                        matchCount++;
                                        if (matchCount === 256) {
                                            return;
                                        }
                                    }
                                }
                            }
                        }
                    }
                    else {
                        //y z x
                        const subChunkBitsPerBlock = this.bitsPerBlock[subChunkId];
                        const bpw = Math.floor(32 / subChunkBitsPerBlock);
                        const disallowedIndices = [];
                        const blockPals = this.blockPalettes[subChunkId];
                        if (blockPals) {
                            Log_1.default.assert(blockPals.blocks !== undefined, "WCDBTA");
                            for (let iPal = 0; iPal < blockPals.blocks.length; iPal++) {
                                const block = blockPals.blocks[iPal];
                                if (block.shortTypeName === "air" ||
                                    block.shortTypeName === "flower" ||
                                    block.shortTypeName === "tallgrass") {
                                    disallowedIndices.push(iPal);
                                }
                            }
                            for (let iY = 15; iY >= 0; iY--) {
                                const yIndex = iY + this.getStartYFromSubChunkIndex(subChunkId);
                                for (let iZ = 0; iZ < 16; iZ++) {
                                    for (let iX = 0; iX < 16; iX++) {
                                        if (yIndex >= this.blockTops[iX][iZ]) {
                                            const blockIndex = iX * 256 + iZ * CHUNK_Z_SIZE + iY;
                                            const byteStart = this.blockDataStart[subChunkId] + Math.floor(blockIndex / bpw) * 4;
                                            const blocksIn = blockIndex % bpw;
                                            let word = DataUtilities_1.default.getUnsignedInteger(bytes[byteStart], bytes[byteStart + 1], bytes[byteStart + 2], bytes[byteStart + 3], true);
                                            word >>>= subChunkBitsPerBlock * blocksIn;
                                            let value = 0;
                                            for (let i = 0; i < subChunkBitsPerBlock; i++) {
                                                let inc = word % 2;
                                                inc <<= i;
                                                value += inc;
                                                word >>>= 1;
                                            }
                                            let matchesSolidIndex = true;
                                            for (let iDis = 0; iDis < disallowedIndices.length; iDis++) {
                                                if (value === disallowedIndices[iDis]) {
                                                    matchesSolidIndex = false;
                                                }
                                            }
                                            if (matchesSolidIndex) {
                                                matchCount++;
                                                this.blockTops[iX][iZ] = yIndex;
                                                if (matchCount === 256) {
                                                    return;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    getSubChunkIndexFromY(y) {
        return Math.floor((y - this.absoluteZeroY) / SUBCHUNK_Y_SIZE);
    }
    getStartYFromSubChunkIndex(subChunkIndex) {
        return subChunkIndex * SUBCHUNK_Y_SIZE + this.absoluteZeroY;
    }
    getBlockPaletteIndex(x, y, z) {
        Log_1.default.assert(x >= 0 && x <= 15 && z >= 0 && z <= 15, "Unexpected x/z");
        const subChunkId = this.getSubChunkIndexFromY(y);
        const subChunk = this.subChunks[subChunkId];
        if (subChunk === undefined) {
            return undefined;
        }
        const bytes = subChunk.value;
        if (bytes === undefined) {
            return undefined;
        }
        //y z x
        const subChunkY = Math.abs(y % 16);
        const blockIndex = x * 256 + z * CHUNK_Z_SIZE + subChunkY;
        const subChunkBitsPerBlock = this.bitsPerBlock[subChunkId];
        const bpw = Math.floor(32 / subChunkBitsPerBlock);
        const byteStart = this.blockDataStart[subChunkId] + Math.floor(blockIndex / bpw) * 4;
        const blocksIn = blockIndex % bpw;
        let word = DataUtilities_1.default.getUnsignedInteger(bytes[byteStart], bytes[byteStart + 1], bytes[byteStart + 2], bytes[byteStart + 3], true);
        //  Log.assert(x !== 15 || y !== 80 || z !== 15);
        word >>>= subChunkBitsPerBlock * blocksIn;
        let value = 0;
        for (let i = 0; i < subChunkBitsPerBlock; i++) {
            let inc = word % 2;
            inc <<= i;
            value += inc;
            word >>>= 1;
        }
        return value;
    }
    getBlockPaletteIndexList(subChunkId) {
        const blockIndices = [];
        const subChunk = this.subChunks[subChunkId];
        if (subChunk !== undefined) {
            const bytes = subChunk.value;
            if (bytes === undefined) {
                return undefined;
            }
            //y z x
            const subChunkY = this.absoluteZeroY + this.minSubChunkIndex * 16;
            for (let x = 0; x < 16; x++) {
                for (let z = 0; z < 16; z++) {
                    const blockIndex = x * 256 + z * CHUNK_Z_SIZE + subChunkY;
                    const subChunkBitsPerBlock = this.bitsPerBlock[subChunkId];
                    const bpw = Math.floor(32 / subChunkBitsPerBlock);
                    const byteStart = this.blockDataStart[subChunkId] + Math.floor(blockIndex / bpw) * 4;
                    const blocksIn = blockIndex % bpw;
                    let word = DataUtilities_1.default.getUnsignedInteger(bytes[byteStart], bytes[byteStart + 1], bytes[byteStart + 2], bytes[byteStart + 3], true);
                    word >>>= subChunkBitsPerBlock * blocksIn;
                    let value = 0;
                    for (let i = 0; i < subChunkBitsPerBlock; i++) {
                        let inc = word % 2;
                        inc <<= i;
                        value += inc;
                        word >>>= 1;
                    }
                    blockIndices.push(value);
                }
            }
        }
        return blockIndices;
    }
    parseSubChunk(subChunkIndex) {
        const bytes = this.subChunks[subChunkIndex].value;
        if (bytes === undefined || bytes.length <= 0) {
            Log_1.default.fail("Unexpected no bytes supplied for a chunk.");
            return;
        }
        let bitsPerBlock = 1;
        const subChunkVersion = bytes[0]; // should be 1 or 8 or (9 = C&C pt 2?)
        // legacy subchunk format for version 1.0/1.2.13, that predates palette-ized subchunks
        if (subChunkVersion < 8 && subChunkVersion !== 1) {
            this.subChunkFormatType[subChunkIndex] = SubChunkFormatType.subChunk1dot0;
            this.chunkMinY = 0;
            this.world.chunkMinY = Math.min(this.chunkMinY, this.world.chunkMinY);
            return;
        }
        if (!(subChunkVersion === 1 || subChunkVersion === 8 || subChunkVersion === 9)) {
            Log_1.default.fail("Unexpected sub chunk version (" + subChunkVersion + ")");
            return;
        }
        let storageAreas = 1;
        let index = 1;
        if (subChunkVersion !== 1) {
            storageAreas = bytes[1]; // is either one or two, to indicate the number of block storage areas.  the second is the auxiliary "water logged" block area
            Log_1.default.assert(storageAreas === 1 || storageAreas === 2, "Storage areas > 2 not expected");
            index++;
        }
        if (subChunkVersion === 9) {
            // not sure what this second byte is for. observed to be of values 0, 1, and 252
            // maybe this is the minimum subchunk level?
            const interimVal = bytes[2];
            Log_1.default.assert((interimVal >= 0 && interimVal <= 32) || (interimVal >= 224 && interimVal <= 256), "Unexpected chunk index");
            index++;
            this.chunkMinY = -512;
        }
        else if (subChunkVersion === 8) {
            this.chunkMinY = 0;
        }
        if (this.chunkMinY !== undefined) {
            this.world.chunkMinY = Math.min(this.chunkMinY, this.world.chunkMinY);
        }
        for (let sI = 0; sI < storageAreas; sI++) {
            if (bytes[index] % 2 === 1) {
                // if version LSB is set, this indicates a non-save persistence which we should probably never see
                Log_1.default.unexpectedError("Unexpected non-save persistence version found.");
            }
            bitsPerBlock = bytes[index] >>> 1;
            index++;
            // have observed empty subchunks with no actual data besides NBT records, where bitsPerBlock === 0
            if (bitsPerBlock === 0) {
                // const bp = new BlockPalette();
                //   index = bp.parseFromBytes(bytes, index, 1);
            }
            else {
                const blocksPerWord = Math.floor(32 / bitsPerBlock);
                const blockBytes = Math.ceil(4096 / blocksPerWord) * 4;
                let numPaletteEntries = DataUtilities_1.default.getUnsignedInteger(bytes[blockBytes + index], bytes[blockBytes + index + 1], bytes[blockBytes + index + 2], bytes[blockBytes + index + 3], true);
                if (numPaletteEntries >= 4096) {
                    // this is an odd workaround; but it seeems like some worlds have their num palette entries listed as big endian
                    numPaletteEntries = DataUtilities_1.default.getUnsignedInteger(bytes[blockBytes + index], bytes[blockBytes + index + 1], bytes[blockBytes + index + 2], bytes[blockBytes + index + 3], false);
                }
                Log_1.default.assert(numPaletteEntries <= 4096, "Unexpectedly large number of palette entries");
                const bp = new BlockPalette_1.default();
                const blockDataStartIndex = index;
                index = bp.parseFromBytes(bytes, blockBytes + index + 4, numPaletteEntries);
                if (bp.blocks.length !== numPaletteEntries) {
                    Log_1.default.unexpectedError("Unexpected block palette count mismatch.");
                }
                Log_1.default.assert(sI !== storageAreas - 1 || index === bytes.length, "Unexpectedly didn't consume entire subchunk.");
                if (sI === 0) {
                    this.blockDataStart[subChunkIndex] = blockDataStartIndex;
                    this.bitsPerBlock[subChunkIndex] = bitsPerBlock;
                    this.blockPalettes[subChunkIndex] = bp;
                    this.subChunkFormatType[subChunkIndex] = SubChunkFormatType.paletteFrom1dot2dot13;
                }
                else {
                    this.auxBlockDataStart[subChunkIndex] = blockDataStartIndex;
                    this.auxBitsPerBlock[subChunkIndex] = bitsPerBlock;
                    this.auxBlockPalettes[subChunkIndex] = bp;
                    this.subChunkFormatType[subChunkIndex] = SubChunkFormatType.paletteFrom1dot2dot13;
                }
            }
        }
    }
}
exports.default = WorldChunk;

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