/* eslint-disable max-classes-per-file */
import { world } from '@minecraft/server';
import { ChunkXZUtils } from '../util/chunk';
import WBVectorXZ from '../util/wbVectorXZ';
export const CHUNK_SIZE = 10;
export const BLOCKS_PER_CHUNK = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE;
// Note: Do not change. It will break BiomeDataMap.getBiome().
export const BIOME_CHUNK_WIDTH = 3;
export const BIOME_SIZE = BIOME_CHUNK_WIDTH * CHUNK_SIZE;
const chunkToBiomeLoc = new ChunkXZUtils(BIOME_CHUNK_WIDTH);
export var Biome;
(function (Biome) {
    Biome["MANGROVE"] = "M";
    Biome["DESERT"] = "D";
    Biome["TAIGA"] = "T";
    Biome["JUNGLE"] = "J";
    Biome["SAVANNAH"] = "S";
})(Biome || (Biome = {}));
function numToString(num) {
    return (num < 0 ? '' : '+') + num.toString(36);
}
function saveMap(map, toRawValue) {
    map.forEach((v, k) => {
        if (v.changed) {
            const rawValue = toRawValue(v.value);
            world.setDynamicProperty(k, rawValue);
        }
    });
    map.clear();
}
class DataMap {
    constructor(flooredChunk) {
        this.propertyId = '';
        this.chunkLoc = flooredChunk;
        this.computePropertyId();
    }
    selectChunk(flooredChunk) {
        this.chunkLoc = flooredChunk;
        this.computePropertyId();
    }
    getChunk() {
        return this.chunkLoc;
    }
    getPropertyId() {
        return this.propertyId;
    }
}
export class BiomeDataMap extends DataMap {
    computePropertyId() {
        this.propertyId = `${this.chunkLoc.x.toString(36)}${numToString(this.chunkLoc.z)}`;
    }
    static toRawValue(value) {
        return value;
    }
    static fromRawValue(value) {
        return value;
    }
    get() {
        const cached = BiomeDataMap.map.get(this.propertyId);
        if (cached)
            return cached.value;
        const rawValue = world.getDynamicProperty(this.propertyId);
        const value = BiomeDataMap.fromRawValue(rawValue);
        BiomeDataMap.map.set(this.propertyId, { value, changed: false });
        return value;
    }
    set(value) {
        BiomeDataMap.map.set(this.propertyId, { value, changed: true });
    }
    static save() {
        saveMap(BiomeDataMap.map, BiomeDataMap.toRawValue);
    }
}
BiomeDataMap.map = new Map();
export class ChunkDataMap extends DataMap {
    computePropertyId() {
        this.propertyId = `${this.chunkLoc.x.toString(36)}${numToString(this.chunkLoc.y)}${numToString(this.chunkLoc.z)}`;
    }
    static toRawValue(value) {
        if (value === 0)
            return undefined;
        if (value >= BLOCKS_PER_CHUNK)
            return true;
        return value.toString(36);
    }
    static fromRawValue(rawValue) {
        if (typeof rawValue === 'string') {
            return parseInt(rawValue, 36);
        }
        return rawValue ? BLOCKS_PER_CHUNK : 0;
    }
    get() {
        const cached = ChunkDataMap.map.get(this.propertyId);
        if (cached)
            return cached.value;
        const rawValue = world.getDynamicProperty(this.propertyId);
        const value = ChunkDataMap.fromRawValue(rawValue);
        ChunkDataMap.map.set(this.propertyId, { value, changed: false });
        return value;
    }
    set(value) {
        ChunkDataMap.map.set(this.propertyId, { value, changed: true });
    }
    static save() {
        BiomeDataMap.save();
        saveMap(ChunkDataMap.map, ChunkDataMap.toRawValue);
    }
    getStrictBiome() {
        // Convert chunk space to biome space
        const biomeLoc = chunkToBiomeLoc.toChunkSpace(this.chunkLoc);
        const flooredBiomeLoc = biomeLoc.floorAll();
        // Get saved biome
        const biomeMap = new BiomeDataMap(flooredBiomeLoc);
        return biomeMap.get();
    }
    getBiome() {
        // Convert chunk space to biome space
        const biomeLoc = chunkToBiomeLoc.toChunkSpace(this.chunkLoc);
        const flooredBiomeLoc = biomeLoc.floorAll();
        // Get saved biome
        const biomeMap = new BiomeDataMap(flooredBiomeLoc);
        let biome = biomeMap.get();
        // If a biome is known for this 3x3 group of chunks, return that biome.
        // Otherwise, it is possible for a biome from a neighbouring chunk to overlap with this chunk.
        // Kind of like a sphere with a radius of 2.
        if (biome)
            return biome;
        // Find the offset of this chunk relative to the middle chunk of this 3x3 chunk group
        const localCenter = new WBVectorXZ(1, 1);
        const biomeLocInChunkSpace = chunkToBiomeLoc.fromChunkSpace(flooredBiomeLoc);
        // x, z <= {-1, 0, 1}
        const offsetFromCenter = WBVectorXZ
            .fromMC(this.chunkLoc)
            .subtract(biomeLocInChunkSpace)
            .subtract(localCenter);
        // If the offset is x=0,z=0, no overlap can reach this chunk.
        if (offsetFromCenter.equals({ x: 0, z: 0 }))
            return undefined;
        // If the offset is horizontal, look at the closest horizontal neighbour first.
        // Otherwise look at the nearest vertical neighbour.
        if (offsetFromCenter.x !== 0) {
            biomeMap.selectChunk(flooredBiomeLoc.add({ x: offsetFromCenter.x, z: 0 }));
            biome = biomeMap.get();
            if (biome)
                return biome;
        }
        biomeMap.selectChunk(flooredBiomeLoc.add({ x: 0, z: offsetFromCenter.z }));
        biome = biomeMap.get();
        return biome;
    }
    setBiome(biome) {
        // Convert chunk space to biome space
        const biomeLoc = chunkToBiomeLoc.toChunkSpace(this.chunkLoc);
        const flooredBiomeLoc = biomeLoc.floorAll();
        // Get saved biome
        const biomeMap = new BiomeDataMap(flooredBiomeLoc);
        biomeMap.set(biome);
    }
}
ChunkDataMap.map = new Map();
