//===----------------------------------------------------------------------===//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef TEST_STD_UTILITIES_FORMAT_FORMAT_RANGE_FORMAT_RANGE_FMTSTR_FORMAT_FUNCTIONS_TESTS_H
#define TEST_STD_UTILITIES_FORMAT_FORMAT_RANGE_FORMAT_RANGE_FMTSTR_FORMAT_FUNCTIONS_TESTS_H

#include <array>
#include <format>
#include <list>

#include "format.functions.common.h"
#include "make_string.h"
#include "platform_support.h" // locale name macros
#include "test_macros.h"

//
// Types
//

template <class Container>
class test_range_format_string {
public:
  explicit test_range_format_string(Container str) : str_(std::move(str)) {}

  typename Container::const_iterator begin() const { return str_.begin(); }
  typename Container::const_iterator end() const { return str_.end(); }

private:
  Container str_;
};

template <class Container>
constexpr std::range_format std::format_kind<test_range_format_string<Container>> = std::range_format::string;

template <class Container>
class test_range_format_debug_string {
public:
  explicit test_range_format_debug_string(Container str) : str_(std::move(str)) {}

  typename Container::const_iterator begin() const { return str_.begin(); }
  typename Container::const_iterator end() const { return str_.end(); }

private:
  Container str_;
};

template <class Container>
constexpr std::range_format std::format_kind<test_range_format_debug_string<Container>> =
    std::range_format::debug_string;

//
// String
//

template <class CharT, class TestFunction, class ExceptionTest>
void test_string(TestFunction check, ExceptionTest check_exception, auto&& input) {
  check(SV("hello"), SV("{}"), input);

  // *** align-fill & width ***
  check(SV("hello     "), SV("{:10}"), input);
  check(SV("hello*****"), SV("{:*<10}"), input);
  check(SV("__hello___"), SV("{:_^10}"), input);
  check(SV(":::::hello"), SV("{::>10}"), input);

  check(SV("hello     "), SV("{:{}}"), input, 10);
  check(SV("hello*****"), SV("{:*<{}}"), input, 10);
  check(SV("__hello___"), SV("{:_^{}}"), input, 10);
  check(SV(":::::hello"), SV("{::>{}}"), input, 10);

  check_exception("The fill option contains an invalid value", SV("{:}<}"), input);
  check_exception("The fill option contains an invalid value", SV("{:{<}"), input);

  // *** sign ***
  check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input);

  // *** alternate form ***
  check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input);

  // *** zero-padding ***
  check_exception("The width option should not have a leading zero", SV("{:0}"), input);

  // *** precision ***
  check(SV("hel"), SV("{:.3}"), input);
  check(SV("hel"), SV("{:.{}}"), input, 3);

  check(SV("hel  "), SV("{:5.3}"), input);
  check(SV("hel  "), SV("{:{}.{}}"), input, 5, 3);

  // *** locale-specific form ***
  check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input);

  // *** type ***
  check(SV("hello"), SV("{:s}"), input);
  check(SV("\"hello\""), SV("{:?}"), input);
  for (std::basic_string_view<CharT> fmt : fmt_invalid_types<CharT>("s?"))
    check_exception("The type option contains an invalid value for a string formatting argument", fmt, input);
}

template <class CharT, class TestFunction, class ExceptionTest>
void test_string(TestFunction check, ExceptionTest check_exception) {
  // libc++ uses different containers for contiguous and non-contiguous ranges.
  std::basic_string<CharT> input = STR("hello");
  test_string<CharT>(check, check_exception, test_range_format_string<std::basic_string<CharT>>{input});
  test_string<CharT>(check, check_exception, test_range_format_string<std::basic_string_view<CharT>>{input});
  test_string<CharT>(
      check, check_exception, test_range_format_string<std::list<CharT>>{std::list<CharT>{input.begin(), input.end()}});
}

//
// String range
//

