/*
 * Computes the total distance to be travelled by a paced animation.
 *
 * Returns the total distance, or returns COMPUTE_DISTANCE_ERROR if
 * our values don't support distance computation.
 */
double
nsSMILAnimationFunction::ComputePacedTotalDistance(
    const nsSMILValueArray& aValues) const
{
  NS_ASSERTION(GetCalcMode() == CALC_PACED,
               "Calling paced-specific function, but not in paced mode");

  double totalDistance = 0.0;
  for (PRUint32 i = 0; i < aValues.Length() - 1; i++) {
    double tmpDist;
    nsresult rv = aValues[i].ComputeDistance(aValues[i+1], tmpDist);
    if (NS_FAILED(rv)) {
      return COMPUTE_DISTANCE_ERROR;
    }

    // Clamp distance value to 0, just in case we have an evil ComputeDistance
    // implementation somewhere
    NS_ABORT_IF_FALSE(tmpDist >= 0.0f, "distance values must be non-negative");
    tmpDist = NS_MAX(tmpDist, 0.0);

    totalDistance += tmpDist;
  }

  return totalDistance;
}
void
SVGMotionSMILAnimationFunction::CheckKeyPoints()
{
  if (!HasAttr(nsGkAtoms::keyPoints))
    return;

  // attribute is ignored for calcMode="paced" (even if it's got errors)
  if (GetCalcMode() == CALC_PACED) {
    SetKeyPointsErrorFlag(false);
  }

  if (mKeyPoints.IsEmpty()) {
    // keyPoints attr is set, but array is empty => it failed preliminary checks
    SetKeyPointsErrorFlag(true);
    return;
  }

  // Nothing else to check -- we can catch all keyPoints errors elsewhere.
  // -  Formatting & range issues will be caught in SetKeyPoints, and will
  //  result in an empty mKeyPoints array, which will drop us into the error
  //  case above.
  // -  Number-of-entries issues will be caught in CheckKeyTimes (and flagged
  //  as a problem with |keyTimes|), since we use our keyPoints entries to
  //  populate the "values" list, and that list's count gets passed to
  //  CheckKeyTimes.
}
double
nsSMILAnimationFunction::ScaleIntervalProgress(double aProgress,
        uint32_t aIntervalIndex)
{
    if (GetCalcMode() != CALC_SPLINE)
        return aProgress;

    if (!HasAttr(nsGkAtoms::keySplines))
        return aProgress;

    NS_ABORT_IF_FALSE(aIntervalIndex < mKeySplines.Length(),
                      "Invalid interval index");

    nsSMILKeySpline const &spline = mKeySplines[aIntervalIndex];
    return spline.GetSplineValue(aProgress);
}
/**
 * Performs checks for the keyTimes attribute required by the SMIL spec but
 * which depend on other attributes and therefore needs to be updated as
 * dependent attributes are set.
 */
void
nsSMILAnimationFunction::CheckKeyTimes(uint32_t aNumValues)
{
    if (!HasAttr(nsGkAtoms::keyTimes))
        return;

    nsSMILCalcMode calcMode = GetCalcMode();

    // attribute is ignored for calcMode = paced
    if (calcMode == CALC_PACED) {
        SetKeyTimesErrorFlag(false);
        return;
    }

    uint32_t numKeyTimes = mKeyTimes.Length();
    if (numKeyTimes < 1) {
        // keyTimes isn't set or failed preliminary checks
        SetKeyTimesErrorFlag(true);
        return;
    }

    // no. keyTimes == no. values
    // For to-animation the number of values is considered to be 2.
    bool matchingNumOfValues =
        numKeyTimes == (IsToAnimation() ? 2 : aNumValues);
    if (!matchingNumOfValues) {
        SetKeyTimesErrorFlag(true);
        return;
    }

    // first value must be 0
    if (mKeyTimes[0] != 0.0) {
        SetKeyTimesErrorFlag(true);
        return;
    }

    // last value must be 1 for linear or spline calcModes
    if (calcMode != CALC_DISCRETE && numKeyTimes > 1 &&
            mKeyTimes[numKeyTimes - 1] != 1.0) {
        SetKeyTimesErrorFlag(true);
        return;
    }

    SetKeyTimesErrorFlag(false);
}
void SVGMotionSMILAnimationFunction::CheckKeyPoints() {
  if (!HasAttr(nsGkAtoms::keyPoints)) return;

  // attribute is ignored for calcMode="paced" (even if it's got errors)
  if (GetCalcMode() == CALC_PACED) {
    SetKeyPointsErrorFlag(false);
  }

  if (mKeyPoints.Length() != mKeyTimes.Length()) {
    // there must be exactly as many keyPoints as keyTimes
    SetKeyPointsErrorFlag(true);
    return;
  }

  // Nothing else to check -- we can catch all keyPoints errors elsewhere.
  // -  Formatting & range issues will be caught in SetKeyPoints, and will
  //  result in an empty mKeyPoints array, which will drop us into the error
  //  case above.
}
/**
 * Performs checks for the keyTimes attribute required by the SMIL spec but
 * which depend on other attributes and therefore needs to be updated as
 * dependent attributes are set.
 */
