import binarySearch from "../Core/binarySearch.js";
import Check from "../Core/Check.js";
import defaultValue from "../Core/defaultValue.js";
import defined from "../Core/defined.js";
import DeveloperError from "../Core/DeveloperError.js";
import Event from "../Core/Event.js";
import ExtrapolationType from "../Core/ExtrapolationType.js";
import JulianDate from "../Core/JulianDate.js";
import LinearApproximation from "../Core/LinearApproximation.js";

var PackableNumber = {
  packedLength: 1,
  pack: function (value, array, startingIndex) {
    startingIndex = defaultValue(startingIndex, 0);
    array[startingIndex] = value;
  },
  unpack: function (array, startingIndex, result) {
    startingIndex = defaultValue(startingIndex, 0);
    return array[startingIndex];
  },
};

//We can't use splice for inserting new elements because function apply can't handle
//a huge number of arguments.  See https://code.google.com/p/chromium/issues/detail?id=56588
function arrayInsert(array, startIndex, items) {
  var i;
  var arrayLength = array.length;
  var itemsLength = items.length;
  var newLength = arrayLength + itemsLength;

  array.length = newLength;
  if (arrayLength !== startIndex) {
    var q = arrayLength - 1;
    for (i = newLength - 1; i >= startIndex; i--) {
      array[i] = array[q--];
    }
  }

  for (i = 0; i < itemsLength; i++) {
    array[startIndex++] = items[i];
  }
}

function convertDate(date, epoch) {
  if (date instanceof JulianDate) {
    return date;
  }
  if (typeof date === "string") {
    return JulianDate.fromIso8601(date);
  }
  return JulianDate.addSeconds(epoch, date, new JulianDate());
}

var timesSpliceArgs = [];
var valuesSpliceArgs = [];

function mergeNewSamples(epoch, times, values, newData, packedLength) {
  var newDataIndex = 0;
  var i;
  var prevItem;
  var timesInsertionPoint;
  var valuesInsertionPoint;
  var currentTime;
  var nextTime;

  while (newDataIndex < newData.length) {
    currentTime = convertDate(newData[newDataIndex], epoch);
    timesInsertionPoint = binarySearch(times, currentTime, JulianDate.compare);
    var timesSpliceArgsCount = 0;
    var valuesSpliceArgsCount = 0;

    if (timesInsertionPoint < 0) {
      //Doesn't exist, insert as many additional values as we can.
      timesInsertionPoint = ~timesInsertionPoint;

      valuesInsertionPoint = timesInsertionPoint * packedLength;
      prevItem = undefined;
      nextTime = times[timesInsertionPoint];
      while (newDataIndex < newData.length) {
        currentTime = convertDate(newData[newDataIndex], epoch);
        if (
          (defined(prevItem) &&
            JulianDate.compare(prevItem, currentTime) >= 0) ||
          (defined(nextTime) && JulianDate.compare(currentTime, nextTime) >= 0)
        ) {
          break;
        }
        timesSpliceArgs[timesSpliceArgsCount++] = currentTime;
        newDataIndex = newDataIndex + 1;
        for (i = 0; i < packedLength; i++) {
          valuesSpliceArgs[valuesSpliceArgsCount++] = newData[newDataIndex];
          newDataIndex = newDataIndex + 1;
        }
        prevItem = currentTime;
      }

      if (timesSpliceArgsCount > 0) {
        valuesSpliceArgs.length = valuesSpliceArgsCount;
        arrayInsert(values, valuesInsertionPoint, valuesSpliceArgs);

        timesSpliceArgs.length = timesSpliceArgsCount;
        arrayInsert(times, timesInsertionPoint, timesSpliceArgs);
      }
    } else {
      //Found an exact match
      for (i = 0; i < packedLength; i++) {
        newDataIndex++;
        values[timesInsertionPoint * packedLength + i] = newData[newDataIndex];
      }
      newDataIndex++;
    }
  }
}