template <class CharT, class TestFunction, class ExceptionTest>
void test_range_string(TestFunction check, ExceptionTest check_exception, auto&& input) {
  check(SV(R"([Hello, world])"), SV("{}"), input);

  // ***** underlying has no format-spec

  // *** align-fill & width ***
  check(SV(R"([Hello, world]     )"), SV("{:19}"), input);
  check(SV(R"([Hello, world]*****)"), SV("{:*<19}"), input);
  check(SV(R"(__[Hello, world]___)"), SV("{:_^19}"), input);
  check(SV(R"(#####[Hello, world])"), SV("{:#>19}"), input);

  check(SV(R"([Hello, world]     )"), SV("{:{}}"), input, 19);
  check(SV(R"([Hello, world]*****)"), SV("{:*<{}}"), input, 19);
  check(SV(R"(__[Hello, world]___)"), SV("{:_^{}}"), input, 19);
  check(SV(R"(#####[Hello, world])"), SV("{:#>{}}"), input, 19);

  check_exception("The fill option contains an invalid value", SV("{:}<}"), input);
  check_exception("The fill option contains an invalid value", SV("{:{<}"), input);
  check_exception("The fill option contains an invalid value", SV("{::<}"), input);

  // *** sign ***
  check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input);

  // *** alternate form ***
  check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input);

  // *** zero-padding ***
  check_exception("The width option should not have a leading zero", SV("{:0}"), input);

  // *** precision ***
  check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input);

  // *** locale-specific form ***
  check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input);

  // *** n
  check(SV(R"(_Hello, world_)"), SV("{:_^14n}"), input);

  // *** type ***
  check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input);
  check_exception("Type s requires character type as formatting argument", SV("{:s}"), input);
  check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input);

  for (std::basic_string_view<CharT> fmt : fmt_invalid_types<CharT>("s"))
    check_exception("The format specifier should consume the input or end with a '}'", fmt, input);

  // ***** Only underlying has a format-spec
  check(SV(R"([Hello   , world   ])"), SV("{::8}"), input);
  check(SV(R"([Hello***, world***])"), SV("{::*<8}"), input);
  check(SV(R"([_Hello__, _world__])"), SV("{::_^8}"), input);
  check(SV(R"([:::Hello, :::world])"), SV("{:::>8}"), input);

  check(SV(R"([Hello   , world   ])"), SV("{::{}}"), input, 8);
  check(SV(R"([Hello***, world***])"), SV("{::*<{}}"), input, 8);
  check(SV(R"([_Hello__, _world__])"), SV("{::_^{}}"), input, 8);
  check(SV(R"([:::Hello, :::world])"), SV("{:::>{}}"), input, 8);

  check_exception("The fill option contains an invalid value", SV("{::}<}"), input);
  check_exception("The fill option contains an invalid value", SV("{::{<}"), input);

  // *** sign ***
  check_exception("The format specifier should consume the input or end with a '}'", SV("{::-}"), input);

  // *** alternate form ***
  check_exception("The format specifier should consume the input or end with a '}'", SV("{::#}"), input);

  // *** zero-padding ***
  check_exception("The width option should not have a leading zero", SV("{::05}"), input);

  // *** precision ***
  check(SV(R"([Hel, wor])"), SV("{::.3}"), input);

  check(SV(R"([Hel, wor])"), SV("{::.{}}"), input, 3);

  check_exception("The precision option does not contain a value or an argument index", SV("{::.}"), input);

  // *** locale-specific form ***
  check_exception("The format specifier should consume the input or end with a '}'", SV("{::L}"), input);

  // *** type ***
  for (std::basic_string_view<CharT> fmt : fmt_invalid_nested_types<CharT>("s?"))
    check_exception("The type option contains an invalid value for a string formatting argument", fmt, input);

  // ***** Both have a format-spec
  check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^25::>8}"), input);
  check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>8}"), input, 25);
  check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>{}}"), input, 25, 8);

  check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^25::>8}"), input);
  check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>8}"), input, 25);
  check(SV(R"(^^[:::Hello, :::world]^^^)"), SV("{:^^{}::>{}}"), input, 25, 8);

  check_exception(
      "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>8}"), input);
  check_exception(
      "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}}"), input, 25);
}

template <class CharT, class TestFunction, class ExceptionTest>
void test_range_string(TestFunction check, ExceptionTest check_exception) {
  // libc++ uses different containers for contiguous and non-contiguous ranges.
  std::array input{STR("Hello"), STR("world")};
  test_range_string<CharT>(
      check,
      check_exception,
      std::array{test_range_format_string<std::basic_string<CharT>>{input[0]},
                 test_range_format_string<std::basic_string<CharT>>{input[1]}});
  test_range_string<CharT>(
      check,
      check_exception,
      std::array{test_range_format_string<std::basic_string_view<CharT>>{input[0]},
                 test_range_format_string<std::basic_string_view<CharT>>{input[1]}});
  test_range_string<CharT>(
      check,
      check_exception,
      std::array{test_range_format_string<std::list<CharT>>{std::list<CharT>{input[0].begin(), input[0].end()}},
                 test_range_format_string<std::list<CharT>>{std::list<CharT>{input[1].begin(), input[1].end()}}});
  test_range_string<CharT>(
      check,
      check_exception,
      std::list{test_range_format_string<std::list<CharT>>{std::list<CharT>{input[0].begin(), input[0].end()}},
                test_range_format_string<std::list<CharT>>{std::list<CharT>{input[1].begin(), input[1].end()}}});
}

//
// Debug string
//

