Source: nodes/base.js

import { EventEmitter } from "events";
import { OBJECT_PARAMETERS, OPTIONAL_OBJECT_PARAMETERS, MANDATORY_OBJECTS } from "../lib/objectList.js";

/**
 * @desc <strong>Constructor for internal use only</strong>
 * Base class for Max nodes in the Xebra state tree. Through Xebra, Max exposes patchers, mira.frame objects, other Max
 * objects and assignable parameters for each object. Each of these is represented by a different XebraNode subclass.
 * @class
 */
class XebraNode extends EventEmitter {
	/**
	 * @param  {number} id          The id of the node
	 * @param  {string} type        Type identifier of the node
	 * @param  {number} creationSeq The sequence number for the creation of this node
	 */
	constructor(id, type, creationSeq) {
		super();

		this._id = id;
		this._type = type;
		this._creationSeq = creationSeq;
		this._children = new Map();
		this._paramsNameLookup = new Map();
	}

	/**
	 * Destroys the node by destroying all child nodes and removing all attached listeners.
	 * @ignore
	 */
	destroy() {
		/**
		 * Object Destroyed event
		 * @alias XebraNode.destroy
		 * @event XebraNode.destroy
		 * @param {XebraNode} object     The destroyed object
		 */
		this.emit("destroy", this);

		this.removeAllListeners();
	}

	/**
	 * The creation sequence number associated with this node. This number is an increasing integer unique to each node.
	 * @member {number}
	 */
	get creationSequence() {
		return this._creationSeq;
	}

	/**
	 * Unique id associated with each XebraNode.
	 * @readonly
	 * @member {Xebra.NodeId}
	 */
	get id() {
		return this._id;
	}

	/**
	 * @desc Returns whether all of the parameters for the object have been added yet.
	 * @readonly
	 * @private
	 * @type {boolean}
	 */
	get isReady() {
		return true;
	}

	/**
	 * Type associated with this node. For Objects, Frames and Patchers, this will correspond to the class name of the
	 * Max object. For parameters, this will be the name of the associated parameter. Parameters usually correspond to
	 * the name of a Max object's attribute.
	 * @member {string}
	 */
	get type() {
		return this._type;
	}

	/**
	 * @private
	 */
	_getParamForType(type) {
		const id = this._paramsNameLookup.get(type);
		return this.getChild(id);
	}

	/**
	 * Callback when a parameter value is changed due to a modification in Max.
	 * @abstract
	 * @method
	 * @private
	 */
	_onParamChange() {
		throw new Error("Missing subclass implementation for _onParamChange");
	}

	/**
	 * Callback when a parameter value was set by the client.
	 * @abstract
	 * @method
	 * @private
	 */
	_onParamSet() {
		throw new Error("Missing subclass implementation for _onParamSet");
	}

	/**
	 * Adds a child.
	 * @ignore
	 * @param {Xebra.NodeId} id - The id of the child to be added
	 * @param {XebraNode} node - The child to add
	 */
	addChild(id, node) {
		this._children.set(id, node);
	}

	/**
	 * Execute callback function for each child of the node.
	 * @ignore
	 * @param {function} callback - The callback to execute
	 * @param {object} context - The context of the callback
	 */
	forEachChild(callback, context) {
		this._children.forEach(callback, context);
	}

	/**
	 * Returns the child with the given id.
	 * @ignore
	 * @param {Xebra.NodeId}
	 * @return {XebraNode|null}
	 */
	getChild(id) {
		return this._children.get(id) || null;
	}

	/**
	 * Returns all children of the node.
	 * @ignore
	 * @return {XebraNode[]}
	 */
	getChildren() {
		return Array.from(this._children.values());
	}

	/**
	 * Returns whether the given id is a direct child.
	 * @ignore
	 * @param {Xebra.NodeId} id - The id of the potential child
	 * @return {boolean}
	 */
	hasChild(id) {
		return this._children.has(id);
	}

	/**
	 * Removes the direct child connection to the node with the given id.
	 * @ignore
	 * @param {Xebra.NodeId} id - The id of the child to remove the connection to
	 */
	removeChild(id) {
		const child = this.getChild(id);
		if (child) this._children.delete(id);
		return child;
	}

	/**
	 * Adds a Parameter node to this node's children. Also adds the node as a listener for the Parameter node, so local
	 * and remote changes to that node will trigger {@link State.object_changed} events.
	 * @ignore
	 * @listens ParamNode#change
	 * @listens ParamNode#set
	 * @param {ParamNode} param The parameter to add
	 */
	addParam(param) {
		this._paramsNameLookup.set(param.type, param.id);

		param.on("change", this._onParamChange);
		param.on("set", this._onParamSet);

		this.addChild(param.id, param);
	}

	/**
	 * Returns a list of the names of all available parameters.
	 * @return {string[]}
	 */
	getParamTypes() {
		if (OBJECT_PARAMETERS.hasOwnProperty(this.type)) {
			return Object.freeze(OBJECT_PARAMETERS[this.type] || []);
		}
		return Object.freeze(MANDATORY_OBJECTS[this.type] || []);
	}

	/**
	 * Returns a list of the parameters that are not required for this object to be initialized.
	 * @ignore
	 * @return {string[]}
	 */
	getOptionalParamTypes() {
		return Object.freeze(OPTIONAL_OBJECT_PARAMETERS[this.type] || []);
	}

	/**
	 * Returns the value for the parameter with the name <i>type</i>.
	 * @param  {String} type - Parameter type identifier
	 * @return {Xebra.ParamValueType} returns the value(s) of the given parameter type or null
	 */
	getParamValue(type) {
		const param = this._getParamForType(type);
		if (param) return param.value;
		return null;
	}

	/**
	 * Sets the value for the parameter with the name <i>type</i> to the given value.
	 * @param {String} type - Parameter type identifier
	 * @param {Object} value - Parameter value
	 */
	setParamValue(type, value) {
		const param = this._getParamForType(type);
		if (param) param.value = value;
	}
}

export default XebraNode;