import '../../assert/js/assert.js';
import '../../globals.js';
import InfiniteNumberIO from '../../tandem/js/types/InfiniteNumberIO.js';
import IOType from '../../tandem/js/types/IOType.js';
import dot from './dot.js';
const STATE_SCHEMA = {
    min: InfiniteNumberIO,
    max: InfiniteNumberIO
};
class Range {
    /**
     * @param min - the minimum value of the range
     * @param max - the maximum value of the range
     */
    constructor(min, max) {
        this._min = min;
        this._max = max;
    }
    /**
     * Getter for min
     */
    getMin() {
        return this._min;
    }
    get min() {
        return this.getMin();
    }
    set min(min) {
        this.setMin(min);
    }
    /**
     * TODO: Allow chaining, https://github.com/phetsims/dot/issues/122
     * Setter for min
     */
    setMin(min) {
        assert && assert(min <= this._max, `min must be <= max: ${min}`);
        this._min = min;
    }
    /**
     * Getter for max
     */
    getMax() {
        return this._max;
    }
    get max() {
        return this.getMax();
    }
    set max(max) {
        this.setMax(max);
    }
    /**
     * Setter for max
     */
    setMax(max) {
        assert && assert(this._min <= max, `max must be >= to min: ${max}`);
        this._max = max;
    }
    /**
     * Sets the minimum and maximum value of the range
     */
    setMinMax(min, max) {
        assert && assert(min <= max, `max must be >= to min. min: ${min}, max: ${max}`);
        this._min = min;
        this._max = max;
        return this;
    }
    /**
     * Sets the minimum and maximum value of this range from the provided Range.
     */
    set(range) {
        this.setMinMax(range.min, range.max);
        return this;
    }
    addValue(n) {
        this._min = Math.min(this._min, n);
        this._max = Math.max(this._max, n);
    }
    withValue(n) {
        return new Range(Math.min(this._min, n), Math.max(this._max, n));
    }
    /**
     * Makes a copy of this range
     */
    copy() {
        return new Range(this._min, this._max);
    }
    /**
     * Gets the length of this range, that is the difference between the maximum and minimum value of this range
     */
    getLength() {
        return this._max - this._min;
    }
    /**
     * Gets the center of this range, that is the average value of the maximum and minimum value of this range
     */
    getCenter() {
        return (this._max + this._min) / 2;
    }
    /**
     * Determines if this range contains the value
     */
    contains(value) {
        return (value >= this._min) && (value <= this._max);
    }
    /**
     * Does this range contain the specified range?
     */
    containsRange(range) {
        return (this._min <= range.min) && (this._max >= range.max);
    }
    /**
     * Determine if this range overlaps (intersects) with another range
     */
    intersects(range) {
        return (this._max >= range.min) && (range.max >= this._min);
    }
    /**
     * Do the two ranges overlap with one another?  Note that this assumes that
     * This is a open interval.
     */
    intersectsExclusive(range) {
        return (this._max > range.min) && (range.max > this._min);
    }
    /**
     *
     * REVIEW: The naming is not helping me understand that this function is just the immutable version of includeRange().
     *
     * The smallest range that contains both this range and the input range, returned as a copy.
     *
     * The method below is the immutable form of the function includeRange(). The method will return a new range, and will not modify
     * this range.
     */
    union(range) {
        return new Range(Math.min(this.min, range.min), Math.max(this.max, range.max));
    }
    /**
     * REVIEW: The naming is not helping me understand that this function is just the immutable version of constrainRange().
     *
     * The smallest range that is contained by both this range and the input range, returned as a copy.
     *
     * The method below the immutable form of the function constrainRange(). The method below will return a new range, and will not modify
     * this range.
     */
    intersection(range) {
        return new Range(Math.max(this.min, range.min), Math.min(this.max, range.max));
    }
    /**
     * Modifies this range so that it contains both its original range and the input range.
     *
     * This is the mutable form of the function union(). This will mutate (change) this range, in addition to returning
     * this range itself.
     */
    includeRange(range) {
        return this.setMinMax(Math.min(this.min, range.min), Math.max(this.max, range.max));
    }
    /**
     * Modifies this range so that it is the largest range contained both in its original range and in the input range.
     *
     * This is the mutable form of the function intersection(). This will mutate (change) this range, in addition to returning
     * this range itself.
     */
    constrainRange(range) {
        return this.setMinMax(Math.max(this.min, range.min), Math.min(this.max, range.max));
    }
    /**
     * REVIEW: do we also need a mutable form of shifted?
     *
     * Returns a new range that is the same as this range, but shifted by the specified amount.
     */
    shifted(n) {
        return new Range(this.min + n, this.max + n);
    }
    /**
     * Converts the attributes of this range to a string
     */
    toString() {
        return `[Range (min:${this._min} max:${this._max})]`;
    }
    /**
     * Constrains a value to the range.
     */
    constrainValue(value) {
        return Math.min(Math.max(value, this._min), this._max);
    }
    /**
     * Multiply the min and max by the provided value, immutable
     */
    times(value) {
        return new Range(this._min * value, this._max * value);
    }
    /**
     * Multiply the min and max by the provided value, mutable
     */
    multiply(value) {
        this.setMinMax(this._min * value, this._max * value);
        return this;
    }
    /**
     * Determines if this Range is equal to some object.
     */
    equals(object) {
        return (this.constructor === object.constructor) &&
            (this._min === object.min) &&
            (this._max === object.max);
    }
    /**
     * Determines if this Range is approximately equal to some object.
     */
    equalsEpsilon(object, epsilon) {
        return (this.constructor === object.constructor) &&
            (Math.abs(this._min - object.min) <= epsilon) &&
            (Math.abs(this._max - object.max) <= epsilon);
    }
    /**
     * Given a value, normalize it to this Range's length, returning a value between 0 and 1 for values contained in
     * the Range. If the value is not contained in Range, then the return value will not be between 0 and 1.
     */
    getNormalizedValue(value) {
        assert && assert(this.getLength() !== 0, 'cannot get normalized value without a range length');
        return (value - this.min) / this.getLength();
    }
    /**
     * Compute the opposite of a normalized value. Given a normalized value (between 0 and 1). Worked with any number
     * though, (even outside of the range). It is the client's responsibility to clamp if that is important to the
     * usage.
     */
    expandNormalizedValue(normalizedValue) {
        assert && assert(this.getLength() !== 0, 'cannot get expand normalized value without a range length');
        return normalizedValue * this.getLength() + this.min;
    }
    /**
     * In https://github.com/phetsims/dot/issues/57, defaultValue was moved to RangeWithValue.
     * This ES5 getter catches programming errors where defaultValue is still used with Range.
     */
    get defaultValue() {
        throw new Error('defaultValue is undefined, did you mean to use RangeWithValue?');
    }
    toStateObject() {
        return {
            min: InfiniteNumberIO.toStateObject(this.min),
            max: InfiniteNumberIO.toStateObject(this.max)
        };
    }
    // Given a value and a delta to change that value, clamp the delta to make sure the value stays within range.
    clampDelta(value, delta) {
        assert && assert(this.contains(value));
        return value + delta < this.min ? this.min - value :
            value + delta > this.max ? this.max - value :
                delta;
    }
    static fromStateObject(stateObject) {
        return new Range(InfiniteNumberIO.fromStateObject(stateObject.min), InfiniteNumberIO.fromStateObject(stateObject.max));
    }
}
Range.RangeIO = new IOType('RangeIO', {
    valueType: Range,
    documentation: 'A range with "min" and "max" members.',
    stateSchema: STATE_SCHEMA,
    toStateObject: (range) => range.toStateObject(),
    fromStateObject: (stateObject) => Range.fromStateObject(stateObject)
});
Range.EVERYTHING = new Range(Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY);
Range.NOTHING = new Range(Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY);
dot.register('Range', Range);
export default Range;
//# sourceMappingURL=Range.js.map