template <class CharT, class TestFunction, class ExceptionTest>
void test_debug_string(TestFunction check, ExceptionTest check_exception, auto&& input) {
  check(SV("\"hello\""), SV("{}"), input);

  // *** align-fill & width ***
  check(SV("\"hello\"     "), SV("{:12}"), input);
  check(SV("\"hello\"*****"), SV("{:*<12}"), input);
  check(SV("__\"hello\"___"), SV("{:_^12}"), input);
  check(SV(":::::\"hello\""), SV("{::>12}"), input);

  check(SV("\"hello\"     "), SV("{:{}}"), input, 12);
  check(SV("\"hello\"*****"), SV("{:*<{}}"), input, 12);
  check(SV("__\"hello\"___"), SV("{:_^{}}"), input, 12);
  check(SV(":::::\"hello\""), SV("{::>{}}"), input, 12);

  check_exception("The fill option contains an invalid value", SV("{:}<}"), input);
  check_exception("The fill option contains an invalid value", SV("{:{<}"), input);

  // *** sign ***
  check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input);

  // *** alternate form ***
  check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input);

  // *** zero-padding ***
  check_exception("The width option should not have a leading zero", SV("{:0}"), input);

  // *** precision ***
  check(SV("\"he"), SV("{:.3}"), input);
  check(SV("\"he"), SV("{:.{}}"), input, 3);

  check(SV("\"he  "), SV("{:5.3}"), input);
  check(SV("\"he  "), SV("{:{}.{}}"), input, 5, 3);

  // *** locale-specific form ***
  check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input);

  // *** type ***
  check(SV("\"hello\""), SV("{:s}"), input); // escape overrides the type option s
  check(SV("\"hello\""), SV("{:?}"), input);
  for (std::basic_string_view<CharT> fmt : fmt_invalid_types<CharT>("s?"))
    check_exception("The type option contains an invalid value for a string formatting argument", fmt, input);
}

template <class CharT, class TestFunction, class ExceptionTest>
void test_debug_string(TestFunction check, ExceptionTest check_exception) {
  // libc++ uses different containers for contiguous and non-contiguous ranges.
  std::basic_string<CharT> input = STR("hello");
  test_debug_string<CharT>(check, check_exception, test_range_format_debug_string<std::basic_string<CharT>>{input});
  test_debug_string<CharT>(
      check, check_exception, test_range_format_debug_string<std::basic_string_view<CharT>>{input});
  test_debug_string<CharT>(
      check,
      check_exception,
      test_range_format_debug_string<std::list<CharT>>{std::list<CharT>{input.begin(), input.end()}});
}

//
// Debug string range
//

