// Copyright 2020, University of Colorado Boulder

/**
 * AssertUtils is a collection of utility functions for common assertions. Many of these assertions are related to
 * type-checking, useful in a weakly-typed language like JavaScript.
 *
 * To minimize performance impact, these methods should generally be called after testing for the presence of assert,
 * for example: assert && AssertUtils.assertPropertyOf( someProperty, 'number' );
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */

import EnumerationProperty from '../../axon/js/EnumerationProperty.js';
import Property from '../../axon/js/Property.js';
import Range from '../../dot/js/Range.js';
import Enumeration from '../../phet-core/js/Enumeration.js';
import phetcommon from '../../phetcommon/js/phetcommon.js';

const AssertUtils = {

  /**
   * Asserts that a value is a Property, whose value satisfies an optional predicate.
   * @param {Property} property
   * @param {function(value:*):boolean} [predicate]
   * @public
   */
  assertProperty( property, predicate ) {
    assert && assert( property instanceof Property, 'property is not a Property' );
    if ( predicate ) {
      assert && assert( predicate( property.value ), 'Property.value failed predicate' );
    }
  },

  /**
   * Asserts that a value is a Property, whose value is a specified type.
   * @param {Property} property
   * @param {string|Enumeration|constructor} type - primitive type (string), Enumeration type, or object type (constructor)
   * @public
   */
  assertPropertyOf( property, type ) {
    if ( typeof type === 'string' ) {
      assert && AssertUtils.assertProperty( property, value => typeof value === type );
    }
    else if ( type instanceof Enumeration ) {
      assert && AssertUtils.assertProperty( property, value => type.includes( value ) );
    }
    else {
      assert && AssertUtils.assertProperty( property, value => value instanceof type );
    }
  },

  /**
   * Asserts that a value is an integer that satisfies an optional predicate.
   * @param {number} value
   * @param {function(value:number):boolean} [predicate]
   * @returns {boolean}
   * @public
   */
  assertInteger( value, predicate ) {
    assert && assert( typeof value === 'number' && Number.isInteger( value ), `invalid value: ${value}` );
    if ( predicate ) {
      assert && assert( predicate( value ), `value does not satisfy predicate: ${value}` );
    }
  },

  /**
   * Asserts that a value is a positive integer.
   * @param {number} value
   * @returns {boolean}
   * @public
   */
  assertPositiveInteger( value ) {
    assert && AssertUtils.assertInteger( value, value => value > 0 );
  },

  /**
   * Asserts that a value is a positive number.
   * @param {number} value
   * @returns {boolean}
   * @public
   */
  assertPositiveNumber( value ) {
    assert && assert( typeof value === 'number' && value > 0 );
  },

  /**
   * Asserts that a value is a Range, whose value is between min and max inclusive.
   * @param {Range} range
   * @param {number} min
   * @param {number} max
   * @public
   */
  assertRangeBetween( range, min, max ) {
    assert && assert( range instanceof Range, 'invalid range' );
    assert && assert( range.min >= min && range.max <= max, `range exceeds limits: ${range}` );
  },

  /**
   * Asserts that a value is an Array, whose elements satisfy an optional predicate.
   * @param {Array} array
   * @param {function(array:Array):boolean} [predicate]
   * @public
   */
  assertArray( array, predicate ) {
    assert && assert( Array.isArray( array ), 'array is not an Array' );
    if ( predicate ) {
      assert && assert( predicate( array ), 'array failed predicate' );
    }
  },

  /**
   * Asserts that a value is an Array, with elements of a specific type.
   * @param {Array} array
   * @param {string|constructor} type - primitive type (string) or object type (constructor)
   * @public
   */
  assertArrayOf( array, type ) {
    if ( typeof type === 'string' ) {
      assert && AssertUtils.assertArray( array, array => _.every( array, element => typeof element === type ) );
    }
    else {
      assert && AssertUtils.assertArray( array, array => _.every( array, element => element instanceof type ) );
    }
  },

  /**
   * Asserts that a value is an EnumerationProperty, whose values are a specific type of Enumeration.
   * @param {EnumerationProperty} enumerationProperty
   * @param {Enumeration} enumeration
   */
  assertEnumerationPropertyOf( enumerationProperty, enumeration ) {
    assert && assert( enumerationProperty instanceof EnumerationProperty, 'invalid enumerationProperty' );
    assert && assert( enumerationProperty.enumeration === enumeration, 'invalid enumeration' );
  }
};

phetcommon.register( 'AssertUtils', AssertUtils );
export default AssertUtils;
@@ -2455,16 +2455,21 @@
  integer
+, %3E 0
 .%0A   * @
@@ -2635,32 +2635,282 @@
 ue %3E 0 );%0A  %7D,%0A%0A
+  /**%0A   * Asserts that a value is a non-negative integer, %3E= 0.%0A   * @param %7Bnumber%7D value%0A   * @returns %7Bboolean%7D%0A   * @public%0A   */%0A  assertNonNegativeInteger( value ) %7B%0A    assert && AssertUtils.assertInteger( value, value =%3E value %3E= 0 );%0A  %7D,%0A%0A
   /**%0A   * Asser
@@ -2945,16 +2945,21 @@
 e number
+, %3E 0
 .%0A   * @
@@ -3118,32 +3118,274 @@
 ue %3E 0 );%0A  %7D,%0A%0A
+  /**%0A   * Asserts that a value is a non-negative number, %3E= 0.%0A   * @param %7Bnumber%7D value%0A   * @returns %7Bboolean%7D%0A   * @public%0A   */%0A  assertNonNegativeNumber( value ) %7B%0A    assert && assert( typeof value === 'number' && value %3E= 0 );%0A  %7D,%0A%0A
   /**%0A   * Asser

add non-negative checks