/**
 * A {@link Property} whose value is interpolated for a given time from the
 * provided set of samples and specified interpolation algorithm and degree.
 * @alias SampledProperty
 * @constructor
 *
 * @param {Number|Packable} type The type of property.
 * @param {Packable[]} [derivativeTypes] When supplied, indicates that samples will contain derivative information of the specified types.
 *
 *
 * @example
 * //Create a linearly interpolated Cartesian2
 * var property = new Cesium.SampledProperty(Cesium.Cartesian2);
 *
 * //Populate it with data
 * property.addSample(Cesium.JulianDate.fromIso8601('2012-08-01T00:00:00.00Z'), new Cesium.Cartesian2(0, 0));
 * property.addSample(Cesium.JulianDate.fromIso8601('2012-08-02T00:00:00.00Z'), new Cesium.Cartesian2(4, 7));
 *
 * //Retrieve an interpolated value
 * var result = property.getValue(Cesium.JulianDate.fromIso8601('2012-08-01T12:00:00.00Z'));
 *
 * @example
 * //Create a simple numeric SampledProperty that uses third degree Hermite Polynomial Approximation
 * var property = new Cesium.SampledProperty(Number);
 * property.setInterpolationOptions({
 *     interpolationDegree : 3,
 *     interpolationAlgorithm : Cesium.HermitePolynomialApproximation
 * });
 *
 * //Populate it with data
 * property.addSample(Cesium.JulianDate.fromIso8601('2012-08-01T00:00:00.00Z'), 1.0);
 * property.addSample(Cesium.JulianDate.fromIso8601('2012-08-01T00:01:00.00Z'), 6.0);
 * property.addSample(Cesium.JulianDate.fromIso8601('2012-08-01T00:02:00.00Z'), 12.0);
 * property.addSample(Cesium.JulianDate.fromIso8601('2012-08-01T00:03:30.00Z'), 5.0);
 * property.addSample(Cesium.JulianDate.fromIso8601('2012-08-01T00:06:30.00Z'), 2.0);
 *
 * //Samples can be added in any order.
 * property.addSample(Cesium.JulianDate.fromIso8601('2012-08-01T00:00:30.00Z'), 6.2);
 *
 * //Retrieve an interpolated value
 * var result = property.getValue(Cesium.JulianDate.fromIso8601('2012-08-01T00:02:34.00Z'));
 *
 * @see SampledPositionProperty
 */
function SampledProperty(type, derivativeTypes) {
  //>>includeStart('debug', pragmas.debug);
  Check.defined("type", type);
  //>>includeEnd('debug');

  var innerType = type;
  if (innerType === Number) {
    innerType = PackableNumber;
  }
  var packedLength = innerType.packedLength;
  var packedInterpolationLength = defaultValue(
    innerType.packedInterpolationLength,
    packedLength
  );

  var inputOrder = 0;
  var innerDerivativeTypes;
  if (defined(derivativeTypes)) {
    var length = derivativeTypes.length;
    innerDerivativeTypes = new Array(length);
    for (var i = 0; i < length; i++) {
      var derivativeType = derivativeTypes[i];
      if (derivativeType === Number) {
        derivativeType = PackableNumber;
      }
      var derivativePackedLength = derivativeType.packedLength;
      packedLength += derivativePackedLength;
      packedInterpolationLength += defaultValue(
        derivativeType.packedInterpolationLength,
        derivativePackedLength
      );
      innerDerivativeTypes[i] = derivativeType;
    }
    inputOrder = length;
  }

  this._type = type;
  this._innerType = innerType;
  this._interpolationDegree = 1;
  this._interpolationAlgorithm = LinearApproximation;
  this._numberOfPoints = 0;
  this._times = [];
  this._values = [];
  this._xTable = [];
  this._yTable = [];
  this._packedLength = packedLength;
  this._packedInterpolationLength = packedInterpolationLength;
  this._updateTableLength = true;
  this._interpolationResult = new Array(packedInterpolationLength);
  this._definitionChanged = new Event();
  this._derivativeTypes = derivativeTypes;
  this._innerDerivativeTypes = innerDerivativeTypes;
  this._inputOrder = inputOrder;
  this._forwardExtrapolationType = ExtrapolationType.NONE;
  this._forwardExtrapolationDuration = 0;
  this._backwardExtrapolationType = ExtrapolationType.NONE;
  this._backwardExtrapolationDuration = 0;
}