void
nsSMILAnimationFunction::CheckKeyTimes(PRUint32 aNumValues)
{
  if (!HasAttr(nsGkAtoms::keyTimes))
    return;

  // attribute is ignored for calcMode = paced
  if (GetCalcMode() == CALC_PACED) {
    SetKeyTimesErrorFlag(PR_FALSE);
    return;
  }

  if (mKeyTimes.Length() < 1) {
    // keyTimes isn't set or failed preliminary checks
    SetKeyTimesErrorFlag(PR_TRUE);
    return;
  }

  // no. keyTimes == no. values
  if ((mKeyTimes.Length() != aNumValues && !IsToAnimation()) ||
      (IsToAnimation() && mKeyTimes.Length() != 2)) {
    SetKeyTimesErrorFlag(PR_TRUE);
    return;
  }

  // special handling if there is only one keyTime. The spec doesn't say what to
  // do in this case so we allow the keyTime to be either 0 or 1.
  if (mKeyTimes.Length() == 1) {
    double time = mKeyTimes[0];
    SetKeyTimesErrorFlag(!(time == 0.0 || time == 1.0));
    return;
  }

  // According to the spec, the first value should be 0 and for linear or spline
  // calcMode's the last value should be 1, but then an example is give with
  // a spline calcMode and keyTimes "0.0; 0.7". So we don't bother checking
  // the end-values here but just allow bad specs.

  SetKeyTimesErrorFlag(PR_FALSE);
}
/*
 * Scale the interval progress, taking into account any keySplines
 * or discrete methods.
 */
void
nsSMILAnimationFunction::ScaleIntervalProgress(double& aProgress,
                                               PRUint32   aIntervalIndex,
                                               PRUint32   aNumIntervals)
{
  if (GetCalcMode() != CALC_SPLINE)
    return;

  if (!HasAttr(nsGkAtoms::keySplines))
    return;

  NS_ASSERTION(aIntervalIndex < (PRUint32)mKeySplines.Length(),
               "Invalid interval index");
  NS_ASSERTION(aNumIntervals >= 1, "Invalid number of intervals");

  if (aIntervalIndex >= (PRUint32)mKeySplines.Length() ||
      aNumIntervals < 1)
    return;

  nsSMILKeySpline const &spline = mKeySplines[aIntervalIndex];
  aProgress = spline.GetSplineValue(aProgress);
}
void
nsSMILAnimationFunction::CheckKeySplines(PRUint32 aNumValues)
{
  // attribute is ignored if calc mode is not spline
  if (GetCalcMode() != CALC_SPLINE) {
    SetKeySplinesErrorFlag(PR_FALSE);
    return;
  }

  // calc mode is spline but the attribute is not set
  if (!HasAttr(nsGkAtoms::keySplines)) {
    SetKeySplinesErrorFlag(PR_FALSE);
    return;
  }

  if (mKeySplines.Length() < 1) {
    // keyTimes isn't set or failed preliminary checks
    SetKeySplinesErrorFlag(PR_TRUE);
    return;
  }

  // ignore splines if there's only one value
  if (aNumValues == 1 && !IsToAnimation()) {
    SetKeySplinesErrorFlag(PR_FALSE);
    return;
  }

  // no. keySpline specs == no. values - 1
  PRUint32 splineSpecs = mKeySplines.Length();
  if ((splineSpecs != aNumValues - 1 && !IsToAnimation()) ||
      (IsToAnimation() && splineSpecs != 1)) {
    SetKeySplinesErrorFlag(PR_TRUE);
    return;
  }

  SetKeySplinesErrorFlag(PR_FALSE);
}
/*
 * Given the simple progress for a paced animation, this method:
 *  - determines which two elements of the values array we're in between
 *    (returned as aFrom and aTo)
 *  - determines where we are between them
 *    (returned as aIntervalProgress)
 *
 * Returns NS_OK, or NS_ERROR_FAILURE if our values don't support distance
 * computation.
 */
