// Copyright 2017 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 "content/renderer/media/media_stream_constraints_util_sets.h"

#include <cmath>
#include <string>
#include <vector>

#include "content/renderer/media/mock_constraint_factory.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {

using Point = ResolutionSet::Point;

namespace {

const int kDefaultWidth = 640;
const int kDefaultHeight = 480;
constexpr double kDefaultAspectRatio =
    static_cast<double>(kDefaultWidth) / static_cast<double>(kDefaultHeight);

// Defined as macro in order to get more informative line-number information
// when a test fails.
#define EXPECT_POINT_EQ(p1, p2)                     \
  do {                                              \
    EXPECT_DOUBLE_EQ((p1).height(), (p2).height()); \
    EXPECT_DOUBLE_EQ((p1).width(), (p2).width());   \
  } while (0)

// Checks if |point| is an element of |vertices| using
// Point::IsApproximatelyEqualTo() to test for equality.
void VerticesContain(const std::vector<Point>& vertices, const Point& point) {
  bool result = false;
  for (const auto& vertex : vertices) {
    if (point.IsApproximatelyEqualTo(vertex)) {
      result = true;
      break;
    }
  }
  EXPECT_TRUE(result);
}

bool AreCounterclockwise(const std::vector<Point>& vertices) {
  // Single point or segment are trivial cases.
  if (vertices.size() <= 2)
    return true;
  else if (vertices.size() > 6)  // Polygons of more than 6 sides are not valid.
    return false;

  // The polygon defined by a resolution set is always convex and has at most 6
  // sides. When producing a list of the vertices for such a polygon, it is
  // important that they are returned in counterclockwise (or clockwise) order,
  // to make sure that any consecutive pair of vertices (modulo the number of
  // vertices) corresponds to a polygon side. Our implementation uses
  // counterclockwise order.
  // Compute orientation using the determinant of each diagonal in the
  // polygon, using the first vertex as reference.
  Point prev_diagonal = vertices[1] - vertices[0];
  for (auto vertex = vertices.begin() + 2; vertex != vertices.end(); ++vertex) {
    Point current_diagonal = *vertex - vertices[0];
    // The determinant of the two diagonals returns the signed area of the
    // parallelogram they generate. The area is positive if the diagonals are in
    // counterclockwise order, zero if the diagonals have the same direction and
    // negative if the diagonals are in clockwise order.
    // See https://en.wikipedia.org/wiki/Determinant#2_.C3.97_2_matrices.
    double det = prev_diagonal.height() * current_diagonal.width() -
                 current_diagonal.height() * prev_diagonal.width();
    if (det <= 0)
      return false;
    prev_diagonal = current_diagonal;
  }
  return true;
}

// Determines if |vertices| is valid according to the contract for
// ResolutionCandidateSet::ComputeVertices().
bool AreValidVertices(const ResolutionSet& set,
                      const std::vector<Point>& vertices) {
  // Verify that every vertex is included in |set|.
  for (const auto& vertex : vertices) {
    if (!set.ContainsPoint(vertex))
      return false;
  }

  return AreCounterclockwise(vertices);
}

// This function provides an alternative method for computing the projection
// of |point| on the line of segment |s1||s2| to be used to compare the results
// provided by Point::ClosestPointInSegment(). Since it relies on library
// functions, it has larger error in practice than
// Point::ClosestPointInSegment(), so results must be compared with
// Point::IsApproximatelyEqualTo().
// This function only computes projections. The result may be outside the
// segment |s1||s2|.
Point ProjectionOnSegmentLine(const Point& point,
                              const Point& s1,
                              const Point& s2) {
  double segment_slope =
      (s2.width() - s1.width()) / (s2.height() - s1.height());
  double segment_angle = std::atan(segment_slope);
  double norm = std::sqrt(Point::Dot(point, point));
  double angle =
      (point.height() == 0 && point.width() == 0)
          ? 0.0
          : std::atan(point.width() / point.height()) - segment_angle;
  double projection_length = norm * std::cos(angle);
  double projection_height = projection_length * std::cos(segment_angle);
  double projection_width = projection_length * std::sin(segment_angle);
  return Point(projection_height, projection_width);
}

}  // namespace

class MediaStreamConstraintsUtilSetsTest : public testing::Test {
 protected:
  using P = Point;

  Point SelectClosestPointToIdeal(const ResolutionSet& set) {
    return set.SelectClosestPointToIdeal(
        factory_.CreateWebMediaConstraints().Basic(), kDefaultHeight,
        kDefaultWidth);
  }

  MockConstraintFactory factory_;
};

// This test tests the test-harness function AreValidVertices.
TEST_F(MediaStreamConstraintsUtilSetsTest, VertexListValidity) {
  EXPECT_TRUE(AreCounterclockwise({P(1, 1)}));
  EXPECT_TRUE(AreCounterclockwise({P(1, 1)}));
  EXPECT_TRUE(AreCounterclockwise({P(1, 0), P(0, 1)}));
  EXPECT_TRUE(AreCounterclockwise({P(1, 1), P(0, 0), P(1, 0)}));

  // Not in counterclockwise order.
  EXPECT_FALSE(AreCounterclockwise({P(1, 0), P(0, 0), P(1, 1)}));

  // Final vertex aligned with the previous two vertices.
  EXPECT_FALSE(AreCounterclockwise({P(1, 0), P(1, 1), P(1, 1.5), P(1, 0.1)}));

  // Not in counterclockwise order.
  EXPECT_FALSE(
      AreCounterclockwise({P(1, 0), P(3, 0), P(2, 2), P(3.1, 1), P(0, 1)}));

  EXPECT_TRUE(AreCounterclockwise(
      {P(1, 0), P(3, 0), P(3.1, 1), P(3, 2), P(1, 2), P(0.9, 1)}));

  // Not in counterclockwise order.
  EXPECT_FALSE(AreCounterclockwise(
      {P(1, 0), P(3, 0), P(3.1, 1), P(1, 2), P(3, 2), P(0.9, 1)}));

  // Counterclockwise, but more than 6 vertices.
  EXPECT_FALSE(AreCounterclockwise(
      {P(1, 0), P(3, 0), P(3.1, 1), P(3, 2), P(2, 2.1), P(1, 2), P(0.9, 1)}));
}