Object.defineProperties(SampledProperty.prototype, {
  /**
   * Gets a value indicating if this property is constant.  A property is considered
   * constant if getValue always returns the same result for the current definition.
   * @memberof SampledProperty.prototype
   *
   * @type {Boolean}
   * @readonly
   */
  isConstant: {
    get: function () {
      return this._values.length === 0;
    },
  },
  /**
   * Gets the event that is raised whenever the definition of this property changes.
   * The definition is considered to have changed if a call to getValue would return
   * a different result for the same time.
   * @memberof SampledProperty.prototype
   *
   * @type {Event}
   * @readonly
   */
  definitionChanged: {
    get: function () {
      return this._definitionChanged;
    },
  },
  /**
   * Gets the type of property.
   * @memberof SampledProperty.prototype
   * @type {*}
   */
  type: {
    get: function () {
      return this._type;
    },
  },
  /**
   * Gets the derivative types used by this property.
   * @memberof SampledProperty.prototype
   * @type {Packable[]}
   */
  derivativeTypes: {
    get: function () {
      return this._derivativeTypes;
    },
  },
  /**
   * Gets the degree of interpolation to perform when retrieving a value.
   * @memberof SampledProperty.prototype
   * @type {Number}
   * @default 1
   */
  interpolationDegree: {
    get: function () {
      return this._interpolationDegree;
    },
  },
  /**
   * Gets the interpolation algorithm to use when retrieving a value.
   * @memberof SampledProperty.prototype
   * @type {InterpolationAlgorithm}
   * @default LinearApproximation
   */
  interpolationAlgorithm: {
    get: function () {
      return this._interpolationAlgorithm;
    },
  },
  /**
   * Gets or sets the type of extrapolation to perform when a value
   * is requested at a time after any available samples.
   * @memberof SampledProperty.prototype
   * @type {ExtrapolationType}
   * @default ExtrapolationType.NONE
   */
  forwardExtrapolationType: {
    get: function () {
      return this._forwardExtrapolationType;
    },
    set: function (value) {
      if (this._forwardExtrapolationType !== value) {
        this._forwardExtrapolationType = value;
        this._definitionChanged.raiseEvent(this);
      }
    },
  },
  /**
   * Gets or sets the amount of time to extrapolate forward before
   * the property becomes undefined.  A value of 0 will extrapolate forever.
   * @memberof SampledProperty.prototype
   * @type {Number}
   * @default 0
   */
  forwardExtrapolationDuration: {
    get: function () {
      return this._forwardExtrapolationDuration;
    },
    set: function (value) {
      if (this._forwardExtrapolationDuration !== value) {
        this._forwardExtrapolationDuration = value;
        this._definitionChanged.raiseEvent(this);
      }
    },
  },
  /**
   * Gets or sets the type of extrapolation to perform when a value
   * is requested at a time before any available samples.
   * @memberof SampledProperty.prototype
   * @type {ExtrapolationType}
   * @default ExtrapolationType.NONE
   */
  backwardExtrapolationType: {
    get: function () {
      return this._backwardExtrapolationType;
    },
    set: function (value) {
      if (this._backwardExtrapolationType !== value) {
        this._backwardExtrapolationType = value;
        this._definitionChanged.raiseEvent(this);
      }
    },
  },
  /**
   * Gets or sets the amount of time to extrapolate backward
   * before the property becomes undefined.  A value of 0 will extrapolate forever.
   * @memberof SampledProperty.prototype
   * @type {Number}
   * @default 0
   */
  backwardExtrapolationDuration: {
    get: function () {
      return this._backwardExtrapolationDuration;
    },
    set: function (value) {
      if (this._backwardExtrapolationDuration !== value) {
        this._backwardExtrapolationDuration = value;
        this._definitionChanged.raiseEvent(this);
      }
    },
  },
});

/**
 * Gets the value of the property at the provided time.
 *
 * @param {JulianDate} time The time for which to retrieve the value.
 * @param {Object} [result] The object to store the value into, if omitted, a new instance is created and returned.
 * @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
 */
