// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "third_party/blink/renderer/core/animation/css_length_interpolation_type.h"

#include <memory>
#include <utility>

#include "base/memory/ptr_util.h"
#include "third_party/blink/renderer/core/animation/interpolable_length.h"
#include "third_party/blink/renderer/core/animation/length_property_functions.h"
#include "third_party/blink/renderer/core/css/css_identifier_value.h"
#include "third_party/blink/renderer/core/css/resolver/style_builder.h"
#include "third_party/blink/renderer/core/css/resolver/style_resolver_state.h"
#include "third_party/blink/renderer/core/css/scoped_css_value.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/platform/geometry/length_functions.h"

namespace blink {

CSSLengthInterpolationType::CSSLengthInterpolationType(
    PropertyHandle property,
    const PropertyRegistration* registration)
    : CSSInterpolationType(property, registration),
      value_range_(LengthPropertyFunctions::GetValueRange(CssProperty())) {}

float CSSLengthInterpolationType::EffectiveZoom(
    const ComputedStyle& style) const {
  return LengthPropertyFunctions::IsZoomedLength(CssProperty())
             ? style.EffectiveZoom()
             : 1;
}

class InheritedLengthChecker
    : public CSSInterpolationType::CSSConversionChecker {
 public:
  InheritedLengthChecker(const CSSProperty& property, const Length& length)
      : property_(property), length_(length) {}

 private:
  bool IsValid(const StyleResolverState& state,
               const InterpolationValue& underlying) const final {
    Length parent_length;
    LengthPropertyFunctions::GetLength(property_, *state.ParentStyle(),
                                       parent_length);
    return parent_length == length_;
  }

  const CSSProperty& property_;
  const Length length_;
};

InterpolationValue CSSLengthInterpolationType::MaybeConvertNeutral(
    const InterpolationValue&,
    ConversionCheckers&) const {
  return InterpolationValue(InterpolableLength::CreateNeutral());
}

InterpolationValue CSSLengthInterpolationType::MaybeConvertInitial(
    const StyleResolverState&,
    ConversionCheckers& conversion_checkers) const {
  Length initial_length;
  if (!LengthPropertyFunctions::GetInitialLength(CssProperty(), initial_length))
    return nullptr;
  return InterpolationValue(
      InterpolableLength::MaybeConvertLength(initial_length, 1));
}

InterpolationValue CSSLengthInterpolationType::MaybeConvertInherit(
    const StyleResolverState& state,
    ConversionCheckers& conversion_checkers) const {
  if (!state.ParentStyle())
    return nullptr;
  Length inherited_length;
  LengthPropertyFunctions::GetLength(CssProperty(), *state.ParentStyle(),
                                     inherited_length);
  conversion_checkers.push_back(std::make_unique<InheritedLengthChecker>(
      CssProperty(), inherited_length));
  if (inherited_length.IsAuto()) {
    // If the inherited value changes to a length, the InheritedLengthChecker
    // will invalidate the interpolation's cache.
    return nullptr;
  }
  return InterpolationValue(InterpolableLength::MaybeConvertLength(
      inherited_length, EffectiveZoom(*state.ParentStyle())));
}

InterpolationValue CSSLengthInterpolationType::MaybeConvertValue(
    const CSSValue& value,
    const StyleResolverState*,
    ConversionCheckers& conversion_checkers) const {
  if (auto* identifier_value = DynamicTo<CSSIdentifierValue>(value)) {
    CSSValueID value_id = identifier_value->GetValueID();
    double pixels;
    if (!LengthPropertyFunctions::GetPixelsForKeyword(CssProperty(), value_id,
                                                      pixels))
      return nullptr;
    return InterpolationValue(InterpolableLength::CreatePixels(pixels));
  }

  return InterpolationValue(InterpolableLength::MaybeConvertCSSValue(value));
}

PairwiseInterpolationValue CSSLengthInterpolationType::MaybeMergeSingles(
    InterpolationValue&& start,
    InterpolationValue&& end) const {
  return InterpolableLength::MergeSingles(std::move(start.interpolable_value),
                                          std::move(end.interpolable_value));
}

InterpolationValue
CSSLengthInterpolationType::MaybeConvertStandardPropertyUnderlyingValue(
    const ComputedStyle& style) const {
  Length underlying_length;
  if (!LengthPropertyFunctions::GetLength(CssProperty(), style,
                                          underlying_length))
    return nullptr;
  return InterpolationValue(InterpolableLength::MaybeConvertLength(
      underlying_length, EffectiveZoom(style)));
}

const CSSValue* CSSLengthInterpolationType::CreateCSSValue(
    const InterpolableValue& interpolable_value,
    const NonInterpolableValue*,
    const StyleResolverState&) const {
  return To<InterpolableLength>(interpolable_value)
      .CreateCSSValue(value_range_);
}

void CSSLengthInterpolationType::ApplyStandardPropertyValue(
    const InterpolableValue& interpolable_value,
    const NonInterpolableValue* non_interpolable_value,
    StyleResolverState& state) const {
  ComputedStyle& style = *state.Style();
  float zoom = EffectiveZoom(style);
  CSSToLengthConversionData conversion_data = state.CssToLengthConversionData();
  conversion_data.SetZoom(zoom);
  Length length = To<InterpolableLength>(interpolable_value)
                      .CreateLength(conversion_data, value_range_);
  if (LengthPropertyFunctions::SetLength(CssProperty(), style, length)) {
#if DCHECK_IS_ON()
    // Assert that setting the length on ComputedStyle directly is identical to
    // the StyleBuilder code path. This check is useful for catching differences
    // in clamping behaviour.
    Length before;
    Length after;
    DCHECK(LengthPropertyFunctions::GetLength(CssProperty(), style, before));
    StyleBuilder::ApplyProperty(
        GetProperty().GetCSSProperty(), state,
        ScopedCSSValue(*CSSValue::Create(length, zoom), nullptr));
    DCHECK(LengthPropertyFunctions::GetLength(CssProperty(), style, after));
    DCHECK(before.IsSpecified());
    DCHECK(after.IsSpecified());
    const float kSlack = 1e-6;
    const float before_length = FloatValueForLength(before, 100);
    const float after_length = FloatValueForLength(after, 100);
    if (std::isfinite(before_length) && std::isfinite(after_length)) {
      // Test relative difference for large values to avoid floating point
      // inaccuracies tripping the check.
      const float delta = std::abs(before_length) < kSlack
                              ? after_length - before_length
                              : (after_length - before_length) / before_length;
      DCHECK_LT(std::abs(delta), kSlack);
    }
#endif
    return;
  }
  StyleBuilder::ApplyProperty(
      GetProperty().GetCSSProperty(), state,
      ScopedCSSValue(*CSSValue::Create(length, zoom), nullptr));
}

}  // namespace blink