TEST_F(MediaStreamConstraintsUtilSetsTest, PointOperations) {
  const Point kZero(0, 0);

  // Basic equality and inequality
  EXPECT_EQ(P(0, 0), kZero);
  EXPECT_EQ(P(50, 50), P(50, 50));
  EXPECT_NE(kZero, P(50, 50));
  EXPECT_NE(P(50, 50), P(100, 100));
  EXPECT_NE(P(50, 50), P(100, 50));

  // Operations with zero.
  EXPECT_EQ(kZero, kZero + kZero);
  EXPECT_EQ(kZero, kZero - kZero);
  EXPECT_EQ(kZero, 0.0 * kZero);
  EXPECT_EQ(0.0, P::Dot(kZero, kZero));
  EXPECT_EQ(0.0, P::SquareEuclideanDistance(kZero, kZero));
  EXPECT_EQ(kZero, P::ClosestPointInSegment(kZero, kZero, kZero));

  // Operations with zero and nonzero values.
  EXPECT_EQ(P(50, 50), kZero + P(50, 50));
  EXPECT_EQ(P(50, 50) + kZero, kZero + P(50, 50));
  EXPECT_EQ(P(50, 50), P(50, 50) - kZero);
  EXPECT_EQ(kZero, P(50, 50) - P(50, 50));
  EXPECT_EQ(kZero, 0.0 * P(50, 50));
  EXPECT_EQ(0.0, P::Dot(kZero, P(50, 50)));
  EXPECT_EQ(0.0, P::Dot(P(50, 50), kZero));
  EXPECT_EQ(5000, P::SquareEuclideanDistance(kZero, P(50, 50)));
  EXPECT_EQ(P::SquareEuclideanDistance(P(50, 50), kZero),
            P::SquareEuclideanDistance(kZero, P(50, 50)));
  EXPECT_EQ(kZero, P::ClosestPointInSegment(kZero, kZero, P(50, 50)));
  EXPECT_EQ(kZero, P::ClosestPointInSegment(kZero, P(50, 50), kZero));
  EXPECT_EQ(P(50, 50),
            P::ClosestPointInSegment(P(50, 50), P(50, 50), P(50, 50)));

  // Operations with nonzero values.
  // Additions.
  EXPECT_EQ(P(100, 50), P(50, 50) + P(50, 0));
  EXPECT_EQ(P(100, 50), P(50, 0) + P(50, 50));

  // Substractions.
  EXPECT_EQ(P(50, 50), P(100, 100) - P(50, 50));
  EXPECT_EQ(P(50, 50), P(100, 50) - P(50, 0));
  EXPECT_EQ(P(50, 0), P(100, 50) - P(50, 50));

  // Scalar-vector products.
  EXPECT_EQ(P(50, 50), 1.0 * P(50, 50));
  EXPECT_EQ(P(75, 75), 1.5 * P(50, 50));
  EXPECT_EQ(P(200, 100), 2.0 * P(100, 50));
  EXPECT_EQ(2.0 * (P(100, 100) + P(100, 50)),
            2.0 * P(100, 100) + 2.0 * P(100, 50));

  // Dot products.
  EXPECT_EQ(2 * 50 * 100, P::Dot(P(50, 50), P(100, 100)));
  EXPECT_EQ(P::Dot(P(100, 100), P(50, 50)), P::Dot(P(50, 50), P(100, 100)));
  EXPECT_EQ(0, P::Dot(P(100, 0), P(0, 100)));

  // Distances.
  EXPECT_EQ(25, P::SquareEuclideanDistance(P(4, 0), P(0, 3)));
  EXPECT_EQ(75 * 75, P::SquareEuclideanDistance(P(100, 0), P(25, 0)));
  EXPECT_EQ(75 * 75, P::SquareEuclideanDistance(P(0, 100), P(0, 25)));
  EXPECT_EQ(5 * 5 + 9 * 9, P::SquareEuclideanDistance(P(5, 1), P(10, 10)));

  // Closest point to segment from (10,0) to (50,0).
  EXPECT_EQ(P(25, 0), P::ClosestPointInSegment(P(25, 25), P(10, 0), P(50, 0)));
  EXPECT_EQ(P(50, 0),
            P::ClosestPointInSegment(P(100, 100), P(10, 0), P(50, 0)));
  EXPECT_EQ(P(10, 0), P::ClosestPointInSegment(P(0, 100), P(10, 0), P(50, 0)));

  // Closest point to segment from (0,10) to (0,50).
  EXPECT_EQ(P(0, 25), P::ClosestPointInSegment(P(25, 25), P(0, 10), P(0, 50)));
  EXPECT_EQ(P(0, 50),
            P::ClosestPointInSegment(P(100, 100), P(0, 10), P(0, 50)));
  EXPECT_EQ(P(0, 10), P::ClosestPointInSegment(P(100, 0), P(0, 10), P(0, 50)));

  // Closest point to segment from (0,10) to (10,0).
  EXPECT_EQ(P(5, 5), P::ClosestPointInSegment(P(25, 25), P(0, 10), P(10, 0)));
  EXPECT_EQ(P(5, 5), P::ClosestPointInSegment(P(100, 100), P(0, 10), P(10, 0)));
  EXPECT_EQ(P(10, 0), P::ClosestPointInSegment(P(100, 0), P(0, 10), P(10, 0)));
  EXPECT_EQ(P(0, 10), P::ClosestPointInSegment(P(0, 100), P(0, 10), P(10, 0)));
}

TEST_F(MediaStreamConstraintsUtilSetsTest, ResolutionUnconstrained) {
  ResolutionSet set;
  EXPECT_TRUE(set.ContainsPoint(0, 0));
  EXPECT_TRUE(set.ContainsPoint(1, 1));
  EXPECT_TRUE(set.ContainsPoint(2000, 2000));
  EXPECT_FALSE(set.IsHeightEmpty());
  EXPECT_FALSE(set.IsWidthEmpty());
  EXPECT_FALSE(set.IsAspectRatioEmpty());
}

TEST_F(MediaStreamConstraintsUtilSetsTest, ResolutionConstrained) {
  ResolutionSet set = ResolutionSet::FromHeight(10, 100);
  EXPECT_FALSE(set.ContainsPoint(0, 0));
  EXPECT_TRUE(set.ContainsPoint(10, 10));
  EXPECT_TRUE(set.ContainsPoint(50, 50));
  EXPECT_TRUE(set.ContainsPoint(100, 100));
  EXPECT_FALSE(set.ContainsPoint(500, 500));
  EXPECT_FALSE(set.IsHeightEmpty());
  EXPECT_FALSE(set.IsWidthEmpty());
  EXPECT_FALSE(set.IsAspectRatioEmpty());

  set = ResolutionSet::FromHeight(0, 100);
  EXPECT_TRUE(set.ContainsPoint(0, 0));
  EXPECT_TRUE(set.ContainsPoint(10, 10));
  EXPECT_TRUE(set.ContainsPoint(50, 50));
  EXPECT_TRUE(set.ContainsPoint(100, 100));
  EXPECT_FALSE(set.ContainsPoint(500, 500));
  EXPECT_FALSE(set.IsHeightEmpty());
  EXPECT_FALSE(set.IsWidthEmpty());
  EXPECT_FALSE(set.IsAspectRatioEmpty());

  set = ResolutionSet::FromHeight(100, ResolutionSet::kMaxDimension);
  EXPECT_FALSE(set.ContainsPoint(0, 0));
  EXPECT_FALSE(set.ContainsPoint(10, 10));
  EXPECT_FALSE(set.ContainsPoint(50, 50));
  EXPECT_TRUE(set.ContainsPoint(100, 100));
  EXPECT_TRUE(set.ContainsPoint(500, 500));
  EXPECT_FALSE(set.IsHeightEmpty());
  EXPECT_FALSE(set.IsWidthEmpty());
  EXPECT_FALSE(set.IsAspectRatioEmpty());

  set = ResolutionSet::FromWidth(10, 100);
  EXPECT_FALSE(set.ContainsPoint(0, 0));
  EXPECT_TRUE(set.ContainsPoint(10, 10));
  EXPECT_TRUE(set.ContainsPoint(50, 50));
  EXPECT_TRUE(set.ContainsPoint(100, 100));
  EXPECT_FALSE(set.ContainsPoint(500, 500));
  EXPECT_FALSE(set.IsHeightEmpty());
  EXPECT_FALSE(set.IsWidthEmpty());
  EXPECT_FALSE(set.IsAspectRatioEmpty());

  set = ResolutionSet::FromWidth(0, 100);
  EXPECT_TRUE(set.ContainsPoint(0, 0));
  EXPECT_TRUE(set.ContainsPoint(10, 10));
  EXPECT_TRUE(set.ContainsPoint(50, 50));
  EXPECT_TRUE(set.ContainsPoint(100, 100));
  EXPECT_FALSE(set.ContainsPoint(500, 500));
  EXPECT_FALSE(set.IsHeightEmpty());
  EXPECT_FALSE(set.IsWidthEmpty());
  EXPECT_FALSE(set.IsAspectRatioEmpty());

  set = ResolutionSet::FromWidth(100, ResolutionSet::kMaxDimension);
  EXPECT_FALSE(set.ContainsPoint(0, 0));
  EXPECT_FALSE(set.ContainsPoint(10, 10));
  EXPECT_FALSE(set.ContainsPoint(50, 50));
  EXPECT_TRUE(set.ContainsPoint(100, 100));
  EXPECT_TRUE(set.ContainsPoint(500, 500));
  EXPECT_FALSE(set.IsHeightEmpty());
  EXPECT_FALSE(set.IsWidthEmpty());
  EXPECT_FALSE(set.IsAspectRatioEmpty());

  set = ResolutionSet::FromAspectRatio(1.0, 2.0);
  EXPECT_TRUE(set.ContainsPoint(0, 0));
  EXPECT_TRUE(set.ContainsPoint(10, 10));
  EXPECT_TRUE(set.ContainsPoint(10, 20));
  EXPECT_TRUE(set.ContainsPoint(100, 100));
  EXPECT_TRUE(set.ContainsPoint(2000, 4000));
  EXPECT_FALSE(set.ContainsPoint(1, 50));
  EXPECT_FALSE(set.ContainsPoint(50, 1));
  EXPECT_FALSE(set.IsHeightEmpty());
  EXPECT_FALSE(set.IsWidthEmpty());
  EXPECT_FALSE(set.IsAspectRatioEmpty());

  set = ResolutionSet::FromAspectRatio(0.0, 2.0);
  EXPECT_TRUE(set.ContainsPoint(0, 0));
  EXPECT_TRUE(set.ContainsPoint(10, 10));
  EXPECT_TRUE(set.ContainsPoint(10, 20));
  EXPECT_TRUE(set.ContainsPoint(100, 100));
  EXPECT_TRUE(set.ContainsPoint(2000, 4000));
  EXPECT_FALSE(set.ContainsPoint(1, 50));
  EXPECT_TRUE(set.ContainsPoint(50, 1));
  EXPECT_FALSE(set.IsHeightEmpty());
  EXPECT_FALSE(set.IsWidthEmpty());
  EXPECT_FALSE(set.IsAspectRatioEmpty());

  set = ResolutionSet::FromAspectRatio(1.0, HUGE_VAL);
  EXPECT_TRUE(set.ContainsPoint(0, 0));
  EXPECT_TRUE(set.ContainsPoint(10, 10));
  EXPECT_TRUE(set.ContainsPoint(10, 20));
  EXPECT_TRUE(set.ContainsPoint(100, 100));
  EXPECT_TRUE(set.ContainsPoint(2000, 4000));
  EXPECT_TRUE(set.ContainsPoint(1, 50));
  EXPECT_FALSE(set.ContainsPoint(50, 1));
  EXPECT_FALSE(set.IsHeightEmpty());
  EXPECT_FALSE(set.IsWidthEmpty());
  EXPECT_FALSE(set.IsAspectRatioEmpty());
}