SampledProperty.prototype.getValue = function (time, result) {
  //>>includeStart('debug', pragmas.debug);
  Check.defined("time", time);
  //>>includeEnd('debug');

  var times = this._times;
  var timesLength = times.length;
  if (timesLength === 0) {
    return undefined;
  }

  var timeout;
  var innerType = this._innerType;
  var values = this._values;
  var index = binarySearch(times, time, JulianDate.compare);

  if (index < 0) {
    index = ~index;

    if (index === 0) {
      var startTime = times[index];
      timeout = this._backwardExtrapolationDuration;
      if (
        this._backwardExtrapolationType === ExtrapolationType.NONE ||
        (timeout !== 0 &&
          JulianDate.secondsDifference(startTime, time) > timeout)
      ) {
        return undefined;
      }
      if (this._backwardExtrapolationType === ExtrapolationType.HOLD) {
        return innerType.unpack(values, 0, result);
      }
    }

    if (index >= timesLength) {
      index = timesLength - 1;
      var endTime = times[index];
      timeout = this._forwardExtrapolationDuration;
      if (
        this._forwardExtrapolationType === ExtrapolationType.NONE ||
        (timeout !== 0 && JulianDate.secondsDifference(time, endTime) > timeout)
      ) {
        return undefined;
      }
      if (this._forwardExtrapolationType === ExtrapolationType.HOLD) {
        index = timesLength - 1;
        return innerType.unpack(values, index * innerType.packedLength, result);
      }
    }

    var xTable = this._xTable;
    var yTable = this._yTable;
    var interpolationAlgorithm = this._interpolationAlgorithm;
    var packedInterpolationLength = this._packedInterpolationLength;
    var inputOrder = this._inputOrder;

    if (this._updateTableLength) {
      this._updateTableLength = false;
      var numberOfPoints = Math.min(
        interpolationAlgorithm.getRequiredDataPoints(
          this._interpolationDegree,
          inputOrder
        ),
        timesLength
      );
      if (numberOfPoints !== this._numberOfPoints) {
        this._numberOfPoints = numberOfPoints;
        xTable.length = numberOfPoints;
        yTable.length = numberOfPoints * packedInterpolationLength;
      }
    }

    var degree = this._numberOfPoints - 1;
    if (degree < 1) {
      return undefined;
    }

    var firstIndex = 0;
    var lastIndex = timesLength - 1;
    var pointsInCollection = lastIndex - firstIndex + 1;

    if (pointsInCollection >= degree + 1) {
      var computedFirstIndex = index - ((degree / 2) | 0) - 1;
      if (computedFirstIndex < firstIndex) {
        computedFirstIndex = firstIndex;
      }
      var computedLastIndex = computedFirstIndex + degree;
      if (computedLastIndex > lastIndex) {
        computedLastIndex = lastIndex;
        computedFirstIndex = computedLastIndex - degree;
        if (computedFirstIndex < firstIndex) {
          computedFirstIndex = firstIndex;
        }
      }

      firstIndex = computedFirstIndex;
      lastIndex = computedLastIndex;
    }
    var length = lastIndex - firstIndex + 1;

    // Build the tables
    for (var i = 0; i < length; ++i) {
      xTable[i] = JulianDate.secondsDifference(
        times[firstIndex + i],
        times[lastIndex]
      );
    }

    if (!defined(innerType.convertPackedArrayForInterpolation)) {
      var destinationIndex = 0;
      var packedLength = this._packedLength;
      var sourceIndex = firstIndex * packedLength;
      var stop = (lastIndex + 1) * packedLength;

      while (sourceIndex < stop) {
        yTable[destinationIndex] = values[sourceIndex];
        sourceIndex++;
        destinationIndex++;
      }
    } else {
      innerType.convertPackedArrayForInterpolation(
        values,
        firstIndex,
        lastIndex,
        yTable
      );
    }

    // Interpolate!
    var x = JulianDate.secondsDifference(time, times[lastIndex]);
    var interpolationResult;
    if (inputOrder === 0 || !defined(interpolationAlgorithm.interpolate)) {
      interpolationResult = interpolationAlgorithm.interpolateOrderZero(
        x,
        xTable,
        yTable,
        packedInterpolationLength,
        this._interpolationResult
      );
    } else {
      var yStride = Math.floor(packedInterpolationLength / (inputOrder + 1));
      interpolationResult = interpolationAlgorithm.interpolate(
        x,
        xTable,
        yTable,
        yStride,
        inputOrder,
        inputOrder,
        this._interpolationResult
      );
    }

    if (!defined(innerType.unpackInterpolationResult)) {
      return innerType.unpack(interpolationResult, 0, result);
    }
    return innerType.unpackInterpolationResult(
      interpolationResult,
      values,
      firstIndex,
      lastIndex,
      result
    );
  }
  return innerType.unpack(values, index * this._packedLength, result);
};

