//===----------------------------------------------------------------------===//
// 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
//
//===----------------------------------------------------------------------===//

// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: no-localization
// UNSUPPORTED: libcpp-has-no-incomplete-format

// TODO FMT It seems GCC uses too much memory in the CI and fails.
// UNSUPPORTED: gcc-12

// REQUIRES: locale.fr_FR.UTF-8
// REQUIRES: locale.ja_JP.UTF-8

// <chrono>

// template<class charT> struct formatter<chrono::year_month_weekday_last, charT>;

#include <chrono>
#include <format>

#include <cassert>
#include <concepts>
#include <locale>
#include <iostream>
#include <type_traits>

#include "formatter_tests.h"
#include "make_string.h"
#include "platform_support.h" // locale name macros
#include "string_literal.h"
#include "test_macros.h"

template <class CharT>
static void test_no_chrono_specs() {
  // Valid
  check(SV("1970/Jan/Mon[last]"),
        SV("{}"),
        std::chrono::year_month_weekday_last{
            std::chrono::year{1970}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{1}}});
  check(SV("*1970/Jan/Mon[last]*"),
        SV("{:*^20}"),
        std::chrono::year_month_weekday_last{
            std::chrono::year{1970}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{1}}});
  check(SV("*1970/Jan/Mon[last]"),
        SV("{:*>19}"),
        std::chrono::year_month_weekday_last{
            std::chrono::year{1970}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{1}}});

  // Invalid
  check(SV("1970/Jan/8 is not a valid weekday[last]"),
        SV("{}"),
        std::chrono::year_month_weekday_last{
            std::chrono::year{1970}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{8}}});
  check(SV("1970/0 is not a valid month/Mon[last]"),
        SV("{}"),
        std::chrono::year_month_weekday_last{
            std::chrono::year{1970}, std::chrono::month{0}, std::chrono::weekday_last{std::chrono::weekday{1}}});
  check(SV("-32768 is not a valid year/Jan/Mon[last]"),
        SV("{}"),
        std::chrono::year_month_weekday_last{
            std::chrono::year{-32768}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{1}}});
}

