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

#include "ui/gfx/geometry/transform_operations.h"

#include <stddef.h>

#include <limits>
#include <utility>
#include <vector>

#include "base/cxx17_backports.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/geometry/box_f.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/test/geometry_util.h"
#include "ui/gfx/geometry/vector3d_f.h"

namespace gfx {
namespace {

void ExpectTransformOperationEqual(const TransformOperation& lhs,
                                   const TransformOperation& rhs) {
  EXPECT_EQ(lhs.type, rhs.type);
  EXPECT_TRANSFORM_EQ(lhs.matrix, rhs.matrix);
  switch (lhs.type) {
    case TransformOperation::TRANSFORM_OPERATION_TRANSLATE:
      EXPECT_FLOAT_EQ(lhs.translate.x, rhs.translate.x);
      EXPECT_FLOAT_EQ(lhs.translate.y, rhs.translate.y);
      EXPECT_FLOAT_EQ(lhs.translate.z, rhs.translate.z);
      break;
    case TransformOperation::TRANSFORM_OPERATION_ROTATE:
      EXPECT_FLOAT_EQ(lhs.rotate.axis.x, rhs.rotate.axis.x);
      EXPECT_FLOAT_EQ(lhs.rotate.axis.y, rhs.rotate.axis.y);
      EXPECT_FLOAT_EQ(lhs.rotate.axis.z, rhs.rotate.axis.z);
      EXPECT_FLOAT_EQ(lhs.rotate.angle, rhs.rotate.angle);
      break;
    case TransformOperation::TRANSFORM_OPERATION_SCALE:
      EXPECT_FLOAT_EQ(lhs.scale.x, rhs.scale.x);
      EXPECT_FLOAT_EQ(lhs.scale.y, rhs.scale.y);
      EXPECT_FLOAT_EQ(lhs.scale.z, rhs.scale.z);
      break;
    case TransformOperation::TRANSFORM_OPERATION_SKEWX:
    case TransformOperation::TRANSFORM_OPERATION_SKEWY:
    case TransformOperation::TRANSFORM_OPERATION_SKEW:
      EXPECT_FLOAT_EQ(lhs.skew.x, rhs.skew.x);
      EXPECT_FLOAT_EQ(lhs.skew.y, rhs.skew.y);
      break;
    case TransformOperation::TRANSFORM_OPERATION_PERSPECTIVE:
      EXPECT_FLOAT_EQ(lhs.perspective_m43, rhs.perspective_m43);
      break;
    case TransformOperation::TRANSFORM_OPERATION_MATRIX:
    case TransformOperation::TRANSFORM_OPERATION_IDENTITY:
      break;
  }
}

TEST(TransformOperationTest, TransformTypesAreUnique) {
  std::vector<std::unique_ptr<TransformOperations>> transforms;

  std::unique_ptr<TransformOperations> to_add(
      std::make_unique<TransformOperations>());
  to_add->AppendTranslate(1, 0, 0);
  transforms.push_back(std::move(to_add));

  to_add = std::make_unique<TransformOperations>();
  to_add->AppendRotate(0, 0, 1, 2);
  transforms.push_back(std::move(to_add));

  to_add = std::make_unique<TransformOperations>();
  to_add->AppendScale(2, 2, 2);
  transforms.push_back(std::move(to_add));

  to_add = std::make_unique<TransformOperations>();
  to_add->AppendSkew(1, 0);
  transforms.push_back(std::move(to_add));

  to_add = std::make_unique<TransformOperations>();
  to_add->AppendPerspective(800);
  transforms.push_back(std::move(to_add));

  for (size_t i = 0; i < transforms.size(); ++i) {
    for (size_t j = 0; j < transforms.size(); ++j) {
      bool matches_type = transforms[i]->MatchesTypes(*transforms[j]);
      EXPECT_TRUE((i == j && matches_type) || !matches_type);
    }
  }
}

TEST(TransformOperationTest, MatchingPrefixSameLength) {
  TransformOperations translates;
  translates.AppendTranslate(1, 0, 0);
  translates.AppendTranslate(1, 0, 0);
  translates.AppendTranslate(1, 0, 0);

  TransformOperations skews;
  skews.AppendSkew(0, 2);
  skews.AppendSkew(0, 2);
  skews.AppendSkew(0, 2);

  TransformOperations translates2;
  translates2.AppendTranslate(0, 2, 0);
  translates2.AppendTranslate(0, 2, 0);
  translates2.AppendTranslate(0, 2, 0);

  TransformOperations mixed;
  mixed.AppendTranslate(0, 2, 0);
  mixed.AppendScale(2, 1, 1);
  mixed.AppendSkew(0, 2);

  TransformOperations translates3 = translates2;

  EXPECT_EQ(0UL, translates.MatchingPrefixLength(skews));
  EXPECT_EQ(3UL, translates.MatchingPrefixLength(translates2));
  EXPECT_EQ(3UL, translates.MatchingPrefixLength(translates3));
  EXPECT_EQ(1UL, translates.MatchingPrefixLength(mixed));
}

TEST(TransformOperationTest, MatchingPrefixDifferentLength) {
  TransformOperations translates;
  translates.AppendTranslate(1, 0, 0);
  translates.AppendTranslate(1, 0, 0);
  translates.AppendTranslate(1, 0, 0);

  TransformOperations skews;
  skews.AppendSkew(2, 0);
  skews.AppendSkew(2, 0);

  TransformOperations translates2;
  translates2.AppendTranslate(0, 2, 0);
  translates2.AppendTranslate(0, 2, 0);

  TransformOperations none;

  EXPECT_EQ(0UL, translates.MatchingPrefixLength(skews));
  // Pad the length of the shorter list provided all previous operation-
  // pairs match per spec
  // (https://drafts.csswg.org/css-transforms/#interpolation-of-transforms).
  EXPECT_EQ(3UL, translates.MatchingPrefixLength(translates2));
  EXPECT_EQ(3UL, translates.MatchingPrefixLength(none));
}

std::vector<std::unique_ptr<TransformOperations>> GetIdentityOperations() {
  std::vector<std::unique_ptr<TransformOperations>> operations;
  std::unique_ptr<TransformOperations> to_add(
      std::make_unique<TransformOperations>());
  operations.push_back(std::move(to_add));

  to_add = std::make_unique<TransformOperations>();
  to_add->AppendTranslate(0, 0, 0);
  operations.push_back(std::move(to_add));

  to_add = std::make_unique<TransformOperations>();
  to_add->AppendTranslate(0, 0, 0);
  to_add->AppendTranslate(0, 0, 0);
  operations.push_back(std::move(to_add));

  to_add = std::make_unique<TransformOperations>();
  to_add->AppendScale(1, 1, 1);
  operations.push_back(std::move(to_add));

  to_add = std::make_unique<TransformOperations>();
  to_add->AppendScale(1, 1, 1);
  to_add->AppendScale(1, 1, 1);
  operations.push_back(std::move(to_add));

  to_add = std::make_unique<TransformOperations>();
  to_add->AppendSkew(0, 0);
  operations.push_back(std::move(to_add));

  to_add = std::make_unique<TransformOperations>();
  to_add->AppendSkew(0, 0);
  to_add->AppendSkew(0, 0);
  operations.push_back(std::move(to_add));

  to_add = std::make_unique<TransformOperations>();
  to_add->AppendRotate(0, 0, 1, 0);
  operations.push_back(std::move(to_add));

  to_add = std::make_unique<TransformOperations>();
  to_add->AppendRotate(0, 0, 1, 0);
  to_add->AppendRotate(0, 0, 1, 0);
  operations.push_back(std::move(to_add));

  to_add = std::make_unique<TransformOperations>();
  to_add->AppendMatrix(gfx::Transform());
  operations.push_back(std::move(to_add));

  to_add = std::make_unique<TransformOperations>();
  to_add->AppendMatrix(gfx::Transform());
  to_add->AppendMatrix(gfx::Transform());
  operations.push_back(std::move(to_add));

  return operations;
}

TEST(TransformOperationTest, MatchingPrefixLengthOrder) {
  TransformOperations mix_order_identity;
  mix_order_identity.AppendTranslate(0, 0, 0);
  mix_order_identity.AppendScale(1, 1, 1);
  mix_order_identity.AppendTranslate(0, 0, 0);

  TransformOperations mix_order_one;
  mix_order_one.AppendTranslate(0, 1, 0);
  mix_order_one.AppendScale(2, 1, 3);
  mix_order_one.AppendTranslate(1, 0, 0);

  TransformOperations mix_order_two;
  mix_order_two.AppendTranslate(0, 1, 0);
  mix_order_two.AppendTranslate(1, 0, 0);
  mix_order_two.AppendScale(2, 1, 3);

  EXPECT_EQ(3UL, mix_order_identity.MatchingPrefixLength(mix_order_one));
  EXPECT_EQ(1UL, mix_order_identity.MatchingPrefixLength(mix_order_two));
  EXPECT_EQ(1UL, mix_order_one.MatchingPrefixLength(mix_order_two));
}

TEST(TransformOperationTest, NoneAlwaysMatches) {
  std::vector<std::unique_ptr<TransformOperations>> operations =
      GetIdentityOperations();

  TransformOperations none_operation;
  for (const auto& operation : operations) {
    EXPECT_EQ(operation->size(),
              operation->MatchingPrefixLength(none_operation));
  }
}

TEST(TransformOperationTest, ApplyTranslate) {
  SkScalar x = 1;
  SkScalar y = 2;
  SkScalar z = 3;
  TransformOperations operations;
  operations.AppendTranslate(x, y, z);
  gfx::Transform expected;
  expected.Translate3d(x, y, z);
  EXPECT_TRANSFORM_EQ(expected, operations.Apply());
}

TEST(TransformOperationTest, ApplyRotate) {
  SkScalar x = 1;
  SkScalar y = 2;
  SkScalar z = 3;
  SkScalar degrees = 80;
  TransformOperations operations;
  operations.AppendRotate(x, y, z, degrees);
  gfx::Transform expected;
  expected.RotateAbout(gfx::Vector3dF(x, y, z), degrees);
  EXPECT_TRANSFORM_EQ(expected, operations.Apply());
}

TEST(TransformOperationTest, ApplyScale) {
  SkScalar x = 1;
  SkScalar y = 2;
  SkScalar z = 3;
  TransformOperations operations;
  operations.AppendScale(x, y, z);
  gfx::Transform expected;
  expected.Scale3d(x, y, z);
  EXPECT_TRANSFORM_EQ(expected, operations.Apply());
}

TEST(TransformOperationTest, ApplySkew) {
  SkScalar x = 1;
  SkScalar y = 2;
  TransformOperations operations;
  operations.AppendSkew(x, y);
  gfx::Transform expected;
  expected.Skew(x, y);
  EXPECT_TRANSFORM_EQ(expected, operations.Apply());
}

TEST(TransformOperationTest, ApplyPerspective) {
  SkScalar depth = 800;
  TransformOperations operations;
  operations.AppendPerspective(depth);
  gfx::Transform expected;
  expected.ApplyPerspectiveDepth(depth);
  EXPECT_TRANSFORM_EQ(expected, operations.Apply());
}

TEST(TransformOperationTest, ApplyMatrix) {
  SkScalar dx = 1;
  SkScalar dy = 2;
  SkScalar dz = 3;
  gfx::Transform expected_matrix;
  expected_matrix.Translate3d(dx, dy, dz);
  TransformOperations matrix_transform;
  matrix_transform.AppendMatrix(expected_matrix);
  EXPECT_TRANSFORM_EQ(expected_matrix, matrix_transform.Apply());
}

TEST(TransformOperationTest, ApplyOrder) {
  SkScalar sx = 2;
  SkScalar sy = 4;
  SkScalar sz = 8;

  SkScalar dx = 1;
  SkScalar dy = 2;
  SkScalar dz = 3;

  TransformOperations operations;
  operations.AppendScale(sx, sy, sz);
  operations.AppendTranslate(dx, dy, dz);

  gfx::Transform expected_scale_matrix;
  expected_scale_matrix.Scale3d(sx, sy, sz);

  gfx::Transform expected_translate_matrix;
  expected_translate_matrix.Translate3d(dx, dy, dz);

  gfx::Transform expected_combined_matrix = expected_scale_matrix;
  expected_combined_matrix.PreConcat(expected_translate_matrix);

  EXPECT_TRANSFORM_EQ(expected_combined_matrix, operations.Apply());
}

TEST(TransformOperationTest, BlendOrder) {
  SkScalar sx1 = 2;
  SkScalar sy1 = 4;
  SkScalar sz1 = 8;

  SkScalar dx1 = 1;
  SkScalar dy1 = 2;
  SkScalar dz1 = 3;

  SkScalar sx2 = 4;
  SkScalar sy2 = 8;
  SkScalar sz2 = 16;

  SkScalar dx2 = 10;
  SkScalar dy2 = 20;
  SkScalar dz2 = 30;

  SkScalar sx3 = 2;
  SkScalar sy3 = 1;
  SkScalar sz3 = 1;

  TransformOperations operations_from;
  operations_from.AppendScale(sx1, sy1, sz1);
  operations_from.AppendTranslate(dx1, dy1, dz1);

  TransformOperations operations_to;
  operations_to.AppendScale(sx2, sy2, sz2);
  operations_to.AppendTranslate(dx2, dy2, dz2);

  gfx::Transform scale_from;
  scale_from.Scale3d(sx1, sy1, sz1);
  gfx::Transform translate_from;
  translate_from.Translate3d(dx1, dy1, dz1);

  gfx::Transform scale_to;
  scale_to.Scale3d(sx2, sy2, sz2);
  gfx::Transform translate_to;
  translate_to.Translate3d(dx2, dy2, dz2);

  SkScalar progress = 0.25f;

  TransformOperations operations_expected;
  operations_expected.AppendScale(
      gfx::Tween::FloatValueBetween(progress, sx1, sx2),
      gfx::Tween::FloatValueBetween(progress, sy1, sy2),
      gfx::Tween::FloatValueBetween(progress, sz1, sz2));

  operations_expected.AppendTranslate(
      gfx::Tween::FloatValueBetween(progress, dx1, dx2),
      gfx::Tween::FloatValueBetween(progress, dy1, dy2),
      gfx::Tween::FloatValueBetween(progress, dz1, dz2));

  gfx::Transform blended_scale = scale_to;
  blended_scale.Blend(scale_from, progress);

  gfx::Transform blended_translate = translate_to;
  blended_translate.Blend(translate_from, progress);

  gfx::Transform expected = blended_scale;
  expected.PreConcat(blended_translate);

  TransformOperations blended = operations_to.Blend(operations_from, progress);

  EXPECT_TRANSFORM_EQ(expected, blended.Apply());
  EXPECT_TRANSFORM_EQ(operations_expected.Apply(), blended.Apply());
  EXPECT_EQ(operations_expected.size(), blended.size());
  for (size_t i = 0; i < operations_expected.size(); ++i) {
    TransformOperation expected_op = operations_expected.at(i);
    TransformOperation blended_op = blended.at(i);
    SCOPED_TRACE(i);
    ExpectTransformOperationEqual(expected_op, blended_op);
  }

  TransformOperations base_operations_expected = operations_expected;

  // Create a mismatch in number of operations. Pairwise interpolation is still
  // used when the operations match up to the length of the shorter list.
  operations_to.AppendScale(sx3, sy3, sz3);

  gfx::Transform appended_scale;
  appended_scale.Scale3d(sx3, sy3, sz3);

  gfx::Transform blended_append_scale = appended_scale;
  blended_append_scale.Blend(gfx::Transform(), progress);
  expected.PreConcat(blended_append_scale);

  operations_expected.AppendScale(
      gfx::Tween::FloatValueBetween(progress, 1, sx3),
      gfx::Tween::FloatValueBetween(progress, 1, sy3),
      gfx::Tween::FloatValueBetween(progress, 1, sz3));

  blended = operations_to.Blend(operations_from, progress);

  EXPECT_TRANSFORM_EQ(expected, blended.Apply());
  EXPECT_TRANSFORM_EQ(operations_expected.Apply(), blended.Apply());
  EXPECT_EQ(operations_expected.size(), blended.size());
  for (size_t i = 0; i < operations_expected.size(); ++i) {
    TransformOperation expected_op = operations_expected.at(i);
    TransformOperation blended_op = blended.at(i);
    SCOPED_TRACE(i);
    ExpectTransformOperationEqual(expected_op, blended_op);
  }

  // Create a mismatch, forcing matrix interpolation for the last operator pair.
  operations_from.AppendRotate(0, 0, 1, 90);

  blended = operations_to.Blend(operations_from, progress);

  gfx::Transform transform_from;
  transform_from.RotateAboutZAxis(90);
  gfx::Transform transform_to;
  transform_to.Scale3d(sx3, sy3, sz3);
  gfx::Transform blended_matrix = transform_to;
  blended_matrix.Blend(transform_from, progress);

  expected = blended_scale;
  expected.PreConcat(blended_translate);
  expected.PreConcat(blended_matrix);

  operations_expected = base_operations_expected;
  operations_expected.AppendMatrix(blended_matrix);

  EXPECT_TRANSFORM_EQ(expected, blended.Apply());
  EXPECT_TRANSFORM_EQ(operations_expected.Apply(), blended.Apply());
  EXPECT_EQ(operations_expected.size(), blended.size());
  for (size_t i = 0; i < operations_expected.size(); ++i) {
    TransformOperation expected_op = operations_expected.at(i);
    TransformOperation blended_op = blended.at(i);
    SCOPED_TRACE(i);
    ExpectTransformOperationEqual(expected_op, blended_op);
  }
}

static void CheckProgress(SkScalar progress,
                          const gfx::Transform& from_matrix,
                          const gfx::Transform& to_matrix,
                          const TransformOperations& from_transform,
                          const TransformOperations& to_transform) {
  gfx::Transform expected_matrix = to_matrix;
  expected_matrix.Blend(from_matrix, progress);
  EXPECT_TRANSFORM_EQ(expected_matrix,
                      to_transform.Blend(from_transform, progress).Apply());
}

TEST(TransformOperationTest, BlendProgress) {
  SkScalar sx = 2;
  SkScalar sy = 4;
  SkScalar sz = 8;
  TransformOperations operations_from;
  operations_from.AppendScale(sx, sy, sz);

  gfx::Transform matrix_from;
  matrix_from.Scale3d(sx, sy, sz);

  sx = 4;
  sy = 8;
  sz = 16;
  TransformOperations operations_to;
  operations_to.AppendScale(sx, sy, sz);

  gfx::Transform matrix_to;
  matrix_to.Scale3d(sx, sy, sz);

  CheckProgress(-1, matrix_from, matrix_to, operations_from, operations_to);
  CheckProgress(0, matrix_from, matrix_to, operations_from, operations_to);
  CheckProgress(0.25f, matrix_from, matrix_to, operations_from, operations_to);
  CheckProgress(0.5f, matrix_from, matrix_to, operations_from, operations_to);
  CheckProgress(1, matrix_from, matrix_to, operations_from, operations_to);
  CheckProgress(2, matrix_from, matrix_to, operations_from, operations_to);
}

TEST(TransformOperationTest, BlendWhenTypesDoNotMatch) {
  SkScalar sx1 = 2;
  SkScalar sy1 = 4;
  SkScalar sz1 = 8;

  SkScalar dx1 = 1;
  SkScalar dy1 = 2;
  SkScalar dz1 = 3;

  SkScalar sx2 = 4;
  SkScalar sy2 = 8;
  SkScalar sz2 = 16;

  SkScalar dx2 = 10;
  SkScalar dy2 = 20;
  SkScalar dz2 = 30;

  TransformOperations operations_from;
  operations_from.AppendScale(sx1, sy1, sz1);
  operations_from.AppendTranslate(dx1, dy1, dz1);

  TransformOperations operations_to;
  operations_to.AppendTranslate(dx2, dy2, dz2);
  operations_to.AppendScale(sx2, sy2, sz2);

  gfx::Transform from;
  from.Scale3d(sx1, sy1, sz1);
  from.Translate3d(dx1, dy1, dz1);

  gfx::Transform to;
  to.Translate3d(dx2, dy2, dz2);
  to.Scale3d(sx2, sy2, sz2);

  SkScalar progress = 0.25f;

  gfx::Transform expected = to;
  expected.Blend(from, progress);

  EXPECT_TRANSFORM_EQ(expected,
                      operations_to.Blend(operations_from, progress).Apply());
}

TEST(TransformOperationTest, LargeRotationsWithSameAxis) {
  TransformOperations operations_from;
  operations_from.AppendRotate(0, 0, 1, 0);

  TransformOperations operations_to;
  operations_to.AppendRotate(0, 0, 2, 360);

  SkScalar progress = 0.5f;

  gfx::Transform expected;
  expected.RotateAbout(gfx::Vector3dF(0, 0, 1), 180);

  EXPECT_TRANSFORM_EQ(expected,
                      operations_to.Blend(operations_from, progress).Apply());
}

TEST(TransformOperationTest, LargeRotationsWithSameAxisInDifferentDirection) {
  TransformOperations operations_from;
  operations_from.AppendRotate(0, 0, 1, 180);

  TransformOperations operations_to;
  operations_to.AppendRotate(0, 0, -1, 180);

  SkScalar progress = 0.5f;

  gfx::Transform expected;

  EXPECT_TRANSFORM_EQ(expected,
                      operations_to.Blend(operations_from, progress).Apply());
}

TEST(TransformOperationTest, LargeRotationsWithDifferentAxes) {
  TransformOperations operations_from;
  operations_from.AppendRotate(0, 0, 1, 175);

  TransformOperations operations_to;
  operations_to.AppendRotate(0, 1, 0, 175);

  SkScalar progress = 0.5f;
  gfx::Transform matrix_from;
  matrix_from.RotateAbout(gfx::Vector3dF(0, 0, 1), 175);

  gfx::Transform matrix_to;
  matrix_to.RotateAbout(gfx::Vector3dF(0, 1, 0), 175);

  gfx::Transform expected = matrix_to;
  expected.Blend(matrix_from, progress);

  EXPECT_TRANSFORM_EQ(expected,
                      operations_to.Blend(operations_from, progress).Apply());
}

TEST(TransformOperationTest, RotationFromZeroDegDifferentAxes) {
  TransformOperations operations_from;
  operations_from.AppendRotate(0, 0, 1, 0);

  TransformOperations operations_to;
  operations_to.AppendRotate(0, 1, 0, 450);

  SkScalar progress = 0.5f;
  gfx::Transform expected;
  expected.RotateAbout(gfx::Vector3dF(0, 1, 0), 225);
  EXPECT_TRANSFORM_EQ(expected,
                      operations_to.Blend(operations_from, progress).Apply());
}

TEST(TransformOperationTest, RotationFromZeroDegSameAxes) {
  TransformOperations operations_from;
  operations_from.AppendRotate(0, 0, 1, 0);

  TransformOperations operations_to;
  operations_to.AppendRotate(0, 0, 1, 450);

  SkScalar progress = 0.5f;
  gfx::Transform expected;
  expected.RotateAbout(gfx::Vector3dF(0, 0, 1), 225);
  EXPECT_TRANSFORM_EQ(expected,
                      operations_to.Blend(operations_from, progress).Apply());
}

TEST(TransformOperationTest, RotationToZeroDegDifferentAxes) {
  TransformOperations operations_from;
  operations_from.AppendRotate(0, 1, 0, 450);

  TransformOperations operations_to;
  operations_to.AppendRotate(0, 0, 1, 0);

  SkScalar progress = 0.5f;
  gfx::Transform expected;
  expected.RotateAbout(gfx::Vector3dF(0, 1, 0), 225);
  EXPECT_TRANSFORM_EQ(expected,
                      operations_to.Blend(operations_from, progress).Apply());
}

TEST(TransformOperationTest, RotationToZeroDegSameAxes) {
  TransformOperations operations_from;
  operations_from.AppendRotate(0, 0, 1, 450);

  TransformOperations operations_to;
  operations_to.AppendRotate(0, 0, 1, 0);

  SkScalar progress = 0.5f;
  gfx::Transform expected;
  expected.RotateAbout(gfx::Vector3dF(0, 0, 1), 225);
  EXPECT_TRANSFORM_EQ(expected,
                      operations_to.Blend(operations_from, progress).Apply());
}

TEST(TransformOperationTest, BlendRotationFromIdentity) {
  std::vector<std::unique_ptr<TransformOperations>> identity_operations =
      GetIdentityOperations();

  for (const auto& identity_operation : identity_operations) {
    TransformOperations operations;
    operations.AppendRotate(0, 0, 1, 90);

    SkScalar progress = 0.5f;

    gfx::Transform expected;
    expected.RotateAbout(gfx::Vector3dF(0, 0, 1), 45);

    EXPECT_TRANSFORM_EQ(
        expected, operations.Blend(*identity_operation, progress).Apply());

    progress = -0.5f;

    expected.MakeIdentity();
    expected.RotateAbout(gfx::Vector3dF(0, 0, 1), -45);

    EXPECT_TRANSFORM_EQ(
        expected, operations.Blend(*identity_operation, progress).Apply());

    progress = 1.5f;

    expected.MakeIdentity();
    expected.RotateAbout(gfx::Vector3dF(0, 0, 1), 135);

    EXPECT_TRANSFORM_EQ(
        expected, operations.Blend(*identity_operation, progress).Apply());
  }
}

TEST(TransformOperationTest, BlendTranslationFromIdentity) {
  std::vector<std::unique_ptr<TransformOperations>> identity_operations =
      GetIdentityOperations();

  for (const auto& identity_operation : identity_operations) {
    TransformOperations operations;
    operations.AppendTranslate(2, 2, 2);

    SkScalar progress = 0.5f;

    gfx::Transform expected;
    expected.Translate3d(1, 1, 1);

    EXPECT_TRANSFORM_EQ(
        expected, operations.Blend(*identity_operation, progress).Apply());

    progress = -0.5f;

    expected.MakeIdentity();
    expected.Translate3d(-1, -1, -1);

    EXPECT_TRANSFORM_EQ(
        expected, operations.Blend(*identity_operation, progress).Apply());

    progress = 1.5f;

    expected.MakeIdentity();
    expected.Translate3d(3, 3, 3);

    EXPECT_TRANSFORM_EQ(
        expected, operations.Blend(*identity_operation, progress).Apply());
  }
}

TEST(TransformOperationTest, BlendScaleFromIdentity) {
  std::vector<std::unique_ptr<TransformOperations>> identity_operations =
      GetIdentityOperations();

  for (const auto& identity_operation : identity_operations) {
    TransformOperations operations;
    operations.AppendScale(3, 3, 3);

    SkScalar progress = 0.5f;

    gfx::Transform expected;
    expected.Scale3d(2, 2, 2);

    EXPECT_TRANSFORM_EQ(
        expected, operations.Blend(*identity_operation, progress).Apply());

    progress = -0.5f;

    expected.MakeIdentity();
    expected.Scale3d(0, 0, 0);

    EXPECT_TRANSFORM_EQ(
        expected, operations.Blend(*identity_operation, progress).Apply());

    progress = 1.5f;

    expected.MakeIdentity();
    expected.Scale3d(4, 4, 4);

    EXPECT_TRANSFORM_EQ(
        expected, operations.Blend(*identity_operation, progress).Apply());
  }
}

TEST(TransformOperationTest, BlendSkewFromEmpty) {
  TransformOperations empty_operation;

  TransformOperations operations;
  operations.AppendSkew(2, 2);

  SkScalar progress = 0.5f;

  gfx::Transform expected;
  expected.Skew(1, 1);

  EXPECT_TRANSFORM_EQ(expected,
                      operations.Blend(empty_operation, progress).Apply());

  progress = -0.5f;

  expected.MakeIdentity();
  expected.Skew(-1, -1);

  EXPECT_TRANSFORM_EQ(expected,
                      operations.Blend(empty_operation, progress).Apply());

  progress = 1.5f;

  expected.MakeIdentity();
  expected.Skew(3, 3);

  EXPECT_TRANSFORM_EQ(expected,
                      operations.Blend(empty_operation, progress).Apply());
}

TEST(TransformOperationTest, BlendPerspectiveFromIdentity) {
  std::vector<std::unique_ptr<TransformOperations>> identity_operations =
      GetIdentityOperations();

  for (const auto& identity_operation : identity_operations) {
    TransformOperations operations;
    operations.AppendPerspective(1000);

    SkScalar progress = 0.5f;

    gfx::Transform expected;
    expected.ApplyPerspectiveDepth(2000);

    EXPECT_TRANSFORM_EQ(
        expected, operations.Blend(*identity_operation, progress).Apply());
  }
}

TEST(TransformOperationTest, BlendRotationToIdentity) {
  std::vector<std::unique_ptr<TransformOperations>> identity_operations =
      GetIdentityOperations();

  for (const auto& identity_operation : identity_operations) {
    TransformOperations operations;
    operations.AppendRotate(0, 0, 1, 90);

    SkScalar progress = 0.5f;

    gfx::Transform expected;
    expected.RotateAbout(gfx::Vector3dF(0, 0, 1), 45);

    EXPECT_TRANSFORM_EQ(
        expected, identity_operation->Blend(operations, progress).Apply());
  }
}

TEST(TransformOperationTest, BlendTranslationToIdentity) {
  std::vector<std::unique_ptr<TransformOperations>> identity_operations =
      GetIdentityOperations();

  for (const auto& identity_operation : identity_operations) {
    TransformOperations operations;
    operations.AppendTranslate(2, 2, 2);

    SkScalar progress = 0.5f;

    gfx::Transform expected;
    expected.Translate3d(1, 1, 1);

    EXPECT_TRANSFORM_EQ(
        expected, identity_operation->Blend(operations, progress).Apply());
  }
}

TEST(TransformOperationTest, BlendScaleToIdentity) {
  std::vector<std::unique_ptr<TransformOperations>> identity_operations =
      GetIdentityOperations();

  for (const auto& identity_operation : identity_operations) {
    TransformOperations operations;
    operations.AppendScale(3, 3, 3);

    SkScalar progress = 0.5f;

    gfx::Transform expected;
    expected.Scale3d(2, 2, 2);

    EXPECT_TRANSFORM_EQ(
        expected, identity_operation->Blend(operations, progress).Apply());
  }
}

TEST(TransformOperationTest, BlendSkewToEmpty) {
  TransformOperations empty_operation;

  TransformOperations operations;
  operations.AppendSkew(2, 2);

  SkScalar progress = 0.5f;

  gfx::Transform expected;
  expected.Skew(1, 1);

  EXPECT_TRANSFORM_EQ(expected,
                      empty_operation.Blend(operations, progress).Apply());
}

TEST(TransformOperationTest, BlendPerspectiveToIdentity) {
  std::vector<std::unique_ptr<TransformOperations>> identity_operations =
      GetIdentityOperations();

  for (const auto& identity_operation : identity_operations) {
    TransformOperations operations;
    operations.AppendPerspective(1000);

    SkScalar progress = 0.5f;

    gfx::Transform expected;
    expected.ApplyPerspectiveDepth(2000);

    EXPECT_TRANSFORM_EQ(
        expected, identity_operation->Blend(operations, progress).Apply());
  }
}

TEST(TransformOperationTest, ExtrapolatePerspectiveBlending) {
  TransformOperations operations1;
  operations1.AppendPerspective(1000);

  TransformOperations operations2;
  operations2.AppendPerspective(500);

  gfx::Transform expected;
  expected.ApplyPerspectiveDepth(400);

  EXPECT_TRANSFORM_EQ(expected, operations1.Blend(operations2, -0.5).Apply());

  expected.MakeIdentity();
  expected.ApplyPerspectiveDepth(2000);

  EXPECT_TRANSFORM_EQ(expected, operations1.Blend(operations2, 1.5).Apply());
}

TEST(TransformOperationTest, ExtrapolateMatrixBlending) {
  gfx::Transform transform1;
  transform1.Translate3d(1, 1, 1);
  TransformOperations operations1;
  operations1.AppendMatrix(transform1);

  gfx::Transform transform2;
  transform2.Translate3d(3, 3, 3);
  TransformOperations operations2;
  operations2.AppendMatrix(transform2);

  gfx::Transform expected;
  EXPECT_TRANSFORM_EQ(expected, operations1.Blend(operations2, 1.5).Apply());

  expected.Translate3d(4, 4, 4);
  EXPECT_TRANSFORM_EQ(expected, operations1.Blend(operations2, -0.5).Apply());
}

TEST(TransformOperationTest, NonDecomposableBlend) {
  TransformOperations non_decomposible_transform;
  auto non_decomposible_matrix = gfx::Transform::MakeScale(0);
  non_decomposible_transform.AppendMatrix(non_decomposible_matrix);

  TransformOperations identity_transform;
  gfx::Transform identity_matrix;
  identity_transform.AppendMatrix(identity_matrix);

  // Before the half-way point, we should return the 'from' matrix.
  EXPECT_TRANSFORM_EQ(
      non_decomposible_matrix,
      identity_transform.Blend(non_decomposible_transform, 0.0f).Apply());
  EXPECT_TRANSFORM_EQ(
      non_decomposible_matrix,
      identity_transform.Blend(non_decomposible_transform, 0.49f).Apply());

  // After the half-way point, we should return the 'to' matrix.
  EXPECT_TRANSFORM_EQ(
      identity_matrix,
      identity_transform.Blend(non_decomposible_transform, 0.5f).Apply());
  EXPECT_TRANSFORM_EQ(
      identity_matrix,
      identity_transform.Blend(non_decomposible_transform, 1.0f).Apply());
}

TEST(TransformOperationTest, BlendedBoundsWhenTypesDoNotMatch) {
  TransformOperations operations_from;
  operations_from.AppendScale(2.0, 4.0, 8.0);
  operations_from.AppendTranslate(1.0, 2.0, 3.0);

  TransformOperations operations_to;
  operations_to.AppendTranslate(10.0, 20.0, 30.0);
  operations_to.AppendScale(4.0, 8.0, 16.0);

  gfx::BoxF box(1.f, 1.f, 1.f);
  gfx::BoxF bounds;

  SkScalar min_progress = 0.f;
  SkScalar max_progress = 1.f;

  EXPECT_FALSE(operations_to.BlendedBoundsForBox(
      box, operations_from, min_progress, max_progress, &bounds));
}

TEST(TransformOperationTest, BlendedBoundsForIdentity) {
  TransformOperations operations_from;
  operations_from.AppendIdentity();
  TransformOperations operations_to;
  operations_to.AppendIdentity();

  gfx::BoxF box(1.f, 2.f, 3.f);
  gfx::BoxF bounds;

  SkScalar min_progress = 0.f;
  SkScalar max_progress = 1.f;

  EXPECT_TRUE(operations_to.BlendedBoundsForBox(
      box, operations_from, min_progress, max_progress, &bounds));
  EXPECT_EQ(box.ToString(), bounds.ToString());
}

TEST(TransformOperationTest, BlendedBoundsForTranslate) {
  TransformOperations operations_from;
  operations_from.AppendTranslate(3.0, -4.0, 2.0);
  TransformOperations operations_to;
  operations_to.AppendTranslate(7.0, 4.0, -2.0);

  gfx::BoxF box(1.f, 2.f, 3.f, 4.f, 4.f, 4.f);
  gfx::BoxF bounds;

  SkScalar min_progress = -0.5f;
  SkScalar max_progress = 1.5f;
  EXPECT_TRUE(operations_to.BlendedBoundsForBox(
      box, operations_from, min_progress, max_progress, &bounds));
  EXPECT_EQ(gfx::BoxF(2.f, -6.f, -1.f, 12.f, 20.f, 12.f).ToString(),
            bounds.ToString());

  min_progress = 0.f;
  max_progress = 1.f;
  EXPECT_TRUE(operations_to.BlendedBoundsForBox(
      box, operations_from, min_progress, max_progress, &bounds));
  EXPECT_EQ(gfx::BoxF(4.f, -2.f, 1.f, 8.f, 12.f, 8.f).ToString(),
            bounds.ToString());

  TransformOperations identity;
  EXPECT_TRUE(operations_to.BlendedBoundsForBox(box, identity, min_progress,
                                                max_progress, &bounds));
  EXPECT_EQ(gfx::BoxF(1.f, 2.f, 1.f, 11.f, 8.f, 6.f).ToString(),
            bounds.ToString());

  EXPECT_TRUE(identity.BlendedBoundsForBox(box, operations_from, min_progress,
                                           max_progress, &bounds));
  EXPECT_EQ(gfx::BoxF(1.f, -2.f, 3.f, 7.f, 8.f, 6.f).ToString(),
            bounds.ToString());
}

TEST(TransformOperationTest, BlendedBoundsForScale) {
  TransformOperations operations_from;
  operations_from.AppendScale(3.0, 0.5, 2.0);
  TransformOperations operations_to;
  operations_to.AppendScale(7.0, 4.0, -2.0);

  gfx::BoxF box(1.f, 2.f, 3.f, 4.f, 4.f, 4.f);
  gfx::BoxF bounds;

  SkScalar min_progress = -0.5f;
  SkScalar max_progress = 1.5f;
  EXPECT_TRUE(operations_to.BlendedBoundsForBox(
      box, operations_from, min_progress, max_progress, &bounds));
  EXPECT_EQ(gfx::BoxF(1.f, -7.5f, -28.f, 44.f, 42.f, 56.f).ToString(),
            bounds.ToString());

  min_progress = 0.f;
  max_progress = 1.f;
  EXPECT_TRUE(operations_to.BlendedBoundsForBox(
      box, operations_from, min_progress, max_progress, &bounds));
  EXPECT_EQ(gfx::BoxF(3.f, 1.f, -14.f, 32.f, 23.f, 28.f).ToString(),
            bounds.ToString());

  TransformOperations identity;
  EXPECT_TRUE(operations_to.BlendedBoundsForBox(box, identity, min_progress,
                                                max_progress, &bounds));
  EXPECT_EQ(gfx::BoxF(1.f, 2.f, -14.f, 34.f, 22.f, 21.f).ToString(),
            bounds.ToString());

  EXPECT_TRUE(identity.BlendedBoundsForBox(box, operations_from, min_progress,
                                           max_progress, &bounds));
  EXPECT_EQ(gfx::BoxF(1.f, 1.f, 3.f, 14.f, 5.f, 11.f).ToString(),
            bounds.ToString());
}

TEST(TransformOperationTest, BlendedBoundsWithZeroScale) {
  TransformOperations zero_scale;
  zero_scale.AppendScale(0.0, 0.0, 0.0);
  TransformOperations non_zero_scale;
  non_zero_scale.AppendScale(2.0, -4.0, 5.0);

  gfx::BoxF box(1.f, 2.f, 3.f, 4.f, 4.f, 4.f);
  gfx::BoxF bounds;

  SkScalar min_progress = 0.f;
  SkScalar max_progress = 1.f;
  EXPECT_TRUE(zero_scale.BlendedBoundsForBox(box, non_zero_scale, min_progress,
                                             max_progress, &bounds));
  EXPECT_EQ(gfx::BoxF(0.f, -24.f, 0.f, 10.f, 24.f, 35.f).ToString(),
            bounds.ToString());

  EXPECT_TRUE(non_zero_scale.BlendedBoundsForBox(box, zero_scale, min_progress,
                                                 max_progress, &bounds));
  EXPECT_EQ(gfx::BoxF(0.f, -24.f, 0.f, 10.f, 24.f, 35.f).ToString(),
            bounds.ToString());

  EXPECT_TRUE(zero_scale.BlendedBoundsForBox(box, zero_scale, min_progress,
                                             max_progress, &bounds));
  EXPECT_EQ(gfx::BoxF().ToString(), bounds.ToString());
}

TEST(TransformOperationTest, BlendedBoundsForRotationTrivial) {
  TransformOperations operations_from;
  operations_from.AppendRotate(0.f, 0.f, 1.f, 0.f);
  TransformOperations operations_to;
  operations_to.AppendRotate(0.f, 0.f, 1.f, 360.f);

  float sqrt_2 = sqrt(2.f);
  gfx::BoxF box(-sqrt_2, -sqrt_2, 0.f, sqrt_2, sqrt_2, 0.f);
  gfx::BoxF bounds;

  // Since we're rotating 360 degrees, any box with dimensions between 0 and
  // 2 * sqrt(2) should give the same result.
  float sizes[] = {0.f, 0.1f, sqrt_2, 2.f * sqrt_2};
  for (float size : sizes) {
    box.set_size(size, size, 0.f);
    SkScalar min_progress = 0.f;
    SkScalar max_progress = 1.f;
    EXPECT_TRUE(operations_to.BlendedBoundsForBox(
        box, operations_from, min_progress, max_progress, &bounds));
    EXPECT_EQ(gfx::BoxF(-2.f, -2.f, 0.f, 4.f, 4.f, 0.f).ToString(),
              bounds.ToString());
  }
}

TEST(TransformOperationTest, BlendedBoundsForRotationAllExtrema) {
  // If the normal is out of the plane, we can have up to 6 extrema (a min/max
  // in each dimension) between the endpoints of the arc. This test ensures that
  // we consider all 6.
  TransformOperations operations_from;
  operations_from.AppendRotate(1.f, 1.f, 1.f, 30.f);
  TransformOperations operations_to;
  operations_to.AppendRotate(1.f, 1.f, 1.f, 390.f);

  gfx::BoxF box(1.f, 0.f, 0.f, 0.f, 0.f, 0.f);
  gfx::BoxF bounds;

  float min = -1.f / 3.f;
  float max = 1.f;
  float size = max - min;
  EXPECT_TRUE(operations_to.BlendedBoundsForBox(box, operations_from, 0.f, 1.f,
                                                &bounds));
  EXPECT_EQ(gfx::BoxF(min, min, min, size, size, size).ToString(),
            bounds.ToString());
}

TEST(TransformOperationTest, BlendedBoundsForRotationDifferentAxes) {
  // We can handle rotations about a single axis. If the axes are different,
  // we revert to matrix interpolation for which inflated bounds cannot be
  // computed.
  TransformOperations operations_from;
  operations_from.AppendRotate(1.f, 1.f, 1.f, 30.f);
  TransformOperations operations_to_same;
  operations_to_same.AppendRotate(1.f, 1.f, 1.f, 390.f);
  TransformOperations operations_to_opposite;
  operations_to_opposite.AppendRotate(-1.f, -1.f, -1.f, 390.f);
  TransformOperations operations_to_different;
  operations_to_different.AppendRotate(1.f, 3.f, 1.f, 390.f);

  gfx::BoxF box(1.f, 0.f, 0.f, 0.f, 0.f, 0.f);
  gfx::BoxF bounds;

  EXPECT_TRUE(operations_to_same.BlendedBoundsForBox(box, operations_from, 0.f,
                                                     1.f, &bounds));
  EXPECT_TRUE(operations_to_opposite.BlendedBoundsForBox(box, operations_from,
                                                         0.f, 1.f, &bounds));
  EXPECT_FALSE(operations_to_different.BlendedBoundsForBox(box, operations_from,
                                                           0.f, 1.f, &bounds));
}

TEST(TransformOperationTest, BlendedBoundsForRotationPointOnAxis) {
  // Checks that if the point to rotate is sitting on the axis of rotation, that
  // it does not get affected.
  TransformOperations operations_from;
  operations_from.AppendRotate(1.f, 1.f, 1.f, 30.f);
  TransformOperations operations_to;
  operations_to.AppendRotate(1.f, 1.f, 1.f, 390.f);

  gfx::BoxF box(1.f, 1.f, 1.f, 0.f, 0.f, 0.f);
  gfx::BoxF bounds;

  EXPECT_TRUE(operations_to.BlendedBoundsForBox(box, operations_from, 0.f, 1.f,
                                                &bounds));
  EXPECT_EQ(box.ToString(), bounds.ToString());
}

TEST(TransformOperationTest, BlendedBoundsForRotationProblematicAxes) {
  // Zeros in the components of the axis of rotation turned out to be tricky to
  // deal with in practice. This function tests some potentially problematic
  // axes to ensure sane behavior.

  // Some common values used in the expected boxes.
  float dim1 = 0.292893f;
  float dim2 = sqrt(2.f);
  float dim3 = 2.f * dim2;

  struct {
    float x;
    float y;
    float z;
    gfx::BoxF expected;
  } tests[] = {{0.f, 0.f, 0.f, gfx::BoxF(1.f, 1.f, 1.f, 0.f, 0.f, 0.f)},
               {1.f, 0.f, 0.f, gfx::BoxF(1.f, -dim2, -dim2, 0.f, dim3, dim3)},
               {0.f, 1.f, 0.f, gfx::BoxF(-dim2, 1.f, -dim2, dim3, 0.f, dim3)},
               {0.f, 0.f, 1.f, gfx::BoxF(-dim2, -dim2, 1.f, dim3, dim3, 0.f)},
               {1.f, 1.f, 0.f, gfx::BoxF(dim1, dim1, -1.f, dim2, dim2, 2.f)},
               {0.f, 1.f, 1.f, gfx::BoxF(-1.f, dim1, dim1, 2.f, dim2, dim2)},
               {1.f, 0.f, 1.f, gfx::BoxF(dim1, -1.f, dim1, dim2, 2.f, dim2)}};

  for (const auto& test : tests) {
    float x = test.x;
    float y = test.y;
    float z = test.z;
    TransformOperations operations_from;
    operations_from.AppendRotate(x, y, z, 0.f);
    TransformOperations operations_to;
    operations_to.AppendRotate(x, y, z, 360.f);
    gfx::BoxF box(1.f, 1.f, 1.f, 0.f, 0.f, 0.f);
    gfx::BoxF bounds;

    EXPECT_TRUE(operations_to.BlendedBoundsForBox(box, operations_from, 0.f,
                                                  1.f, &bounds));
    EXPECT_EQ(test.expected.ToString(), bounds.ToString());
  }
}

static void ExpectBoxesApproximatelyEqual(const gfx::BoxF& lhs,
                                          const gfx::BoxF& rhs,
                                          float tolerance) {
  EXPECT_NEAR(lhs.x(), rhs.x(), tolerance);
  EXPECT_NEAR(lhs.y(), rhs.y(), tolerance);
  EXPECT_NEAR(lhs.z(), rhs.z(), tolerance);
  EXPECT_NEAR(lhs.width(), rhs.width(), tolerance);
  EXPECT_NEAR(lhs.height(), rhs.height(), tolerance);
  EXPECT_NEAR(lhs.depth(), rhs.depth(), tolerance);
}

static void EmpiricallyTestBounds(const TransformOperations& from,
                                  const TransformOperations& to,
                                  SkScalar min_progress,
                                  SkScalar max_progress,
                                  bool test_containment_only) {
  gfx::BoxF box(200.f, 500.f, 100.f, 100.f, 300.f, 200.f);
  gfx::BoxF bounds;
  EXPECT_TRUE(
      to.BlendedBoundsForBox(box, from, min_progress, max_progress, &bounds));

  bool first_time = true;
  gfx::BoxF empirical_bounds;
  static const size_t kNumSteps = 10;
  for (size_t step = 0; step < kNumSteps; ++step) {
    float t = step / (kNumSteps - 1.f);
    t = gfx::Tween::FloatValueBetween(t, min_progress, max_progress);
    gfx::Transform partial_transform = to.Blend(from, t).Apply();
    gfx::BoxF transformed = partial_transform.MapBox(box);

    if (first_time) {
      empirical_bounds = transformed;
      first_time = false;
    } else {
      empirical_bounds.Union(transformed);
    }
  }

  if (test_containment_only) {
    gfx::BoxF unified_bounds = bounds;
    unified_bounds.Union(empirical_bounds);
    // Convert to the screen space rects these boxes represent.
    gfx::Rect bounds_rect = ToEnclosingRect(
        gfx::RectF(bounds.x(), bounds.y(), bounds.width(), bounds.height()));
    gfx::Rect unified_bounds_rect = ToEnclosingRect(
        gfx::RectF(unified_bounds.x(), unified_bounds.y(),
                   unified_bounds.width(), unified_bounds.height()));
    EXPECT_EQ(bounds_rect.ToString(), unified_bounds_rect.ToString());
  } else {
    // Our empirical estimate will be a little rough since we're only doing
    // 100 samples.
    static const float kTolerance = 1e-2f;
    ExpectBoxesApproximatelyEqual(empirical_bounds, bounds, kTolerance);
  }
}

static void EmpiricallyTestBoundsEquality(const TransformOperations& from,
                                          const TransformOperations& to,
                                          SkScalar min_progress,
                                          SkScalar max_progress) {
  EmpiricallyTestBounds(from, to, min_progress, max_progress, false);
}

static void EmpiricallyTestBoundsContainment(const TransformOperations& from,
                                             const TransformOperations& to,
                                             SkScalar min_progress,
                                             SkScalar max_progress) {
  EmpiricallyTestBounds(from, to, min_progress, max_progress, true);
}

TEST(TransformOperationTest, BlendedBoundsForRotationEmpiricalTests) {
  // Sets up various axis angle combinations, computes the bounding box and
  // empirically tests that the transformed bounds are indeed contained by the
  // computed bounding box.

  struct {
    float x;
    float y;
    float z;
  } axes[] = {{1.f, 1.f, 1.f},   {-1.f, -1.f, -1.f}, {-1.f, 2.f, 3.f},
              {1.f, -2.f, 3.f},  {1.f, 2.f, -3.f},   {0.f, 0.f, 0.f},
              {1.f, 0.f, 0.f},   {0.f, 1.f, 0.f},    {0.f, 0.f, 1.f},
              {1.f, 1.f, 0.f},   {0.f, 1.f, 1.f},    {1.f, 0.f, 1.f},
              {-1.f, 0.f, 0.f},  {0.f, -1.f, 0.f},   {0.f, 0.f, -1.f},
              {-1.f, -1.f, 0.f}, {0.f, -1.f, -1.f},  {-1.f, 0.f, -1.f}};

  struct {
    float theta_from;
    float theta_to;
  } angles[] = {{5.f, 10.f},     {10.f, 5.f},     {0.f, 360.f},  {20.f, 180.f},
                {-20.f, -180.f}, {180.f, -220.f}, {220.f, 320.f}};

  // We can go beyond the range [0, 1] (the bezier might slide out of this range
  // at either end), but since the first and last knots are at (0, 0) and (1, 1)
  // we will never go within it, so these tests are sufficient.
  struct {
    float min_progress;
    float max_progress;
  } progresses[] = {
      {0.f, 1.f},
      {-.25f, 1.25f},
  };

  for (const auto& axis : axes) {
    for (const auto& angle : angles) {
      for (const auto& progress : progresses) {
        float x = axis.x;
        float y = axis.y;
        float z = axis.z;
        TransformOperations operations_from;
        operations_from.AppendRotate(x, y, z, angle.theta_from);
        TransformOperations operations_to;
        operations_to.AppendRotate(x, y, z, angle.theta_to);
        EmpiricallyTestBoundsContainment(operations_from, operations_to,
                                         progress.min_progress,
                                         progress.max_progress);
      }
    }
  }
}

TEST(TransformOperationTest, PerspectiveMatrixAndTransformBlendingEquivalency) {
  TransformOperations from_operations;
  from_operations.AppendPerspective(200);

  TransformOperations to_operations;
  to_operations.AppendPerspective(1000);

  gfx::Transform from_transform;
  from_transform.ApplyPerspectiveDepth(200);

  gfx::Transform to_transform;
  to_transform.ApplyPerspectiveDepth(1000);

  static const int steps = 20;
  for (int i = 0; i < steps; ++i) {
    double progress = static_cast<double>(i) / (steps - 1);

    gfx::Transform blended_matrix = to_transform;
    EXPECT_TRUE(blended_matrix.Blend(from_transform, progress));

    gfx::Transform blended_transform =
        to_operations.Blend(from_operations, progress).Apply();

    EXPECT_TRANSFORM_EQ(blended_matrix, blended_transform);
  }
}

TEST(TransformOperationTest, BlendedBoundsForPerspective) {
  struct {
    float from_depth;
    float to_depth;
  } perspective_depths[] = {
      {600.f, 400.f},
      {800.f, 1000.f},
      {800.f, std::numeric_limits<float>::infinity()},
  };

  struct {
    float min_progress;
    float max_progress;
  } progresses[] = {
      {0.f, 1.f},
      {-0.1f, 1.1f},
  };

  for (const auto& perspective_depth : perspective_depths) {
    for (const auto& progress : progresses) {
      TransformOperations operations_from;
      operations_from.AppendPerspective(perspective_depth.from_depth);
      TransformOperations operations_to;
      operations_to.AppendPerspective(perspective_depth.to_depth);
      EmpiricallyTestBoundsEquality(operations_from, operations_to,
                                    progress.min_progress,
                                    progress.max_progress);
    }
  }
}

TEST(TransformOperationTest, BlendedBoundsForSkew) {
  struct {
    float from_x;
    float from_y;
    float to_x;
    float to_y;
  } skews[] = {
      {1.f, 0.5f, 0.5f, 1.f},
      {2.f, 1.f, 0.5f, 0.5f},
  };

  struct {
    float min_progress;
    float max_progress;
  } progresses[] = {
      {0.f, 1.f},
      {-0.1f, 1.1f},
  };

  for (const auto& skew : skews) {
    for (const auto& progress : progresses) {
      TransformOperations operations_from;
      operations_from.AppendSkew(skew.from_x, skew.from_y);
      TransformOperations operations_to;
      operations_to.AppendSkew(skew.to_x, skew.to_y);
      EmpiricallyTestBoundsEquality(operations_from, operations_to,
                                    progress.min_progress,
                                    progress.max_progress);
    }
  }
}

TEST(TransformOperationTest, NonCommutativeRotations) {
  TransformOperations operations_from;
  operations_from.AppendRotate(1.0, 0.0, 0.0, 0.0);
  operations_from.AppendRotate(0.0, 1.0, 0.0, 0.0);
  TransformOperations operations_to;
  operations_to.AppendRotate(1.0, 0.0, 0.0, 45.0);
  operations_to.AppendRotate(0.0, 1.0, 0.0, 135.0);

  gfx::BoxF box(0, 0, 0, 1, 1, 1);
  gfx::BoxF bounds;

  SkScalar min_progress = 0.0f;
  SkScalar max_progress = 1.0f;
  EXPECT_TRUE(operations_to.BlendedBoundsForBox(
      box, operations_from, min_progress, max_progress, &bounds));
  gfx::Transform blended_transform =
      operations_to.Blend(operations_from, max_progress).Apply();
  gfx::Point3F blended_point(0.9f, 0.9f, 0.0f);
  blended_point = blended_transform.MapPoint(blended_point);
  gfx::BoxF expanded_bounds = bounds;
  expanded_bounds.ExpandTo(blended_point);
  EXPECT_EQ(bounds.ToString(), expanded_bounds.ToString());
}

TEST(TransformOperationTest, BlendedBoundsForSequence) {
  TransformOperations operations_from;
  operations_from.AppendTranslate(1.0, -5.0, 1.0);
  operations_from.AppendScale(-1.0, 2.0, 3.0);
  operations_from.AppendTranslate(2.0, 4.0, -1.0);
  TransformOperations operations_to;
  operations_to.AppendTranslate(13.0, -1.0, 5.0);
  operations_to.AppendScale(-3.0, -2.0, 5.0);
  operations_to.AppendTranslate(6.0, -2.0, 3.0);

  gfx::BoxF box(1.f, 2.f, 3.f, 4.f, 4.f, 4.f);
  gfx::BoxF bounds;

  SkScalar min_progress = -0.5f;
  SkScalar max_progress = 1.5f;
  EXPECT_TRUE(operations_to.BlendedBoundsForBox(
      box, operations_from, min_progress, max_progress, &bounds));
  EXPECT_EQ(gfx::BoxF(-57.f, -59.f, -1.f, 76.f, 112.f, 80.f).ToString(),
            bounds.ToString());

  min_progress = 0.f;
  max_progress = 1.f;
  EXPECT_TRUE(operations_to.BlendedBoundsForBox(
      box, operations_from, min_progress, max_progress, &bounds));
  EXPECT_EQ(gfx::BoxF(-32.f, -25.f, 7.f, 42.f, 44.f, 48.f).ToString(),
            bounds.ToString());

  TransformOperations identity;
  EXPECT_TRUE(operations_to.BlendedBoundsForBox(box, identity, min_progress,
                                                max_progress, &bounds));
  EXPECT_EQ(gfx::BoxF(-33.f, -13.f, 3.f, 57.f, 19.f, 52.f).ToString(),
            bounds.ToString());

  EXPECT_TRUE(identity.BlendedBoundsForBox(box, operations_from, min_progress,
                                           max_progress, &bounds));
  EXPECT_EQ(gfx::BoxF(-7.f, -3.f, 2.f, 15.f, 23.f, 20.f).ToString(),
            bounds.ToString());
}

TEST(TransformOperationTest, IsTranslationWithSingleOperation) {
  TransformOperations empty_operations;
  EXPECT_TRUE(empty_operations.IsTranslation());

  TransformOperations identity;
  identity.AppendIdentity();
  EXPECT_TRUE(identity.IsTranslation());

  TransformOperations translate;
  translate.AppendTranslate(1.f, 2.f, 3.f);
  EXPECT_TRUE(translate.IsTranslation());

  TransformOperations rotate;
  rotate.AppendRotate(1.f, 2.f, 3.f, 4.f);
  EXPECT_FALSE(rotate.IsTranslation());

  TransformOperations scale;
  scale.AppendScale(1.f, 2.f, 3.f);
  EXPECT_FALSE(scale.IsTranslation());

  TransformOperations skew;
  skew.AppendSkew(1.f, 2.f);
  EXPECT_FALSE(skew.IsTranslation());

  TransformOperations perspective;
  perspective.AppendPerspective(1.f);
  EXPECT_FALSE(perspective.IsTranslation());

  TransformOperations identity_matrix;
  identity_matrix.AppendMatrix(gfx::Transform());
  EXPECT_TRUE(identity_matrix.IsTranslation());

  TransformOperations translation_matrix;
  gfx::Transform translation_transform;
  translation_transform.Translate3d(1.f, 2.f, 3.f);
  translation_matrix.AppendMatrix(translation_transform);
  EXPECT_TRUE(translation_matrix.IsTranslation());

  TransformOperations scaling_matrix;
  gfx::Transform scaling_transform;
  scaling_transform.Scale(2.f, 2.f);
  scaling_matrix.AppendMatrix(scaling_transform);
  EXPECT_FALSE(scaling_matrix.IsTranslation());
}

TEST(TransformOperationTest, IsTranslationWithMultipleOperations) {
  TransformOperations operations1;
  operations1.AppendSkew(1.f, 2.f);
  operations1.AppendTranslate(1.f, 2.f, 3.f);
  operations1.AppendIdentity();
  EXPECT_FALSE(operations1.IsTranslation());

  TransformOperations operations2;
  operations2.AppendIdentity();
  operations2.AppendTranslate(3.f, 2.f, 1.f);
  gfx::Transform translation_transform;
  translation_transform.Translate3d(1.f, 2.f, 3.f);
  operations2.AppendMatrix(translation_transform);
  EXPECT_TRUE(operations2.IsTranslation());
}

TEST(TransformOperationTest, ScaleComponent) {
  SkScalar scale;

  // Scale.
  TransformOperations operations1;
  operations1.AppendScale(-3.f, 2.f, 5.f);
  EXPECT_TRUE(operations1.ScaleComponent(&scale));
  EXPECT_EQ(5.f, scale);

  // Translate.
  TransformOperations operations2;
  operations2.AppendTranslate(1.f, 2.f, 3.f);
  EXPECT_TRUE(operations2.ScaleComponent(&scale));
  EXPECT_EQ(1.f, scale);

  // Rotate.
  TransformOperations operations3;
  operations3.AppendRotate(1.f, 2.f, 3.f, 4.f);
  EXPECT_TRUE(operations3.ScaleComponent(&scale));
  EXPECT_EQ(1.f, scale);

  // Matrix that's only a translation.
  TransformOperations operations4;
  gfx::Transform translation_transform;
  translation_transform.Translate3d(1.f, 2.f, 3.f);
  operations4.AppendMatrix(translation_transform);
  EXPECT_TRUE(operations4.ScaleComponent(&scale));
  EXPECT_EQ(1.f, scale);

  // Matrix that includes scale.
  TransformOperations operations5;
  gfx::Transform matrix;
  matrix.RotateAboutZAxis(30.0);
  matrix.Scale(-7.f, 6.f);
  matrix.Translate3d(gfx::Vector3dF(3.f, 7.f, 1.f));
  operations5.AppendMatrix(matrix);
  EXPECT_TRUE(operations5.ScaleComponent(&scale));
  EXPECT_EQ(7.f, scale);

  // Matrix with perspective.
  TransformOperations operations6;
  matrix.ApplyPerspectiveDepth(2000.f);
  operations6.AppendMatrix(matrix);
  EXPECT_FALSE(operations6.ScaleComponent(&scale));

  // Skew.
  TransformOperations operations7;
  operations7.AppendSkew(30.f, 60.f);
  EXPECT_TRUE(operations7.ScaleComponent(&scale));
  EXPECT_EQ(2.f, scale);

  // Perspective.
  TransformOperations operations8;
  operations8.AppendPerspective(500.f);
  EXPECT_FALSE(operations8.ScaleComponent(&scale));

  // Translate + Scale.
  TransformOperations operations9;
  operations9.AppendTranslate(1.f, 2.f, 3.f);
  operations9.AppendScale(2.f, 5.f, 4.f);
  EXPECT_TRUE(operations9.ScaleComponent(&scale));
  EXPECT_EQ(5.f, scale);

  // Translate + Scale + Matrix with translate.
  operations9.AppendMatrix(translation_transform);
  EXPECT_TRUE(operations9.ScaleComponent(&scale));
  EXPECT_EQ(5.f, scale);

  // Scale + translate.
  TransformOperations operations10;
  operations10.AppendScale(2.f, 3.f, 2.f);
  operations10.AppendTranslate(1.f, 2.f, 3.f);
  EXPECT_TRUE(operations10.ScaleComponent(&scale));
  EXPECT_EQ(3.f, scale);

  // Two Scales.
  TransformOperations operations11;
  operations11.AppendScale(2.f, 3.f, 2.f);
  operations11.AppendScale(-3.f, -2.f, -3.f);
  EXPECT_TRUE(operations11.ScaleComponent(&scale));
  EXPECT_EQ(9.f, scale);

  // Scale + Matrix.
  TransformOperations operations12;
  operations12.AppendScale(2.f, 2.f, 2.f);
  gfx::Transform scaling_transform;
  scaling_transform.Scale(2.f, 2.f);
  operations12.AppendMatrix(scaling_transform);
  EXPECT_TRUE(operations12.ScaleComponent(&scale));
  EXPECT_EQ(4.f, scale);

  // Scale + Rotate.
  TransformOperations operations13;
  operations13.AppendScale(2.f, 2.f, 2.f);
  operations13.AppendRotate(1.f, 2.f, 3.f, 4.f);
  EXPECT_TRUE(operations13.ScaleComponent(&scale));
  EXPECT_EQ(2.f, scale);

  // Scale + Skew.
  TransformOperations operations14;
  operations14.AppendScale(2.f, 2.f, 2.f);
  operations14.AppendSkew(60.f, 45.f);
  EXPECT_TRUE(operations14.ScaleComponent(&scale));
  EXPECT_EQ(4.f, scale);

  // Scale + Perspective.
  TransformOperations operations15;
  operations15.AppendScale(2.f, 2.f, 2.f);
  operations15.AppendPerspective(1.f);
  EXPECT_FALSE(operations15.ScaleComponent(&scale));

  // Matrix with skew.
  TransformOperations operations16;
  gfx::Transform skew_transform;
  skew_transform.Skew(50.f, 60.f);
  operations16.AppendMatrix(skew_transform);
  EXPECT_TRUE(operations16.ScaleComponent(&scale));
  EXPECT_EQ(2.f, scale);
}

TEST(TransformOperationsTest, ApproximateEquality) {
  float noise = 1e-7f;
  float tolerance = 1e-5f;
  TransformOperations lhs;
  TransformOperations rhs;

  // Empty lists of operations are trivially equal.
  EXPECT_TRUE(lhs.ApproximatelyEqual(rhs, tolerance));

  rhs.AppendIdentity();
  rhs.AppendTranslate(0, 0, 0);
  rhs.AppendRotate(1, 0, 0, 0);
  rhs.AppendScale(1, 1, 1);
  rhs.AppendSkew(0, 0);
  rhs.AppendMatrix(gfx::Transform());

  // Even though both lists operations are effectively the identity matrix, rhs
  // has a different number of operations and is therefore different.
  EXPECT_FALSE(lhs.ApproximatelyEqual(rhs, tolerance));

  rhs.AppendPerspective(800);

  // Assignment should produce equal lists of operations.
  lhs = rhs;
  EXPECT_TRUE(lhs.ApproximatelyEqual(rhs, tolerance));

  // Cannot affect identity operations.
  lhs.at(0).translate.x = 1;
  EXPECT_TRUE(lhs.ApproximatelyEqual(rhs, tolerance));

  lhs.at(1).translate.x += noise;
  EXPECT_TRUE(lhs.ApproximatelyEqual(rhs, tolerance));
  lhs.at(1).translate.x += 1;
  EXPECT_FALSE(lhs.ApproximatelyEqual(rhs, tolerance));

  lhs = rhs;
  lhs.at(2).rotate.angle += noise;
  EXPECT_TRUE(lhs.ApproximatelyEqual(rhs, tolerance));
  lhs.at(2).rotate.angle = 1;
  EXPECT_FALSE(lhs.ApproximatelyEqual(rhs, tolerance));

  lhs = rhs;
  lhs.at(3).scale.x += noise;
  EXPECT_TRUE(lhs.ApproximatelyEqual(rhs, tolerance));
  lhs.at(3).scale.x += 1;
  EXPECT_FALSE(lhs.ApproximatelyEqual(rhs, tolerance));

  lhs = rhs;
  lhs.at(4).skew.x += noise;
  EXPECT_TRUE(lhs.ApproximatelyEqual(rhs, tolerance));
  lhs.at(4).skew.x = 2;
  EXPECT_FALSE(lhs.ApproximatelyEqual(rhs, tolerance));

  lhs = rhs;
  lhs.at(5).matrix.Translate3d(noise, 0, 0);
  EXPECT_TRUE(lhs.ApproximatelyEqual(rhs, tolerance));
  lhs.at(5).matrix.Translate3d(1, 1, 1);
  EXPECT_FALSE(lhs.ApproximatelyEqual(rhs, tolerance));

  lhs = rhs;
  lhs.at(6).perspective_m43 += noise;
  EXPECT_TRUE(lhs.ApproximatelyEqual(rhs, tolerance));
  lhs.at(6).perspective_m43 = -1.0f / 810;
  EXPECT_FALSE(lhs.ApproximatelyEqual(rhs, tolerance));
}

}  // namespace

// This test is intentionally outside the anonymous namespace for visibility as
// it needs to be friend of TransformOperations.
TEST(TransformOperationsTest, TestDecompositionCache) {
  TransformOperations transforms;
  EXPECT_EQ(0UL, transforms.decomposed_transforms_.size());
  EXPECT_TRUE(transforms.ComputeDecomposedTransform(0));
  EXPECT_EQ(1UL, transforms.decomposed_transforms_.size());

  // Reset cache when appending a scale transform.
  transforms.AppendScale(2.f, 2.f, 2.f);
  EXPECT_EQ(0UL, transforms.decomposed_transforms_.size());
  EXPECT_TRUE(transforms.ComputeDecomposedTransform(1));
  EXPECT_EQ(1UL, transforms.decomposed_transforms_.size());
  EXPECT_TRUE(transforms.ComputeDecomposedTransform(1));
  EXPECT_EQ(1UL, transforms.decomposed_transforms_.size());
  EXPECT_TRUE(transforms.ComputeDecomposedTransform(0));
  EXPECT_EQ(2UL, transforms.decomposed_transforms_.size());

  // Reset cache when appending a rotation transform.
  transforms.AppendRotate(1, 0, 0, 45);
  EXPECT_EQ(0UL, transforms.decomposed_transforms_.size());
  EXPECT_TRUE(transforms.ComputeDecomposedTransform(0));
  EXPECT_EQ(1UL, transforms.decomposed_transforms_.size());

  // Reset cache when appending a translation transform.
  transforms.AppendTranslate(1, 1, 1);
  EXPECT_EQ(0UL, transforms.decomposed_transforms_.size());
  EXPECT_TRUE(transforms.ComputeDecomposedTransform(0));
  EXPECT_EQ(1UL, transforms.decomposed_transforms_.size());

  // Reset cache when appending a skew transform.
  transforms.AppendSkew(1, 0);
  EXPECT_EQ(0UL, transforms.decomposed_transforms_.size());
  EXPECT_TRUE(transforms.ComputeDecomposedTransform(0));
  EXPECT_EQ(1UL, transforms.decomposed_transforms_.size());

  // Reset cache when appending a perspective transform.
  transforms.AppendPerspective(800);
  EXPECT_EQ(0UL, transforms.decomposed_transforms_.size());
  EXPECT_TRUE(transforms.ComputeDecomposedTransform(0));
  EXPECT_EQ(1UL, transforms.decomposed_transforms_.size());

  // Reset cache when appending a matrix transform.
  transforms.AppendMatrix(gfx::Transform());
  EXPECT_EQ(0UL, transforms.decomposed_transforms_.size());
  EXPECT_TRUE(transforms.ComputeDecomposedTransform(0));
  EXPECT_EQ(1UL, transforms.decomposed_transforms_.size());

  // Reset cache when appending a generic transform operation.
  transforms.Append(TransformOperation());
  EXPECT_EQ(0UL, transforms.decomposed_transforms_.size());
  EXPECT_TRUE(transforms.ComputeDecomposedTransform(0));
  EXPECT_EQ(1UL, transforms.decomposed_transforms_.size());
}

TEST(TransformOperationTest, BlendSkewMismatch) {
  TransformOperations from_ops, to_ops, expected_ops;
  from_ops.AppendSkewX(0);
  from_ops.AppendRotate(0, 0, 1, 0);
  to_ops.AppendSkewY(0);
  to_ops.AppendRotate(0, 0, 1, 360);

  // Skew types do not match so use matrix interpolation
  expected_ops.AppendMatrix(gfx::Transform());

  TransformOperations blended_ops = to_ops.Blend(from_ops, 0.5);
  ASSERT_EQ(blended_ops.size(), 1u);
  ExpectTransformOperationEqual(blended_ops.at(0), expected_ops.at(0));
}

TEST(TransformOperationTest, BlendSkewMatch) {
  TransformOperations from_ops, to_ops, expected_ops;
  from_ops.AppendSkew(30, 0);
  from_ops.AppendRotate(0, 0, 1, 0);
  to_ops.AppendSkew(0, 30);
  to_ops.AppendRotate(0, 0, 1, 360);

  // Skew types match so interpolate as a function.
  expected_ops.AppendSkew(15, 15);
  expected_ops.AppendRotate(0, 0, 1, 180);

  TransformOperations blended_ops = to_ops.Blend(from_ops, 0.5);
  ASSERT_EQ(blended_ops.size(), 2u);
  ExpectTransformOperationEqual(blended_ops.at(0), expected_ops.at(0));
  ExpectTransformOperationEqual(blended_ops.at(1), expected_ops.at(1));
}

TEST(TransformOperationsTest, Rotate360IsNotIdentityOperation) {
  TransformOperations operations;
  operations.AppendRotate(0, 0, 2, 360);
  EXPECT_FALSE(operations.IsIdentity());
  EXPECT_TRUE(operations.Apply().IsIdentity());
}

}  // namespace gfx