/**
 * Sets the algorithm and degree to use when interpolating a value.
 *
 * @param {Object} [options] Object with the following properties:
 * @param {InterpolationAlgorithm} [options.interpolationAlgorithm] The new interpolation algorithm.  If undefined, the existing property will be unchanged.
 * @param {Number} [options.interpolationDegree] The new interpolation degree.  If undefined, the existing property will be unchanged.
 */
SampledProperty.prototype.setInterpolationOptions = function (options) {
  if (!defined(options)) {
    return;
  }

  var valuesChanged = false;

  var interpolationAlgorithm = options.interpolationAlgorithm;
  var interpolationDegree = options.interpolationDegree;

  if (
    defined(interpolationAlgorithm) &&
    this._interpolationAlgorithm !== interpolationAlgorithm
  ) {
    this._interpolationAlgorithm = interpolationAlgorithm;
    valuesChanged = true;
  }

  if (
    defined(interpolationDegree) &&
    this._interpolationDegree !== interpolationDegree
  ) {
    this._interpolationDegree = interpolationDegree;
    valuesChanged = true;
  }

  if (valuesChanged) {
    this._updateTableLength = true;
    this._definitionChanged.raiseEvent(this);
  }
};

/**
 * Adds a new sample.
 *
 * @param {JulianDate} time The sample time.
 * @param {Packable} value The value at the provided time.
 * @param {Packable[]} [derivatives] The array of derivatives at the provided time.
 */
SampledProperty.prototype.addSample = function (time, value, derivatives) {
  var innerDerivativeTypes = this._innerDerivativeTypes;
  var hasDerivatives = defined(innerDerivativeTypes);

  //>>includeStart('debug', pragmas.debug);
  Check.defined("time", time);
  Check.defined("value", value);
  if (hasDerivatives) {
    Check.defined("derivatives", derivatives);
  }
  //>>includeEnd('debug');

  var innerType = this._innerType;
  var data = [];
  data.push(time);
  innerType.pack(value, data, data.length);

  if (hasDerivatives) {
    var derivativesLength = innerDerivativeTypes.length;
    for (var x = 0; x < derivativesLength; x++) {
      innerDerivativeTypes[x].pack(derivatives[x], data, data.length);
    }
  }
  mergeNewSamples(
    undefined,
    this._times,
    this._values,
    data,
    this._packedLength
  );
  this._updateTableLength = true;
  this._definitionChanged.raiseEvent(this);
};

/**
 * Adds an array of samples.
 *
 * @param {JulianDate[]} times An array of JulianDate instances where each index is a sample time.
 * @param {Packable[]} values The array of values, where each value corresponds to the provided times index.
 * @param {Array[]} [derivativeValues] An array where each item is the array of derivatives at the equivalent time index.
 *
 * @exception {DeveloperError} times and values must be the same length.
 * @exception {DeveloperError} times and derivativeValues must be the same length.
 */
SampledProperty.prototype.addSamples = function (
  times,
  values,
  derivativeValues
) {
  var innerDerivativeTypes = this._innerDerivativeTypes;
  var hasDerivatives = defined(innerDerivativeTypes);

  //>>includeStart('debug', pragmas.debug);
  Check.defined("times", times);
  Check.defined("values", values);
  if (times.length !== values.length) {
    throw new DeveloperError("times and values must be the same length.");
  }
  if (
    hasDerivatives &&
    (!defined(derivativeValues) || derivativeValues.length !== times.length)
  ) {
    throw new DeveloperError(
      "times and derivativeValues must be the same length."
    );
  }
  //>>includeEnd('debug');

  var innerType = this._innerType;
  var length = times.length;
  var data = [];
  for (var i = 0; i < length; i++) {
    data.push(times[i]);
    innerType.pack(values[i], data, data.length);

    if (hasDerivatives) {
      var derivatives = derivativeValues[i];
      var derivativesLength = innerDerivativeTypes.length;
      for (var x = 0; x < derivativesLength; x++) {
        innerDerivativeTypes[x].pack(derivatives[x], data, data.length);
      }
    }
  }
  mergeNewSamples(
    undefined,
    this._times,
    this._values,
    data,
    this._packedLength
  );
  this._updateTableLength = true;
  this._definitionChanged.raiseEvent(this);
};