TEST_F(MediaStreamConstraintsUtilSetsTest, ResolutionTrivialEmptiness) {
  ResolutionSet set = ResolutionSet::FromHeight(100, 10);
  EXPECT_TRUE(set.IsEmpty());
  EXPECT_TRUE(set.IsHeightEmpty());
  EXPECT_FALSE(set.IsWidthEmpty());
  EXPECT_FALSE(set.IsAspectRatioEmpty());

  set = ResolutionSet::FromWidth(100, 10);
  EXPECT_TRUE(set.IsEmpty());
  EXPECT_FALSE(set.IsHeightEmpty());
  EXPECT_TRUE(set.IsWidthEmpty());
  EXPECT_FALSE(set.IsAspectRatioEmpty());

  set = ResolutionSet::FromAspectRatio(100.0, 10.0);
  EXPECT_TRUE(set.IsEmpty());
  EXPECT_FALSE(set.IsHeightEmpty());
  EXPECT_FALSE(set.IsWidthEmpty());
  EXPECT_TRUE(set.IsAspectRatioEmpty());
}

TEST_F(MediaStreamConstraintsUtilSetsTest, ResolutionLineConstraintsEmptiness) {
  ResolutionSet set(1, 1, 1, 1, 1, 1);
  EXPECT_FALSE(set.IsEmpty());
  EXPECT_FALSE(set.ContainsPoint(0, 0));
  EXPECT_TRUE(set.ContainsPoint(1, 1));
  EXPECT_FALSE(set.ContainsPoint(1, 0));
  EXPECT_FALSE(set.ContainsPoint(0, 1));

  // Three lines that do not intersect in the same point is empty.
  set = ResolutionSet(1, 1, 1, 1, 0.5, 0.5);
  EXPECT_TRUE(set.IsEmpty());
  EXPECT_TRUE(set.IsAspectRatioEmpty());
  EXPECT_FALSE(set.ContainsPoint(0, 0));
  EXPECT_FALSE(set.ContainsPoint(1, 1));
  EXPECT_FALSE(set.ContainsPoint(1, 0));
  EXPECT_FALSE(set.ContainsPoint(0, 1));
}

TEST_F(MediaStreamConstraintsUtilSetsTest, ResolutionBoxEmptiness) {
  const int kMin = 100;
  const int kMax = 200;
  // Max aspect ratio below box.
  ResolutionSet set(kMin, kMax, kMin, kMax, 0.4, 0.4);
  EXPECT_TRUE(set.IsEmpty());
  EXPECT_TRUE(set.IsAspectRatioEmpty());

  // Min aspect ratio above box.
  set = ResolutionSet(kMin, kMax, kMin, kMax, 3.0, HUGE_VAL);
  EXPECT_TRUE(set.IsEmpty());
  EXPECT_TRUE(set.IsAspectRatioEmpty());

  // Min aspect ratio crosses box.
  set = ResolutionSet(kMin, kMax, kMin, kMax, 1.0, HUGE_VAL);
  EXPECT_FALSE(set.IsEmpty());

  // Max aspect ratio crosses box.
  set = ResolutionSet(kMin, kMax, kMin, kMax, 0.0, 1.0);
  EXPECT_FALSE(set.IsEmpty());

  // Min and max aspect ratios cross box.
  set = ResolutionSet(kMin, kMax, kMin, kMax, 0.9, 1.1);
  EXPECT_FALSE(set.IsEmpty());

  // Min and max aspect ratios cover box.
  set = ResolutionSet(kMin, kMax, kMin, kMax, 0.2, 100);
  EXPECT_FALSE(set.IsEmpty());
}

TEST_F(MediaStreamConstraintsUtilSetsTest, ResolutionPointIntersection) {
  ResolutionSet set1(1, 2, 1, 2, 0.0, HUGE_VAL);
  ResolutionSet set2 = ResolutionSet::FromExactAspectRatio(0.5);
  auto intersection = set1.Intersection(set2);

  // The intersection should contain only the point (h=2, w=1)
  EXPECT_TRUE(intersection.ContainsPoint(2, 1));

  // It should not contain any point in the vicinity of the included point
  // (integer version).
  EXPECT_FALSE(intersection.ContainsPoint(1, 0));
  EXPECT_FALSE(intersection.ContainsPoint(2, 0));
  EXPECT_FALSE(intersection.ContainsPoint(3, 0));
  EXPECT_FALSE(intersection.ContainsPoint(1, 1));
  EXPECT_FALSE(intersection.ContainsPoint(3, 1));
  EXPECT_FALSE(intersection.ContainsPoint(1, 2));
  EXPECT_FALSE(intersection.ContainsPoint(2, 2));
  EXPECT_FALSE(intersection.ContainsPoint(3, 2));

  // It should not contain any point in the vicinity of the included point
  // (floating-point version).
  EXPECT_FALSE(intersection.ContainsPoint(P(2.0001, 1.0001)));
  EXPECT_FALSE(intersection.ContainsPoint(P(2.0001, 1.0)));
  EXPECT_FALSE(intersection.ContainsPoint(P(2.0001, 0.9999)));
  EXPECT_FALSE(intersection.ContainsPoint(P(2.0, 1.0001)));
  EXPECT_FALSE(intersection.ContainsPoint(P(2.0, 0.9999)));
  EXPECT_FALSE(intersection.ContainsPoint(P(1.9999, 1.0001)));
  EXPECT_FALSE(intersection.ContainsPoint(P(1.9999, 1.0)));
  EXPECT_FALSE(intersection.ContainsPoint(P(1.9999, 0.9999)));
}

