Source: builder.js

import { Block, LevelObject } from "./block.js";
import { Dimension, Coordinate, Coordinate2D, Coordinate3D } from "./coordinate.js";
import { Level, Level2D, Level3D } from "./level.js";

/**
 * Represents a builder for creating LevelZ Levels.
 * @classdesc Level Builder for LevelZ
 */
export class LevelBuilder {

    /**
     * The dimension of the level.
     * @type {number}
     */
    dimension;

    /**
     * The headers of the level.
     * @type {Map<string, any>}
     */
    headers = new Map();

    /**
     * The blocks of the level.
     * @type {Set<LevelObject>}
     */
    blocks = new Set();

    /**
     * Constructs a new Level Builder.
     * @constructs LevelBuilder
     * @param {Dimension} dimension The dimension for the level.
     * @example
     * new LevelBuilder(Dimension.TWO)
     */
    constructor(dimension) {
        if (dimension !== 2 && dimension !== 3)
            throw new SyntaxError('Invalid dimension; must be 2 or 3')
        this.dimension = dimension
    }

    /**
     * Sets the spawn point for the level.
     * @param {Coordinate} coordinate The coordinate to spawn the object at.
     * @throws Invalid coordinate; must be 2D or 3D (according to the {@link dimension})
     */
    set spawn(coordinate) {
        if (coordinate instanceof Array) {
            if (coordinate.length !== this.dimension)
                throw new SyntaxError(`Invalid coordinate; must be ${this.dimension}D`)

            if (this.dimension === 2)
                this.headers.set('spawn', new Coordinate2D(coordinate[0], coordinate[1]))

            if (this.dimension === 3)
                this.headers.set('spawn', new Coordinate3D(coordinate[0], coordinate[1], coordinate[2]))

            return
        }

        if (this.dimension == 2 && !(coordinate instanceof Coordinate2D))
            throw new SyntaxError('Invalid coordinate; must be 2D')

        if (this.dimension == 3 && !(coordinate instanceof Coordinate3D))
            throw new SyntaxError('Invalid coordinate; must be 3D')

        this.headers.set('spawn', coordinate)
    }

    /**
     * Sets a header value.
     * @param {string} key The key of the header 
     * @param {*} value The value of the header
     */
    header(key, value) {
        this.headers.set(key, value)
    }

    /**
     * Adds a Block to the level.
     * @param {LevelObject} block The level object to add to the level. 
     * @throws Invalid coordinate; must be 2D or 3D (according to the {@link dimension})
     */
    block(block) {
        if (this.dimension === 2 && !(block.coordinate instanceof Coordinate2D))
            throw new SyntaxError('Invalid coordinate; object coordinate must be 2D')

        if (this.dimension === 3 && !(block.coordinate instanceof Coordinate3D))
            throw new SyntaxError('Invalid coordinate; object coordinate must be 3D')

        this.blocks.add(block)
    }

    /**
     * Adds a Block to the level.
     * @param {string} name The name of the block.
     * @param {Coordinate|number[]} coordinate The coordinate of the block.
     * @param {Map<string, any>|Object.<string, any>} properties The properties of the block.
     */
    block(name, coordinate, properties) {
        this.block(new LevelObject(new Block(name, properties), coordinate))
    }

    /**
     * Builds the level.
     * @returns {Level} The built level.
     * @example
     * LevelBuilder.create2D().block('grass', [0, 0]).build()
     */
    build() {
        if (this.dimension === 2) return new Level2D(this.headers, this.blocks)
        if (this.dimension === 3) return new Level3D(this.headers, this.blocks)

        throw new SyntaxError('Invalid dimension; must be 2 or 3')
    }

    // Statics

    /**
     * Creates a new 2D Level Builder.
     * @returns {LevelBuilder} A new 2D Level Builder.
     */
    static create2D() {
        return new LevelBuilder(Dimension.TWO)
    }

    /**
     * Creates a new 3D Level Builder.
     * @returns {LevelBuilder} A new 3D Level Builder.
     */
    static create3D() {
        return new LevelBuilder(Dimension.THREE)
    }

}