/**
 * Adds samples as a single packed array where each new sample is represented as a date,
 * followed by the packed representation of the corresponding value and derivatives.
 *
 * @param {Number[]} packedSamples The array of packed samples.
 * @param {JulianDate} [epoch] If any of the dates in packedSamples are numbers, they are considered an offset from this epoch, in seconds.
 */
SampledProperty.prototype.addSamplesPackedArray = function (
  packedSamples,
  epoch
) {
  //>>includeStart('debug', pragmas.debug);
  Check.defined("packedSamples", packedSamples);
  //>>includeEnd('debug');

  mergeNewSamples(
    epoch,
    this._times,
    this._values,
    packedSamples,
    this._packedLength
  );
  this._updateTableLength = true;
  this._definitionChanged.raiseEvent(this);
};

/**
 * Removes a sample at the given time, if present.
 *
 * @param {JulianDate} time The sample time.
 * @returns {Boolean} <code>true</code> if a sample at time was removed, <code>false</code> otherwise.
 */
SampledProperty.prototype.removeSample = function (time) {
  //>>includeStart('debug', pragmas.debug);
  Check.defined("time", time);
  //>>includeEnd('debug');

  var index = binarySearch(this._times, time, JulianDate.compare);
  if (index < 0) {
    return false;
  }
  removeSamples(this, index, 1);
  return true;
};

function removeSamples(property, startIndex, numberToRemove) {
  var packedLength = property._packedLength;
  property._times.splice(startIndex, numberToRemove);
  property._values.splice(
    startIndex * packedLength,
    numberToRemove * packedLength
  );
  property._updateTableLength = true;
  property._definitionChanged.raiseEvent(property);
}

/**
 * Removes all samples for the given time interval.
 *
 * @param {TimeInterval} time The time interval for which to remove all samples.
 */
SampledProperty.prototype.removeSamples = function (timeInterval) {
  //>>includeStart('debug', pragmas.debug);
  Check.defined("timeInterval", timeInterval);
  //>>includeEnd('debug');

  var times = this._times;
  var startIndex = binarySearch(times, timeInterval.start, JulianDate.compare);
  if (startIndex < 0) {
    startIndex = ~startIndex;
  } else if (!timeInterval.isStartIncluded) {
    ++startIndex;
  }
  var stopIndex = binarySearch(times, timeInterval.stop, JulianDate.compare);
  if (stopIndex < 0) {
    stopIndex = ~stopIndex;
  } else if (timeInterval.isStopIncluded) {
    ++stopIndex;
  }

  removeSamples(this, startIndex, stopIndex - startIndex);
};

/**
 * Compares this property to the provided property and returns
 * <code>true</code> if they are equal, <code>false</code> otherwise.
 *
 * @param {Property} [other] The other property.
 * @returns {Boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
 */
SampledProperty.prototype.equals = function (other) {
  if (this === other) {
    return true;
  }
  if (!defined(other)) {
    return false;
  }

  if (
    this._type !== other._type || //
    this._interpolationDegree !== other._interpolationDegree || //
    this._interpolationAlgorithm !== other._interpolationAlgorithm
  ) {
    return false;
  }

  var derivativeTypes = this._derivativeTypes;
  var hasDerivatives = defined(derivativeTypes);
  var otherDerivativeTypes = other._derivativeTypes;
  var otherHasDerivatives = defined(otherDerivativeTypes);
  if (hasDerivatives !== otherHasDerivatives) {
    return false;
  }

  var i;
  var length;
  if (hasDerivatives) {
    length = derivativeTypes.length;
    if (length !== otherDerivativeTypes.length) {
      return false;
    }

    for (i = 0; i < length; i++) {
      if (derivativeTypes[i] !== otherDerivativeTypes[i]) {
        return false;
      }
    }
  }

  var times = this._times;
  var otherTimes = other._times;
  length = times.length;

  if (length !== otherTimes.length) {
    return false;
  }

  for (i = 0; i < length; i++) {
    if (!JulianDate.equals(times[i], otherTimes[i])) {
      return false;
    }
  }

  var values = this._values;
  var otherValues = other._values;
  length = values.length;

  //Since time lengths are equal, values length and other length are guaranteed to be equal.
  for (i = 0; i < length; i++) {
    if (values[i] !== otherValues[i]) {
      return false;
    }
  }

  return true;
};

//Exposed for testing.
SampledProperty._mergeNewSamples = mergeNewSamples;
export default SampledProperty;
