// Formatting library for C++ - scanning API test
//
// Copyright (c) 2019 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.

#include "scan.h"

#include <time.h>

#include <climits>
#include <thread>

#include "fmt/os.h"
#include "gmock/gmock.h"
#include "gtest-extra.h"

TEST(scan_test, read_text) {
  fmt::string_view s = "foo";
  auto end = fmt::scan_to(s, "foo");
  EXPECT_EQ(end, s.end());
  EXPECT_THROW_MSG(fmt::scan<int>("fob", "foo"), fmt::format_error,
                   "invalid input");
}

TEST(scan_test, read_int) {
  EXPECT_EQ(fmt::scan<int>("42", "{}")->value(), 42);
  EXPECT_EQ(fmt::scan<int>("-42", "{}")->value(), -42);
  EXPECT_EQ(fmt::scan<int>("42", "{:}")->value(), 42);
  EXPECT_THROW_MSG(fmt::scan<int>(std::to_string(INT_MAX + 1u), "{}"),
                   fmt::format_error, "number is too big");
}

TEST(scan_test, read_long_long) {
  EXPECT_EQ(fmt::scan<long long>("42", "{}")->value(), 42);
  EXPECT_EQ(fmt::scan<long long>("-42", "{}")->value(), -42);
}

TEST(scan_test, read_uint) {
  EXPECT_EQ(fmt::scan<unsigned>("42", "{}")->value(), 42);
  EXPECT_THROW_MSG(fmt::scan<unsigned>("-42", "{}"), fmt::format_error,
                   "invalid input");
}

TEST(scan_test, read_ulong_long) {
  EXPECT_EQ(fmt::scan<unsigned long long>("42", "{}")->value(), 42);
  EXPECT_THROW_MSG(fmt::scan<unsigned long long>("-42", "{}")->value(),
                   fmt::format_error, "invalid input");
}

TEST(scan_test, read_hex) {
  EXPECT_EQ(fmt::scan<unsigned>("2a", "{:x}")->value(), 42);
  auto num_digits = std::numeric_limits<unsigned>::digits / 4;
  EXPECT_THROW_MSG(
      fmt::scan<unsigned>(fmt::format("1{:0{}}", 0, num_digits), "{:x}")
          ->value(),
      fmt::format_error, "number is too big");
}

TEST(scan_test, read_string) {
  EXPECT_EQ(fmt::scan<std::string>("foo", "{}")->value(), "foo");
}

TEST(scan_test, read_string_view) {
  EXPECT_EQ(fmt::scan<fmt::string_view>("foo", "{}")->value(), "foo");
}

TEST(scan_test, separator) {
  int n1 = 0, n2 = 0;
  fmt::scan_to("10 20", "{} {}", n1, n2);
  EXPECT_EQ(n1, 10);
  EXPECT_EQ(n2, 20);
}

struct num {
  int value;
};

namespace fmt {
template <> struct scanner<num> {
  bool hex = false;

  auto parse(scan_parse_context& ctx) -> scan_parse_context::iterator {
    auto it = ctx.begin(), end = ctx.end();
    if (it != end && *it == 'x') {
      hex = true;
      ++it;
    }
    if (it != end && *it != '}') report_error("invalid format");
    return it;
  }

  template <class ScanContext>
  auto scan(num& n, ScanContext& ctx) const -> typename ScanContext::iterator {
    return hex ? scan_to(ctx, "{:x}", n.value) : scan_to(ctx, "{}", n.value);
  }
};
}  // namespace fmt

TEST(scan_test, read_custom) {
  EXPECT_EQ(fmt::scan<num>("42", "{}")->value().value, 42);
  EXPECT_EQ(fmt::scan<num>("2a", "{:x}")->value().value, 42);
}

TEST(scan_test, invalid_format) {
  EXPECT_THROW_MSG(fmt::scan_to("", "{}"), fmt::format_error,
                   "argument index out of range");
  EXPECT_THROW_MSG(fmt::scan_to("", "{"), fmt::format_error,
                   "invalid format string");
}

namespace std {
  using fmt::scan;
  using fmt::scan_error;
}

TEST(scan_test, example) {
  // Example from https://wg21.link/p1729r3.
  if (auto result = std::scan<std::string, int>("answer = 42", "{} = {}")) {
    auto range = result->range();
    EXPECT_EQ(range.begin(), range.end());
    EXPECT_EQ(result->begin(), result->end());
#ifdef __cpp_structured_bindings
    const auto& [key, value] = result->values();
    EXPECT_EQ(key, "answer");
    EXPECT_EQ(value, 42);
#endif
  } else {
    std::scan_error error = result.error();
    (void)error;
    FAIL();
  }
}

TEST(scan_test, end_of_input) { fmt::scan<int>("", "{}"); }

#if FMT_USE_FCNTL
TEST(scan_test, file) {
  auto pipe = fmt::pipe();

  fmt::string_view input = "10 20";
  pipe.write_end.write(input.data(), input.size());
  pipe.write_end.close();

  int n1 = 0, n2 = 0;
  fmt::buffered_file f = pipe.read_end.fdopen("r");
  fmt::scan_to(f.get(), "{} {}", n1, n2);
  EXPECT_EQ(n1, 10);
  EXPECT_EQ(n2, 20);
}

TEST(scan_test, lock) {
  auto pipe = fmt::pipe();

  std::thread producer([&]() {
    fmt::string_view input = "42 ";
    for (int i = 0; i < 1000; ++i)
      pipe.write_end.write(input.data(), input.size());
    pipe.write_end.close();
  });

  std::atomic<int> count(0);
  fmt::buffered_file f = pipe.read_end.fdopen("r");
  auto fun = [&]() {
    int value = 0;
    while (fmt::scan_to(f.get(), "{}", value)) {
      if (value != 42) {
        pipe.read_end.close();
        EXPECT_EQ(value, 42);
        break;
      }
      ++count;
    }
  };
  std::thread consumer1(fun);
  std::thread consumer2(fun);

  producer.join();
  consumer1.join();
  consumer2.join();
  EXPECT_EQ(count, 1000);
}
#endif  // FMT_USE_FCNTL
