text: /**
   * @module meteoJS/timeline/visualisation
   */
  import addEventFunctions from '../Events.js';
  import Timeline from '../Timeline.js';
  
  /**
   * Returns a textual representation of a time according to a passed format.
   * 
   * This function could be used to convert Date-objects to a readable time with
   * external libraries. One such implementation is given by
   * {@link module:meteoJS/timeline/visualisation.makeTimeTextCallbackFunction}.
   * 
   * @typedef {Function}
   *   module:meteoJS/timeline/visualisation~timeTextCallbackFunction
   * @param {Date} time - A valid datetime.
   * @param {string} format - Format string.
   * @returns {string} Textual representation.
   */
  
  /**
   * Options for Visualisation.
   * 
   * @typedef {Object} module:meteoJS/timeline/visualisation~options
   * @param {module:meteoJS/timeline.Timeline} [timeline]
   *   Timeline object.
   * @param {external:jQuery} [node] - Node.
   * @param {module:meteoJS/timeline/animation.Animation} [animation]
   *   Animation object. If specified, the animation will be stopped on user
   *   interaction with the visualisation object.
   * @param {boolean} [enabledStepsOnly=true] - Use only enabled times.
   * @param {boolean} [allEnabledStepsOnly=false]
   *   Use only times that are enabled by all sets of time.
   * @param {string} [textInvalid='-']
   *   Output string, if time of timeline is invalid.
   * @param {module:meteoJS/timeline/visualisation~timeTextCallbackFunction}
   *   [getTimeText]
   *   Returns a textual representation of a time according to a passed format.
   * @param {string} [outputTimezone]
   *   'local' for browser local timezone.
   */
  
  /**
   * Object to visualise {@link module:meteoJS/timeline.Timeline}.
   * 
   * How to create your own visualisation object:
   * * Inherit this object
   * * Inherit method onChangeTime()
   * * Inherit method onChangeTimes()
   * * Inherit method emptyNode()
   * * Inherit method onInitNode()
   * * To get all times to display use getTimelineTimes()
   * * Use method timeToText() to convert a Date to String.
   * * Use method attachEventListener() to attach event listeners on a object.
   *   The event listener will be deleted automatically if the output is muted.
   * * At the end of the constructor call "this.setNode(this.options.node);".
   * 
   * <pre><code>import Visualisation from 'meteojs/timeline/Visualisation';</code></pre>
   * 
   * @abstract
   * @listens module:meteoJS/timeline#change:time
   * @listens module:meteoJS/timeline#change:times
   * @listens module:meteoJS/timeline#change:enabledTimes
   */
  export class Visualisation {
    
    /**
     * @param {module:meteoJS/timeline/visualisation~options} options - Options.
     */
    constructor({
      timeline = undefined,
      node = undefined,
      animation = undefined,
      enabledStepsOnly = true,
      allEnabledStepsOnly = false,
      textInvalid = '-',
      getTimeText = undefined,
      outputTimezone = undefined
    } = {}) {
      /**
       * @type Object
       * @private
       */
      this.options = {
        timeline,
        node,
        animation,
        enabledStepsOnly,
        allEnabledStepsOnly,
        textInvalid,
        getTimeText,
        outputTimezone
      };
      
      // Normalize options
      if (this.options.timeline === undefined)
        this.options.timeline = new Timeline();
      
      /**
       * @member {Array[]}
       * @private
       */
      this.listeners = [];
      
      /**
       * @member {undefined|mixed}
       * @private
       */
      this.inputListener = undefined;
    }
    
    /**
     * Sets jQuery-Node for output.
     * 
     * @public
     * @param {external:jQuery|undefined} node Node, undefined to mute the output.
     * @returns {module:meteoJS/timeline/visualisation.Visualisation} This.
     */
    setNode(node) {
      if (this.options.node !== undefined)
        this.emptyNode();
    
      if (node === undefined) {
        this.detachEventListeners();
        this.options.node = node;
      }
      else {
        this.options.node = node;
        var isListenersDefined = this.listeners.length > 0;
        if (!isListenersDefined) {
          this.attachEventListener(this.options.timeline, 'change:time', function () {
            this.onChangeTime();
          }, this);
          this.attachEventListener(this.options.timeline, 'change:times', function () {
            this.onChangeTimes();
          }, this);
          this.attachEventListener(this.options.timeline, 'change:enabledTimes', function () {
            this.onChangeTimes();
          }, this);
        }
        this.onInitNode(isListenersDefined);
        this.onChangeTimes();
        this.onChangeTime();
      }
    
      if (this.inputListener === undefined)
        this.inputListener = this.on('input', function () {
          if (this.options.animation !== undefined)
            this.options.animation.stop();
        }, this);
    
      return this;
    }
    
    /**
     * Gets current value of output timezone.
     * 
     * @public
     * @returns {string|undefined} Output timezone.
     */
    getOutputTimezone() {
      return this.options.outputTimezone;
    }
    
    /**
     * Sets output timezone, undefined for UTC.
     * 
     * @public
     * @param {string|undefined} outputTimezone Timezone for datetime output.
     * @returns {module:meteoJS/timeline/visualisation.Visualisation} This.
     */
    setOutputTimezone(outputTimezone) {
      var updateOutput = (this.options.outputTimezone != outputTimezone);
      this.options.outputTimezone = outputTimezone;
      if (updateOutput &&
          this.options.node !== undefined) {
        this.onChangeTimes();
        this.onChangeTime();
      }
      return this;
    }
    
    /**
     * Called if the timeline triggers the
     * {@link module:meteoJS/timeline#change:time} event.
     * Prerequisite: this.options.node must be defined.
     * 
     * @abstract
     * @protected
     */
    onChangeTime() {}
    
    /**
     * Called if the timeline triggers the {@link module:meteoJS/timeline#change:times}
     * or {@link module:meteoJS/timeline#change:enabledTimes} event.
     * Prerequisite: this.options.node must be defined.
     * 
     * @abstract
     * @protected
     */
    onChangeTimes() {}
    
    /**
     * Called to empty the output node. Mainly if the output is muted.
     * Prerequisite: this.options.node must be defined.
     * 
     * @abstract
     * @protected
     */
    emptyNode() {}
    
    /**
     * Called once an output node is set.
     * Prerequisite: this.options.node must be defined.
     * 
     * @abstract
     * @protected
     * @param {boolean} isListenersDefined
     *   True if the event listeners are already set.
     */
    onInitNode() {}
    
    /**
     * Returns the times to display. This could be either all times in the timeline
     * or only the enabled times or the all enabled times. The user of the
     * visualisation object select this by the options.
     * 
     * @protected
     * @returns {Date[]} Times.
     */
    getTimelineTimes() {
      var methodName = this.options.allEnabledStepsOnly ?
        'getAllEnabledTimes' :
        this.options.enabledStepsOnly ? 'getEnabledTimes' : 'getTimes';
      return this.options.timeline[methodName]();
    }
    
    /**
     * Converts a Date-object to a string.
     * 
     * @protected
     * @param {Date} time - Time.
     * @param {string} format - Format string, passed to the .
     * @returns {string} String.
     */
    timeToText(time, format) {
      if (isNaN(time))
        return this.options.textInvalid;
      if (this.options.getTimeText !== undefined)
        return this.options.getTimeText.call(this, time, format);
      return time.toISOString();
    }
    
    /**
     * Attach an event listener on an object. Object could be a jQuery-object or
     * an object using {@link module:meteoJS/events}.
     * 
     * @protected
     * @param {Object} obj - Object to put the event listener on.
     * @param {mixed} listener - Event listener key.
     * @param {Function} func - Function to be executed when event is triggered.
     * @param {Object} [thisArg] - This in the function func when event triggered.
     */
    attachEventListener(obj, listener, func, thisArg) {
      this.listeners.push([obj, listener]);
      obj.on(listener, func, thisArg);
    }
    
    /**
     * Detaches all event listeners.
     * 
     * @private
     */
    detachEventListeners() {
      this.listeners.forEach(function (listenerArr) {
        if ('un' in listenerArr[0])
          listenerArr[0].un(listenerArr[1]);
        else if ('off' in listenerArr[0])
          listenerArr[0].off(listenerArr[1]);
      });
      this.listeners = [];
    }
    
  }
  addEventFunctions(Visualisation.prototype);
  export default Visualisation;
  
  /**
   * moment.js object.
   * 
   * @external momentjs
   * @see {@link https://momentjs.com}
   */
  
  /**
   * Format a Date-object via the {@link https://momentjs.com|Moment.js} library.
   * 
   * <pre><code>import { makeTimeTextCallbackFunction } from 'meteojs/timeline/Visualisation';</code></pre>
   * 
   * @param {external:momentjs} moment - Moment.js object.
   * @returns {module:meteoJS/timeline/visualisation~timeTextCallbackFunction}
   *   Callback.
   */
  export function makeTimeTextCallbackFunction(moment) {
    return function (time, format) {
      const m = moment.utc(time);
      if (this.options.outputTimezone !== undefined)
        (this.options.outputTimezone == 'local')
          ? m.local()
          : m.tz(this.options.outputTimezone);
      return m.format(format);
    };
  }