// Copyright 2019 The Chromium Authors
// 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/platform/geometry/calculation_value.h"

#include "base/memory/values_equivalent.h"
#include "third_party/blink/renderer/platform/geometry/blend.h"
#include "third_party/blink/renderer/platform/geometry/calculation_expression_node.h"
#include "third_party/blink/renderer/platform/wtf/size_assertions.h"

namespace blink {

// static
const CalculationValue* CalculationValue::CreateSimplified(
    const CalculationExpressionNode* expression,
    Length::ValueRange range) {
  if (expression->IsPixelsAndPercent()) {
    return MakeGarbageCollected<CalculationValue>(
        To<CalculationExpressionPixelsAndPercentNode>(*expression)
            .GetPixelsAndPercent(),
        range);
  }
  return MakeGarbageCollected<CalculationValue>(PassKey(), expression, range);
}

CalculationValue::CalculationValue(PassKey,
                                   const CalculationExpressionNode* expression,
                                   Length::ValueRange range)
    : expression_(expression),
      is_non_negative_(range == Length::ValueRange::kNonNegative) {
  CHECK(expression);
}

CalculationValue::~CalculationValue() = default;

void CalculationValue::Trace(Visitor* visitor) const {
  visitor->Trace(expression_);
}

float CalculationValue::Evaluate(float max_value,
                                 const EvaluationInput& input) const {
  float value = expression_ ? expression_->Evaluate(max_value, input)
                            : Pixels() + Percent() / 100 * max_value;
  // Censor NaN.
  if (std::isnan(value)) {
    value = 0;
  }
  if (IsNonNegative() && value < 0) {
    value = 0;
  }
  return ClampTo<float>(value);
}

bool CalculationValue::operator==(const CalculationValue& other) const {
  return value_.pixels == other.value_.pixels &&
         value_.percent == other.value_.percent &&
         base::ValuesEquivalent(expression_, other.expression_) &&
         is_non_negative_ == other.is_non_negative_;
}

const CalculationExpressionNode* CalculationValue::GetOrCreateExpression()
    const {
  if (expression_) {
    return expression_.Get();
  }
  return MakeGarbageCollected<CalculationExpressionPixelsAndPercentNode>(
      GetPixelsAndPercent());
}

const CalculationValue* CalculationValue::Blend(
    const CalculationValue& from,
    double progress,
    Length::ValueRange range) const {
  if (!IsExpression() && !from.IsExpression()) {
    PixelsAndPercent from_pixels_and_percent = from.GetPixelsAndPercent();
    PixelsAndPercent to_pixels_and_percent = GetPixelsAndPercent();
    const float pixels = blink::Blend(from_pixels_and_percent.pixels,
                                      to_pixels_and_percent.pixels, progress);
    const float percent = blink::Blend(from_pixels_and_percent.percent,
                                       to_pixels_and_percent.percent, progress);
    bool has_explicit_pixels = from_pixels_and_percent.has_explicit_pixels |
                               to_pixels_and_percent.has_explicit_pixels;
    bool has_explicit_percent = from_pixels_and_percent.has_explicit_percent |
                                to_pixels_and_percent.has_explicit_percent;
    return MakeGarbageCollected<CalculationValue>(
        PixelsAndPercent(pixels, percent, has_explicit_pixels,
                         has_explicit_percent),
        range);
  }

  const auto* blended_from =
      CalculationExpressionOperationNode::CreateSimplified(
          CalculationExpressionOperationNode::Children(
              {from.GetOrCreateExpression(),
               MakeGarbageCollected<CalculationExpressionNumberNode>(
                   1.0 - progress)}),
          CalculationOperator::kMultiply);
  const auto* blended_to = CalculationExpressionOperationNode::CreateSimplified(
      CalculationExpressionOperationNode::Children(
          {GetOrCreateExpression(),
           MakeGarbageCollected<CalculationExpressionNumberNode>(progress)}),
      CalculationOperator::kMultiply);
  const auto* result_expression =
      CalculationExpressionOperationNode::CreateSimplified(
          {blended_from, blended_to}, CalculationOperator::kAdd);
  return CreateSimplified(result_expression, range);
}

const CalculationValue* CalculationValue::SubtractFromOneHundredPercent()
    const {
  if (!IsExpression()) {
    PixelsAndPercent result(-Pixels(), 100 - Percent(), HasExplicitPixels(),
                            /*has_explicit_percent=*/true);
    return MakeGarbageCollected<CalculationValue>(result,
                                                  Length::ValueRange::kAll);
  }
  const auto* hundred_percent =
      MakeGarbageCollected<CalculationExpressionPixelsAndPercentNode>(
          PixelsAndPercent(0, 100, false, true));
  const auto* result_expression =
      CalculationExpressionOperationNode::CreateSimplified(
          CalculationExpressionOperationNode::Children(
              {hundred_percent, GetOrCreateExpression()}),
          CalculationOperator::kSubtract);
  return CreateSimplified(result_expression, Length::ValueRange::kAll);
}

const CalculationValue* CalculationValue::Add(
    const CalculationValue& other) const {
  const auto* result_expression =
      CalculationExpressionOperationNode::CreateSimplified(
          {GetOrCreateExpression(), other.GetOrCreateExpression()},
          CalculationOperator::kAdd);
  return CreateSimplified(result_expression, Length::ValueRange::kAll);
}

const CalculationValue* CalculationValue::Zoom(double factor) const {
  if (expression_) {
    return CreateSimplified(expression_->Zoom(factor), GetValueRange());
  }
  PixelsAndPercent result(Pixels() * factor, Percent(), HasExplicitPixels(),
                          HasExplicitPercent());
  return MakeGarbageCollected<CalculationValue>(result, GetValueRange());
}

bool CalculationValue::HasAuto() const {
  return expression_ && expression_->HasAuto();
}

bool CalculationValue::HasContentOrIntrinsicSize() const {
  return expression_ && expression_->HasContentOrIntrinsicSize();
}

bool CalculationValue::HasAutoOrContentOrIntrinsicSize() const {
  return expression_ && expression_->HasAutoOrContentOrIntrinsicSize();
}

bool CalculationValue::HasPercent() const {
  if (expression_) {
    return expression_->HasPercent();
  }
  return HasExplicitPercent();
}

bool CalculationValue::HasPercentOrStretch() const {
  if (expression_) {
    return expression_->HasPercentOrStretch();
  }
  return HasExplicitPercent();
}

bool CalculationValue::HasStretch() const {
  return expression_ && expression_->HasStretch();
}

bool CalculationValue::HasMinContent() const {
  // `HasContentOrIntrinsicSize` is comparatively faster than `HasMinContent`.
  return expression_ && expression_->HasContentOrIntrinsicSize() &&
         expression_->HasMinContent();
}

bool CalculationValue::HasMaxContent() const {
  // `HasContentOrIntrinsicSize` is comparatively faster than `HasMaxContent`.
  return expression_ && expression_->HasContentOrIntrinsicSize() &&
         expression_->HasMaxContent();
}

bool CalculationValue::HasFitContent() const {
  // `HasContentOrIntrinsicSize` is comparatively faster than `HasFitContent`.
  return expression_ && expression_->HasContentOrIntrinsicSize() &&
         expression_->HasFitContent();
}

bool CalculationValue::HasOnlyFixedAndPercent() const {
  if (expression_) {
    return !expression_->HasAutoOrContentOrIntrinsicSize() &&
           !expression_->HasStretch();
  }
  return true;
}

}  // namespace blink
