text: /* *
   *
   *  (c) 2009-2020 Øystein Moseng
   *
   *  TimelineEvent class definition.
   *
   *  License: www.highcharts.com/license
   *
   *  !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
   *
   * */
  'use strict';
  import H from '../../Core/Globals.js';
  import U from '../../Core/Utilities.js';
  var merge = U.merge, splat = U.splat, uniqueKey = U.uniqueKey;
  /**
   * A set of options for the TimelineEvent class.
   *
   * @requires module:modules/sonification
   *
   * @private
   * @interface Highcharts.TimelineEventOptionsObject
   */ /**
  * The object we want to sonify when playing the TimelineEvent. Can be any
  * object that implements the `sonify` and `cancelSonify` functions. If this is
  * not supplied, the TimelineEvent is considered a silent event, and the onEnd
  * event is immediately called.
  * @name Highcharts.TimelineEventOptionsObject#eventObject
  * @type {*}
  */ /**
  * Options to pass on to the eventObject when playing it.
  * @name Highcharts.TimelineEventOptionsObject#playOptions
  * @type {object|undefined}
  */ /**
  * The time at which we want this event to play (in milliseconds offset). This
  * is not used for the TimelineEvent.play function, but rather intended as a
  * property to decide when to call TimelineEvent.play. Defaults to 0.
  * @name Highcharts.TimelineEventOptionsObject#time
  * @type {number|undefined}
  */ /**
  * Unique ID for the event. Generated automatically if not supplied.
  * @name Highcharts.TimelineEventOptionsObject#id
  * @type {string|undefined}
  */ /**
  * Callback called when the play has finished.
  * @name Highcharts.TimelineEventOptionsObject#onEnd
  * @type {Function|undefined}
  */
  import utilities from './Utilities.js';
  /* eslint-disable no-invalid-this, valid-jsdoc */
  /**
   * The TimelineEvent class. Represents a sound event on a timeline.
   *
   * @requires module:modules/sonification
   *
   * @private
   * @class
   * @name Highcharts.TimelineEvent
   *
   * @param {Highcharts.TimelineEventOptionsObject} options
   * Options for the TimelineEvent.
   */
  function TimelineEvent(options) {
      this.init(options || {});
  }
  TimelineEvent.prototype.init = function (options) {
      this.options = options;
      this.time = options.time || 0;
      this.id = this.options.id = options.id || uniqueKey();
  };
  /**
   * Play the event. Does not take the TimelineEvent.time option into account,
   * and plays the event immediately.
   *
   * @function Highcharts.TimelineEvent#play
   *
   * @param {Highcharts.TimelineEventOptionsObject} [options]
   *        Options to pass in to the eventObject when playing it.
   *
   * @return {void}
   */
  TimelineEvent.prototype.play = function (options) {
      var eventObject = this.options.eventObject, masterOnEnd = this.options.onEnd, playOnEnd = options && options.onEnd, playOptionsOnEnd = this.options.playOptions &&
          this.options.playOptions.onEnd, playOptions = merge(this.options.playOptions, options);
      if (eventObject && eventObject.sonify) {
          // If we have multiple onEnds defined, use all
          playOptions.onEnd = masterOnEnd || playOnEnd || playOptionsOnEnd ?
              function () {
                  var args = arguments;
                  [masterOnEnd, playOnEnd, playOptionsOnEnd].forEach(function (onEnd) {
                      if (onEnd) {
                          onEnd.apply(this, args);
                      }
                  });
              } : void 0;
          eventObject.sonify(playOptions);
      }
      else {
          if (playOnEnd) {
              playOnEnd();
          }
          if (masterOnEnd) {
              masterOnEnd();
          }
      }
  };
  /**
   * Cancel the sonification of this event. Does nothing if the event is not
   * currently sonifying.
   *
   * @function Highcharts.TimelineEvent#cancel
   *
   * @param {boolean} [fadeOut=false]
   *        Whether or not to fade out as we stop. If false, the event is
   *        cancelled synchronously.
   */
  TimelineEvent.prototype.cancel = function (fadeOut) {
      this.options.eventObject.cancelSonify(fadeOut);
  };
  /**
   * A set of options for the TimelinePath class.
   *
   * @requires module:modules/
   *
   * @private
   * @interface Highcharts.TimelinePathOptionsObject
   */ /**
  * List of TimelineEvents to play on this track.
  * @name Highcharts.TimelinePathOptionsObject#events
  * @type {Array<Highcharts.TimelineEvent>}
  */ /**
  * If this option is supplied, this path ignores all events and just waits for
  * the specified number of milliseconds before calling onEnd.
  * @name Highcharts.TimelinePathOptionsObject#silentWait
  * @type {number|undefined}
  */ /**
  * Unique ID for this timeline path. Automatically generated if not supplied.
  * @name Highcharts.TimelinePathOptionsObject#id
  * @type {string|undefined}
  */ /**
  * Callback called before the path starts playing.
  * @name Highcharts.TimelinePathOptionsObject#onStart
  * @type {Function|undefined}
  */ /**
  * Callback function to call before an event plays.
  * @name Highcharts.TimelinePathOptionsObject#onEventStart
  * @type {Function|undefined}
  */ /**
  * Callback function to call after an event has stopped playing.
  * @name Highcharts.TimelinePathOptionsObject#onEventEnd
  * @type {Function|undefined}
  */ /**
  * Callback called when the whole path is finished.
  * @name Highcharts.TimelinePathOptionsObject#onEnd
  * @type {Function|undefined}
  */
  /**
   * The TimelinePath class. Represents a track on a timeline with a list of
   * sound events to play at certain times relative to each other.
   *
   * @requires module:modules/sonification
   *
   * @private
   * @class
   * @name Highcharts.TimelinePath
   *
   * @param {Highcharts.TimelinePathOptionsObject} options
   *        Options for the TimelinePath.
   */
  function TimelinePath(options) {
      this.init(options);
  }
  TimelinePath.prototype.init = function (options) {
      this.options = options;
      this.id = this.options.id = options.id || uniqueKey();
      this.cursor = 0;
      this.eventsPlaying = {};
      // Handle silent wait, otherwise use events from options
      this.events = options.silentWait ?
          [
              new TimelineEvent({ time: 0 }),
              new TimelineEvent({ time: options.silentWait })
          ] :
          this.options.events;
      // Reference optionally provided by the user that indicates the intended
      // duration of the path. Unused by TimelinePath itself.
      this.targetDuration = options.targetDuration || options.silentWait;
      // We need to sort our events by time
      this.sortEvents();
      // Get map from event ID to index
      this.updateEventIdMap();
      // Signal events to fire
      this.signalHandler = new utilities.SignalHandler(['playOnEnd', 'masterOnEnd', 'onStart', 'onEventStart', 'onEventEnd']);
      this.signalHandler.registerSignalCallbacks(merge(options, { masterOnEnd: options.onEnd }));
  };
  /**
   * Sort the internal event list by time.
   * @private
   */
  TimelinePath.prototype.sortEvents = function () {
      this.events = this.events.sort(function (a, b) {
          return a.time - b.time;
      });
  };
  /**
   * Update the internal eventId to index map.
   * @private
   */
  TimelinePath.prototype.updateEventIdMap = function () {
      this.eventIdMap = this.events.reduce(function (acc, cur, i) {
          acc[cur.id] = i;
          return acc;
      }, {});
  };
  /**
   * Add events to the path. Should not be done while the path is playing.
   * The new events are inserted according to their time property.
   * @private
   * @param {Array<Highcharts.TimelineEvent>} newEvents - The new timeline events
   * to add.
   */
  TimelinePath.prototype.addTimelineEvents = function (newEvents) {
      this.events = this.events.concat(newEvents);
      this.sortEvents(); // Sort events by time
      this.updateEventIdMap(); // Update the event ID to index map
  };
  /**
   * Get the current TimelineEvent under the cursor.
   * @private
   * @return {Highcharts.TimelineEvent} The current timeline event.
   */
  TimelinePath.prototype.getCursor = function () {
      return this.events[this.cursor];
  };
  /**
   * Set the current TimelineEvent under the cursor.
   * @private
   * @param {string} eventId
   * The ID of the timeline event to set as current.
   * @return {boolean}
   * True if there is an event with this ID in the path. False otherwise.
   */
  TimelinePath.prototype.setCursor = function (eventId) {
      var ix = this.eventIdMap[eventId];
      if (typeof ix !== 'undefined') {
          this.cursor = ix;
          return true;
      }
      return false;
  };
  /**
   * Play the timeline from the current cursor.
   * @private
   * @param {Function} onEnd
   * Callback to call when play finished. Does not override other onEnd callbacks.
   * @return {void}
   */
  TimelinePath.prototype.play = function (onEnd) {
      this.pause();
      this.signalHandler.emitSignal('onStart');
      this.signalHandler.clearSignalCallbacks(['playOnEnd']);
      this.signalHandler.registerSignalCallbacks({ playOnEnd: onEnd });
      this.playEvents(1);
  };
  /**
   * Play the timeline backwards from the current cursor.
   * @private
   * @param {Function} onEnd
   * Callback to call when play finished. Does not override other onEnd callbacks.
   * @return {void}
   */
  TimelinePath.prototype.rewind = function (onEnd) {
      this.pause();
      this.signalHandler.emitSignal('onStart');
      this.signalHandler.clearSignalCallbacks(['playOnEnd']);
      this.signalHandler.registerSignalCallbacks({ playOnEnd: onEnd });
      this.playEvents(-1);
  };
  /**
   * Reset the cursor to the beginning.
   * @private
   */
  TimelinePath.prototype.resetCursor = function () {
      this.cursor = 0;
  };
  /**
   * Reset the cursor to the end.
   * @private
   */
  TimelinePath.prototype.resetCursorEnd = function () {
      this.cursor = this.events.length - 1;
  };
  /**
   * Cancel current playing. Leaves the cursor intact.
   * @private
   * @param {boolean} [fadeOut=false] - Whether or not to fade out as we stop. If
   * false, the path is cancelled synchronously.
   */
  TimelinePath.prototype.pause = function (fadeOut) {
      var timelinePath = this;
      // Cancel next scheduled play
      clearTimeout(timelinePath.nextScheduledPlay);
      // Cancel currently playing events
      Object.keys(timelinePath.eventsPlaying).forEach(function (id) {
          if (timelinePath.eventsPlaying[id]) {
              timelinePath.eventsPlaying[id].cancel(fadeOut);
          }
      });
      timelinePath.eventsPlaying = {};
  };
  /**
   * Play the events, starting from current cursor, and going in specified
   * direction.
   * @private
   * @param {number} direction
   * The direction to play, 1 for forwards and -1 for backwards.
   * @return {void}
   */
  TimelinePath.prototype.playEvents = function (direction) {
      var timelinePath = this, curEvent = timelinePath.events[this.cursor], nextEvent = timelinePath.events[this.cursor + direction], timeDiff, onEnd = function (signalData) {
          timelinePath.signalHandler.emitSignal('masterOnEnd', signalData);
          timelinePath.signalHandler.emitSignal('playOnEnd', signalData);
      };
      // Store reference to path on event
      curEvent.timelinePath = timelinePath;
      // Emit event, cancel if returns false
      if (timelinePath.signalHandler.emitSignal('onEventStart', curEvent) === false) {
          onEnd({
              event: curEvent,
              cancelled: true
          });
          return;
      }
      // Play the current event
      timelinePath.eventsPlaying[curEvent.id] = curEvent;
      curEvent.play({
          onEnd: function (cancelled) {
              var signalData = {
                  event: curEvent,
                  cancelled: !!cancelled
              };
              // Keep track of currently playing events for cancelling
              delete timelinePath.eventsPlaying[curEvent.id];
              // Handle onEventEnd
              timelinePath.signalHandler.emitSignal('onEventEnd', signalData);
              // Reached end of path?
              if (!nextEvent) {
                  onEnd(signalData);
              }
          }
      });
      // Schedule next
      if (nextEvent) {
          timeDiff = Math.abs(nextEvent.time - curEvent.time);
          if (timeDiff < 1) {
              // Play immediately
              timelinePath.cursor += direction;
              timelinePath.playEvents(direction);
          }
          else {
              // Schedule after the difference in ms
              this.nextScheduledPlay = setTimeout(function () {
                  timelinePath.cursor += direction;
                  timelinePath.playEvents(direction);
              }, timeDiff);
          }
      }
  };
  /* ************************************************************************** *
   *  TIMELINE                                                                  *
   * ************************************************************************** */
  /**
   * A set of options for the Timeline class.
   *
   * @requires module:modules/sonification
   *
   * @private
   * @interface Highcharts.TimelineOptionsObject
   */ /**
  * List of TimelinePaths to play. Multiple paths can be grouped together and
  * played simultaneously by supplying an array of paths in place of a single
  * path.
  * @name Highcharts.TimelineOptionsObject#paths
  * @type {Array<(Highcharts.TimelinePath|Array<Highcharts.TimelinePath>)>}
  */ /**
  * Callback function to call before a path plays.
  * @name Highcharts.TimelineOptionsObject#onPathStart
  * @type {Function|undefined}
  */ /**
  * Callback function to call after a path has stopped playing.
  * @name Highcharts.TimelineOptionsObject#onPathEnd
  * @type {Function|undefined}
  */ /**
  * Callback called when the whole path is finished.
  * @name Highcharts.TimelineOptionsObject#onEnd
  * @type {Function|undefined}
  */
  /**
   * The Timeline class. Represents a sonification timeline with a list of
   * timeline paths with events to play at certain times relative to each other.
   *
   * @requires module:modules/sonification
   *
   * @private
   * @class
   * @name Highcharts.Timeline
   *
   * @param {Highcharts.TimelineOptionsObject} options
   *        Options for the Timeline.
   */
  function Timeline(options) {
      this.init(options || {});
  }
  Timeline.prototype.init = function (options) {
      this.options = options;
      this.cursor = 0;
      this.paths = options.paths || [];
      this.pathsPlaying = {};
      this.signalHandler = new utilities.SignalHandler(['playOnEnd', 'masterOnEnd', 'onPathStart', 'onPathEnd']);
      this.signalHandler.registerSignalCallbacks(merge(options, { masterOnEnd: options.onEnd }));
  };
  /**
   * Play the timeline forwards from cursor.
   * @private
   * @param {Function} [onEnd]
   * Callback to call when play finished. Does not override other onEnd callbacks.
   * @return {void}
   */
  Timeline.prototype.play = function (onEnd) {
      this.pause();
      this.signalHandler.clearSignalCallbacks(['playOnEnd']);
      this.signalHandler.registerSignalCallbacks({ playOnEnd: onEnd });
      this.playPaths(1);
  };
  /**
   * Play the timeline backwards from cursor.
   * @private
   * @param {Function} onEnd
   * Callback to call when play finished. Does not override other onEnd callbacks.
   * @return {void}
   */
  Timeline.prototype.rewind = function (onEnd) {
      this.pause();
      this.signalHandler.clearSignalCallbacks(['playOnEnd']);
      this.signalHandler.registerSignalCallbacks({ playOnEnd: onEnd });
      this.playPaths(-1);
  };
  /**
   * Play the timeline in the specified direction.
   * @private
   * @param {number} direction
   * Direction to play in. 1 for forwards, -1 for backwards.
   * @return {void}
   */
  Timeline.prototype.playPaths = function (direction) {
      var timeline = this;
      var signalHandler = timeline.signalHandler;
      if (!timeline.paths.length) {
          var emptySignal = {
              cancelled: false
          };
          signalHandler.emitSignal('playOnEnd', emptySignal);
          signalHandler.emitSignal('masterOnEnd', emptySignal);
          return;
      }
      var curPaths = splat(this.paths[this.cursor]), nextPaths = this.paths[this.cursor + direction], pathsEnded = 0, 
      // Play a path
      playPath = function (path) {
          // Emit signal and set playing state
          signalHandler.emitSignal('onPathStart', path);
          timeline.pathsPlaying[path.id] = path;
          // Do the play
          path[direction > 0 ? 'play' : 'rewind'](function (callbackData) {
              // Play ended callback
              // Data to pass to signal callbacks
              var cancelled = callbackData && callbackData.cancelled, signalData = {
                  path: path,
                  cancelled: cancelled
              };
              // Clear state and send signal
              delete timeline.pathsPlaying[path.id];
              signalHandler.emitSignal('onPathEnd', signalData);
              // Handle next paths
              pathsEnded++;
              if (pathsEnded >= curPaths.length) {
                  // We finished all of the current paths for cursor.
                  if (nextPaths && !cancelled) {
                      // We have more paths, move cursor along
                      timeline.cursor += direction;
                      // Reset upcoming path cursors before playing
                      splat(nextPaths).forEach(function (nextPath) {
                          nextPath[direction > 0 ? 'resetCursor' : 'resetCursorEnd']();
                      });
                      // Play next
                      timeline.playPaths(direction);
                  }
                  else {
                      // If it is the last path in this direction, call onEnd
                      signalHandler.emitSignal('playOnEnd', signalData);
                      signalHandler.emitSignal('masterOnEnd', signalData);
                  }
              }
          });
      };
      // Go through the paths under cursor and play them
      curPaths.forEach(function (path) {
          if (path) {
              // Store reference to timeline
              path.timeline = timeline;
              // Leave a timeout to let notes fade out before next play
              setTimeout(function () {
                  playPath(path);
              }, H.sonification.fadeOutDuration);
          }
      });
  };
  /**
   * Stop the playing of the timeline. Cancels all current sounds, but does not
   * affect the cursor.
   * @private
   * @param {boolean} [fadeOut=false]
   * Whether or not to fade out as we stop. If false, the timeline is cancelled
   * synchronously.
   * @return {void}
   */
  Timeline.prototype.pause = function (fadeOut) {
      var timeline = this;
      // Cancel currently playing events
      Object.keys(timeline.pathsPlaying).forEach(function (id) {
          if (timeline.pathsPlaying[id]) {
              timeline.pathsPlaying[id].pause(fadeOut);
          }
      });
      timeline.pathsPlaying = {};
  };
  /**
   * Reset the cursor to the beginning of the timeline.
   * @private
   * @return {void}
   */
  Timeline.prototype.resetCursor = function () {
      this.paths.forEach(function (paths) {
          splat(paths).forEach(function (path) {
              path.resetCursor();
          });
      });
      this.cursor = 0;
  };
  /**
   * Reset the cursor to the end of the timeline.
   * @private
   * @return {void}
   */
  Timeline.prototype.resetCursorEnd = function () {
      this.paths.forEach(function (paths) {
          splat(paths).forEach(function (path) {
              path.resetCursorEnd();
          });
      });
      this.cursor = this.paths.length - 1;
  };
  /**
   * Set the current TimelineEvent under the cursor. If multiple paths are being
   * played at the same time, this function only affects a single path (the one
   * that contains the eventId that is passed in).
   * @private
   * @param {string} eventId
   * The ID of the timeline event to set as current.
   * @return {boolean}
   * True if the cursor was set, false if no TimelineEvent was found for this ID.
   */
  Timeline.prototype.setCursor = function (eventId) {
      return this.paths.some(function (paths) {
          return splat(paths).some(function (path) {
              return path.setCursor(eventId);
          });
      });
  };
  /**
   * Get the current TimelineEvents under the cursors. This function will return
   * the event under the cursor for each currently playing path, as an object
   * where the path ID is mapped to the TimelineEvent under that path's cursor.
   * @private
   * @return {Highcharts.Dictionary<Highcharts.TimelineEvent>}
   * The TimelineEvents under each path's cursors.
   */
  Timeline.prototype.getCursor = function () {
      return this.getCurrentPlayingPaths().reduce(function (acc, cur) {
          acc[cur.id] = cur.getCursor();
          return acc;
      }, {});
  };
  /**
   * Check if timeline is reset or at start.
   * @private
   * @return {boolean}
   * True if timeline is at the beginning.
   */
  Timeline.prototype.atStart = function () {
      if (this.cursor) {
          return false;
      }
      return !splat(this.paths[0]).some(function (path) {
          return path.cursor;
      });
  };
  /**
   * Get the current TimelinePaths being played.
   * @private
   * @return {Array<Highcharts.TimelinePath>}
   * The TimelinePaths currently being played.
   */
  Timeline.prototype.getCurrentPlayingPaths = function () {
      if (!this.paths.length) {
          return [];
      }
      return splat(this.paths[this.cursor]);
  };
  // Export the classes
  var timelineClasses = {
      TimelineEvent: TimelineEvent,
      TimelinePath: TimelinePath,
      Timeline: Timeline
  };
  export default timelineClasses;
  