TEST_F(MediaStreamConstraintsUtilSetsTest, ResolutionLineIntersection) {
  ResolutionSet set1(1, 2, 1, 2, 0.0, HUGE_VAL);
  ResolutionSet set2 = ResolutionSet::FromExactAspectRatio(1.0);

  // The intersection should contain (1,1) and (2,2)
  auto intersection = set1.Intersection(set2);
  EXPECT_TRUE(intersection.ContainsPoint(1, 1));
  EXPECT_TRUE(intersection.ContainsPoint(2, 2));

  // It should not contain the other points in the bounding box.
  EXPECT_FALSE(intersection.ContainsPoint(1, 2));
  EXPECT_FALSE(intersection.ContainsPoint(2, 1));
}

TEST_F(MediaStreamConstraintsUtilSetsTest, ResolutionBoxIntersection) {
  const int kMin1 = 0;
  const int kMax1 = 2;
  ResolutionSet set1(kMin1, kMax1, kMin1, kMax1, 0.0, HUGE_VAL);

  const int kMin2 = 1;
  const int kMax2 = 3;
  ResolutionSet set2(kMin2, kMax2, kMin2, kMax2, 0.0, HUGE_VAL);

  auto intersection = set1.Intersection(set2);
  for (int i = kMin1; i <= kMax2; ++i) {
    for (int j = kMin1; j <= kMax2; ++j) {
      if (i >= kMin2 && j >= kMin2 && i <= kMax1 && j <= kMax1)
        EXPECT_TRUE(intersection.ContainsPoint(i, j));
      else
        EXPECT_FALSE(intersection.ContainsPoint(i, j));
    }
  }
}

TEST_F(MediaStreamConstraintsUtilSetsTest, ResolutionPointSetClosestPoint) {
  const int kHeight = 10;
  const int kWidth = 10;
  const double kAspectRatio = 1.0;
  ResolutionSet set(kHeight, kHeight, kWidth, kWidth, kAspectRatio,
                    kAspectRatio);

  for (int height = 0; height < 100; height += 10) {
    for (int width = 0; width < 100; width += 10) {
      EXPECT_EQ(P(kHeight, kWidth), set.ClosestPointTo(P(height, width)));
    }
  }
}

TEST_F(MediaStreamConstraintsUtilSetsTest, ResolutionLineSetClosestPoint) {
  {
    const int kHeight = 10;
    auto set = ResolutionSet::FromExactHeight(kHeight);
    for (int height = 0; height < 100; height += 10) {
      for (int width = 0; width < 100; width += 10) {
        EXPECT_EQ(P(kHeight, width), set.ClosestPointTo(P(height, width)));
      }
    }
    const int kWidth = 10;
    set = ResolutionSet::FromExactWidth(kWidth);
    for (int height = 0; height < 100; height += 10) {
      for (int width = 0; width < 100; width += 10) {
        EXPECT_EQ(P(height, kWidth), set.ClosestPointTo(P(height, width)));
      }
    }
  }

  {
    const double kAspectRatios[] = {0.0, 0.1, 0.2, 0.5,
                                    1.0, 2.0, 5.0, HUGE_VAL};
    for (double aspect_ratio : kAspectRatios) {
      auto set = ResolutionSet::FromExactAspectRatio(aspect_ratio);
      for (int height = 0; height < 100; height += 10) {
        for (int width = 0; width < 100; width += 10) {
          Point point(height, width);
          Point expected =
              ProjectionOnSegmentLine(point, P(0, 0), P(1, aspect_ratio));
          Point actual = set.ClosestPointTo(point);
          // This requires higher tolerance than ExpectPointEx due to the larger
          // error of the alternative projection method.
          EXPECT_TRUE(expected.IsApproximatelyEqualTo(actual));
        }
      }
    }
  }
}

TEST_F(MediaStreamConstraintsUtilSetsTest, ResolutionGeneralSetClosestPoint) {
  // This set contains the following vertices:
  // (10, 10), (20, 10), (100, 50), (100, 100), (100/1.5, 100), (10, 15)
  ResolutionSet set(10, 100, 10, 100, 0.5, 1.5);

  // Check that vertices are the closest points to themselves.
  auto vertices = set.ComputeVertices();
  for (auto& vertex : vertices)
    EXPECT_EQ(vertex, set.ClosestPointTo(vertex));

  // Point inside the set.
  EXPECT_EQ(P(11, 11), set.ClosestPointTo(P(11, 11)));

  // Close to horizontal segment (10, 10) (20, 10).
  EXPECT_EQ(P(15, 10), set.ClosestPointTo(P(15, 9)));

  // Close to horizontal segment (100, 100) (100/1.5, 100).
  EXPECT_EQ(P(99, 100), set.ClosestPointTo(P(99, 200)));

  // Close to vertical segment (10, 15) (10, 10).
  EXPECT_EQ(P(10, 12.5), set.ClosestPointTo(P(2, 12.5)));

  // Close to vertical segment (100, 50) (100, 100).
  EXPECT_EQ(P(100, 75), set.ClosestPointTo(P(120, 75)));

  // Close to oblique segment (20, 10) (100, 50)
  {
    Point point(70, 15);
    Point expected = ProjectionOnSegmentLine(point, P(20, 10), P(100, 50));
    Point actual = set.ClosestPointTo(point);
    EXPECT_POINT_EQ(expected, actual);
  }

  // Close to oblique segment (100/1.5, 100) (10, 15)
  {
    Point point(12, 70);
    Point expected =
        ProjectionOnSegmentLine(point, P(100 / 1.5, 100), P(10, 15));
    Point actual = set.ClosestPointTo(point);
    EXPECT_POINT_EQ(expected, actual);
  }

  // Points close to vertices.
  EXPECT_EQ(P(10, 10), set.ClosestPointTo(P(9, 9)));
  EXPECT_EQ(P(20, 10), set.ClosestPointTo(P(20, 9)));
  EXPECT_EQ(P(100, 50), set.ClosestPointTo(P(101, 50)));
  EXPECT_EQ(P(100, 100), set.ClosestPointTo(P(101, 101)));
  EXPECT_EQ(P(100 / 1.5, 100), set.ClosestPointTo(P(100 / 1.5, 101)));
  EXPECT_EQ(P(10, 15), set.ClosestPointTo(P(9, 15)));
}

