nsresult
nsSMILAnimationFunction::InterpolateResult(const nsSMILValueArray& aValues,
                                           nsSMILValue& aResult,
                                           nsSMILValue& aBaseValue)
{
  nsresult rv = NS_OK;
  const nsSMILTime& dur = mSimpleDuration.GetMillis();

  // Sanity Checks
  NS_ABORT_IF_FALSE(mSampleTime >= 0.0f, "Sample time should not be negative");
  NS_ABORT_IF_FALSE(dur >= 0.0f, "Simple duration should not be negative");

  if (mSampleTime >= dur || mSampleTime < 0.0f) {
    NS_ERROR("Animation sampled outside interval");
    return NS_ERROR_FAILURE;
  }

  if ((!IsToAnimation() && aValues.Length() < 2) ||
      (IsToAnimation()  && aValues.Length() != 1)) {
    NS_ERROR("Unexpected number of values");
    return NS_ERROR_FAILURE;
  }
  // End Sanity Checks

  double fTime = double(mSampleTime);
  double fDur = double(dur);

  // Get the normalised progress through the simple duration
  double simpleProgress = (fDur > 0.0) ? fTime / fDur : 0.0;

  // Handle bad keytimes (where first != 0 and/or last != 1)
  // See http://brian.sol1.net/svg/range-for-keytimes for more info.
  if (HasAttr(nsGkAtoms::keyTimes) &&
      GetCalcMode() != CALC_PACED) {
    double first = mKeyTimes[0];
    if (first > 0.0 && simpleProgress < first) {
      if (!IsToAnimation())
        aResult = aValues[0];
      return rv;
    }
    double last = mKeyTimes[mKeyTimes.Length() - 1];
    if (last < 1.0 && simpleProgress >= last) {
      if (IsToAnimation())
        aResult = aValues[0];
      else
        aResult = aValues[aValues.Length() - 1];
      return rv;
    }
  }

  if (GetCalcMode() != CALC_DISCRETE) {
    // Get the normalised progress between adjacent values
    const nsSMILValue* from = nsnull;
    const nsSMILValue* to = nsnull;
    double intervalProgress;
    if (IsToAnimation()) {
      from = &aBaseValue;
      to = &aValues[0];
      if (GetCalcMode() == CALC_PACED) {
        // Note: key[Times/Splines/Points] are ignored for calcMode="paced"
        intervalProgress = simpleProgress;
      } else {
        ScaleSimpleProgress(simpleProgress);
        intervalProgress = simpleProgress;
        ScaleIntervalProgress(intervalProgress, 0, 1);
      }
    } else {
      if (GetCalcMode() == CALC_PACED) {
        rv = ComputePacedPosition(aValues, simpleProgress,
                                  intervalProgress, from, to);
        // Note: If the above call fails, we'll skip the "from->Interpolate"
        // call below, and we'll drop into the CALC_DISCRETE section
        // instead. (as the spec says we should, because our failure was
        // presumably due to the values being non-additive)
      } else { // GetCalcMode() == CALC_LINEAR or GetCalcMode() == CALC_SPLINE
        ScaleSimpleProgress(simpleProgress);
        PRUint32 index = (PRUint32)floor(simpleProgress *
                                         (aValues.Length() - 1));
        from = &aValues[index];
        to = &aValues[index + 1];
        intervalProgress = simpleProgress * (aValues.Length() - 1) - index;
        ScaleIntervalProgress(intervalProgress, index, aValues.Length() - 1);
      }
    }
    if (NS_SUCCEEDED(rv)) {
      NS_ABORT_IF_FALSE(from, "NULL from-value during interpolation");
      NS_ABORT_IF_FALSE(to, "NULL to-value during interpolation");
      NS_ABORT_IF_FALSE(0.0f <= intervalProgress && intervalProgress < 1.0f,
                      "Interval progress should be in the range [0, 1)");
      rv = from->Interpolate(*to, intervalProgress, aResult);
    }
  }

  // Discrete-CalcMode case
  // Note: If interpolation failed (isn't supported for this type), the SVG
  // spec says to force discrete mode.
  if (GetCalcMode() == CALC_DISCRETE || NS_FAILED(rv)) {
    if (IsToAnimation()) {
      // SMIL 3, 12.6.4: Since a to animation has only 1 value, a discrete to
      // animation will simply set the to value for the simple duration.
      aResult = aValues[0];
    } else {
      PRUint32 index = (PRUint32) floor(simpleProgress * (aValues.Length()));
      aResult = aValues[index];
    }
    rv = NS_OK;
  }
  return rv;
}
nsresult
nsSMILAnimationFunction::InterpolateResult(const nsSMILValueArray& aValues,
        nsSMILValue& aResult,
        nsSMILValue& aBaseValue)
{
    // Sanity check animation values
    if ((!IsToAnimation() && aValues.Length() < 2) ||
            (IsToAnimation()  && aValues.Length() != 1)) {
        NS_ERROR("Unexpected number of values");
        return NS_ERROR_FAILURE;
    }

    if (IsToAnimation() && aBaseValue.IsNull()) {
        return NS_ERROR_FAILURE;
    }

    // Get the normalised progress through the simple duration.
    //
    // If we have an indefinite simple duration, just set the progress to be
    // 0 which will give us the expected behaviour of the animation being fixed at
    // its starting point.
    double simpleProgress = 0.0;

    if (mSimpleDuration.IsDefinite()) {
        nsSMILTime dur = mSimpleDuration.GetMillis();

        NS_ABORT_IF_FALSE(dur >= 0, "Simple duration should not be negative");
        NS_ABORT_IF_FALSE(mSampleTime >= 0, "Sample time should not be negative");

        if (mSampleTime >= dur || mSampleTime < 0) {
            NS_ERROR("Animation sampled outside interval");
            return NS_ERROR_FAILURE;
        }

        if (dur > 0) {
            simpleProgress = (double)mSampleTime / dur;
        } // else leave simpleProgress at 0.0 (e.g. if mSampleTime == dur == 0)
    }

    nsresult rv = NS_OK;
    nsSMILCalcMode calcMode = GetCalcMode();
    if (calcMode != CALC_DISCRETE) {
        // Get the normalised progress between adjacent values
        const nsSMILValue* from = nullptr;
        const nsSMILValue* to = nullptr;
        // Init to -1 to make sure that if we ever forget to set this, the
        // NS_ABORT_IF_FALSE that tests that intervalProgress is in range will fail.
        double intervalProgress = -1.f;
        if (IsToAnimation()) {
            from = &aBaseValue;
            to = &aValues[0];
            if (calcMode == CALC_PACED) {
                // Note: key[Times/Splines/Points] are ignored for calcMode="paced"
                intervalProgress = simpleProgress;
            } else {
                double scaledSimpleProgress =
                    ScaleSimpleProgress(simpleProgress, calcMode);
                intervalProgress = ScaleIntervalProgress(scaledSimpleProgress, 0);
            }
        } else if (calcMode == CALC_PACED) {
            rv = ComputePacedPosition(aValues, simpleProgress,
                                      intervalProgress, from, to);
            // Note: If the above call fails, we'll skip the "from->Interpolate"
            // call below, and we'll drop into the CALC_DISCRETE section
            // instead. (as the spec says we should, because our failure was
            // presumably due to the values being non-additive)
        } else { // calcMode == CALC_LINEAR or calcMode == CALC_SPLINE
            double scaledSimpleProgress =
                ScaleSimpleProgress(simpleProgress, calcMode);
            uint32_t index = (uint32_t)floor(scaledSimpleProgress *
                                             (aValues.Length() - 1));
            from = &aValues[index];
            to = &aValues[index + 1];
            intervalProgress =
                scaledSimpleProgress * (aValues.Length() - 1) - index;
            intervalProgress = ScaleIntervalProgress(intervalProgress, index);
        }

        if (NS_SUCCEEDED(rv)) {
            NS_ABORT_IF_FALSE(from, "NULL from-value during interpolation");
            NS_ABORT_IF_FALSE(to, "NULL to-value during interpolation");
            NS_ABORT_IF_FALSE(0.0f <= intervalProgress && intervalProgress < 1.0f,
                              "Interval progress should be in the range [0, 1)");
            rv = from->Interpolate(*to, intervalProgress, aResult);
        }
    }

    // Discrete-CalcMode case
    // Note: If interpolation failed (isn't supported for this type), the SVG
    // spec says to force discrete mode.
    if (calcMode == CALC_DISCRETE || NS_FAILED(rv)) {
        double scaledSimpleProgress =
            ScaleSimpleProgress(simpleProgress, CALC_DISCRETE);

        // Floating-point errors can mean that, for example, a sample time of 29s in
        // a 100s duration animation gives us a simple progress of 0.28999999999
        // instead of the 0.29 we'd expect. Normally this isn't a noticeable
        // problem, but when we have sudden jumps in animation values (such as is
        // the case here with discrete animation) we can get unexpected results.
        //
        // To counteract this, before we perform a floor() on the animation
        // progress, we add a tiny fudge factor to push us into the correct interval
        // in cases where floating-point errors might cause us to fall short.
        static const double kFloatingPointFudgeFactor = 1.0e-16;
        if (scaledSimpleProgress + kFloatingPointFudgeFactor <= 1.0) {
            scaledSimpleProgress += kFloatingPointFudgeFactor;
        }

        if (IsToAnimation()) {
            // We don't follow SMIL 3, 12.6.4, where discrete to animations
            // are the same as <set> animations.  Instead, we treat it as a
            // discrete animation with two values (the underlying value and
            // the to="" value), and honor keyTimes="" as well.
            uint32_t index = (uint32_t)floor(scaledSimpleProgress * 2);
            aResult = index == 0 ? aBaseValue : aValues[0];
        } else {
            uint32_t index = (uint32_t)floor(scaledSimpleProgress * aValues.Length());
            aResult = aValues[index];
        }
        rv = NS_OK;
    }
    return rv;
}