/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <folly/lang/ToAscii.h>

#include <string>

#include <folly/portability/GTest.h>

template <typename F>
static void to_ascii_size_16(F _) {
  EXPECT_EQ(1, _(0x0));
  EXPECT_EQ(1, _(0x1));
  EXPECT_EQ(1, _(0xf));
  EXPECT_EQ(2, _(0x10));
  EXPECT_EQ(2, _(0x11));
  EXPECT_EQ(2, _(0xff));
  EXPECT_EQ(3, _(0x100));
  EXPECT_EQ(3, _(0x101));
  EXPECT_EQ(15, _(0xfffffffffffffff));
  EXPECT_EQ(16, _(0x1000000000000000));
  EXPECT_EQ(16, _(0x1000000000000001));
  EXPECT_EQ(16, _(0xffffffffffffffff));
}

template <typename F>
static void to_ascii_size_10(F _) {
  EXPECT_EQ(1, _(0));
  EXPECT_EQ(1, _(1));
  EXPECT_EQ(1, _(9));
  EXPECT_EQ(2, _(10));
  EXPECT_EQ(2, _(99));
  EXPECT_EQ(3, _(100));
  EXPECT_EQ(3, _(999));
  EXPECT_EQ(4, _(1000));
  EXPECT_EQ(4, _(9999));
  EXPECT_EQ(20, _(18446744073709551615u));
}

template <typename F>
static void to_ascii_size_8(F _) {
  EXPECT_EQ(1, _(00));
  EXPECT_EQ(1, _(01));
  EXPECT_EQ(1, _(07));
  EXPECT_EQ(2, _(010));
  EXPECT_EQ(2, _(011));
  EXPECT_EQ(2, _(077));
  EXPECT_EQ(3, _(0100));
  EXPECT_EQ(3, _(0101));
  EXPECT_EQ(21, _(0777777777777777777777));
  EXPECT_EQ(22, _(01000000000000000000000));
  EXPECT_EQ(22, _(01000000000000000000001));
  EXPECT_EQ(22, _(01777777777777777777777));
}

template <typename F>
static void to_ascii_size_u64_10_compare(F _) {
  //  the first X nonnegatives
  for (uint64_t i = 0; i < 100000; i++) {
    EXPECT_EQ(std::to_string(i).size(), _(i));
  }

  //  all powers of 2
  for (auto p = 0; p < 64; p++) {
    for (auto n : {-1, 0, 1}) {
      auto const v = folly::constexpr_pow(uint64_t(2), p) + n;
      EXPECT_EQ(std::to_string(v).size(), _(v));
    }
  }

  //  all powers of 10
  for (auto p = 0; p < 20; p++) {
    for (auto n : {-1, 0, 1}) {
      auto const v = folly::constexpr_pow(uint64_t(2), p) + n;
      EXPECT_EQ(std::to_string(v).size(), _(v));
    }
  }
}

struct ToAsciiTest : testing::Test {};

TEST_F(ToAsciiTest, to_ascii_powers_u64_16) {
  using _ = folly::detail::to_ascii_powers<16, uint64_t>;
  EXPECT_EQ(16, _::size);
  EXPECT_EQ(1, _::data.data[0]);
  EXPECT_EQ(16, _::data.data[1]);
  EXPECT_EQ(256, _::data.data[2]);
}

TEST_F(ToAsciiTest, to_ascii_powers_u64_10) {
  using _ = folly::detail::to_ascii_powers<10, uint64_t>;
  EXPECT_EQ(20, _::size);
  EXPECT_EQ(1, _::data.data[0]);
  EXPECT_EQ(10, _::data.data[1]);
  EXPECT_EQ(100, _::data.data[2]);
}

TEST_F(ToAsciiTest, to_ascii_powers_u64_8) {
  using _ = folly::detail::to_ascii_powers<8, uint64_t>;
  EXPECT_EQ(22, _::size);
  EXPECT_EQ(1, _::data.data[0]);
  EXPECT_EQ(8, _::data.data[1]);
  EXPECT_EQ(64, _::data.data[2]);
}

TEST_F(ToAsciiTest, to_ascii_size_max_u64_16) {
  constexpr auto const actual = folly::to_ascii_size_max<16, uint64_t>;
  EXPECT_EQ(16, actual);
}

TEST_F(ToAsciiTest, to_ascii_size_max_u64_10) {
  constexpr auto const actual = folly::to_ascii_size_max<10, uint64_t>;
  EXPECT_EQ(20, actual);
}