TEST_F(MediaStreamConstraintsUtilSetsTest, ResolutionIdealIntersects) {
  ResolutionSet set(100, 1000, 100, 1000, 0.5, 2.0);

  const int kIdealHeight = 500;
  const int kIdealWidth = 1000;
  const double kIdealAspectRatio = 1.5;

  // Ideal height.
  {
    factory_.Reset();
    factory_.basic().height.SetIdeal(kIdealHeight);
    Point point = SelectClosestPointToIdeal(set);
    EXPECT_POINT_EQ(Point(kIdealHeight, kIdealHeight * kDefaultAspectRatio),
                    point);
  }

  // Ideal width.
  {
    factory_.Reset();
    factory_.basic().width.SetIdeal(kIdealWidth);
    Point point = SelectClosestPointToIdeal(set);
    EXPECT_POINT_EQ(Point(kIdealWidth / kDefaultAspectRatio, kIdealWidth),
                    point);
  }

  // Ideal aspect ratio.
  {
    factory_.Reset();
    factory_.basic().aspect_ratio.SetIdeal(kIdealAspectRatio);
    Point point = SelectClosestPointToIdeal(set);
    EXPECT_DOUBLE_EQ(kDefaultHeight, point.height());
    EXPECT_DOUBLE_EQ(kDefaultHeight * kIdealAspectRatio, point.width());
  }

  // Ideal height and width.
  {
    factory_.Reset();
    factory_.basic().height.SetIdeal(kIdealHeight);
    factory_.basic().width.SetIdeal(kIdealWidth);
    Point point = SelectClosestPointToIdeal(set);
    EXPECT_POINT_EQ(Point(kIdealHeight, kIdealWidth), point);
  }

  // Ideal height and aspect-ratio.
  {
    factory_.Reset();
    factory_.basic().height.SetIdeal(kIdealHeight);
    factory_.basic().aspect_ratio.SetIdeal(kIdealAspectRatio);
    Point point = SelectClosestPointToIdeal(set);
    EXPECT_POINT_EQ(Point(kIdealHeight, kIdealHeight * kIdealAspectRatio),
                    point);
  }

  // Ideal width and aspect-ratio.
  {
    factory_.Reset();
    factory_.basic().width.SetIdeal(kIdealWidth);
    factory_.basic().aspect_ratio.SetIdeal(kIdealAspectRatio);
    Point point = SelectClosestPointToIdeal(set);
    EXPECT_POINT_EQ(Point(kIdealWidth / kIdealAspectRatio, kIdealWidth), point);
  }

  // Ideal height, width and aspect-ratio.
  {
    factory_.Reset();
    factory_.basic().height.SetIdeal(kIdealHeight);
    factory_.basic().width.SetIdeal(kIdealWidth);
    factory_.basic().aspect_ratio.SetIdeal(kIdealAspectRatio);
    Point point = SelectClosestPointToIdeal(set);
    // Ideal aspect ratio should be ignored.
    EXPECT_POINT_EQ(Point(kIdealHeight, kIdealWidth), point);
  }
}

TEST_F(MediaStreamConstraintsUtilSetsTest, ResolutionIdealOutsideSinglePoint) {
  // This set is a triangle with vertices (100,100), (1000,100) and (1000,1000).
  ResolutionSet set(100, 1000, 100, 1000, 0.0, 1.0);

  const int kIdealHeight = 50;
  const int kIdealWidth = 1100;
  const double kIdealAspectRatio = 0.09;
  const Point kVertex1(100, 100);
  const Point kVertex2(1000, 100);
  const Point kVertex3(1000, 1000);

  // Ideal height.
  {
    factory_.Reset();
    factory_.basic().height.SetIdeal(kIdealHeight);
    Point point = SelectClosestPointToIdeal(set);
    EXPECT_POINT_EQ(kVertex1, point);
  }

  // Ideal width.
  {
    factory_.Reset();
    factory_.basic().width.SetIdeal(kIdealWidth);
    Point point = SelectClosestPointToIdeal(set);
    EXPECT_POINT_EQ(kVertex3, point);
  }

  // Ideal aspect ratio.
  {
    factory_.Reset();
    factory_.basic().aspect_ratio.SetIdeal(kIdealAspectRatio);
    Point point = SelectClosestPointToIdeal(set);
    EXPECT_POINT_EQ(kVertex2, point);
  }

  // Ideal height and width.
  {
    factory_.Reset();
    factory_.basic().height.SetIdeal(kIdealHeight);
    factory_.basic().width.SetIdeal(kIdealWidth);
    Point point = SelectClosestPointToIdeal(set);
    Point expected = set.ClosestPointTo(Point(kIdealHeight, kIdealWidth));
    EXPECT_POINT_EQ(expected, point);
  }

  // Ideal height and aspect-ratio.
  {
    factory_.Reset();
    factory_.basic().height.SetIdeal(kIdealHeight);
    factory_.basic().aspect_ratio.SetIdeal(kIdealAspectRatio);
    Point point = SelectClosestPointToIdeal(set);
    Point expected = set.ClosestPointTo(
        Point(kIdealHeight, kIdealHeight * kIdealAspectRatio));
    EXPECT_POINT_EQ(expected, point);
  }

  // Ideal width and aspect-ratio.
  {
    factory_.Reset();
    factory_.basic().width.SetIdeal(kIdealWidth);
    factory_.basic().aspect_ratio.SetIdeal(kIdealAspectRatio);
    Point point = SelectClosestPointToIdeal(set);
    Point expected =
        set.ClosestPointTo(Point(kIdealWidth / kIdealAspectRatio, kIdealWidth));
    EXPECT_POINT_EQ(expected, point);
  }

  // Ideal height, width and aspect-ratio.
  {
    factory_.Reset();
    factory_.basic().height.SetIdeal(kIdealHeight);
    factory_.basic().width.SetIdeal(kIdealWidth);
    factory_.basic().aspect_ratio.SetIdeal(kIdealAspectRatio);
    Point point = SelectClosestPointToIdeal(set);
    // kIdealAspectRatio is ignored if all three ideals are given.
    Point expected = set.ClosestPointTo(Point(kIdealHeight, kIdealWidth));
    EXPECT_POINT_EQ(expected, point);
  }
}

TEST_F(MediaStreamConstraintsUtilSetsTest,
       ResolutionIdealOutsideMultiplePoints) {
  // This set is a triangle with vertices (100,100), (1000,100) and (1000,1000).
  ResolutionSet set(100, 1000, 100, 1000, 0.0, 1.0);

  const int kIdealHeight = 1100;
  const int kIdealWidth = 50;
  const double kIdealAspectRatio = 11.0;
  const Point kVertex1(100, 100);
  const Point kVertex2(1000, 100);
  const Point kVertex3(1000, 1000);

  // Ideal height.
  {
    factory_.Reset();
    factory_.basic().height.SetIdeal(kIdealHeight);
    Point point = SelectClosestPointToIdeal(set);
    // Parallel to the side between kVertex2 and kVertex3. Point closest to
    // default aspect ratio is kVertex3.
    EXPECT_POINT_EQ(kVertex3, point);
  }

  // Ideal width.
  {
    factory_.Reset();
    factory_.basic().width.SetIdeal(kIdealWidth);
    Point point = SelectClosestPointToIdeal(set);
    // Parallel to the side between kVertex1 and kVertex2. Point closest to
    // default aspect ratio is kVertex1.
    EXPECT_POINT_EQ(kVertex1, point);
  }

  // Ideal aspect ratio.
  {
    factory_.Reset();
    factory_.basic().aspect_ratio.SetIdeal(kIdealAspectRatio);
    Point point = SelectClosestPointToIdeal(set);
    // The side between kVertex1 and kVertex3 is closest. The points closest to
    // default dimensions are (kDefaultHeight, kDefaultHeight * AR)
    // and (kDefaultWidth / AR, kDefaultWidth). Since the aspect ratio of the
    // polygon side is less than the default, the algorithm preserves the
    // default width.
    Point expected(kDefaultWidth / kVertex1.AspectRatio(), kDefaultWidth);
    EXPECT_POINT_EQ(expected, point);
    EXPECT_TRUE(set.ContainsPoint(expected));
  }
}