template <class CharT>
static void test_invalid_values() {
  // *** Invalid weekday ***

  // Weekday name conversion
  check_exception(
      "formatting a weekday name needs a valid weekday",
      SV("{:%a}"),
      std::chrono::year_month_weekday_last{
          std::chrono::year{1970}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{8}}});
  check_exception(
      "formatting a weekday name needs a valid weekday",
      SV("{:%A}"),
      std::chrono::year_month_weekday_last{
          std::chrono::year{1970}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{8}}});

  // Weekday conversion
  check_exception(
      "formatting a weekday needs a valid weekday",
      SV("{:%u}"),
      std::chrono::year_month_weekday_last{
          std::chrono::year{1970}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{8}}});
  check_exception(
      "formatting a weekday needs a valid weekday",
      SV("{:%w}"),
      std::chrono::year_month_weekday_last{
          std::chrono::year{1970}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{8}}});
  check_exception(
      "formatting a weekday needs a valid weekday",
      SV("{:%Ou}"),
      std::chrono::year_month_weekday_last{
          std::chrono::year{1970}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{8}}});
  check_exception(
      "formatting a weekday needs a valid weekday",
      SV("{:%Ow}"),
      std::chrono::year_month_weekday_last{
          std::chrono::year{1970}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{8}}});

  // Day of year field
  check_exception(
      "formatting a day of year needs a valid date",
      SV("{:%j}"),
      std::chrono::year_month_weekday_last{
          std::chrono::year{1970}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{8}}});

  // Month name conversion
  check(SV("Jan"),
        SV("{:%b}"),
        std::chrono::year_month_weekday_last{
            std::chrono::year{1970}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{8}}});
  check(SV("Jan"),
        SV("{:%h}"),
        std::chrono::year_month_weekday_last{
            std::chrono::year{1970}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{8}}});
  check(SV("January"),
        SV("{:%B}"),
        std::chrono::year_month_weekday_last{
            std::chrono::year{1970}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{8}}});

  // *** Invalid month ***

  // Weekday name conversion
  check(SV("Mon"),
        SV("{:%a}"),
        std::chrono::year_month_weekday_last{
            std::chrono::year{1970}, std::chrono::month{0}, std::chrono::weekday_last{std::chrono::weekday{1}}});
  check(SV("Monday"),
        SV("{:%A}"),
        std::chrono::year_month_weekday_last{
            std::chrono::year{1970}, std::chrono::month{0}, std::chrono::weekday_last{std::chrono::weekday{1}}});
  // Weekday conversion
  check(SV("1"),
        SV("{:%u}"),
        std::chrono::year_month_weekday_last{
            std::chrono::year{1970}, std::chrono::month{0}, std::chrono::weekday_last{std::chrono::weekday{1}}});
  check(SV("1"),
        SV("{:%w}"),
        std::chrono::year_month_weekday_last{
            std::chrono::year{1970}, std::chrono::month{0}, std::chrono::weekday_last{std::chrono::weekday{1}}});
  check(SV("1"),
        SV("{:%Ou}"),
        std::chrono::year_month_weekday_last{
            std::chrono::year{1970}, std::chrono::month{0}, std::chrono::weekday_last{std::chrono::weekday{1}}});
  check(SV("1"),
        SV("{:%Ow}"),
        std::chrono::year_month_weekday_last{
            std::chrono::year{1970}, std::chrono::month{0}, std::chrono::weekday_last{std::chrono::weekday{1}}});

  // Day of year field
  check_exception(
      "formatting a day of year needs a valid date",
      SV("{:%j}"),
      std::chrono::year_month_weekday_last{
          std::chrono::year{1970}, std::chrono::month{0}, std::chrono::weekday_last{std::chrono::weekday{1}}});

  // Month name conversion
  check_exception(
      "formatting a month name from an invalid month number",
      SV("{:%b}"),
      std::chrono::year_month_weekday_last{
          std::chrono::year{1970}, std::chrono::month{0}, std::chrono::weekday_last{std::chrono::weekday{1}}});
  check_exception(
      "formatting a month name from an invalid month number",
      SV("{:%h}"),
      std::chrono::year_month_weekday_last{
          std::chrono::year{1970}, std::chrono::month{0}, std::chrono::weekday_last{std::chrono::weekday{1}}});
  check_exception(
      "formatting a month name from an invalid month number",
      SV("{:%B}"),
      std::chrono::year_month_weekday_last{
          std::chrono::year{1970}, std::chrono::month{0}, std::chrono::weekday_last{std::chrono::weekday{1}}});

  // *** Invalid year ***

  // Weekday name conversion
  check(SV("Mon"),
        SV("{:%a}"),
        std::chrono::year_month_weekday_last{
            std::chrono::year{-32768}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{1}}});
  check(SV("Monday"),
        SV("{:%A}"),
        std::chrono::year_month_weekday_last{
            std::chrono::year{-32768}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{1}}});

  // Weekday conversion
  check(SV("1"),
        SV("{:%u}"),
        std::chrono::year_month_weekday_last{
            std::chrono::year{-32768}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{1}}});
  check(SV("1"),
        SV("{:%w}"),
        std::chrono::year_month_weekday_last{
            std::chrono::year{-32768}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{1}}});
  check(SV("1"),
        SV("{:%Ou}"),
        std::chrono::year_month_weekday_last{
            std::chrono::year{-32768}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{1}}});
  check(SV("1"),
        SV("{:%Ow}"),
        std::chrono::year_month_weekday_last{
            std::chrono::year{-32768}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{1}}});

  // Day of year field
  check_exception(
      "formatting a day of year needs a valid date",
      SV("{:%j}"),
      std::chrono::year_month_weekday_last{
          std::chrono::year{-32768}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{1}}});

  // Month name conversion
  check(SV("Jan"),
        SV("{:%b}"),
        std::chrono::year_month_weekday_last{
            std::chrono::year{-32768}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{1}}});
  check(SV("Jan"),
        SV("{:%h}"),
        std::chrono::year_month_weekday_last{
            std::chrono::year{-32768}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{1}}});
  check(SV("January"),
        SV("{:%B}"),
        std::chrono::year_month_weekday_last{
            std::chrono::year{-32768}, std::chrono::month{1}, std::chrono::weekday_last{std::chrono::weekday{1}}});
}

template <class CharT>
static void test() {
  test_no_chrono_specs<CharT>();
  test_invalid_values<CharT>();
}

int main(int, char**) {
  test<char>();

#ifndef TEST_HAS_NO_WIDE_CHARACTERS
  test<wchar_t>();
#endif

  return 0;
}