TEST_F(ToAsciiTest, to_ascii_size_max_u64_8) {
  constexpr auto const actual = folly::to_ascii_size_max<8, uint64_t>;
  EXPECT_EQ(22, actual);
}

TEST_F(ToAsciiTest, to_ascii_size_imuls_16) {
  to_ascii_size_16(folly::detail::to_ascii_size_imuls<16>);
}

TEST_F(ToAsciiTest, to_ascii_size_idivs_16) {
  to_ascii_size_16(folly::detail::to_ascii_size_idivs<16>);
}

TEST_F(ToAsciiTest, to_ascii_size_array_16) {
  to_ascii_size_16(folly::detail::to_ascii_size_array<16>);
}

TEST_F(ToAsciiTest, to_ascii_size_clzll_16) {
  to_ascii_size_16(folly::detail::to_ascii_size_clzll<16>);
}

TEST_F(ToAsciiTest, to_ascii_size_imuls_10) {
  to_ascii_size_10(folly::detail::to_ascii_size_imuls<10>);
}

TEST_F(ToAsciiTest, to_ascii_size_idivs_10) {
  to_ascii_size_10(folly::detail::to_ascii_size_idivs<10>);
}

TEST_F(ToAsciiTest, to_ascii_size_array_10) {
  to_ascii_size_10(folly::detail::to_ascii_size_array<10>);
}

TEST_F(ToAsciiTest, to_ascii_size_clzll_10) {
  to_ascii_size_10(folly::detail::to_ascii_size_clzll<10>);
}

TEST_F(ToAsciiTest, to_ascii_size_imuls_8) {
  to_ascii_size_8(folly::detail::to_ascii_size_imuls<8>);
}

TEST_F(ToAsciiTest, to_ascii_size_idivs_8) {
  to_ascii_size_8(folly::detail::to_ascii_size_idivs<8>);
}

TEST_F(ToAsciiTest, to_ascii_size_array_8) {
  to_ascii_size_8(folly::detail::to_ascii_size_array<8>);
}

TEST_F(ToAsciiTest, to_ascii_size_clzll_8) {
  to_ascii_size_8(folly::detail::to_ascii_size_clzll<8>);
}

TEST_F(ToAsciiTest, to_ascii_size_imuls_10_compare) {
  to_ascii_size_u64_10_compare(folly::detail::to_ascii_size_imuls<10>);
}

TEST_F(ToAsciiTest, to_ascii_size_idivs_10_compare) {
  to_ascii_size_u64_10_compare(folly::detail::to_ascii_size_idivs<10>);
}

TEST_F(ToAsciiTest, to_ascii_size_array_10_compare) {
  to_ascii_size_u64_10_compare(folly::detail::to_ascii_size_array<10>);
}

TEST_F(ToAsciiTest, to_ascii_size_clzll_10_compare) {
  to_ascii_size_u64_10_compare(folly::detail::to_ascii_size_clzll<10>);
}

template <uint64_t Base>
struct inputs;

template <>
struct inputs<10> {
  static constexpr uint64_t const data[22] = {
      0,
      1,
      12,
      123,
      1234,
      12345,
      123456,
      1234567,
      12345678,
      123456789,
      1234567890,
      12345678901,
      123456789012,
      1234567890123,
      12345678901234,
      123456789012345,
      1234567890123456,
      12345678901234567,
      123456789012345678,
      1234567890123456789,
      -2ull,
      -1ull,
  };
};
constexpr uint64_t const inputs<10>::data[22];

template <uint64_t Base, typename F>
static void to_ascii_compare(F _) {
  for (auto const n : inputs<Base>::data) {
    auto const expected = std::to_string(n);
    SCOPED_TRACE(expected);
    std::string actual(folly::to_ascii_size_max<Base, uint64_t>, '\0');
    auto const size = _(&actual[0], n);
    EXPECT_EQ(expected.size(), size);
    EXPECT_EQ(expected, actual.substr(0, size));
  }
}

using abc = folly::to_ascii_alphabet_lower;

TEST_F(ToAsciiTest, to_ascii_basic_10_compare) {
  to_ascii_compare<10>([](auto out, auto v) {
    return folly::detail::to_ascii_with_basic<10, abc>(out, v);
  });
}

TEST_F(ToAsciiTest, to_ascii_array_10_compare) {
  to_ascii_compare<10>([](auto out, auto v) {
    return folly::detail::to_ascii_with_array<10, abc>(out, v);
  });
}

TEST_F(ToAsciiTest, to_ascii_table_10_compare) {
  to_ascii_compare<10>([](auto out, auto v) {
    return folly::detail::to_ascii_with_table<10, abc>(out, v);
  });
}