TEST_F(MediaStreamConstraintsUtilSetsTest,
       ResolutionUnconstrainedExtremeIdeal) {
  ResolutionSet set;

  // Ideal height.
  {
    factory_.Reset();
    factory_.basic().height.SetIdeal(std::numeric_limits<long>::max());
    Point point = SelectClosestPointToIdeal(set);
    EXPECT_POINT_EQ(
        Point(ResolutionSet::kMaxDimension, ResolutionSet::kMaxDimension),
        point);
    factory_.basic().height.SetIdeal(0);
    point = SelectClosestPointToIdeal(set);
    EXPECT_POINT_EQ(Point(0, 0), point);
  }

  // Ideal width.
  {
    factory_.Reset();
    factory_.basic().width.SetIdeal(std::numeric_limits<long>::max());
    Point point = SelectClosestPointToIdeal(set);
    EXPECT_POINT_EQ(Point(ResolutionSet::kMaxDimension / kDefaultAspectRatio,
                          ResolutionSet::kMaxDimension),
                    point);
    factory_.basic().width.SetIdeal(0);
    point = SelectClosestPointToIdeal(set);
    EXPECT_POINT_EQ(Point(0, 0), point);
  }

  // Ideal Aspect Ratio.
  {
    factory_.Reset();
    factory_.basic().aspect_ratio.SetIdeal(HUGE_VAL);
    Point point = SelectClosestPointToIdeal(set);
    EXPECT_POINT_EQ(Point(0, ResolutionSet::kMaxDimension), point);
    factory_.basic().aspect_ratio.SetIdeal(0.0);
    point = SelectClosestPointToIdeal(set);
    EXPECT_POINT_EQ(Point(ResolutionSet::kMaxDimension, 0), point);
  }
}