template <class CharT, class TestFunction, class ExceptionTest>
void test_range_debug_string(TestFunction check, ExceptionTest check_exception, auto&& input) {
  // ***** underlying has no format-spec

  // *** align-fill & width ***
  check(SV(R"(["Hello", "world"]     )"), SV("{:23}"), input);
  check(SV(R"(["Hello", "world"]*****)"), SV("{:*<23}"), input);
  check(SV(R"(__["Hello", "world"]___)"), SV("{:_^23}"), input);
  check(SV(R"(#####["Hello", "world"])"), SV("{:#>23}"), input);

  check(SV(R"(["Hello", "world"]     )"), SV("{:{}}"), input, 23);
  check(SV(R"(["Hello", "world"]*****)"), SV("{:*<{}}"), input, 23);
  check(SV(R"(__["Hello", "world"]___)"), SV("{:_^{}}"), input, 23);
  check(SV(R"(#####["Hello", "world"])"), SV("{:#>{}}"), input, 23);

  check_exception("The fill option contains an invalid value", SV("{:}<}"), input);
  check_exception("The fill option contains an invalid value", SV("{:{<}"), input);
  check_exception("The fill option contains an invalid value", SV("{::<}"), input);

  // *** sign ***
  check_exception("The format specifier should consume the input or end with a '}'", SV("{:-}"), input);

  // *** alternate form ***
  check_exception("The format specifier should consume the input or end with a '}'", SV("{:#}"), input);

  // *** zero-padding ***
  check_exception("The width option should not have a leading zero", SV("{:0}"), input);

  // *** precision ***
  check_exception("The format specifier should consume the input or end with a '}'", SV("{:.}"), input);

  // *** locale-specific form ***
  check_exception("The format specifier should consume the input or end with a '}'", SV("{:L}"), input);

  // *** n
  check(SV(R"(_"Hello", "world"_)"), SV("{:_^18n}"), input);

  // *** type ***
  check_exception("Type m requires a pair or a tuple with two elements", SV("{:m}"), input);
  check_exception("Type s requires character type as formatting argument", SV("{:s}"), input);
  check_exception("Type ?s requires character type as formatting argument", SV("{:?s}"), input);

  for (std::basic_string_view<CharT> fmt : fmt_invalid_types<CharT>("s"))
    check_exception("The format specifier should consume the input or end with a '}'", fmt, input);

  // ***** Only underlying has a format-spec
  check(SV(R"(["Hello"   , "world"   ])"), SV("{::10}"), input);
  check(SV(R"(["Hello"***, "world"***])"), SV("{::*<10}"), input);
  check(SV(R"([_"Hello"__, _"world"__])"), SV("{::_^10}"), input);
  check(SV(R"([:::"Hello", :::"world"])"), SV("{:::>10}"), input);

  check(SV(R"(["Hello"   , "world"   ])"), SV("{::{}}"), input, 10);
  check(SV(R"(["Hello"***, "world"***])"), SV("{::*<{}}"), input, 10);
  check(SV(R"([_"Hello"__, _"world"__])"), SV("{::_^{}}"), input, 10);
  check(SV(R"([:::"Hello", :::"world"])"), SV("{:::>{}}"), input, 10);

  check_exception("The fill option contains an invalid value", SV("{::}<}"), input);
  check_exception("The fill option contains an invalid value", SV("{::{<}"), input);

  // *** sign ***
  check_exception("The format specifier should consume the input or end with a '}'", SV("{::-}"), input);

  // *** alternate form ***
  check_exception("The format specifier should consume the input or end with a '}'", SV("{::#}"), input);

  // *** zero-padding ***
  check_exception("The width option should not have a leading zero", SV("{::05}"), input);

  // *** precision ***
  check(SV(R"(["He, "wo])"), SV("{::.3}"), input);

  check(SV(R"(["He, "wo])"), SV("{::.{}}"), input, 3);

  check_exception("The precision option does not contain a value or an argument index", SV("{::.}"), input);

  // *** locale-specific form ***
  check_exception("The format specifier should consume the input or end with a '}'", SV("{::L}"), input);

  // *** type ***
  for (std::basic_string_view<CharT> fmt : fmt_invalid_nested_types<CharT>("s?"))
    check_exception("The type option contains an invalid value for a string formatting argument", fmt, input);

  // ***** Both have a format-spec
  check(SV(R"(^^[:::"Hello", :::"world"]^^^)"), SV("{:^^29::>10}"), input);
  check(SV(R"(^^[:::"Hello", :::"world"]^^^)"), SV("{:^^{}::>10}"), input, 29);
  check(SV(R"(^^[:::"Hello", :::"world"]^^^)"), SV("{:^^{}::>{}}"), input, 29, 10);

  check(SV(R"(^^[:::"Hello", :::"world"]^^^)"), SV("{:^^29::>10}"), input);
  check(SV(R"(^^[:::"Hello", :::"world"]^^^)"), SV("{:^^{}::>10}"), input, 29);
  check(SV(R"(^^[:::"Hello", :::"world"]^^^)"), SV("{:^^{}::>{}}"), input, 29, 10);

  check_exception(
      "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>10}"), input);
  check_exception(
      "The argument index value is too large for the number of arguments supplied", SV("{:^^{}::>{}}"), input, 29);
}

template <class CharT, class TestFunction, class ExceptionTest>
void test_range_debug_string(TestFunction check, ExceptionTest check_exception) {
  // libc++ uses different containers for contiguous and non-contiguous ranges.
  std::array input{STR("Hello"), STR("world")};
  test_range_debug_string<CharT>(
      check,
      check_exception,
      std::array{test_range_format_debug_string<std::basic_string<CharT>>{input[0]},
                 test_range_format_debug_string<std::basic_string<CharT>>{input[1]}});
  test_range_debug_string<CharT>(
      check,
      check_exception,
      std::array{test_range_format_debug_string<std::basic_string_view<CharT>>{input[0]},
                 test_range_format_debug_string<std::basic_string_view<CharT>>{input[1]}});
  test_range_debug_string<CharT>(
      check,
      check_exception,
      std::array{test_range_format_debug_string<std::list<CharT>>{std::list<CharT>{input[0].begin(), input[0].end()}},
                 test_range_format_debug_string<std::list<CharT>>{std::list<CharT>{input[1].begin(), input[1].end()}}});
  test_range_debug_string<CharT>(
      check,
      check_exception,
      std::list{test_range_format_debug_string<std::list<CharT>>{std::list<CharT>{input[0].begin(), input[0].end()}},
                test_range_format_debug_string<std::list<CharT>>{std::list<CharT>{input[1].begin(), input[1].end()}}});
}

//
// Driver
//

template <class CharT, class TestFunction, class ExceptionTest>
void format_tests(TestFunction check, ExceptionTest check_exception) {
  test_string<CharT>(check, check_exception);
  test_range_string<CharT>(check, check_exception);

  test_debug_string<CharT>(check, check_exception);
  test_range_debug_string<CharT>(check, check_exception);
}

#endif //  TEST_STD_UTILITIES_FORMAT_FORMAT_RANGE_FORMAT_RANGE_FMTSTR_FORMAT_FUNCTIONS_TESTS_H