nsresult
nsSMILAnimationFunction::ComputePacedPosition(const nsSMILValueArray& aValues,
                                              double aSimpleProgress,
                                              double& aIntervalProgress,
                                              const nsSMILValue*& aFrom,
                                              const nsSMILValue*& aTo)
{
  NS_ASSERTION(0.0f <= aSimpleProgress && aSimpleProgress < 1.0f,
               "aSimpleProgress is out of bounds");
  NS_ASSERTION(GetCalcMode() == CALC_PACED,
               "Calling paced-specific function, but not in paced mode");
  NS_ABORT_IF_FALSE(aValues.Length() >= 2, "Unexpected number of values");

  // Trivial case: If we have just 2 values, then there's only one interval
  // for us to traverse, and our progress across that interval is the exact
  // same as our overall progress.
  if (aValues.Length() == 2) {
    aIntervalProgress = aSimpleProgress;
    aFrom = &aValues[0];
    aTo = &aValues[1];
    return NS_OK;
  }

  double totalDistance = ComputePacedTotalDistance(aValues);
  if (totalDistance == COMPUTE_DISTANCE_ERROR)
    return NS_ERROR_FAILURE;

  // total distance we should have moved at this point in time.
  // (called 'remainingDist' due to how it's used in loop below)
  double remainingDist = aSimpleProgress * totalDistance;

  // Must be satisfied, because totalDistance is a sum of (non-negative)
  // distances, and aSimpleProgress is non-negative
  NS_ASSERTION(remainingDist >= 0, "distance values must be non-negative");

  // Find where remainingDist puts us in the list of values
  // Note: We could optimize this next loop by caching the
  // interval-distances in an array, but maybe that's excessive.
  for (PRUint32 i = 0; i < aValues.Length() - 1; i++) {
    // Note: The following assertion is valid because remainingDist should
    // start out non-negative, and this loop never shaves off more than its
    // current value.
    NS_ASSERTION(remainingDist >= 0, "distance values must be non-negative");

    double curIntervalDist;
    nsresult rv = aValues[i].ComputeDistance(aValues[i+1], curIntervalDist);
    NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv),
                      "If we got through ComputePacedTotalDistance, we should "
                      "be able to recompute each sub-distance without errors");

    NS_ASSERTION(curIntervalDist >= 0, "distance values must be non-negative");
    // Clamp distance value at 0, just in case ComputeDistance is evil.
    curIntervalDist = NS_MAX(curIntervalDist, 0.0);

    if (remainingDist >= curIntervalDist) {
      remainingDist -= curIntervalDist;
    } else {
      // NOTE: If we get here, then curIntervalDist necessarily is not 0. Why?
      // Because this clause is only hit when remainingDist < curIntervalDist,
      // and if curIntervalDist were 0, that would mean remainingDist would
      // have to be < 0.  But that can't happen, because remainingDist (as
      // a distance) is non-negative by definition.
      NS_ASSERTION(curIntervalDist != 0,
                   "We should never get here with this set to 0...");

      // We found the right spot -- an interpolated position between
      // values i and i+1.
      aFrom = &aValues[i];
      aTo = &aValues[i+1];
      aIntervalProgress = remainingDist / curIntervalDist;
      return NS_OK;
    }
  }

  NS_NOTREACHED("shouldn't complete loop & get here -- if we do, "
                "then aSimpleProgress was probably out of bounds");
  return NS_ERROR_FAILURE;
}
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;
}