TEST_F(MediaStreamConstraintsUtilSetsTest, ResolutionVertices) {
  // Empty set.
  {
    ResolutionSet set(1000, 100, 1000, 100, 0.5, 1.5);
    ASSERT_TRUE(set.IsEmpty());
    auto vertices = set.ComputeVertices();
    EXPECT_EQ(0U, vertices.size());
    EXPECT_TRUE(AreValidVertices(set, vertices));
  }

  // Three lines that intersect at the same point.
  {
    ResolutionSet set(1, 1, 1, 1, 1, 1);
    EXPECT_FALSE(set.IsEmpty());
    auto vertices = set.ComputeVertices();
    EXPECT_EQ(1U, vertices.size());
    VerticesContain(vertices, Point(1, 1));
    EXPECT_TRUE(AreValidVertices(set, vertices));
  }

  // A line segment with the lower-left and upper-right corner of the box.
  {
    ResolutionSet set(0, 100, 0, 100, 1.0, 1.0);
    EXPECT_FALSE(set.IsEmpty());
    auto vertices = set.ComputeVertices();
    EXPECT_EQ(2U, vertices.size());
    VerticesContain(vertices, Point(0, 0));
    VerticesContain(vertices, Point(100, 100));
    EXPECT_TRUE(AreValidVertices(set, vertices));

    set = ResolutionSet(0, 100, 0, 100, 1.0, HUGE_VAL);
    EXPECT_FALSE(set.IsEmpty());
    vertices = set.ComputeVertices();
    EXPECT_EQ(3U, vertices.size());
    VerticesContain(vertices, Point(0, 0));
    VerticesContain(vertices, Point(100, 100));
    VerticesContain(vertices, Point(0, 100));
    EXPECT_TRUE(AreValidVertices(set, vertices));

    set = ResolutionSet(0, 100, 0, 100, 0, 1.0);
    EXPECT_FALSE(set.IsEmpty());
    vertices = set.ComputeVertices();
    EXPECT_EQ(3U, vertices.size());
    VerticesContain(vertices, Point(0, 0));
    VerticesContain(vertices, Point(100, 100));
    VerticesContain(vertices, Point(100, 0));
    EXPECT_TRUE(AreValidVertices(set, vertices));
  }

  // A line segment that crosses the bottom and right sides of the box.
  {
    const double kAspectRatio = 50.0 / 75.0;
    ResolutionSet set(50, 100, 50, 100, kAspectRatio, kAspectRatio);
    auto vertices = set.ComputeVertices();
    EXPECT_EQ(2U, vertices.size());
    VerticesContain(vertices, Point(50 / kAspectRatio, 50));
    VerticesContain(vertices, Point(100, 100.0 * kAspectRatio));
    EXPECT_TRUE(AreValidVertices(set, vertices));

    set = ResolutionSet(50, 100, 50, 100, kAspectRatio, HUGE_VAL);
    vertices = set.ComputeVertices();
    EXPECT_EQ(5U, vertices.size());
    VerticesContain(vertices, Point(50 / kAspectRatio, 50));
    VerticesContain(vertices, Point(100, 100.0 * kAspectRatio));
    VerticesContain(vertices, Point(50, 50));
    VerticesContain(vertices, Point(50, 100));
    VerticesContain(vertices, Point(100, 100));
    EXPECT_TRUE(AreValidVertices(set, vertices));

    set = ResolutionSet(50, 100, 50, 100, 0.0, kAspectRatio);
    vertices = set.ComputeVertices();
    EXPECT_EQ(3U, vertices.size());
    VerticesContain(vertices, Point(50 / kAspectRatio, 50));
    VerticesContain(vertices, Point(100, 100.0 * kAspectRatio));
    VerticesContain(vertices, Point(100, 50));
    EXPECT_TRUE(AreValidVertices(set, vertices));
  }

  // A line segment that crosses the left and top sides of the box.
  {
    const double kAspectRatio = 75.0 / 50.0;
    ResolutionSet set(50, 100, 50, 100, kAspectRatio, kAspectRatio);
    auto vertices = set.ComputeVertices();
    EXPECT_EQ(2U, vertices.size());
    VerticesContain(vertices, Point(50, 50 * kAspectRatio));
    VerticesContain(vertices, Point(100 / kAspectRatio, 100));
    EXPECT_TRUE(AreValidVertices(set, vertices));

    set = ResolutionSet(50, 100, 50, 100, kAspectRatio, HUGE_VAL);
    vertices = set.ComputeVertices();
    EXPECT_EQ(3U, vertices.size());
    VerticesContain(vertices, Point(50, 50 * kAspectRatio));
    VerticesContain(vertices, Point(100 / kAspectRatio, 100));
    VerticesContain(vertices, Point(50, 100));
    EXPECT_TRUE(AreValidVertices(set, vertices));

    set = ResolutionSet(50, 100, 50, 100, 0.0, kAspectRatio);
    vertices = set.ComputeVertices();
    EXPECT_EQ(5U, vertices.size());
    VerticesContain(vertices, Point(50, 50 * kAspectRatio));
    VerticesContain(vertices, Point(100 / kAspectRatio, 100));
    VerticesContain(vertices, Point(50, 50));
    VerticesContain(vertices, Point(100, 100));
    VerticesContain(vertices, Point(100, 50));
    EXPECT_TRUE(AreValidVertices(set, vertices));
  }

  // An aspect ratio constraint crosses the bottom and top sides of the box.
  {
    const double kAspectRatio = 75.0 / 50.0;
    ResolutionSet set(0, 100, 50, 100, kAspectRatio, kAspectRatio);
    auto vertices = set.ComputeVertices();
    EXPECT_EQ(2U, vertices.size());
    VerticesContain(vertices, Point(50 / kAspectRatio, 50));
    VerticesContain(vertices, Point(100 / kAspectRatio, 100));
    EXPECT_TRUE(AreValidVertices(set, vertices));

    set = ResolutionSet(0, 100, 50, 100, kAspectRatio, HUGE_VAL);
    vertices = set.ComputeVertices();
    EXPECT_EQ(4U, vertices.size());
    VerticesContain(vertices, Point(50 / kAspectRatio, 50));
    VerticesContain(vertices, Point(100 / kAspectRatio, 100));
    VerticesContain(vertices, Point(0, 50));
    VerticesContain(vertices, Point(0, 100));
    EXPECT_TRUE(AreValidVertices(set, vertices));

    set = ResolutionSet(0, 100, 50, 100, 0.0, kAspectRatio);
    vertices = set.ComputeVertices();
    EXPECT_EQ(4U, vertices.size());
    VerticesContain(vertices, Point(50 / kAspectRatio, 50));
    VerticesContain(vertices, Point(100 / kAspectRatio, 100));
    VerticesContain(vertices, Point(100, 50));
    VerticesContain(vertices, Point(100, 100));
    EXPECT_TRUE(AreValidVertices(set, vertices));
  }

  // An aspect-ratio constraint crosses the left and right sides of the box.
  {
    const double kAspectRatio = 75.0 / 50.0;
    ResolutionSet set(50, 100, 0, 200, kAspectRatio, kAspectRatio);
    auto vertices = set.ComputeVertices();
    // This one fails if floating-point precision is too high.
    EXPECT_EQ(2U, vertices.size());
    VerticesContain(vertices, Point(50, 50 * kAspectRatio));
    VerticesContain(vertices, Point(100, 100 * kAspectRatio));
    EXPECT_TRUE(AreValidVertices(set, vertices));

    set = ResolutionSet(50, 100, 0, 200, kAspectRatio, HUGE_VAL);
    vertices = set.ComputeVertices();
    // This one fails if floating-point precision is too high.
    EXPECT_EQ(4U, vertices.size());
    VerticesContain(vertices, Point(50, 50 * kAspectRatio));
    VerticesContain(vertices, Point(100, 100 * kAspectRatio));
    VerticesContain(vertices, Point(50, 200));
    VerticesContain(vertices, Point(100, 200));
    EXPECT_TRUE(AreValidVertices(set, vertices));

    set = ResolutionSet(50, 100, 0, 200, 0.0, kAspectRatio);
    vertices = set.ComputeVertices();
    EXPECT_EQ(4U, vertices.size());
    VerticesContain(vertices, Point(50, 50 * kAspectRatio));
    VerticesContain(vertices, Point(100, 100 * kAspectRatio));
    VerticesContain(vertices, Point(50, 0));
    VerticesContain(vertices, Point(100, 0));
    EXPECT_TRUE(AreValidVertices(set, vertices));
  }

  // Aspect-ratio lines touch the corners of the box.
  {
    ResolutionSet set(50, 100, 50, 100, 0.5, 2.0);
    auto vertices = set.ComputeVertices();
    EXPECT_EQ(4U, vertices.size());
    VerticesContain(vertices, Point(50, 50));
    VerticesContain(vertices, Point(100, 50));
    VerticesContain(vertices, Point(50, 100));
    VerticesContain(vertices, Point(100, 100));
    EXPECT_TRUE(AreValidVertices(set, vertices));
  }

  // Hexagons.
  {
    ResolutionSet set(10, 100, 10, 100, 0.5, 1.5);
    auto vertices = set.ComputeVertices();
    EXPECT_EQ(6U, vertices.size());
    VerticesContain(vertices, Point(10, 10));
    VerticesContain(vertices, Point(100, 100));
    VerticesContain(vertices, Point(10, 10 * 1.5));
    VerticesContain(vertices, Point(100 / 1.5, 100));
    VerticesContain(vertices, Point(10 / 0.5, 10));
    VerticesContain(vertices, Point(100, 100 * 0.5));
    EXPECT_TRUE(AreValidVertices(set, vertices));

    set = ResolutionSet(50, 100, 50, 100, 50.0 / 75.0, 75.0 / 50.0);
    vertices = set.ComputeVertices();
    EXPECT_EQ(6U, vertices.size());
    VerticesContain(vertices, Point(50, 50));
    VerticesContain(vertices, Point(100, 100));
    VerticesContain(vertices, Point(75, 50));
    VerticesContain(vertices, Point(50, 75));
    VerticesContain(vertices, Point(100, 100.0 * 50.0 / 75.0));
    VerticesContain(vertices, Point(100 * 50.0 / 75.0, 100.0));
    EXPECT_TRUE(AreValidVertices(set, vertices));
  }

  // Both aspect-ratio constraints cross the left and top sides of the box.
  {
    ResolutionSet set(10, 100, 10, 100, 1.5, 1.7);
    auto vertices = set.ComputeVertices();
    EXPECT_EQ(4U, vertices.size());
    VerticesContain(vertices, Point(10, 10 * 1.5));
    VerticesContain(vertices, Point(10, 10 * 1.7));
    VerticesContain(vertices, Point(100 / 1.5, 100));
    VerticesContain(vertices, Point(100 / 1.7, 100));
    EXPECT_TRUE(AreValidVertices(set, vertices));
  }

  // Both aspect-ratio constraints cross the left and right sides of the box.
  {
    ResolutionSet set(10, 100, 10, ResolutionSet::kMaxDimension, 1.5, 1.7);
    auto vertices = set.ComputeVertices();
    EXPECT_EQ(4U, vertices.size());
    VerticesContain(vertices, Point(10, 10 * 1.5));
    VerticesContain(vertices, Point(10, 10 * 1.7));
    VerticesContain(vertices, Point(100, 100 * 1.5));
    VerticesContain(vertices, Point(100, 100 * 1.7));
    EXPECT_TRUE(AreValidVertices(set, vertices));
  }

  // Both aspect-ratio constraints cross the bottom and top sides of the box.
  {
    ResolutionSet set(10, 100, 50, 100, 2.0, 4.0);
    auto vertices = set.ComputeVertices();
    EXPECT_EQ(4U, vertices.size());
    VerticesContain(vertices, Point(50 / 2.0, 50));
    VerticesContain(vertices, Point(100 / 2.0, 100));
    VerticesContain(vertices, Point(50 / 4.0, 50));
    VerticesContain(vertices, Point(100 / 4.0, 100));
    EXPECT_TRUE(AreValidVertices(set, vertices));
  }

  // Both aspect-ratio constraints cross the bottom and right sides of the box.
  {
    ResolutionSet set(10, 100, 50, 100, 0.7, 0.9);
    auto vertices = set.ComputeVertices();
    EXPECT_EQ(4U, vertices.size());
    VerticesContain(vertices, Point(50 / 0.7, 50));
    VerticesContain(vertices, Point(50 / 0.9, 50));
    VerticesContain(vertices, Point(100, 100 * 0.7));
    VerticesContain(vertices, Point(100, 100 * 0.9));
    EXPECT_TRUE(AreValidVertices(set, vertices));
  }

  // Pentagons.
  {
    ResolutionSet set(10, 100, 50, 100, 0.7, 4.0);
    auto vertices = set.ComputeVertices();
    EXPECT_EQ(5U, vertices.size());
    VerticesContain(vertices, Point(50 / 0.7, 50));
    VerticesContain(vertices, Point(100, 100 * 0.7));
    VerticesContain(vertices, Point(50 / 4.0, 50));
    VerticesContain(vertices, Point(100 / 4.0, 100));
    VerticesContain(vertices, Point(100, 100));
    EXPECT_TRUE(AreValidVertices(set, vertices));

    set = ResolutionSet(50, 100, 10, 100, 0.7, 1.5);
    vertices = set.ComputeVertices();
    EXPECT_EQ(5U, vertices.size());
    VerticesContain(vertices, Point(50, 50 * 0.7));
    VerticesContain(vertices, Point(100, 100 * 0.7));
    VerticesContain(vertices, Point(50, 50 * 1.5));
    VerticesContain(vertices, Point(100 / 1.5, 100));
    VerticesContain(vertices, Point(100, 100));
    EXPECT_TRUE(AreValidVertices(set, vertices));
  }

  // Extreme aspect ratios, for completeness.
  {
    ResolutionSet set(0, 100, 0, ResolutionSet::kMaxDimension, 0.0, 0.0);
    auto vertices = set.ComputeVertices();
    EXPECT_EQ(2U, vertices.size());
    VerticesContain(vertices, Point(0, 0));
    VerticesContain(vertices, Point(100, 0));
    EXPECT_TRUE(AreValidVertices(set, vertices));

    set = ResolutionSet(0, ResolutionSet::kMaxDimension, 0, 100, HUGE_VAL,
                        HUGE_VAL);
    vertices = set.ComputeVertices();
    EXPECT_EQ(2U, vertices.size());
    VerticesContain(vertices, Point(0, 0));
    VerticesContain(vertices, Point(0, 100));
    EXPECT_TRUE(AreValidVertices(set, vertices));
  }
}

