<commit_before>// Copyright 2017-2019, University of Colorado Boulder

/**
 * Manages a queue of Utterances that are read in order by a screen reader.  This queue typically reads
 * things in a first-in-first-out manner, but it is possible to send an alert directly to the front of
 * the queue.  Items in the queue are sent to the screen reader front to back with a certain delay interval.
 *
 * Screen readers are inconsistent in the way that they order alerts, some use last-in-first-out order,
 * others use first-in-first-out order, others just read the last alert that was provided. This queue
 * manages order and improves consistency.
 *
 * NOTE: utteranceQueue is a type but instantiated and returned as a singleton.  It is initialized by Sim.js and if
 * something adds an alert to the queue before Sim.js has initialized the queue, the result will be a silent no-op.
 *
 * @author <NAME> (PhET Interactive Simulations)
 * @author <NAME> (PhET Interactive Simulations)
 */
define( require => {
  'use strict';

  // modules
  const AlertableDef = require( 'SCENERY_PHET/accessibility/AlertableDef' );
  const ariaHerald = require( 'SCENERY_PHET/accessibility/ariaHerald' );
  const PhetioObject = require( 'TANDEM/PhetioObject' );
  const sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  const Tandem = require( 'TANDEM/Tandem' );
  const timer = require( 'AXON/timer' );
  const Utterance = require( 'SCENERY_PHET/accessibility/Utterance' );
  const UtteranceQueueIO = require( 'SCENERY_PHET/accessibility/UtteranceQueueIO' );

  /**
   * Can't be called, used only for a singleton, see end of this file.
   * @constructor
   */
  class UtteranceQueue extends PhetioObject {
    constructor() {
      super();

      // @private {boolean} initialization is like utteranceQueue's constructor. No-ops all around if not
      // initialized (cheers). See initialize();
      this._initialized = false;

      // @private {Array.<Utterance>} - array of Utterances, spoken in first to last order
      this.queue = [];

      // @private {number} the interval for sending alerts to the screen reader, in milliseconds
      // this value is relatively arbitrary, but it was discovered that spacing alerts at an
      // interval forced screen readers to read things in FIFO order
      this._stepInterval = 500;

      // whether or not Utterances moving through the queue are read by a screen reader
      this._muted = false;

      // whether the UtterancesQueue is alerting, and if you can add/remove utterances
      this._enabled = true;
    }


    /**
     * Add an utterance ot the end of the queue.  If the utterance has a type of alert which
     * is already in the queue, the older alert will be immediately removed.
     *
     * @public
     * @param {AlertableDef} utterance
     */
    addToBack( utterance ) {
      assert && assert( AlertableDef.isAlertableDef( utterance ), 'trying to alert something that isn\'t alertable: ' + utterance );

      // No-op function if the utteranceQueue is disabled
      if ( !this._enabled || !this._initialized ) {
        return;
      }

      if ( typeof utterance === 'string' ) {
        utterance = new Utterance( { alert: utterance } );
      }

      // clear utterances of the same group as the one being added
      this.clearUtteranceGroup( utterance );

      this.queue.push( utterance );
    }

    /**
     * Convenience function to help with nullable values. No-op if null or nothing is passed in
     * @param {null|AlertableDef} [utterance]
     */
    addToBackIfDefined( utterance ) {
      if ( utterance !== null && utterance !== undefined ) {
        assert && assert( AlertableDef.isAlertableDef( utterance ), 'trying to alert something that isn\'t alertable: ' + utterance );

        this.addToBack( utterance );
      }
    }

    /**
     * Add an utterance to the front of the queue to be read immediately.
     * @param {AlertableDef} utterance
     */
    addToFront( utterance ) {
      assert && assert( AlertableDef.isAlertableDef( utterance ), 'trying to alert something that isn\'t alertable: ' + utterance );

      // No-op function if the utteranceQueue is disabled
      if ( !this._enabled || !this._initialized ) {
        return;
      }

      if ( typeof utterance === 'string' ) {
        utterance = new Utterance( { alert: utterance } );
      }

      // remove any utterances of the same group as the one being added
      this.clearUtteranceGroup( utterance );

      this.queue.unshift( utterance );
    }

    /**
     * Move to the next item in the queue. Checks the Utterance predicate first, if predicate
     * returns false, no alert will be read. Called privately by timer.setInterval
     *
     * @private
     */
    next() {

      // find the next item to announce - generally the next item in the queue, unless it has a delay specified that
      // is greater than the amount of time that the utterance has been sitting in the queue
      let nextUtterance;
      for ( let i = 0; i < this.queue.length; i++ ) {
        const utterance = this.queue[ i ];

        const alertStable = utterance.alertStable;
        const utteranceStabilized = utterance.stableTime > utterance.alertStableDelay;
        const alertMaximumDelay = utterance.timeInQueue > utterance.alertMaximumDelay;

        if ( !alertStable || utteranceStabilized || alertMaximumDelay ) {
          nextUtterance = utterance;
          this.queue.splice( i, 1 );

          // if waiting for stability but we hit the alertMaximimumDelay, reset time for this
          // utterance in queue
          if ( nextUtterance.alertStable ) {
            nextUtterance.timeInQueue = 0;
          }

          break;
        }
      }

      // only speak the utterance if the Utterance predicate returns true
      if ( nextUtterance && !this._muted && nextUtterance.predicate() ) {

        // just get the text of the Utterance once! This is because getting it triggers updates in the Utterance that
        // should only be triggered on alert! See Utterance.getTextToAlert
        const text = nextUtterance.getTextToAlert();

        // phet-io event to the data stream
        this.phetioStartEvent( 'announced', { utterance: text } );

        // Pass the utterance text on to be set in the PDOM.
        ariaHerald.announcePolite( text );

        this.phetioEndEvent();
      }
    }

    /**
     * Called by addToFront and addToBack, do not call this. Clears the queue of all utterances of the specified group
     * to support the behavior of alertStable. See Utterance.uniqueId for description of this feature.
     *
     * @param {number} uniqueId
     * @private
     */
    clearUtteranceGroup( utterance ) {

      if ( utterance.alertStable ) {

        // reset the time watching utterance stability since it has been added to the queue<commit_msg>remove group terminology, see #485<commit_after>// Copyright 2017-2019, University of Colorado Boulder

/**
 * Manages a queue of Utterances that are read in order by a screen reader.  This queue typically reads
 * things in a first-in-first-out manner, but it is possible to send an alert directly to the front of
 * the queue.  Items in the queue are sent to the screen reader front to back with a certain delay interval.
 *
 * Screen readers are inconsistent in the way that they order alerts, some use last-in-first-out order,
 * others use first-in-first-out order, others just read the last alert that was provided. This queue
 * manages order and improves consistency.
 *
 * NOTE: utteranceQueue is a type but instantiated and returned as a singleton.  It is initialized by Sim.js and if
 * something adds an alert to the queue before Sim.js has initialized the queue, the result will be a silent no-op.
 *
 * @author <NAME> (PhET Interactive Simulations)
 * @author <NAME> (PhET Interactive Simulations)
 */
define( require => {
  'use strict';

  // modules
  const AlertableDef = require( 'SCENERY_PHET/accessibility/AlertableDef' );
  const ariaHerald = require( 'SCENERY_PHET/accessibility/ariaHerald' );
  const PhetioObject = require( 'TANDEM/PhetioObject' );
  const sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  const Tandem = require( 'TANDEM/Tandem' );
  const timer = require( 'AXON/timer' );
  const Utterance = require( 'SCENERY_PHET/accessibility/Utterance' );
  const UtteranceQueueIO = require( 'SCENERY_PHET/accessibility/UtteranceQueueIO' );

  /**
   * Can't be called, used only for a singleton, see end of this file.
   * @constructor
   */
  class UtteranceQueue extends PhetioObject {
    constructor() {
      super();

      // @private {boolean} initialization is like utteranceQueue's constructor. No-ops all around if not
      // initialized (cheers). See initialize();
      this._initialized = false;

      // @private {Array.<Utterance>} - array of Utterances, spoken in first to last order
      this.queue = [];

      // @private {number} the interval for sending alerts to the screen reader, in milliseconds
      // this value is relatively arbitrary, but it was discovered that spacing alerts at an
      // interval forced screen readers to read things in FIFO order
      this._stepInterval = 500;

      // whether or not Utterances moving through the queue are read by a screen reader
      this._muted = false;

      // whether the UtterancesQueue is alerting, and if you can add/remove utterances
      this._enabled = true;
    }


    /**
     * Add an utterance ot the end of the queue.  If the utterance has a type of alert which
     * is already in the queue, the older alert will be immediately removed.
     *
     * @public
     * @param {AlertableDef} utterance
     */
    addToBack( utterance ) {
      assert && assert( AlertableDef.isAlertableDef( utterance ), 'trying to alert something that isn\'t alertable: ' + utterance );

      // No-op function if the utteranceQueue is disabled
      if ( !this._enabled || !this._initialized ) {
        return;
      }

      if ( typeof utterance === 'string' ) {
        utterance = new Utterance( { alert: utterance } );
      }

      // clear any utterances if they are duplicates of the one being added
      this.clearUtterances( utterance );

      this.queue.push( utterance );
    }

    /**
     * Convenience function to help with nullable values. No-op if null or nothing is passed in
     * @param {null|AlertableDef} [utterance]
     */
    addToBackIfDefined( utterance ) {
      if ( utterance !== null && utterance !== undefined ) {
        assert && assert( AlertableDef.isAlertableDef( utterance ), 'trying to alert something that isn\'t alertable: ' + utterance );

        this.addToBack( utterance );
      }
    }

    /**
     * Add an utterance to the front of the queue to be read immediately.
     * @param {AlertableDef} utterance
     */
    addToFront( utterance ) {
      assert && assert( AlertableDef.isAlertableDef( utterance ), 'trying to alert something that isn\'t alertable: ' + utterance );

      // No-op function if the utteranceQueue is disabled
      if ( !this._enabled || !this._initialized ) {
        return;
      }

      if ( typeof utterance === 'string' ) {
        utterance = new Utterance( { alert: utterance } );
      }

      // remove any utterances if they are duplicates of the one being added
      this.clearUtterances( utterance );

      this.queue.unshift( utterance );
    }

    /**
     * Move to the next item in the queue. Checks the Utterance predicate first, if predicate
     * returns false, no alert will be read. Called privately by timer.setInterval
     *
     * @private
     */
    next() {

      // find the next item to announce - generally the next item in the queue, unless it has a delay specified that
      // is greater than the amount of time that the utterance has been sitting in the queue
      let nextUtterance;
      for ( let i = 0; i < this.queue.length; i++ ) {
        const utterance = this.queue[ i ];

        const alertStable = utterance.alertStable;
        const utteranceStabilized = utterance.stableTime > utterance.alertStableDelay;
        const alertMaximumDelay = utterance.timeInQueue > utterance.alertMaximumDelay;

        if ( !alertStable || utteranceStabilized || alertMaximumDelay ) {
          nextUtterance = utterance;
          this.queue.splice( i, 1 );

          // if waiting for stability but we hit the alertMaximimumDelay, reset time for this
          // utterance in queue
          if ( nextUtterance.alertStable ) {
            nextUtterance.timeInQueue = 0;
          }

          break;
        }
      }

      // only speak the utterance if the Utterance predicate returns true
      if ( nextUtterance && !this._muted && nextUtterance.predicate() ) {

        // just get the text of the Utterance once! This is because getting it triggers updates in the Utterance that
        // should only be triggered on alert! See Utterance.getTextToAlert
        const text = nextUtterance.getTextToAlert();

        // phet-io event to the data stream
        this.phetioStartEvent( 'announced', { utterance: text } );

        // Pass the utterance text on to be set in the PDOM.
        ariaHerald.announcePolite( text );

        this.phetioEndEvent();
      }
    }

    /**
     * Called by addToFront and addToBack, do not call this. Clears the queue of all utterances duplicates
     * to support the behavior of alertStable. See Utterance.uniqueId for description of this feature.
     *
     * @param {number} uniqueId
     * @private
     */
    clearUtterances( utterance ) {

      if ( utterance.alertStable ) {

        // reset the time watching utterance stability since it has been added to the queue
{'fraction_non_alphanumeric': 0.0835942619966504, 'fraction_numerical': 0.002257336343115124, 'mean_word_length': 2.6712109061748195, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4103861', 'n_tokens_mistral': 3911, 'n_tokens_neox': 3741, 'n_words': 1461}