Source: nodes/node.js

wcPlayNodes.wcClass.extend('wcNode', 'Node', '', {
  /**
   * The foundation class for all nodes.<br>
   * When inheriting, make sure to include 'this._super(parent, pos);' at the top of your init functions.<br>
   * <b>Should be inherited and never constructed directly</b>.
   * @class wcNode
   * @param {string} parent - The parent object of this node.
   * @param {wcPlay~Coordinates} pos - The position of this node in the visual editor.
   */
  init: function(parent, pos) {
    this._super(parent, pos);
    this.color = '#FFFFFF';
    if (!this.name) {
      this.name = '';
    }

    this._activeTracker = null;
    this._viewportSize = null;

    this.pos = {
      x: pos && pos.x || 0,
      y: pos && pos.y || 0
    };

    this.chain = {
      entry: [],
      exit: []
    };
    this.properties = [];

    this._meta = {
      flash: false,
      flashDelta: 0,
      color: null,
      broken: 0,
      awake: false,
      dirty: true,
      threads: [],
      description: '',
      details: ''
    };
    this._break = false;
    this._log = false;

    this._parent = parent;

    // Give the node its default properties.
    this.createProperty(wcNode.PROPERTY_ENABLED, wcPlay.PROPERTY.TOGGLE, true, {description: 'Disabled nodes will be treated as if they were not there, all connections will be ignored.', input: true, output: true});

    // Add this node to its parent.
    if (this._parent) {
      this._parent.__addNode(this);
    }

    // Assign this node a unique ID if possible.
    var engine = this.engine();
    if (engine) {
      this.id = engine.__nextNodeId();
    }
  },

  /**
   * @member {string} wcNode#name - The title name of this node, editable by the user and shown in the editor view.
   */
  name: '',

  /**
   * Outputs a log message.
   * @function wcNode#log
   * @param {...string} args - The log messages.
   */
  log: function(args) {
    /* eslint-disable no-console */
    args = Array.prototype.slice.call(arguments);
    args.splice(0, 0, 'wcNode:');
    console.log.apply(console, args);
    /* eslint-enable no-console */
  },

  /**
   * Outputs an error message.
   * @function wcNode#error
   * @param {...string} args - The log messages.
   */
  error: function(args) {
    /* eslint-disable no-console */
    args = Array.prototype.slice.call(arguments);
    args.splice(0, 0, 'wcNode ERROR:');
    if (console.error) {
      console.error.apply(console, args);
    } else {
      console.log.apply(console, args);
    }
    /* eslint-enable no-console */
  },

  /**
   * Inherits a new class from this node.
   * @function wcNode#extend
   * @param {string} className - The class name for your node, this should be unique between all global class names.
   * @param {string} displayName - The display name of your node.
   * @param {string} category - The category to display your node in the editor palette.
   * @param {Object} classDef - An object that defines your class with all functions and variables.
   */

  /**
   * Destroys and removes the node.
   * @function wcNode#destroy
   */
  destroy: function() {
    var i = 0, item = null;
    this.onDestroying();

    // Remove all links.
    for (i = 0; i < this.chain.entry.length; ++i) {
      item = this.chain.entry[i];
      this.disconnectEntry(item.name);
    }

    for (i = 0; i < this.chain.exit.length; ++i) {
      item = this.chain.exit[i];
      this.disconnectExit(item.name);
    }

    for (i = 0; i < this.properties.length; ++i) {
      item = this.properties[i];
      this.disconnectInput(item.name);
      this.disconnectOutput(item.name);
    }

    this.reset();

    // Remove the node from its parent.
    this._parent && this._parent.__removeNode(this);
    this.onDestroyed();
  },

  /**
   * Resets all properties to their initial values.
   * @function wcNode#reset
   */
  reset: function() {
    this.onReset();

    this.resetThreads();
    this._meta.awake = false;
    this._meta.dirty = true;
    this._meta.broken = 0;
    this._meta.paused = false;

    for (var i = 0; i < this.properties.length; ++i) {
      this.properties[i].value = this.properties[i].initialValue;
    }
  },

  /**
   * Resets only latent running threads.
   * @function wcNode#resetThreads
   */
  resetThreads: function() {
    var engine = this.engine();
    for (var i = 0; i < this._meta.threads.length; ++i) {
      var thread = this._meta.threads[i];
      if (typeof thread.id === 'number') {
        // Number values indicate either a timeout or an interval, clear them both.
        clearTimeout(thread.id);
        clearInterval(thread.id);
      } else if (typeof thread.id.__clear === 'function') {
        // wcNodeTimeoutEvent has a __clear method that will clear this timeout.
        thread.id.__clear();
      } else if (typeof thread.id.abort === 'function') {
        // jqXHR has an abort method that will stop the ajax call.
        // Using the built in fetch request will also create the abort method.
        thread.id.abort();
      } else if (typeof thread.id === 'function') {
        // A function callback is simply called.
        this._activeTracker = thread.tracker;
        thread.id();
      }
      engine && engine.endFlowTracker(thread.tracker);
    }
    this._meta.threads = [];
  },

  /**
   * Imports previously [exported]{@link wcNode#export} data to generate this node.
   * @function wcNode#import
   * @param {Object} data - The data to import.
   * @param {Number[]} [idMap] - If supplied, identifies a mapping of old ID's to new ID's, any not found in this list will be unchanged.
   */
  import: function(data, idMap) {
    var i = 0, chain = null, targetNode = null;
    this.onImporting(data, idMap);

    this.id = idMap && idMap[data.id] || data.id;
    this.name = data.name,
    this.color = data.color,
    this.pos.x = data.pos.x,
    this.pos.y = data.pos.y,
    this.debugBreak(data.breakpoint);

    // Restore property values.
    for (i = 0; i < data.properties.length; ++i) {
      this.initialProperty(data.properties[i].name, data.properties[i].initialValue);
      this.property(data.properties[i].name, data.properties[i].value);
    }

    var engine = this.engine();
    if (!engine) {
      return;
    }

    if (this.id > engine.__curNodeId()) {
      engine.__curNodeId(this.id);
    }

    // Re-connect all chains.
    for (i = 0; i < data.entryChains.length; ++i) {
      chain = data.entryChains[i];
      targetNode = engine.nodeById((idMap && idMap[chain.outNodeId]) || chain.outNodeId);
      if (targetNode && this._parent === targetNode._parent) {
        this.connectEntry(chain.inName, targetNode, chain.outName);
      }
    }
    for (i = 0; i < data.exitChains.length; ++i) {
      chain = data.exitChains[i];
      targetNode = engine.nodeById((idMap && idMap[chain.inNodeId]) || chain.inNodeId);
      if (targetNode && this._parent === targetNode._parent) {
        this.connectExit(chain.outName, targetNode, chain.inName);
      }
    }
    for (i = 0; i < data.inputChains.length; ++i) {
      chain = data.inputChains[i];
      targetNode = engine.nodeById((idMap && idMap[chain.outNodeId]) || chain.outNodeId);
      if (targetNode && this._parent === targetNode._parent) {
        this.connectInput(chain.inName, targetNode, chain.outName);
      }
    }
    for (i = 0; i < data.outputChains.length; ++i) {
      chain = data.outputChains[i];
      targetNode = engine.nodeById((idMap && idMap[chain.inNodeId]) || chain.inNodeId);
      if (targetNode && this._parent === targetNode._parent) {
        this.connectOutput(chain.outName, targetNode, chain.inName);
      }
    }
    this._meta.dirty = true;

    this.onImported(data, idMap);
  },

  /**
   * Exports information about this node as well as all connected chain data so it can be [imported]{@link wcNode#import} later.
   * @function wcNode#export
   * @param {boolean} [minimal] - If true, only the most important data should be exported, this means current values and redundant link connections are omitted.
   * @returns {Object} - The exported data for this node.
   */
  export: function(minimal) {
    var data = {
      className: this.className,
      id: this.id,
      name: this.name,
      color: this.color,
      pos: {
        x: this.pos.x,
        y: this.pos.y
      },
      breakpoint: this._break,
      properties: this.listProperties(minimal),
      exitChains: this.listExitChains(),
      outputChains: this.listOutputChains()
    };

    // Include additional info if we aren't minimal
    if (!minimal) {
      data.entryChains = this.listEntryChains();
      data.inputChains = this.listInputChains();
    } else {
      data.entryChains = [];
      data.inputChains = [];
    }

    this.onExport(data, minimal);
    return data;
  },

  /**
   * Retrieves the wcPlay engine that owns this node.
   * @function wcNode#engine
   * @returns {wcPlay|null} - Either the wcPlay engine, or null if it doesn't belong to one.
   */
  engine: function() {
    var play = this._parent;
    while (play && !(play.instanceOf('wcPlay'))) {
      play = play._parent;
    }
    return play || null;
  },

  /**
   * Sets, or Gets this node's enabled state.
   * @function wcNode#enabled
   * @param {boolean} [enabled] - If supplied, will assign a new enabled state.
   * @returns {boolean} - The current enabled state.
   */
  enabled: function(enabled) {
    if (enabled !== undefined) {
      this.property(wcNode.PROPERTY_ENABLED, enabled? true: false);
      this._meta.dirty = true;
    }

    return this.property(wcNode.PROPERTY_ENABLED);
  },

  /**
   * Gets, or Sets whether this node is paused, or any nodes inside if it is a composite.<br>
   * When pausing, all {@link wcNode#setTimeout} events are also paused so they don't jump ahead of the debugger.
   * @function wcNode#paused
   * @param {boolean} paused - If supplied, will assign a new paused state.
   * @returns {boolean} - Whether this, or inner nodes, are paused.
   */
  paused: function(paused) {
    var i = 0;
    if (paused !== undefined) {
      // Pausing the node.
      if (paused) {
        for (i = 0; i < this._meta.threads.length; ++i) {
          if (typeof this._meta.threads[i].id.pause === 'function') {
            // wcNodeTimeoutEvent has a pause method.
            this._meta.threads[i].id.pause();
          }
        }
        this._meta.paused = true;
      } else {
        for (i = 0; i < this._meta.threads.length; ++i) {
          if (typeof this._meta.threads[i].id.resume === 'function') {
            // wcNodeTimeoutEvent has a resume method.
            this._meta.threads[i].id.resume();
          }
        }

        this._meta.paused = false;
      }
    }
    return this._meta.paused;
  },

  /**
   * Retrieves whether the node has been broken via breakpoint in the debugger tool.
   * @function wcNode#isBroken
   * @returns {boolean} - Whether the script is 'broken' (paused).
   */
  isBroken: function() {
    return this._meta.broken > 0;
  },

  /**
   * Sets, or Gets this node's debug log state.
   * @function wcNode#debugLog
   * @param {boolean} [enabled] - If supplied, will assign a new debug log state.
   * @returns {boolean} - The current debug log state.
   */
  debugLog: function(enabled) {
    if (enabled !== undefined) {
      this._log = enabled? true: false;
    }

    var engine = this.engine();
    return (!engine || engine.silent())? false: this._log;
  },

  /**
   * Sets, or Gets this node's debug pause state.
   * @function wcNode#debugBreak
   * @param {boolean} [enabled] - If supplied, will assign a new debug pause state.
   * @returns {boolean} - The current debug pause state.
   */
  debugBreak: function(enabled) {
    if (enabled !== undefined) {
      this._break = enabled? true: false;
    }

    var engine = this.engine();
    return (engine && engine.debugging() && this._break);
  },

  /**
   * Gets, or Sets the description for this node. This is usually shown as a tooltip for the node within the editor tool.
   * @function wcNode#description
   * @param {string} [description] - If supplied, will assign a new description for this node.
   * @returns {string} - The current description of this node.
   */
  description: function(description) {
    if (description !== undefined) {
      this._meta.description = description;
    }

    return this._meta.description;
  },

  /**
   * Gets, or Sets the very verbose description details for this node. This is usually shown as a popup dialog to further explain the user of the node.
   * @function wcNode#details
   * @param {string} [details] - If supplied, will assign a new description details for this node.
   * @returns {string} - The current description details of this node.
   */
  details: function(details) {
    if (details !== undefined) {
      this._meta.details = details;
    }

    return this._meta.details;
  },

  /**
   * Determines whether a search value matches this node.
   * @function wcNode#search
   * @param {string} search - The search value.
   * @returns {boolean} - True if the search matches this node.
   */
  search: function(search) {
    if (this.type.toLowerCase().indexOf(search) > -1 ||
        this.name.toLowerCase().indexOf(search) > -1) {
      return true;
    }

    return false;
  },

  /**
   * Utility function for setting a timed event in a way that is compatible with live debugging in the editor tool.
   * @function wcNode#setTimeout
   * @param {Function} callback - A callback function to call when the time has elapsed. As an added convenience, 'this' will be the node instance.
   * @param {number} delay - The time delay, in milliseconds, to wait before calling the callback function.
   * @example
   * onActivated: function(name) {
   *   this._super(name);
   *
   *   // Now set a timeout to wait for 'Milliseconds' amount of time.
   *   var delay = this.property('milliseconds');
   *
   *   // Start a timeout event using the node's built in timeout handler.
   *   this.setTimeout(function() {
   *     this.activateExit('out');
   *   }, delay);
   * }
   */
  setTimeout: function(callback, delay) {
    var timer = new wcNodeTimeoutEvent(this, callback, delay);
    this.beginThread(timer);
    if (!this._meta.paused) {
      timer.resume();
    }
  },

  /** <br>
   * Utility function for setting an interval update in a way that is compatible with live debugging in the editor tool.
   * <b>Note:</b> You can call {@link wcNode#resetThreads} if you want to cancel any existing intervals running on your node.
   * @function wcNode#setInterval
   * @param {Function} callback - A callback function to call each time the time interval has elapsed. As an added convenience, 'this' will be the node instance.
   * @param {number} interval - The time interval, in milliseconds, between each call to callback.
   * @example
   * onActivated: function(name) {
   *   var interval = this.property('milliseconds');
   *   this.resetThreads();
   *
   *   this.setInterval(function() {
   *     this.activateExit('out');
   *   }, interval);
   * }
   */
  setInterval: function(callback, interval) {
    function __onInterval() {
      callback && callback.call(this);

      // Really just call the set timeout, over and over.
      this.setTimeout(__onInterval, interval);
    }

    this.setTimeout(__onInterval, interval);
  },

  /**
   * Utility function for performing an AJAX request in a way that is compatible with live debugging in the editor tool.<br>
   * The success, error, and complete callback functions are changed so that the 'this' object is the node instance, or the custom context if you provided a context in your options.<br>
   * Note: This method specifically uses JQuery for the ajax operation, so you will need to include that library if you intend to use this.
   * @function wcNode#ajax
   * @param {string} [url] - Option URL to send the request, if not supplied, it should be provided in the options parameter.
   * @param {Object} [options] - The options for the request, as described here: {@link http://api.jquery.com/jquery.ajax/}.
   * @returns {jqXHR|function} - The jQuery XHR object generated by the ajax request. If an older version of jQuery is used, you will receive a function instead.
   */
  ajax: function(url, options) {
    if (typeof url === 'object') {
      options = url;
    }

    if (!options) {
      options = {};
    }

    if (typeof url === 'string') {
      options.url = url;
    }

    var cancelled = false;
    var self = this;
    var context = options.context || this;
    function __wrapCallbackComplete(cb) {
      return function() {
        setTimeout(function() {
          self.finishThread(xhr);
        }, 0);
        __wrapCallback(cb)();
      };
    }
    function __wrapCallback(cb) {
      return function() {
        if (!cancelled) {
          cb && cb.apply(context, arguments);
        }
      };
    }

    options.success  = __wrapCallback(options.success);
    options.error    = __wrapCallback(options.error);
    options.complete = __wrapCallbackComplete(options.complete);

    var xhr = $.ajax(options);

    // Failsafe in case we are running an older version of jQuery which does not yet return the jqXHR object.
    if (xhr === undefined) {
      xhr = function() {
        cancelled = true;
      };
    }
    return this.beginThread(xhr);
  },

  /**
   * Utility function for performing a fetch request in a way that is compatible with live debugging in the editor tool.<br>
   * The success, error, and complete callback functions are changed so that the 'this' object is the node instance, or the custom context if you provided a context in your options.<br>
   * Note: This method specifically uses browsers fetch which is an experimental technology and not supported by all browsers unless a polyfill is used.
   * @see https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
   * @function wcNode#fetch
   * @param {string} url - URL to send the request.
   * @param {Object} [options] - The options for the request, as described here: {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch}.
   * @returns {Promise.<Object>} - The promise object that carries the response. This will throw if the response was not a success.
   */
  fetch: function(url, options) {
    var cancelled = false;
    var self = this;
    var promise = null;
    try {
      promise = fetch(url, options).catch(function(err) {
        // Something bad happened before we got a response! Perhaps invalid options?
        setTimeout(function() {
          self.finishThread(promise);
        }, 0);
        throw err;
      }).then(function(result) {
        // Finish the thread.
        setTimeout(function() {
          self.finishThread(promise);
        }, 0);

        // Check the status of the response.
        if (!cancelled) {
          if (result.status >= 200 && result.status < 300) {
            return result.text();
          } else {
            var err = new Error(result.statusText);
            err.response = result;
            throw err;
          }
        } else {
          throw new Error('Fetch operation was cancelled.');
        }
      });
    } catch (err) {
      promise = new Promise(function(resolve, reject) {
        reject(err);
      });
    }
    // Provide my own 'abort' method to call in the
    // case we want to cancel the request.
    // Promises can not really be cancelled, but I can
    // at least stop the chain from reaching the caller.
    promise.abort = function() {
      cancelled = true;
    };
    return this.beginThread(promise);
  },

  /**
   * If your node takes time to process, call this to begin a thread that will keep the node 'active' until you close the thread with {@link wcNode#finishThread}.<br>
   * This ensures that, even if a node is executed more than once at the same time, each 'thread' is kept track of individually.<br>
   * <b>Note:</b> This is not necessary if your node executes immediately without a timeout.
   * <b>Also Note:</b> If using a setTimeout event, it is recommended that you use {@link wcNode#setTimeout} instead.
   * @function wcNode#beginThread
   * @param {Number|Function} id - The thread ID, generated by a call to setTimeout, setInterval, or a function to call when we want to force cancel the job.
   * @returns {number} - The id that was given {@link wcNode#finishThread}.
   * @example
   *  onActivated: function(name) {
   *    this._super(name);
   *
   *    // Always fire the 'out' link immediately.
   *    this.activateExit('out');
   *
   *    // Now set a timeout to wait for 'Milliseconds' amount of time.
   *    var self = this;
   *    var delay = this.property('milliseconds');
   *
   *    // Start a new thread that will keep the node alive until we are finished.
   *    var thread = this.beginThread(setTimeout(function() {
   *      // Once the time has completed, fire the 'Finished' link and finish our thread.
   *      self.activateExit('finished');
   *      self.finishThread(thread);
   *    }, delay));
   *  }
   */
  beginThread: function(id) {
    var thread = {
      id: id,
      tracker: this._activeTracker
    };

    this._meta.threads.push(thread);
    this._meta.flash = true;
    this._meta.awake = true;
    return id;
  },

  /**
   * Finishes a previously started thread from {@link wcNode#beginThread}.<br>
   * <b>Note:</b> If you do not properly finish a thread that was generated, your node will remain forever in its active state.
   * @function wcNode#finishThread
   * @param {Number|Function} id - The thread ID to close, returned to you by the call to {@link wcNode#beginThread}.
   */
  finishThread: function(id) {
    var index = this._meta.threads.findIndex(function(thread) {
      return thread.id === id;
    });

    if (index > -1) {
      var tracker = this._meta.threads[index].tracker;

      this._meta.threads.splice(index, 1);

      if (!this._meta.threads.length) {
        this._meta.awake = false;
      }

      // Finish any trackers.
      var engine = this.engine();
      setTimeout(function() {
        engine && engine.endFlowTracker(tracker);
      });
      this._activeTracker = tracker;
    }
  },

  /**
   * Gets, or Sets the current position of the node.
   * @function wcNode#pos
   * @param {wcPlay~Coordinates} [pos] - If supplied, will assign a new position for this node.
   * @returns {wcPlay~Coordinates} - The current position of this node.
   */
  pos: function(pos) {
    if (pos !== undefined) {
      this.pos.x = pos.x;
      this.pos.y = pos.y;
      this._meta.dirty = true;
    }

    return {x: this.pos.x, y: this.pos.y};
  },

  /**
   * Creates a new entry link on the node.
   * @function wcNode#createEntry
   * @param {string} name - The name of the entry link.
   * @param {string} [description] - An optional description to display as a tooltip for this link.
   * @returns {boolean} - Fails if the entry link name already exists.
   */
  createEntry: function(name, description) {
    for (var i = 0; i < this.chain.entry.length; ++i) {
      if (this.chain.entry[i].name === name) {
        return false;
      }
    }

    this.chain.entry.push({
      name: name,
      active: false,
      links: [],
      meta: {
        flash: false,
        flashDelta: 0,
        broken: 0,
        color: '#000000',
        description: description
      }
    });
    this._meta.dirty = true;
    return true;
  },

  /**
   * Creates a new exit link on the node.
   * @function wcNode#createExit
   * @param {string} name - The name of the exit link.
   * @param {string} [description] - An optional description to display as a tooltip for this link.
   * @returns {boolean} - Fails if the exit link name already exists.
   */
  createExit: function(name, description) {
    for (var i = 0; i < this.chain.exit.length; ++i) {
      if (this.chain.exit[i].name === name) {
        return false;
      }
    }

    this.chain.exit.push({
      name: name,
      links: [],
      meta: {
        flash: false,
        flashDelta: 0,
        broken: 0,
        color: '#000000',
        description: description
      }
    });
    this._meta.dirty = true;
    return true;
  },

  /**
   * Creates a new property.
   * @function wcNode#createProperty
   * @param {string} name - The name of the property.
   * @param {wcPlay.PROPERTY} type - The type of property.
   * @param {Object} [initialValue] - A initial value for this property when the script starts.
   * @param {Object} [options] - Additional options for this property, see {@link wcPlay.PROPERTY}.
   * @returns {boolean} - Fails if the property does not exist.
   */
  createProperty: function(name, type, initialValue, options) {
    // Make sure this property doesn't already exist.
    for (var i = 0; i < this.properties.length; ++i) {
      if (this.properties[i].name === name) {
        return false;
      }
    }

    if (initialValue === undefined) {
      initialValue = 0;
    }

    this.properties.push({
      name: name,
      value: initialValue,
      initialValue: initialValue,
      type: type,
      inputs: [],
      outputs: [],
      options: options || {},
      inputMeta: {
        flash: false,
        flashDelta: 0,
        broken: 0,
        color: '#000000'
      },
      outputMeta: {
        flash: false,
        flashDelta: 0,
        broken: 0,
        color: '#000000'
      }
    });
    this._meta.dirty = true;
    return true;
  },

  /**
   * Removes an entry link from the node.
   * @function wcNode#removeEntry
   * @param {string} name - The name of the entry link to remove.
   * @returns {boolean} - Fails if the link does not exist.
   */
  removeEntry: function(name) {
    for (var i = 0; i < this.chain.entry.length; ++i) {
      if (this.chain.entry[i].name === name) {
        if (this.disconnectEntry(name) === wcNode.CONNECT_RESULT.SUCCESS) {
          this.chain.entry.splice(i, 1);
          this._meta.dirty = true;
          return true;
        }
      }
    }
    return false;
  },

  /**
   * Removes an exit link from the node.
   * @function wcNode#removeExit
   * @param {string} name - The name of the exit link to remove.
   * @returns {boolean} - Fails if the link does not exist.
   */
  removeExit: function(name) {
    for (var i = 0; i < this.chain.exit.length; ++i) {
      if (this.chain.exit[i].name === name) {
        if (this.disconnectExit(name) === wcNode.CONNECT_RESULT.SUCCESS) {
          this.chain.exit.splice(i, 1);
          this._meta.dirty = true;
          return true;
        }
      }
    }
    return false;
  },

  /**
   * Removes a property from the node.
   * @function wcNode#removeProperty
   * @param {string} name - The name of the property to remove.
   * @returns {boolean} - Fails if the property does not exist.
   */
  removeProperty: function(name) {
    for (var i = 0; i < this.properties.length; ++i) {
      if (this.properties[i].name === name) {
        if (this.disconnectInput(name) === wcNode.CONNECT_RESULT.SUCCESS &&
            this.disconnectOutput(name) === wcNode.CONNECT_RESULT.SUCCESS) {
          this.properties.splice(i, 1);
          this._meta.dirty = true;
          return true;
        }
      }
    }
    return false;
  },

  /**
   * Renames an entry link on this node while preserving all connected chains.
   * @function wcNode#renameEntry
   * @param {string} oldName - The old (current) name of the link.
   * @param {string} newName - The new name of the link.
   * @returns {boolean} - Fails if the new name already exists, or the old name does not.
   */
  renameEntry: function(oldName, newName) {
    if (!this.createEntry(newName)) {
      return false;
    }

    if (this.createEntry(oldName)) {
      this.removeEntry(oldName);
      this.removeEntry(newName);
      return false;
    }

    var chains = this.listEntryChains(oldName);
    this.removeEntry(oldName);

    var engine = this.engine();
    if (engine) {
      for (var i = 0; i < chains.length; ++i) {
        this.connectEntry(newName, engine.nodeById(chains[i].outNodeId), chains[i].outName);
      }
    }
    this._meta.dirty = true;
    return true;
  },

  /**
   * Renames an exit link on this node while preserving all connected chains.
   * @function wcNode#renameExit
   * @param {string} oldName - The old (current) name of the link.
   * @param {string} newName - The new name of the link.
   * @returns {boolean} - Fails if the new name already exists, or the old name does not.
   */
  renameExit: function(oldName, newName) {
    if (!this.createExit(newName)) {
      return false;
    }

    if (this.createExit(oldName)) {
      this.removeExit(oldName);
      this.removeExit(newName);
      return false;
    }

    var chains = this.listExitChains(oldName);
    this.removeExit(oldName);

    var engine = this.engine();
    if (engine) {
      for (var i = 0; i < chains.length; ++i) {
        this.connectExit(newName, engine.nodeById(chains[i].inNodeId), chains[i].inName);
      }
    }
    this._meta.dirty = true;
    return true;
  },

  /**
   * Renames a property on this node while preserving all connected chains.
   * @function wcNode#renameProperty
   * @param {string} oldName - The old (current) name of the link.
   * @param {string} newName - The new name of the link.
   * @returns {boolean} - Fails if the new name already exists, or the old name does not.
   */
  renameProperty: function(oldName, newName) {
    var prop = null, i = 0;
    for (i = 0; i < this.properties.length; ++i) {
      if (this.properties[i].name === oldName) {
        prop = this.properties[i];
      }
      if (this.properties[i].name === newName) {
        return false;
      }
    }

    if (!prop) {
      return false;
    }

    this.createProperty(newName, prop.type, prop.initialValue, prop.options);
    this.property(newName, prop.value, false);

    var inputChains = this.listInputChains(oldName);
    var outputChains= this.listOutputChains(oldName);
    this.removeProperty(oldName);

    var engine = this.engine();
    if (engine) {
      for (i = 0; i < inputChains.length; ++i) {
        this.connectInput(newName, engine.nodeById(inputChains[i].outNodeId), inputChains[i].outName);
      }
      for (i = 0; i < outputChains.length; ++i) {
        this.connectOutput(newName, engine.nodeById(outputChains[i].inNodeId), outputChains[i].inName);
      }
    }
    this._meta.dirty = true;
    return true;
  },

  /**
   * Connects an entry link on this node to an exit link of another.
   * @function wcNode#connectEntry
   * @param {string} name - The name of the entry link on this node.
   * @param {wcNode} targetNode - The target node to link to.
   * @param {string} targetName - The name of the target node's exit link to link to.
   * @returns {wcNode.CONNECT_RESULT} - The result.
   */
  connectEntry: function(name, targetNode, targetName) {
    if (!(targetNode && targetNode.instanceOf('wcNode'))) {
      return wcNode.CONNECT_RESULT.NOT_FOUND;
    }

    var myLink = null;
    var targetLink = null;
    var i = 0;

    // Find my link.
    for (i = 0; i < this.chain.entry.length; ++i) {
      if (this.chain.entry[i].name === name) {
        myLink = this.chain.entry[i];
        break;
      }
    }

    // Find the target link.
    for (i = 0; i < targetNode.chain.exit.length; ++i) {
      if (targetNode.chain.exit[i].name === targetName) {
        targetLink = targetNode.chain.exit[i];
        break;
      }
    }

    if (!myLink || !targetLink) {
      return wcNode.CONNECT_RESULT.NOT_FOUND;
    }

    // Make sure the connection doesn't already exist.
    for (i = 0; i < myLink.links.length; ++i) {
      if (myLink.links[i].node === targetNode && myLink.links[i].name === targetLink.name) {
        return wcNode.CONNECT_RESULT.ALREADY_CONNECTED;
      }
    }

    for (i = 0; i < targetLink.links.length; ++i) {
      if (targetLink.links[i].node === this && targetLink.links[i].name === myLink.name) {
        return wcNode.CONNECT_RESULT.ALREADY_CONNECTED;
      }
    }

    // Now make the connection.
    myLink.links.push({
      name: targetLink.name,
      node: targetNode
    });

    targetLink.links.push({
      name: myLink.name,
      node: this
    });

    // Notify of the connection change.
    this.onConnect(true, myLink.name, wcNode.LINK_TYPE.ENTRY, targetNode, targetLink.name, wcNode.LINK_TYPE.EXIT);
    targetNode.onConnect(true, targetLink.name, wcNode.LINK_TYPE.EXIT, this, myLink.name, wcNode.LINK_TYPE.ENTRY);
    return wcNode.CONNECT_RESULT.SUCCESS;
  },

  /**
   * Connects an exit link on this node to an entry link of another.
   * @function wcNode#connectExit
   * @param {string} name - The name of the exit link on this node.
   * @param {wcNode} targetNode - The target node to link to.
   * @param {string} targetName - The name of the target node's entry link to link to.
   * @returns {wcNode.CONNECT_RESULT} - The result.
   */
  connectExit: function(name, targetNode, targetName) {
    if (!(targetNode && targetNode.instanceOf('wcNode'))) {
      return wcNode.CONNECT_RESULT.NOT_FOUND;
    }

    var myLink = null;
    var targetLink = null;
    var i = 0;

    // Find my link.
    for (i = 0; i < this.chain.exit.length; ++i) {
      if (this.chain.exit[i].name === name) {
        myLink = this.chain.exit[i];
        break;
      }
    }

    // Find the target link.
    for (i = 0; i < targetNode.chain.entry.length; ++i) {
      if (targetNode.chain.entry[i].name === targetName) {
        targetLink = targetNode.chain.entry[i];
        break;
      }
    }

    if (!myLink || !targetLink) {
      return wcNode.CONNECT_RESULT.NOT_FOUND;
    }

    // Make sure the connection doesn't already exist.
    for (i = 0; i < myLink.links.length; ++i) {
      if (myLink.links[i].node === targetNode && myLink.links[i].name === targetLink.name) {
        return wcNode.CONNECT_RESULT.ALREADY_CONNECTED;
      }
    }

    for (i = 0; i < targetLink.links.length; ++i) {
      if (targetLink.links[i].node === this && targetLink.links[i].name === myLink.name) {
        return wcNode.CONNECT_RESULT.ALREADY_CONNECTED;
      }
    }

    // Now make the connection.
    myLink.links.push({
      name: targetLink.name,
      node: targetNode
    });

    targetLink.links.push({
      name: myLink.name,
      node: this
    });

    // Notify of the connection change.
    this.onConnect(true, myLink.name, wcNode.LINK_TYPE.EXIT, targetNode, targetLink.name, wcNode.LINK_TYPE.ENTRY);
    targetNode.onConnect(true, targetLink.name, wcNode.LINK_TYPE.ENTRY, this, myLink.name, wcNode.LINK_TYPE.EXIT);
    return wcNode.CONNECT_RESULT.SUCCESS;
  },

  /**
   * Connects a property input link to a target property output link.
   * @function wcNode#connectInput
   * @param {string} name - The name of the property being connected.
   * @param {wcNode} targetNode - The target node to connect with.
   * @param {string} targetName - The name of the property on the target node to connect with.
   * @returns {wcNode.CONNECT_RESULT} - The result.
   */
  connectInput: function(name, targetNode, targetName) {
    if (!(targetNode && targetNode.instanceOf('wcNode'))) {
      return wcNode.CONNECT_RESULT.NOT_FOUND;
    }

    var myProperty = null;
    var targetProperty = null;
    var i = 0;

    // Find my property.
    for (i = 0; i < this.properties.length; ++i) {
      if (this.properties[i].name === name) {
        myProperty = this.properties[i];
        break;
      }
    }

    // Find the target property.
    for (i = 0; i < targetNode.properties.length; ++i) {
      if (targetNode.properties[i].name === targetName) {
        targetProperty = targetNode.properties[i];
        break;
      }
    }

    if (!myProperty || !targetProperty) {
      return wcNode.CONNECT_RESULT.NOT_FOUND;
    }

    // Make sure the connection doesn't already exist.
    for (i = 0; i < myProperty.inputs.length; ++i) {
      if (myProperty.inputs[i].node === targetNode && myProperty.inputs[i].name === targetProperty.name) {
        return wcNode.CONNECT_RESULT.ALREADY_CONNECTED;
      }
    }

    for (i = 0; i < targetProperty.outputs.length; ++i) {
      if (targetProperty.outputs[i].node === this && targetProperty.outputs[i].name === myProperty.name) {
        return wcNode.CONNECT_RESULT.ALREADY_CONNECTED;
      }
    }

    // Ask if this node can connect with the other.
    if (myProperty.options.inputCondition &&
        !myProperty.options.inputCondition.call(this, targetNode, targetProperty.name)) {
      return wcNode.CONNECT_RESULT.REFUSED;
    }

    // Ask if the other node can connect with this.
    if (targetProperty.options.outputCondition &&
        !targetProperty.options.outputCondition.call(targetNode, this, myProperty.name)) {
      return wcNode.CONNECT_RESULT.REFUSED;
    }

    // Now make the connection.
    myProperty.inputs.push({
      name: targetProperty.name,
      node: targetNode
    });

    targetProperty.outputs.push({
      name: myProperty.name,
      node: this
    });

    // Notify of the connection change.
    this.onConnect(true, myProperty.name, wcNode.LINK_TYPE.INPUT, targetNode, targetProperty.name, wcNode.LINK_TYPE.OUTPUT);
    targetNode.onConnect(true, targetProperty.name, wcNode.LINK_TYPE.OUTPUT, this, myProperty.name, wcNode.LINK_TYPE.INPUT);
    return wcNode.CONNECT_RESULT.SUCCESS;
  },

  /**
   * Connects a property output link to a target property input link.
   * @function wcNode#connectOutput
   * @param {string} name - The name of the property being connected.
   * @param {wcNode} targetNode - The target node to connect with.
   * @param {string} targetName - The name of the property on the target node to connect with.
   * @returns {wcNode.CONNECT_RESULT} - The result.
   */
  connectOutput: function(name, targetNode, targetName) {
    if (!(targetNode && targetNode.instanceOf('wcNode'))) {
      return wcNode.CONNECT_RESULT.NOT_FOUND;
    }

    var myProperty = null;
    var targetProperty = null;
    var i = 0;

    // Find my property.
    for (i = 0; i < this.properties.length; ++i) {
      if (this.properties[i].name === name) {
        myProperty = this.properties[i];
        break;
      }
    }

    // Find the target property.
    for (i = 0; i < targetNode.properties.length; ++i) {
      if (targetNode.properties[i].name === targetName) {
        targetProperty = targetNode.properties[i];
        break;
      }
    }

    if (!myProperty || !targetProperty) {
      return wcNode.CONNECT_RESULT.NOT_FOUND;
    }

    // Make sure the connection doesn't already exist.
    for (i = 0; i < myProperty.outputs.length; ++i) {
      if (myProperty.outputs[i].node === targetNode && myProperty.outputs[i].name === targetProperty.name) {
        return wcNode.CONNECT_RESULT.ALREADY_CONNECTED;
      }
    }

    for (i = 0; i < targetProperty.inputs.length; ++i) {
      if (targetProperty.inputs[i].node === this && targetProperty.inputs[i].name === myProperty.name) {
        return wcNode.CONNECT_RESULT.ALREADY_CONNECTED;
      }
    }

    // Ask if this node can connect with the other.
    if (myProperty.options.outputCondition &&
        !myProperty.options.outputCondition.call(this, targetNode, targetProperty.name)) {
      return wcNode.CONNECT_RESULT.REFUSED;
    }

    // Ask if the other node can connect with this.
    if (targetProperty.options.inputCondition &&
        !targetProperty.options.inputCondition.call(targetNode, this, myProperty.name)) {
      return wcNode.CONNECT_RESULT.REFUSED;
    }

    // Now make the connection.
    myProperty.outputs.push({
      name: targetProperty.name,
      node: targetNode
    });

    targetProperty.inputs.push({
      name: myProperty.name,
      node: this
    });

    // Notify of the connection change.
    this.onConnect(true, myProperty.name, wcNode.LINK_TYPE.OUTPUT, targetNode, targetProperty.name, wcNode.LINK_TYPE.INPUT);
    targetNode.onConnect(true, targetProperty.name, wcNode.LINK_TYPE.INPUT, this, myProperty.name, wcNode.LINK_TYPE.OUTPUT);
    return wcNode.CONNECT_RESULT.SUCCESS;
  },

  /**
   * Disconnects a chain, or all chains, from an entry link.
   * @function wcNode#disconnectEntry
   * @param {string} name - The name of the entry link.
   * @param {wcNode} [targetNode] - If supplied, will only remove links to the specified target node.
   * @param {string} [targetName] - If supplied, will only remove links to the specified named exit links.
   * @returns {wcNode.CONNECT_RESULT} - The result of the disconnection.
   */
  disconnectEntry: function(name, targetNode, targetName) {
    var i = 0;
    var myLink = null;

    // Find my entry link.
    for (i = 0; i < this.chain.entry.length; ++i) {
      if (this.chain.entry[i].name === name) {
        myLink = this.chain.entry[i];
        break;
      }
    }

    if (!myLink) {
      return wcNode.CONNECT_RESULT.NOT_FOUND;
    }

    // Iterate through all chained links and disconnect as necessary.
    for (i = 0; i < myLink.links.length; ++i) {
      var targetLink = myLink.links[i];
      if ((!targetNode || targetNode === targetLink.node) && (!targetName || targetName === targetLink.name)) {
        // Remove this link.
        myLink.links.splice(i, 1);
        i--;

        targetLink.node.disconnectExit(targetLink.name, this, name);
      }
    }
    return wcNode.CONNECT_RESULT.SUCCESS;
  },

  /**
   * Disconnects a chain, or all chains, from an exit link.
   * @function wcNode#disconnectExit
   * @param {string} name - The name of the exit link.
   * @param {wcNode} [targetNode] - If supplied, will only remove links to the specified target node.
   * @param {string} [targetName] - If supplied, will only remove links to the specified named entry links.
   * @returns {wcNode.CONNECT_RESULT} - The result of the disconnection.
   */
  disconnectExit: function(name, targetNode, targetName) {
    // Find my exit link.
    var myLink = null, i = 0;
    for (i = 0; i < this.chain.exit.length; ++i) {
      if (this.chain.exit[i].name === name) {
        myLink = this.chain.exit[i];
        break;
      }
    }

    if (!myLink) {
      return wcNode.CONNECT_RESULT.NOT_FOUND;
    }

    // Iterate through all chained links and disconnect as necessary.
    for (i = 0; i < myLink.links.length; ++i) {
      var targetLink = myLink.links[i];
      if ((!targetNode || targetNode === targetLink.node) && (!targetName || targetName === targetLink.name)) {
        // Remove this link.
        myLink.links.splice(i, 1);
        i--;

        targetLink.node.disconnectEntry(targetLink.name, this, name);
      }
    }
    return wcNode.CONNECT_RESULT.SUCCESS;
  },

  /**
   * Disconnects a chain, or all chains, from a property input.
   * @function wcNode#disconnectInput
   * @param {string} name - The name of the property.
   * @param {wcNode} [targetNode] - If supplied, will only remove links to the specified target node.
   * @param {string} [targetName] - If supplied, will only remove links to the specified named property output links.
   * @returns {wcNode.CONNECT_RESULT} - The result of the disconnection.
   */
  disconnectInput: function(name, targetNode, targetName) {
    // Find my property.
    var myProperty = null, i = 0;
    for (i = 0; i < this.properties.length; ++i) {
      if (this.properties[i].name === name) {
        myProperty = this.properties[i];
        break;
      }
    }

    if (!myProperty) {
      return wcNode.CONNECT_RESULT.NOT_FOUND;
    }

    // Iterate through all chained links and disconnect as necessary.
    for (i = 0; i < myProperty.inputs.length; ++i) {
      var targetProperty = myProperty.inputs[i];
      if ((!targetNode || targetNode === targetProperty.node) && (!targetName || targetName === targetProperty.name)) {
        // Remove this link.
        myProperty.inputs.splice(i, 1);
        i--;

        targetProperty.node.disconnectOutput(targetProperty.name, this, name);
      }
    }
    return wcNode.CONNECT_RESULT.SUCCESS;
  },

  /**
   * Disconnects a chain, or all chains, from a property output.
   * @function wcNode#disconnectOutput
   * @param {string} name - The name of the property.
   * @param {wcNode} [targetNode] - If supplied, will only remove links to the specified target node.
   * @param {string} [targetName] - If supplied, will only remove links to the specified named property input links.
   * @returns {wcNode.CONNECT_RESULT} - The result of the disconnection.
   */
  disconnectOutput: function(name, targetNode, targetName) {
    // Find my property.
    var myProperty = null, i = 0;
    for (i = 0; i < this.properties.length; ++i) {
      if (this.properties[i].name === name) {
        myProperty = this.properties[i];
        break;
      }
    }

    if (!myProperty) {
      return wcNode.CONNECT_RESULT.NOT_FOUND;
    }

    // Iterate through all chained links and disconnect as necessary.
    for (i = 0; i < myProperty.outputs.length; ++i) {
      var targetProperty = myProperty.outputs[i];
      if ((!targetNode || targetNode === targetProperty.node) && (!targetName || targetName === targetProperty.name)) {
        // Remove this link.
        myProperty.outputs.splice(i, 1);
        i--;

        targetProperty.node.disconnectInput(targetProperty.name, this, name);
      }
    }
    return wcNode.CONNECT_RESULT.SUCCESS;
  },

  /**
   * Activates an entry link and activates this node.
   * @function wcNode#activateEntry
   * @param {string} name - The name of the entry link to trigger.
   * @param {wcNode} fromNode - The node triggering the entry.
   * @param {string} fromName - The Exit link name.
   * @param {wcPlay~FlowTracker} [tracker] - Optional flow tracker.
   * @returns {boolean} - Fails if the entry link does not exist.
   */
  activateEntry: function(name, fromNode, fromName, tracker) {
    var engine = this.engine();
    for (var i = 0; i < this.chain.entry.length; ++i) {
      if (this.chain.entry[i].name === name) {
        // Always queue the trigger so execution is not immediate.
        if (engine) {
          engine.queueNodeEntry(this, this.chain.entry[i].name, fromNode, fromName, false, tracker);
        }
        return true;
      }
    }

    if (engine) {
      // Timeout one frame before attempting to close this tracker.
      setTimeout(function() {
        engine.endFlowTracker(tracker);
      }, 0);
    }
    return false;
  },

  /**
   * Activates an exit link.
   * @function wcNode#activateExit
   * @param {string} name - The name of the exit link to trigger.
   * @param {Function} [done] - An optional callback to call when the entire exit chain has finished.
   * @returns {boolean} - Fails if the exit link does not exist or this node is disabled.
   */
  activateExit: function(name, done) {
    if (!this.enabled()) {
      // Node not enabled, unable to process.
      done && done();
      return false;
    }

    if (this.debugLog()) {
      this.log('DEBUG: Node "' + this.category + '.' + this.type + (this.name? ' (' + this.name + ')': '') + '" Triggered Exit link "' + name + '"');
    }

    var engine = this.engine();
    for (var i = 0; i < this.chain.exit.length; ++i) {
      var exitLink = this.chain.exit[i];
      if (exitLink.name === name) {
        var queued = false;
        var activeTracker = this._activeTracker;

        if (typeof done === 'function') {
          activeTracker = engine.beginFlowTracker(this, activeTracker, done);
          done = null;
        }

        // Activate all entry links chained to this exit.
        for (var a = 0; a < exitLink.links.length; ++a) {
          if (exitLink.links[a].node) {
            queued = true;
            exitLink.links[a].node.activateEntry(exitLink.links[a].name, this, name, engine.beginFlowTracker(exitLink.links[a].node, activeTracker));
          }
        }

        // If we did not queue another node to activate, we should manually flash this link.
        if (!queued) {
          this.chain.exit[i].meta.flash = true;
          this._meta.flash = true;
          // Timeout one frame before attempting to close this tracker.
          setTimeout(function() {
            engine.endFlowTracker(activeTracker);
          }, 0);
          done && done();
        }
        return true;
      }
    }

    // No link exists with the name provided.
    done && done();
    return false;
  },

  /**
   * Gets the type of a property.
   * @function wcNode#propertyType
   * @param {string} name - The name of the property.
   * @returns {wcPlay.PROPERTY|null} - Returns null if the property was not found.
   */
  propertyType: function(name) {
    for (var i = 0; i < this.properties.length; ++i) {
      var prop = this.properties[i];
      if (prop.name === name) {
        return prop.type;
      }
    }
  },

  /**
   * Gets the options assigned to a property, you may change attributes from here.
   * @function wcNode#propertyOptions
   * @param {string} name - The name of the property.
   * @returns {Object|null} - The options object associated with the property, or null if the property does not exist.
   */
  propertyOptions: function(name) {
    for (var i = 0; i < this.properties.length; ++i) {
      var prop = this.properties[i];
      if (prop.name === name) {
        // Assume the user will change options for this property, so make it dirty.
        this._meta.dirty = true;
        return prop.options;
      }
    }
    return null;
  },

  /**
   * Gets, or Sets the value of a property.
   * @function wcNode#property
   * @param {string} name - The name of the property.
   * @param {Object} [value] - If supplied, will assign a new value to the property.
   * @param {boolean} [forceOrSilent] - If supplied, true will force the change event to be sent to all chained properties even if this value didn't change while false will force the change to not be chained.
   * @param {boolean} [forceUpstream] - Contrary to normal operation, if this is true then the property change will be sent backwards, from this property's input link to any outputs connected to it.
   * @param {external:wcUndoManager} [undo] - If the change is triggered by the user and undo management is enabled, this will be the undo manager.
   * @returns {Object|undefined} - The value of the property, or undefined if not found.
   */
  property: function(name, value, forceOrSilent, forceUpstream, undo) {
    var i = 0, a = 0;
    for (i = 0; i < this.properties.length; ++i) {
      var prop = this.properties[i];
      if (prop.name === name) {
        if (value !== undefined) {
          // Retrieve the current value of the property
          var oldValue = prop.value;

          // Apply restrictions to the property based on its type and options supplied.
          switch (prop.type) {
            case wcPlay.PROPERTY.TOGGLE:
              value = value? true: false;
              break;
            case wcPlay.PROPERTY.NUMBER:
              var min = (prop.options.min !== undefined? prop.options.min: -Infinity);
              var max = (prop.options.max !== undefined? prop.options.max:  Infinity);
              var num = Math.min(max, Math.max(min, parseInt(value)));
              if (isNaN(num)) {
                value = Math.min(max, Math.max(min, 0));
              }
              break;
            case wcPlay.PROPERTY.STRING:
              var len = prop.options.maxlength;
              if (len) {
                value = value.toString().substring(0, len);
              }
              break;
            case wcPlay.PROPERTY.SELECT:
              var items = prop.options.items;
              if (typeof items === 'function') {
                items = items.call(this);
              }
              var found = false;
              if (Array.isArray(items)) {
                for (a = 0; a < items.length; ++a) {
                  if (typeof items[a] === 'object') {
                    if (items[a].value == value) {
                      found = true;
                      break;
                    }
                  } else {
                    if (items[a] == value) {
                      found = true;
                      break;
                    }
                  }
                }
              }

              if (!found) {
                if (!prop.options.hasOwnProperty('allowNone') || prop.options.allowNone) {
                  if (prop.options.hasOwnProperty('noneValue')) {
                    value = prop.options.noneValue;
                  } else {
                    value = '';
                  }
                }
              }
              break;
          }

          var engine = this.engine();
          prop.outputMeta.flash = true;
          if (this.debugBreak() || (engine && engine.stepping())) {
            prop.outputMeta.broken++;
          }

          // Notify about to change event.
          if (forceOrSilent || prop.value !== value) {
            value = this.onPropertyChanging(prop.name, oldValue, value, undo) || value;
          }

          if (forceOrSilent || prop.value !== value) {
            this._meta.dirty = true;
            prop.value = value;

            // Notify that the property has changed.
            this.onPropertyChanged(prop.name, oldValue, value, undo);

            // Linked properties must sync with their initial values as well.
            if (prop.options.linked) {
              this.initialProperty(prop.name, value, undefined, undefined, undo);
            }

            // Now follow any output links and assign the new value to them as well.
            if (forceOrSilent === undefined || forceOrSilent) {
              for (a = 0; a < prop.outputs.length; ++a) {
                if (prop.outputs[a].node) {
                  if (undo) {
                    // Triggered by a user through the editor, this change should propagate immediately.
                    prop.outputs[a].node.property(prop.outputs[a].name, value, undefined, undefined, undo);
                  } else {
                    prop.outputs[a].node.activateProperty(prop.outputs[a].name, value, undefined);
                  }
                }
              }
            }

            // Now propagate the change upstream if necessary.
            if (forceUpstream) {
              for (a = 0; a < prop.inputs.length; ++a) {
                if (prop.inputs[a].node) {
                  if (undo) {
                    // Triggered by a user through the editor, this change should propagate immediately.
                    prop.outputs[a].node.property(prop.outputs[a].name, value, true, true, undo);
                  } else {
                    prop.inputs[a].node.activateProperty(prop.inputs[a].name, value, true);
                  }
                }
              }
            }
          }
        }

        return this.onPropertyGet(prop.name) || prop.value;
      }
    }
  },

  /**
   * Gets, or Sets the initial value of a property.
   * @function wcNode#initialProperty
   * @param {string} name - The name of the property.
   * @param {Object} [value] - If supplied, will assign a new default value to the property.
   * @param {boolean} [forceOrSilent] - If supplied, true will force the change event to be sent to all chained properties even if this value didn't change while false will force the change to not be chained.
   * @param {boolean} [forceUpstream] - Contrary to normal operation, if this is true then the property change will be sent backwards, from this property's input link to any outputs connected to it.
   * @param {external:wcUndoManager} [undo] - If the change is triggered by the user and undo management is enabled, this will be the undo manager.
   * @returns {Object|undefined} - The default value of the property, or undefined if not found.
   */
  initialProperty: function(name, value, forceOrSilent, forceUpstream, undo) {
    var i = 0, a = 0;
    for (i = 0; i < this.properties.length; ++i) {
      var prop = this.properties[i];
      if (prop.name === name) {
        if (value !== undefined) {
          value = this.onInitialPropertyChanging(prop.name, prop.initialValue, value, undo) || value;
          if (prop.value == prop.initialValue) {
            this.property(name, value);
          }
          var oldValue = prop.initialValue;

          if (forceOrSilent || prop.initialValue !== value) {
            this._meta.dirty = true;
            prop.initialValue = value;

            // Notify that the property has changed.
            this.onInitialPropertyChanged(prop.name, oldValue, value, undo);

            // Linked properties must sync with their initial values as well.
            if (prop.options.linked) {
              this.property(prop.name, value);
            }

            prop.outputMeta.flash = true;
            var engine = this.engine();
            if (this.debugBreak() || (engine && engine.stepping())) {
              prop.outputMeta.broken++;
            }

            // Now follow any output links and assign the new value to them as well.
            if (forceOrSilent === undefined || forceOrSilent) {
              for (a = 0; a < prop.outputs.length; ++a) {
                prop.outputs[a].node && prop.outputs[a].node.initialProperty(prop.outputs[a].name, value, undefined, false, undo);
              }
            }

            // Now propagate the change upstream if necessary.
            if (forceUpstream) {
              for (a = 0; a < prop.inputs.length; ++a) {
                prop.inputs[a].node && prop.inputs[a].node.initialProperty(prop.inputs[a].name, value, undefined, true, undo);
              }
            }
          }
        }

        return this.onInitialPropertyGet(prop.name) || prop.initialValue;
      }
    }
  },

  /**
   * Activates a property that is about to be changed by the output of another property.
   * @function wcNode#activateProperty
   * @param {string} name - The name of the property.
   * @param {Object} value - The new value of the property.
   * @param {boolean} [upstream] - If true, the activation was from a property in its output, and we are propagating in reverse.
   */
  activateProperty: function(name, value, upstream) {
    var engine = this.engine();
    if (engine) {
      engine.queueNodeProperty(this, name, value, upstream);
    }

    for (var i = 0; i < this.properties.length; ++i) {
      var prop = this.properties[i];
      if (prop.name === name) {
        prop.inputMeta.flash = true;
        if (this.debugBreak() || (engine && engine.stepping())) {
          prop.inputMeta.broken++;
        }
      }
    }
  },

  /**
   * Retrieves a list of all chains connected to an entry link on this node.
   * @function wcNode#listEntryChains
   * @param {string} [name] - The entry link, if omitted, all link chains are retrieved.
   * @param {wcNode[]} [ignoreNodes] - If supplied, will ignore all chains connected to a node in this list.
   * @returns {wcNode~ChainData[]} - A list of all chains connected to this link, if the link was not found, an empty list is returned.
   */
  listEntryChains: function(name, ignoreNodes) {
    var result = [];
    for (var i = 0; i < this.chain.entry.length; ++i) {
      if (!name || this.chain.entry[i].name === name) {
        var myLink = this.chain.entry[i];
        for (var a = 0; a < myLink.links.length; ++a) {
          if (!ignoreNodes || ignoreNodes.indexOf(myLink.links[a].node) === -1) {
            result.push({
              inName: myLink.name,
              inNodeId: this.id,
              outName: myLink.links[a].name,
              outNodeId: myLink.links[a].node.id
            });
          }
        }
      }
    }

    return result;
  },

  /**
   * Retrieves a list of all chains connected to an exit link on this node.
   * @function wcNode#listExitChains
   * @param {string} [name] - The exit link, if omitted, all link chains are retrieved.
   * @param {wcNode[]} [ignoreNodes] - If supplied, will ignore all chains connected to a node in this list.
   * @returns {wcNode~ChainData[]} - A list of all chains connected to this link, if the link was not found, an empty list is returned.
   */
  listExitChains: function(name, ignoreNodes) {
    var result = [];
    for (var i = 0; i < this.chain.exit.length; ++i) {
      if (!name || this.chain.exit[i].name === name) {
        var myLink = this.chain.exit[i];
        for (var a = 0; a < myLink.links.length; ++a) {
          if (!ignoreNodes || ignoreNodes.indexOf(myLink.links[a].node) === -1) {
            result.push({
              inName: myLink.links[a].name,
              inNodeId: myLink.links[a].node.id,
              outName: myLink.name,
              outNodeId: this.id
            });
          }
        }
      }
    }

    return result;
  },

  /**
   * Retrieves a list of all chains connected to a property input link on this node.
   * @function wcNode#listInputChains
   * @param {string} [name] - The property input link, if omitted, all link chains are retrieved.
   * @param {wcNode[]} [ignoreNodes] - If supplied, will ignore all chains connected to a node in this list.
   * @returns {wcNode~ChainData[]} - A list of all chains connected to this link, if the link was not found, an empty list is returned.
   */
  listInputChains: function(name, ignoreNodes) {
    var result = [];
    for (var i = 0; i < this.properties.length; ++i) {
      if (!name || this.properties[i].name === name) {
        var myProp = this.properties[i];
        for (var a = 0; a < myProp.inputs.length; ++a) {
          if (!ignoreNodes || ignoreNodes.indexOf(myProp.inputs[a].node) === -1) {
            result.push({
              inName: myProp.name,
              inNodeId: this.id,
              outName: myProp.inputs[a].name,
              outNodeId: myProp.inputs[a].node.id
            });
          }
        }
      }
    }

    return result;
  },

  /**
   * Retrieves a list of all chains connected to a property output link on this node.
   * @function wcNode#listOutputChains
   * @param {string} [name] - The property output link, if omitted, all link chains are retrieved.
   * @param {wcNode[]} [ignoreNodes] - If supplied, will ignore all chains connected to a node in this list.
   * @returns {wcNode~ChainData[]} - A list of all chains connected to this link, if the link was not found, an empty list is returned.
   */
  listOutputChains: function(name, ignoreNodes) {
    var result = [];
    for (var i = 0; i < this.properties.length; ++i) {
      if (!name || this.properties[i].name === name) {
        var myProp = this.properties[i];
        for (var a = 0; a < myProp.outputs.length; ++a) {
          if (!ignoreNodes || ignoreNodes.indexOf(myProp.outputs[a].node) === -1) {
            result.push({
              inName: myProp.outputs[a].name,
              inNodeId: myProp.outputs[a].node.id,
              outName: myProp.name,
              outNodeId: this.id
            });
          }
        }
      }
    }

    return result;
  },

  /**
   * Retrieves a list of all properties and their values for this node.
   * @function wcNode#listProperties
   * @param {boolean} [minimal] - If true, only the minimal data is listed, this means current values will be omitted.
   * @returns {wcNode~PropertyData[]} - A list of all property data.
   */
  listProperties: function(minimal) {
    var result = [];
    for (var i = 0; i < this.properties.length; ++i) {
      var myProp = this.properties[i];
      var data = {
        name: myProp.name,
        type: myProp.type,
        initialValue: myProp.initialValue,
        options: myProp.options
      };

      if (!minimal) {
        data.value = myProp.value;
      }

      if (typeof myProp.options.exportValue === 'function') {
        var val = myProp.options.exportValue(myProp.initialValue);
        if (val !== undefined) {
          data.initialValue = val;
        }
      }

      result.push(data);
    }

    return result;
  },

  /**
   * Sets a size for the custom viewport.<br>
   * The custom viewport is a rectangular area embedded into the node's visual display in which you can 'draw' whatever you wish. It appears below the title text and above properties.
   * @function wcNode#viewportSize
   * @param {number} [width] - If supplied, assigns the width of the viewport desired. Use 0 or null to disable the viewport.
   * @param {number} [height] - If supplied, assigns the height of the viewport desired. Use 0 or null to disable the viewport.
   * @returns {wcPlay~Coordinates} - The current size of the viewport.
   * @see wcNode#onViewportDraw
   */
  viewportSize: function(width, height) {
    if (width !== undefined && height !== undefined) {
      this._meta.dirty = true;
      if (!width || !height) {
        this._viewportSize = null;
      } else {
        this._viewportSize = {
          x: width,
          y: height
        };
      }
    }

    return {x: this._viewportSize.x, y: this._viewportSize.y};
  },

  /**
   * Event that is called when it is time to draw the contents of your custom viewport. It is up to you to stay within the [wcNode.viewportSize]{@link wcNode#viewportSize} you've specified.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onViewportDraw
   * @param {external:Canvas~Context} context - The canvas context to draw on, coordinates 0,0 will be the top left corner of your viewport. It is up to you to stay within the [viewport bounds]{@link wcNode#viewportSize} you have assigned.
   * @param {boolean} readOnly - The editors readonly status, when true, you should not allow changes to the node.
   * @see wcNode#viewportSize
   */
  onViewportDraw: function(context, readOnly) {
    this._super(context, readOnly);
  },

  /**
   * Event that is called when the mouse has entered the viewport area.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onViewportMouseEnter
   * @param {Object} event - The original jquery mouse event.
   * @param {wcPlay~Coordinates} pos - The position of the mouse relative to the viewport area (top left corner is 0,0).
   * @param {boolean} readOnly - The editors readonly status, when true, you should not allow changes to the node.
   */
  onViewportMouseEnter: function(event, pos, readOnly) {
    this._super(event, pos, readOnly);
    if (this.debugLog()) {
      this.log('DEBUG: Node "' + this.category + '.' + this.type + (this.name? ' (' + this.name + ')': '') + '" mouse entered custom viewport!');
    }
  },

  /**
   * Event that is called when the mouse has left the viewport area.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onViewportMouseLeave
   * @param {Object} event - The original jquery mouse event.
   * @param {boolean} readOnly - The editors readonly status, when true, you should not allow changes to the node.
   */
  onViewportMouseLeave: function(event, readOnly) {
    this._super(event, readOnly);
    if (this.debugLog()) {
      this.log('DEBUG: Node "' + this.category + '.' + this.type + (this.name? ' (' + this.name + ')': '') + '" mouse left custom viewport!');
    }
  },

  /**
   * Event that is called when the mouse button is pressed over your viewport area.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onViewportMouseDown
   * @param {Object} event - The original jquery mouse event.
   * @param {wcPlay~Coordinates} pos - The position of the mouse relative to the viewport area (top left corner is 0,0).
   * @param {boolean} readOnly - The editors readonly status, when true, you should not allow changes to the node.
   * @returns {Boolean|undefined} - Return true if you want to disable node dragging during mouse down within your viewport.
   */
  onViewportMouseDown: function(event, pos, readOnly) {
    this._super(event, pos, readOnly);
  },

  /**
   * Event that is called when the mouse button is released over your viewport area.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onViewportMouseUp
   * @param {Object} event - The original jquery mouse event.
   * @param {wcPlay~Coordinates} pos - The position of the mouse relative to the viewport area (top left corner is 0,0).
   * @param {boolean} readOnly - The editors readonly status, when true, you should not allow changes to the node.
   */
  onViewportMouseUp: function(event, pos, readOnly) {
    this._super(event, pos, readOnly);
  },

  /**
   * Event that is called when the mouse has moved over your viewport area.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onViewportMouseMove
   * @param {Object} event - The original jquery mouse event.
   * @param {wcPlay~Coordinates} pos - The position of the mouse relative to the viewport area (top left corner is 0,0).
   * @param {boolean} readOnly - The editors readonly status, when true, you should not allow changes to the node.
   */
  onViewportMouseMove: function(event, pos, readOnly) {
    this._super(event, pos, readOnly);
  },

  /**
   * Event that is called when the mouse wheel is used over your viewport area.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onViewportMouseWheel
   * @param {Object} event - The original jquery mouse event.
   * @param {wcPlay~Coordinates} pos - The position of the mouse relative to the viewport area (top left corner is 0,0).
   * @param {number} scrollDelta - The scroll amount and direction.
   * @param {boolean} readOnly - The editors readonly status, when true, you should not allow changes to the node.
   */
  onViewportMouseWheel: function(event, pos, scrollDelta, readOnly) {
    this._super(event, pos, scrollDelta, readOnly);
  },

  /**
   * Event that is called when the mouse button is pressed and released in the same spot over your viewport area.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onViewportMouseClick
   * @param {Object} event - The original jquery mouse event.
   * @param {wcPlay~Coordinates} pos - The position of the mouse relative to the viewport area (top left corner is 0,0).
   * @param {boolean} readOnly - The editors readonly status, when true, you should not allow changes to the node.
   */
  onViewportMouseClick: function(event, pos, readOnly) {
    this._super(event, pos, readOnly);
  },

  /**
   * Event that is called when the mouse button is double clicked in the same spot over your viewport area.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onViewportMouseDoubleClick
   * @param {Object} event - The original jquery mouse event.
   * @param {wcPlay~Coordinates} pos - The position of the mouse relative to the viewport area (top left corner is 0,0).
   * @param {boolean} readOnly - The editors readonly status, when true, you should not allow changes to the node.
   * @returns {Boolean|undefined} - Return true if you want to disable node auto-collapse when double clicking.
   */
  onViewportMouseDoubleClick: function(event, pos, readOnly) {
    this._super(event, pos, readOnly);
  },

  /**
   * Event that is called when a connection has been made.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onConnect
   * @param {boolean} isConnecting - True if a connection is being made, false if it is a disconnection.
   * @param {string} name - The name of the link being connected to.
   * @param {wcNode.LINK_TYPE} type - The link's type.
   * @param {wcNode} targetNode - The target node being connected to.
   * @param {string} targetName - The link name on the target node being connected to.
   * @param {wcNode.LINK_TYPE} targetType - The target link's type.
   */
  onConnect: function(isConnecting, name, type, targetNode, targetName, targetType) {
    this._super(isConnecting, name, type, targetNode, targetName, targetType);
    // If we are connecting one of our property outputs to another property, alert them and send your value to them.
    if (isConnecting && type === wcNode.LINK_TYPE.OUTPUT) {
      targetNode.activateProperty(targetName, this.property(name));
      targetNode.initialProperty(targetName, this.initialProperty(name));
    }
  },

  /**
   * Event that is called as soon as the Play script has started.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onStart
   */
  onStart: function() {
    this._super();
    if (this.debugLog()) {
      this.log('DEBUG: Node "' + this.category + '.' + this.type + (this.name? ' (' + this.name + ')': '') + '" started!');
    }
  },

  /**
   * Event that is called as soon as the Play script has stopped.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onStop
   */
  onStop: function() {
    this._super();
    this._meta.dirty = true;

    if (this.debugLog()) {
      this.log('DEBUG: Node "' + this.category + '.' + this.type + (this.name? ' (' + this.name + ')': '') + '" stopped!');
    }
  },

  /**
   * Event that is called when this node is about to be drawn.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onDraw
   */
  onDraw: function() {
    this._super();
  },

  /**
   * Event that is called when an entry link has been activated.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onActivated
   * @param {string} name - The name of the entry link triggered.
   */
  onActivated: function(name) {
    this._super(name);
    if (this.debugLog()) {
      this.log('DEBUG: Node "' + this.category + '.' + this.type + (this.name? ' (' + this.name + ')': '') + '" Triggered Entry link "' + name + '"');
    }
  },

  /**
   * Event that is called when the node is about to change its position.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onMoving
   * @param {wcPlay~Coordinates} oldPos - The current position of the node.
   * @param {wcPlay~Coordinates} newPos - The new position to move the node.
   * @returns {wcPlay~Coordinates|undefined} - Return the new position of the node (usually newPos unless you are restricting the position). If no value is returned, newPos is assumed.
   */
  onMoving: function(oldPos, newPos) {
    this._super(oldPos, newPos);
  },

  /**
   * Event that is called after the node has changed its position.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onMoved
   * @param {wcPlay~Coordinates} oldPos - The old position of the node.
   * @param {wcPlay~Coordinates} newPos - The new position of the node.
   */
  onMoved: function(oldPos, newPos) {
    this._super(oldPos, newPos);
  },

  /**
   * Event that is called when the node's name is about to be edited by the user.<br>
   * You can use this to suggest a list of names that the user can conveniently choose from.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @see http://caniuse.com/#search=datalist
   * @function wcNode#onNameEditSuggestion
   * @returns {wcNode~SelectItem[]|String[]|undefined} - An option list of options to display for the user as suggestions.
   */
  onNameEditSuggestion: function() {
    this._super();
  },

  /**
   * Event that is called when the name of this node is about to change.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onNameChanging
   * @param {string} oldName - The current name.
   * @param {string} newName - The new name.
   * @returns {string|undefined} - Return the new value of the name (usually newValue unless you are restricting the name). If no value is returned, newValue is assumed.
   */
  onNameChanging: function(oldName, newName) {
    this._super(oldName, newName);
  },

  /**
   * Event that is called when the name of this node has changed.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onNameChanged
   * @param {string} oldName - The current name.
   * @param {string} newName - The new name.
   * @param {external:wcUndoManager} [undo] - If the change is triggered by the user and undo management is enabled, this will be the undo manager. Note: The value change is already recorded, use this only if you have other things to record.
   */
  onNameChanged: function(oldName, newName, undo) {
    this._super(oldName, newName, undo);
    this._meta.dirty = true;
  },

  /**
   * Event that is called when a property is about to be changed.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onPropertyChanging
   * @param {string} name - The name of the property.
   * @param {Object} oldValue - The current value of the property.
   * @param {Object} newValue - The new, proposed, value of the property.
   * @param {external:wcUndoManager} [undo] - If the change is triggered by the user and undo management is enabled, this will be the undo manager. Note: The value change is already recorded, use this only if you have other things to record.
   * @returns {Object} - Return the new value of the property (usually newValue unless you are proposing restrictions). If no value is returned, newValue is assumed.
   */
  onPropertyChanging: function(name, oldValue, newValue, undo) {
    this._super(name, oldValue, newValue, undo);
    if (this.debugLog()) {
      this.log('DEBUG: Node "' + this.category + '.' + this.type + (this.name? ' (' + this.name + ')': '') + '" Changing Property "' + name + '" from "' + oldValue + '" to "' + newValue + '"');
    }
  },

  /**
   * Event that is called when a property has changed.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onPropertyChanged
   * @param {string} name - The name of the property.
   * @param {Object} oldValue - The old value of the property.
   * @param {Object} newValue - The new value of the property.
   * @param {external:wcUndoManager} [undo] - If the change is triggered by the user and undo management is enabled, this will be the undo manager. Note: The value change is already recorded, use this only if you have other things to record.
   */
  onPropertyChanged: function(name, oldValue, newValue, undo) {
    this._super(name, oldValue, newValue, undo);
    if (this.debugLog()) {
      this.log('DEBUG: Node "' + this.category + '.' + this.type + (this.name? ' (' + this.name + ')': '') + '" Changed Property "' + name + '" from "' + oldValue + '" to "' + newValue + '"');
    }
  },

  /**
   * Event that is called when the property is being asked its value, before the value is actually retrieved.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onPropertyGet
   * @param {string} name - The name of the property.
   * @returns {Object|undefined} - If a value is returned, that value is what will be retrieved from the get.
   */
  onPropertyGet: function(name) {
    this._super(name);
    // if (this.debugLog()) {
    //   this.log('DEBUG: Node "' + this.category + '.' + this.type + (this.name? ' (' + this.name + ')': '') + '" Requested Property "' + name + '"');
    // }
  },

  /**
   * Event that is called when a property initial value is about to be changed.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onInitialPropertyChanging
   * @param {string} name - The name of the property.
   * @param {Object} oldValue - The current value of the property.
   * @param {Object} newValue - The new, proposed, value of the property.
   * @param {external:wcUndoManager} [undo] - If the change is triggered by the user and undo management is enabled, this will be the undo manager. Note: The value change is already recorded, use this only if you have other things to record.
   * @returns {Object} - Return the new value of the property (usually newValue unless you are proposing restrictions). If no value is returned, newValue is assumed.
   */
  onInitialPropertyChanging: function(name, oldValue, newValue, undo) {
    this._super(name, oldValue, newValue, undo);
    if (this.debugLog()) {
      this.log('DEBUG: Node "' + this.category + '.' + this.type + (this.name? ' (' + this.name + ')': '') + '" Changing Initial Property "' + name + '" from "' + oldValue + '" to "' + newValue + '"');
    }
  },

  /**
   * Event that is called when a property initial value has changed.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onInitialPropertyChanged
   * @param {string} name - The name of the property.
   * @param {Object} oldValue - The old value of the property.
   * @param {Object} newValue - The new value of the property.
   * @param {external:wcUndoManager} [undo] - If the change is triggered by the user and undo management is enabled, this will be the undo manager. Note: The value change is already recorded, use this only if you have other things to record.
   */
  onInitialPropertyChanged: function(name, oldValue, newValue, undo) {
    this._super(name, oldValue, newValue, undo);
    if (this.debugLog()) {
      this.log('DEBUG: Node "' + this.category + '.' + this.type + (this.name? ' (' + this.name + ')': '') + '" Changed Initial Property "' + name + '" from "' + oldValue + '" to "' + newValue + '"');
    }
  },

  /**
   * Event that is called when the property initial value is being asked its value, before the value is actually retrieved.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onInitialPropertyGet
   * @param {string} name - The name of the property.
   * @returns {Object|undefined} - If a value is returned, that value is what will be retrieved from the get.
   */
  onInitialPropertyGet: function(name) {
    this._super(name);
    // if (this.debugLog()) {
    //   this.log('DEBUG: Node "' + this.category + '.' + this.type + (this.name? ' (' + this.name + ')': '') + '" Requested Initial Property "' + name + '"');
    // }
  },

  /**
   * Event that is called when a global property value has changed.
   * Overload this in inherited nodes.<br>
   * <b>Note:</b> Do not call 'this._super(..)' for this function, as the parent does not implement it.
   * @function wcNode#onGlobalPropertyChanged
   * @param {string} name - The name of the global property.
   * @param {Object} oldValue - The old value of the global property.
   * @param {Object} newValue - The new value of the global property.
   */
  onGlobalPropertyChanged: function(name, oldValue, newValue) {
    this._super(name, oldValue, newValue);
  },

  /**
   * Event that is called when a global property has been removed.
   * Overload this in inherited nodes.<br>
   * <b>Note:</b> Do not call 'this._super(..)' for this function, as the parent does not implement it.
   * @function wcNode#onGlobalPropertyRemoved
   * @param {string} name - The name of the global property.
   */
  onGlobalPropertyRemoved: function(name) {
    this._super(name);
  },

  /**
   * Event that is called when a global property has been renamed.
   * Overload this in inherited nodes.<br>
   * <b>Note:</b> Do not call 'this._super(..)' for this function, as the parent does not implement it.
   * @function wcNode#onGlobalPropertyRenamed
   * @param {string} oldName - The old name of the global property.
   * @param {string} newName - The new name of the global property.
   */
  onGlobalPropertyRenamed: function(oldName, newName) {
    this._super(oldName, newName);
  },

  /**
   * Event that is called when a global property initial value has changed.
   * Overload this in inherited nodes.<br>
   * <b>Note:</b> Do not call 'this._super(..)' for this function, as the parent does not implement it.
   * @function wcNode#onGlobalInitialPropertyChanged
   * @param {string} name - The name of the global property.
   * @param {Object} oldValue - The old value of the global property.
   * @param {Object} newValue - The new value of the global property.
   */
  onGlobalInitialPropertyChanged: function(name, oldValue, newValue) {
    this._super(name, oldValue, newValue);
  },

  /**
   * Event that is called when the node is about to be imported. This is your chance to prepare the node for import, or possibly modify the import data.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onImporting
   * @param {Object} data - The data being imported.
   * @param {Number[]} [idMap] - If supplied, identifies a mapping of old ID's to new ID's, any not found in this list will be unchanged.
   */
  onImporting: function(data, idMap) {
    this._super(data, idMap);
  },

  /**
   * Event that is called after the node has imported.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onImported
   * @param {Object} data - The data being imported.
   * @param {Number[]} [idMap] - If supplied, identifies a mapping of old ID's to new ID's, any not found in this list will be unchanged.
   */
  onImported: function(data, idMap) {
    this._super(data, idMap);
  },

  /**
   * Event that is called when the node is being exported, after the export data has been configured.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onExport
   * @param {Object} data - The export data for this node.
   * @param {boolean} [minimal] - If true, only the most important data should be exported, this means current values and redundant link connections are omitted.
   */
  onExport: function(data, minimal) {
    this._super(data, minimal);
  },

  /**
   * Event that is called when the node is about to be reset.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onReset
   */
  onReset: function() {
    this._super();
  },

  /**
   * Event that is called when the node is about to be destroyed.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onDestroying
   */
  onDestroying: function() {
    this._super();
  },

  /**
   * Event that is called after the node has been destroyed.<br>
   * Overload this in inherited nodes, be sure to call 'this._super(..)' at the top.
   * @function wcNode#onDestroyed
   */
  onDestroyed: function() {
    this._super();
  }
});

window.wcNode = {};

/**
 * The type of node link.
 * @enum {string}
 */
wcNode.LINK_TYPE = {
  ENTRY: 'entry',
  EXIT: 'exit',
  INPUT: 'input',
  OUTPUT: 'output'
};

/**
 * The connection result.
 * @enum {string}
 */
wcNode.CONNECT_RESULT = {
  NOT_FOUND: 'not_found',
  ALREADY_CONNECTED: 'already_connected',
  REFUSED: 'refused',
  SUCCESS: 'success'
};


/**
 * Enabled property name.
 * @typedef {string}
 */
wcNode.PROPERTY_ENABLED = 'enabled';