Source: coordinate.js

/**
 * 2D or 3D space.
 * @typedef {2|3} Dimension
 */

/**
 * Represents a Dimension.
 */
export const Dimension = {
    /**
     * Represents the 2D Dimension.
     * @type {Dimension}
     */
    TWO: 2,
    /**
     * Represents the 3D Dimension.
     * @type {Dimension}
     */
    THREE: 3
}

/**
 * Represents an abstract Coordinate.
 * @abstract
 * @classdesc Abstract Coordinate for representation purposes
 */
export class Coordinate {
    constructor() {
        if (new.target === Coordinate)
            throw new TypeError('Cannot instantiate abstract class')
    }

    /**
     * Returns the magnitude of this Coordinate.
     * @returns {number} The magnitude of this Coordinate.
     */
    get magnitude() {
        throw new Error('Abstract method')
    }

    /**
     * Returns the dimension of this Coordinate.
     * @returns {Dimension} The dimension of this Coordinate.
     */
    get dimension() {
        throw new Error('Abstract method')
    }
}

/**
 * Represents a 2D Coordinate.
 * @classdesc 2D Coordinates in LevelZ
 * @extends Coordinate
 */
export class Coordinate2D extends Coordinate {
    /**
     * The X value for this 2D Coordinate.
     * @type {number}
     */
    x;
    
    /**
     * The Y value for this 2D Coordinate.
     * @type {number}
     */
    y;

    /**
     * Constructs a new 2D Coordinate.
     * @constructor
     * @param {number} x The X Value
     * @param {number} y The Y Value
     * @example
     * new Coordinate2D(1, 2)
     */
    constructor(x, y) {
        super()
        if (isNaN(x) || isNaN(y))
            throw new SyntaxError('Invalid input')

        this.x = x
        this.y = y
    }
    
    get magnitude() {
        return Math.sqrt(this.x * this.x + this.y * this.y)
    }

    get dimension() {
        return Dimension.TWO
    }

    /**
     * Returns the string representation of this 2D Coordinate.
     * @returns {string} The string representation of this 2D Coordinate.
     */
    toString() {
        return `[${this.x}, ${this.y}]`
    }

    // Statics

    /**
     * Returns a new 2D Coordinate with X and Y at `0`.
     * @static
     * @returns {Coordinate2D} The 2D Coordinate
     */
    static get zero() {
        return new Coordinate2D(0, 0)
    }

    /**
     * Converts a string to a 2D Coordinate.
     * @static
     * @param {string} str The string to convert 
     * @returns {Coordinate2D} The 2D Coordinate
     * @throws {SyntaxError} If the string is invalid.
     * @example
     * Coordinate2D.fromString('[1, 2]')
     */
    static fromString(str) {
        if (!str || typeof(str) !== 'string')
            throw new SyntaxError('Invalid input')

        if (!str.startsWith('[') || !str.endsWith(']')) throw new SyntaxError('Invalid input')
        
        let values = str.substring(1, str.length - 1).split(',')
        if (values.length !== 2) throw new SyntaxError('Invalid input: 2 values expected')

        return new Coordinate2D(Number(values[0].trim()), Number(values[1].trim()))
    }

}

/**
 * Represents a 3D Coordinate.
 * @classdesc 3D Coordinates in LevelZ
 * @extends Coordinate
 */
export class Coordinate3D extends Coordinate {

    /**
     * The X value for this 3D Coordinate.
     * @type {number}
     */
    x;

    /**
     * The Y value for this 3D Coordinate.
     * @type {number}
     */
    y;

    /**
     * The Z value for this 3D Coordinate.
     * @type {number}
     */
    z;

    /**
     * Constructs a new 3D Coordinate.
     * @param {number} x The X Value
     * @param {number} y The Y Value
     * @param {number} z The Z Value
     * @example
     * new Coordinate3D(1, 2, 3) 
     */
    constructor(x, y, z) {
        super()
        if (isNaN(x) || isNaN(y) || isNaN(z))
            throw new SyntaxError('Invalid input')
        
        this.x = x
        this.y = y
        this.z = z   
    }

    get magnitude() {
        return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z)
    }

    get dimension() {
        return Dimension.THREE
    }

    /**
     * Returns the string representation of this 3D Coordinate.
     * @returns {string} The string representation of this 3D Coordinate.
     */
    toString() {
        return `[${this.x}, ${this.y}, ${this.z}]`
    }

    // Statics

    /**
     * Returns a new 3D Coordinate with X, Y and Z at `0`.
     * @static
     * @returns {Coordinate3D} The 3D Coordinate
     */
    static get zero() {
        return new Coordinate3D(0, 0, 0)
    }

    /**
     * Converts a string to a 3D Coordinate.
     * @static
     * @param {string} str The string to convert 
     * @returns {Coordinate3D} The 3D Coordinate
     * @example
     * Coordinate3D.fromString('[1, 2, 3]')
     */
    static fromString(str) {
        if (!str || typeof(str) !== 'string')
            throw new SyntaxError('Invalid input')
        
        if (!str.startsWith('[') || !str.endsWith(']')) throw new SyntaxError('Invalid input')

        let values = str.substring(1, str.length - 1).split(',')
        if (values.length !== 3) throw new SyntaxError('Invalid input: 3 values expected')

        return new Coordinate3D(Number(values[0].trim()), Number(values[1].trim()), Number(values[2].trim()))
    }

}