// Copyright 2014 the V8 project 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 <limits>

#include "src/base/bounds.h"
#include "src/utils/utils.h"
#include "testing/gtest-support.h"

namespace v8 {
namespace internal {

template <typename T>
class UtilsTest : public ::testing::Test {};

using IntegerTypes =
    ::testing::Types<signed char, unsigned char,
                     short,                    // NOLINT(runtime/int)
                     unsigned short,           // NOLINT(runtime/int)
                     int, unsigned int, long,  // NOLINT(runtime/int)
                     unsigned long,            // NOLINT(runtime/int)
                     long long,                // NOLINT(runtime/int)
                     unsigned long long,       // NOLINT(runtime/int)
                     int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t,
                     int64_t, uint64_t>;

TYPED_TEST_SUITE(UtilsTest, IntegerTypes);

TYPED_TEST(UtilsTest, SaturateSub) {
  TypeParam min = std::numeric_limits<TypeParam>::min();
  TypeParam max = std::numeric_limits<TypeParam>::max();
  EXPECT_EQ(SaturateSub<TypeParam>(min, 0), min);
  EXPECT_EQ(SaturateSub<TypeParam>(max, 0), max);
  EXPECT_EQ(SaturateSub<TypeParam>(max, min), max);
  EXPECT_EQ(SaturateSub<TypeParam>(min, max), min);
  EXPECT_EQ(SaturateSub<TypeParam>(min, max / 3), min);
  EXPECT_EQ(SaturateSub<TypeParam>(min + 1, 2), min);
  if (std::numeric_limits<TypeParam>::is_signed) {
    EXPECT_EQ(SaturateSub<TypeParam>(min, min), static_cast<TypeParam>(0));
    EXPECT_EQ(SaturateSub<TypeParam>(0, min), max);
    EXPECT_EQ(SaturateSub<TypeParam>(max / 3, min), max);
    EXPECT_EQ(SaturateSub<TypeParam>(max / 5, min), max);
    EXPECT_EQ(SaturateSub<TypeParam>(min / 3, max), min);
    EXPECT_EQ(SaturateSub<TypeParam>(min / 9, max), min);
    EXPECT_EQ(SaturateSub<TypeParam>(max, min / 3), max);
    EXPECT_EQ(SaturateSub<TypeParam>(min, max / 3), min);
    EXPECT_EQ(SaturateSub<TypeParam>(max / 3 * 2, min / 2), max);
    EXPECT_EQ(SaturateSub<TypeParam>(min / 3 * 2, max / 2), min);
  } else {
    EXPECT_EQ(SaturateSub<TypeParam>(min, min), min);
    EXPECT_EQ(SaturateSub<TypeParam>(0, min), min);
    EXPECT_EQ(SaturateSub<TypeParam>(0, max), min);
    EXPECT_EQ(SaturateSub<TypeParam>(max / 3, max), min);
    EXPECT_EQ(SaturateSub<TypeParam>(max - 3, max), min);
  }
  TypeParam test_cases[] = {static_cast<TypeParam>(min / 23),
                            static_cast<TypeParam>(max / 3),
                            63,
                            static_cast<TypeParam>(min / 6),
                            static_cast<TypeParam>(max / 55),
                            static_cast<TypeParam>(min / 2),
                            static_cast<TypeParam>(max / 2),
                            0,
                            1,
                            2,
                            3,
                            4,
                            42};
  TRACED_FOREACH(TypeParam, x, test_cases) {
    TRACED_FOREACH(TypeParam, y, test_cases) {
      if (std::numeric_limits<TypeParam>::is_signed) {
        EXPECT_EQ(SaturateSub<TypeParam>(x, y), x - y);
      } else {
        EXPECT_EQ(SaturateSub<TypeParam>(x, y), y > x ? min : x - y);
      }
    }
  }
}

TYPED_TEST(UtilsTest, SaturateAdd) {
  TypeParam min = std::numeric_limits<TypeParam>::min();
  TypeParam max = std::numeric_limits<TypeParam>::max();
  EXPECT_EQ(SaturateAdd<TypeParam>(min, min), min);
  EXPECT_EQ(SaturateAdd<TypeParam>(max, max), max);
  EXPECT_EQ(SaturateAdd<TypeParam>(min, min / 3), min);
  EXPECT_EQ(SaturateAdd<TypeParam>(max / 8 * 7, max / 3 * 2), max);
  EXPECT_EQ(SaturateAdd<TypeParam>(min / 3 * 2, min / 8 * 7), min);
  EXPECT_EQ(SaturateAdd<TypeParam>(max / 20 * 18, max / 25 * 18), max);
  EXPECT_EQ(SaturateAdd<TypeParam>(min / 3 * 2, min / 3 * 2), min);
  EXPECT_EQ(SaturateAdd<TypeParam>(max - 1, 2), max);
  EXPECT_EQ(SaturateAdd<TypeParam>(max - 100, 101), max);
  TypeParam test_cases[] = {static_cast<TypeParam>(min / 23),
                            static_cast<TypeParam>(max / 3),
                            63,
                            static_cast<TypeParam>(min / 6),
                            static_cast<TypeParam>(max / 55),
                            static_cast<TypeParam>(min / 2),
                            static_cast<TypeParam>(max / 2),
                            0,
                            1,
                            2,
                            3,
                            4,
                            42};
  TRACED_FOREACH(TypeParam, x, test_cases) {
    TRACED_FOREACH(TypeParam, y, test_cases) {
      EXPECT_EQ(SaturateAdd<TypeParam>(x, y), x + y);
    }
  }
}

TYPED_TEST(UtilsTest, PassesFilterTest) {
  EXPECT_TRUE(
      PassesFilter(base::CStrVector("abcdefg"), base::CStrVector("abcdefg")));
  EXPECT_TRUE(
      PassesFilter(base::CStrVector("abcdefg"), base::CStrVector("abcdefg*")));
  EXPECT_TRUE(
      PassesFilter(base::CStrVector("abcdefg"), base::CStrVector("abc*")));
  EXPECT_TRUE(PassesFilter(base::CStrVector("abcdefg"), base::CStrVector("*")));
  EXPECT_TRUE(
      PassesFilter(base::CStrVector("abcdefg"), base::CStrVector("-~")));
  EXPECT_TRUE(
      PassesFilter(base::CStrVector("abcdefg"), base::CStrVector("-abcdefgh")));
  EXPECT_TRUE(PassesFilter(base::CStrVector("abdefg"), base::CStrVector("-")));
  EXPECT_FALSE(
      PassesFilter(base::CStrVector("abcdefg"), base::CStrVector("-abcdefg")));
  EXPECT_FALSE(
      PassesFilter(base::CStrVector("abcdefg"), base::CStrVector("-abcdefg*")));
  EXPECT_FALSE(
      PassesFilter(base::CStrVector("abcdefg"), base::CStrVector("-abc*")));
  EXPECT_FALSE(
      PassesFilter(base::CStrVector("abcdefg"), base::CStrVector("-*")));
  EXPECT_FALSE(
      PassesFilter(base::CStrVector("abcdefg"), base::CStrVector("~")));
  EXPECT_FALSE(PassesFilter(base::CStrVector("abcdefg"), base::CStrVector("")));
  EXPECT_FALSE(
      PassesFilter(base::CStrVector("abcdefg"), base::CStrVector("abcdefgh")));

  EXPECT_TRUE(PassesFilter(base::CStrVector(""), base::CStrVector("")));
  EXPECT_TRUE(PassesFilter(base::CStrVector(""), base::CStrVector("*")));
  EXPECT_FALSE(PassesFilter(base::CStrVector(""), base::CStrVector("-")));
  EXPECT_FALSE(PassesFilter(base::CStrVector(""), base::CStrVector("-*")));
  EXPECT_FALSE(PassesFilter(base::CStrVector(""), base::CStrVector("a")));
}

TEST(UtilsTest, IsInBounds) {
// for column consistency and terseness
#define INB(x, y, z) EXPECT_TRUE(base::IsInBounds<size_t>(x, y, z))
#define OOB(x, y, z) EXPECT_FALSE(base::IsInBounds<size_t>(x, y, z))
  INB(0, 0, 1);
  INB(0, 1, 1);
  INB(1, 0, 1);

  OOB(0, 2, 1);
  OOB(2, 0, 1);

  INB(0, 0, 2);
  INB(0, 1, 2);
  INB(0, 2, 2);

  INB(0, 0, 2);
  INB(1, 0, 2);
  INB(2, 0, 2);

  OOB(0, 3, 2);
  OOB(3, 0, 2);

  INB(0, 1, 2);
  INB(1, 1, 2);

  OOB(1, 2, 2);
  OOB(2, 1, 2);

  const size_t max = std::numeric_limits<size_t>::max();
  const size_t half = max / 2;

  // limit cases.
  INB(0, 0, max);
  INB(0, 1, max);
  INB(1, 0, max);
  INB(max, 0, max);
  INB(0, max, max);
  INB(max - 1, 0, max);
  INB(0, max - 1, max);
  INB(max - 1, 1, max);
  INB(1, max - 1, max);

  INB(half, half, max);
  INB(half + 1, half, max);
  INB(half, half + 1, max);

  OOB(max, 0, 0);
  OOB(0, max, 0);
  OOB(max, 0, 1);
  OOB(0, max, 1);
  OOB(max, 0, 2);
  OOB(0, max, 2);

  OOB(max, 0, max - 1);
  OOB(0, max, max - 1);

  // wraparound cases.
  OOB(max, 1, max);
  OOB(1, max, max);
  OOB(max - 1, 2, max);
  OOB(2, max - 1, max);
  OOB(half + 1, half + 1, max);
  OOB(half + 1, half + 1, max);

#undef INB
#undef OOB
}

}  // namespace internal
}  // namespace v8