Source: nodes/resourceObjectMixin.js

import { OBJECTS } from "../lib/objectList.js";
import { EMPTY_RESOURCE } from "../lib/constants.js";
import { ResourceController } from "../lib/resource.js";

function isImage(v) {
	return v && v !== EMPTY_RESOURCE;
}

/**
 * Request and manage remote resources from the Max seach path. A resource includes both metadata, like image size and
 * file type, as well as the file data itself. Only images are currently supported.
 *
 * This Mixin is currently applied to instances of ObjectNode representing fpic, live.tab and live.text.
 *
 * @mixin ResourceObjectMixin
 * @example
 * // An ObjectNode that uses resources will return a nonzero value from getResourceCount
 * const fpicObject;
 * const resourceCount = fpicObject.getResourceCount(); // Will always be one
 *
 * // To get a specific resource, use the getResourceAtIndex function
 * const resource = fpicObject.getResourceAtIndex(0);
 *
 * // ObjectNodes that use resources will manage those resources on their own. If you'd like
 * // to handle the data for that resource as well, then you must register another listener.
 * // The resource doesn't hold on to data from Max once it receives it, so be sure to listen for {@link Resource.event:data_received}
 * // events in order to handle resource data.
 * resource.on("data_received", (filename, data_uri) => {
 * // Do something with the data
 * });
 *
 * // For resources that belong to an ObjectNode, it doesn't make sense to set the filename and
 * // dimensions properties of the resource directly. Rather, you can set the parameters of the
 * // ObjectNode, and it will manage resources itself.
 * fpicObject.setParamValue("pic", "alex.png"); // Will request new resource data.
 */
export default (objClass) => class extends objClass {

	constructor(id, type, creationSeq, patcherId) {
		super(...arguments);

		this._resources = [];
		this._resourceController = new ResourceController();
		let resourceCount = 0;

		if (type === OBJECTS.FPIC) {
			resourceCount = 1;
		} else if (type === OBJECTS.LIVE_TEXT) {
			resourceCount = 2;
		}

		for (let i = 0; i < resourceCount; i++) {
			let resource = this._resourceController.createResource(this.id);
			this._resources.push(resource);
		}
	}

	// Bound callbacks using fat arrow notation

	/**
	 * Callback for handling resource related parameter events
	 * @private
	 * @memberof ResourceObjectMixin
	 * @param {ParamNode}
	 */
	_onParamChangeForResources = (param) => {
		if (this._type === OBJECTS.FPIC) {
			if (param.type === "pic") {
				if (isImage(param.value)) {
					this._resources[0].filename = param.value;
				} else if (!this._resources[0].isEmpty) {
					this._resources[0].clear();
				}
			}
		} else if (this._type === OBJECTS.LIVE_TEXT) {
			if (param.type === "pictures") {
				for (let i = 0; i < param.value.length; i++) {
					if (isImage(param.value[i])) {
						this._resources[i].filename = param.value[i];
					} else if (!this._resources[i].isEmpty) {
						this._resources[i].clear();
					}
				}
			}
		} else if (this._type === OBJECTS.LIVE_TAB) {
			if (param.type === "pictures") {

				// For now, create a whole new array of resources
				this._resources.forEach(r => {
					r.destroy();
				});

				this._resources = [];

				if (param.value) {
					let enumerableValue = param.value;
					if (!(param.value instanceof Array)) {
						enumerableValue = [param.value];
					}

					enumerableValue.forEach((filename) => {
						let res = this._resourceController.createResource(this.id);
						this._resources.push(res);
						res.filename = filename;
					});
				}

				/**
				 * Resources Changed event. Fired internally whenever an object node has a new array of resources.
				 * @event ResourceObjectMixin.resources_changed
				 * @param {ObjectNode} object     This
				 */
				this.emit("resources_changed", this);
			}
		}
	}

	// End of bound callbacks

	/**
	 * @ignore
	 * @override
	 * @memberof ResourceObjectMixin
	 * @instance
	 */
	addParam(param) {
		super.addParam(param);
		param.on("change", this._onParamChangeForResources);
	}

	/**
	 * @memberof ResourceObjectMixin
	 * @instance
	 * @private
	 * @return {Object} the ResourceController for this object's Resources
	 */
	get resourceController() {
		return this._resourceController;
	}

	/**
	 * @memberof ResourceObjectMixin
	 * @instance
	 * @return {number} number of available resources
	 */
	getResourceCount() {
		return this._resources ? this._resources.length : 0;
	}

	/**
	 * @param {number} idx - The resource index
	 * @memberof ResourceObjectMixin
	 * @instance
	 * @throws throws an error if the resource index is out of bounds
	 */
	getResourceAtIndex(idx) {
		if (idx < 0 || idx >= this._resources.length) throw new Error(`Invalid Resource Index. Object has ${this.getResourceCount()} resources.`);
		return this._resources[idx];
	}

	/**
	 * @ignore
	 * @override
	 * @memberof ResourceObjectMixin
	 * @instance
	 */
	destroy() {
		super.destroy();
		this._resourceController.removeAllListeners();
		if (this._resources && this._resources.length) {
			this._resources.forEach((res) => {
				res.destroy();
			});
		}
	}

};