TEST_F(MediaStreamConstraintsUtilSetsTest, NumericRangeSetDouble) {
  using DoubleRangeSet = NumericRangeSet<double>;
  // Set with maximum supported range.
  DoubleRangeSet set;
  EXPECT_EQ(0.0, set.Min());
  EXPECT_EQ(HUGE_VAL, set.Max());
  EXPECT_FALSE(set.IsEmpty());

  // Constrained set.
  const double kMin = 1.0;
  const double kMax = 10.0;
  set = DoubleRangeSet(kMin, kMax);
  EXPECT_EQ(kMin, set.Min());
  EXPECT_EQ(kMax, set.Max());
  EXPECT_FALSE(set.IsEmpty());

  // Empty set.
  set = DoubleRangeSet(kMax, kMin);
  EXPECT_TRUE(set.IsEmpty());

  // Intersection.
  set = DoubleRangeSet(kMin, kMax);
  const double kMin2 = 5.0;
  const double kMax2 = 20.0;
  auto intersection = set.Intersection(DoubleRangeSet(kMin2, kMax2));
  EXPECT_EQ(kMin2, intersection.Min());
  EXPECT_EQ(kMax, intersection.Max());
  EXPECT_FALSE(intersection.IsEmpty());

  // Empty intersection.
  intersection = set.Intersection(DoubleRangeSet(kMax + 1, HUGE_VAL));
  EXPECT_TRUE(intersection.IsEmpty());
}

TEST_F(MediaStreamConstraintsUtilSetsTest, DiscreteSetString) {
  // Universal set.
  using StringSet = DiscreteSet<std::string>;
  StringSet set = StringSet::UniversalSet();
  EXPECT_TRUE(set.Contains("arbitrary"));
  EXPECT_TRUE(set.Contains("strings"));
  EXPECT_FALSE(set.IsEmpty());
  EXPECT_TRUE(set.is_universal());
  EXPECT_FALSE(set.HasExplicitElements());

  // Constrained set.
  set = StringSet(std::vector<std::string>({"a", "b", "c"}));
  EXPECT_TRUE(set.Contains("a"));
  EXPECT_TRUE(set.Contains("b"));
  EXPECT_TRUE(set.Contains("c"));
  EXPECT_FALSE(set.Contains("d"));
  EXPECT_FALSE(set.IsEmpty());
  EXPECT_FALSE(set.is_universal());
  EXPECT_TRUE(set.HasExplicitElements());
  EXPECT_EQ(std::string("a"), set.FirstElement());

  // Empty set.
  set = StringSet::EmptySet();
  EXPECT_FALSE(set.Contains("a"));
  EXPECT_FALSE(set.Contains("b"));
  EXPECT_TRUE(set.IsEmpty());
  EXPECT_FALSE(set.is_universal());
  EXPECT_FALSE(set.HasExplicitElements());

  // Intersection.
  set = StringSet(std::vector<std::string>({"a", "b", "c"}));
  StringSet set2 = StringSet(std::vector<std::string>({"b", "c", "d"}));
  auto intersection = set.Intersection(set2);
  EXPECT_FALSE(intersection.Contains("a"));
  EXPECT_TRUE(intersection.Contains("b"));
  EXPECT_TRUE(intersection.Contains("c"));
  EXPECT_FALSE(intersection.Contains("d"));
  EXPECT_FALSE(intersection.IsEmpty());
  EXPECT_FALSE(intersection.is_universal());
  EXPECT_TRUE(intersection.HasExplicitElements());
  EXPECT_EQ(std::string("b"), intersection.FirstElement());

  // Empty intersection.
  set2 = StringSet(std::vector<std::string>({"d", "e", "f"}));
  intersection = set.Intersection(set2);
  EXPECT_FALSE(intersection.Contains("a"));
  EXPECT_FALSE(intersection.Contains("b"));
  EXPECT_FALSE(intersection.Contains("c"));
  EXPECT_FALSE(intersection.Contains("d"));
  EXPECT_TRUE(intersection.IsEmpty());
  EXPECT_FALSE(intersection.is_universal());
  EXPECT_FALSE(intersection.HasExplicitElements());
}

TEST_F(MediaStreamConstraintsUtilSetsTest, DiscreteSetBool) {
  using BoolSet = DiscreteSet<bool>;
  // Universal set.
  BoolSet set = BoolSet::UniversalSet();
  EXPECT_TRUE(set.Contains(true));
  EXPECT_TRUE(set.Contains(false));
  EXPECT_FALSE(set.IsEmpty());
  EXPECT_TRUE(set.is_universal());
  EXPECT_FALSE(set.HasExplicitElements());

  // Constrained set.
  set = BoolSet({true});
  EXPECT_TRUE(set.Contains(true));
  EXPECT_FALSE(set.Contains(false));
  EXPECT_FALSE(set.IsEmpty());
  EXPECT_FALSE(set.is_universal());
  EXPECT_TRUE(set.HasExplicitElements());
  EXPECT_TRUE(set.FirstElement());

  set = BoolSet({false});
  EXPECT_FALSE(set.Contains(true));
  EXPECT_TRUE(set.Contains(false));
  EXPECT_FALSE(set.IsEmpty());
  EXPECT_FALSE(set.is_universal());
  EXPECT_TRUE(set.HasExplicitElements());
  EXPECT_FALSE(set.FirstElement());

  // Empty set.
  set = BoolSet::EmptySet();
  EXPECT_FALSE(set.Contains(true));
  EXPECT_FALSE(set.Contains(false));
  EXPECT_TRUE(set.IsEmpty());
  EXPECT_FALSE(set.is_universal());
  EXPECT_FALSE(set.HasExplicitElements());

  // Intersection.
  set = BoolSet::UniversalSet();
  auto intersection = set.Intersection(BoolSet({true}));
  EXPECT_TRUE(intersection.Contains(true));
  EXPECT_FALSE(intersection.Contains(false));
  intersection = set.Intersection(set);
  EXPECT_TRUE(intersection.Contains(true));
  EXPECT_TRUE(intersection.Contains(true));

  // Empty intersection.
  set = BoolSet({true});
  intersection = set.Intersection(BoolSet({false}));
  EXPECT_TRUE(intersection.IsEmpty());
}

}  // namespace content
