//===----------------------------------------------------------------------===//
// 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, c++20
// UNSUPPORTED: no-filesystem
// UNSUPPORTED: libcpp-has-no-unicode
// UNSUPPORTED: GCC-ALWAYS_INLINE-FIXME

// TODO PRINT Enable again
// https://reviews.llvm.org/D150044
// https://lab.llvm.org/buildbot/#/builders/237/builds/3578
// UNSUPPORTED: asan, hwasan, msan

// XFAIL: availability-fp_to_chars-missing

// The error exception has no system error string.
// XFAIL: LIBCXX-ANDROID-FIXME

// <print>

// void vprint_unicode(FILE* stream, string_view fmt, format_args args);

// In the library when the stdout is redirected to a file it is no
// longer considered a terminal and the special terminal handling is no
// longer executed. There are tests in
//   libcxx/test/libcxx/input.output/iostream.format/print.fun/
// to validate that behaviour

#include <algorithm>
#include <array>
#include <cassert>
#include <cstddef>
#include <cstdio>
#include <fstream>
#include <iterator>
#include <print>
#include <string_view>

#include "assert_macros.h"
#include "concat_macros.h"
#include "filesystem_test_helper.h"
#include "print_tests.h"
#include "test_macros.h"

scoped_test_env env;
std::string filename = env.create_file("output.txt");

auto test_file = []<class... Args>(std::string_view expected, std::string_view fmt, Args&&... args) {
  FILE* file = fopen(filename.c_str(), "wb");
  assert(file);

  std::vprint_unicode(file, fmt, std::make_format_args(args...));
  std::fclose(file);

  std::ifstream stream{filename.c_str(), std::ios_base::in | std::ios_base::binary};
  std::string out(std::istreambuf_iterator<char>{stream}, {});
  TEST_REQUIRE(out == expected,
               TEST_WRITE_CONCATENATED(
                   "\nFormat string   ", fmt, "\nExpected output ", expected, "\nActual output   ", out, '\n'));
};

auto test_exception = []<class... Args>([[maybe_unused]] std::string_view what,
                                        [[maybe_unused]] std::string_view fmt,
                                        [[maybe_unused]] Args&&... args) {
  FILE* file = fopen(filename.c_str(), "wb");
  assert(file);

  TEST_VALIDATE_EXCEPTION(
      std::format_error,
      [&]([[maybe_unused]] const std::format_error& e) {
        TEST_LIBCPP_REQUIRE(
            e.what() == what,
            TEST_WRITE_CONCATENATED(
                "\nFormat string   ", fmt, "\nExpected exception ", what, "\nActual exception   ", e.what(), '\n'));
      },
      std::vprint_unicode(file, fmt, std::make_format_args(args...)));

  fclose(file);
};

// Glibc fails writing to a wide stream.
#if defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS)
static void test_wide_stream() {
  FILE* file = fopen(filename.c_str(), "wb");
  assert(file);

  int mode = std::fwide(file, 1);
  assert(mode > 0);

  TEST_VALIDATE_EXCEPTION(
      std::system_error,
      [&]([[maybe_unused]] const std::system_error& e) {
        [[maybe_unused]] std::string_view what{"failed to write formatted output"};
        TEST_LIBCPP_REQUIRE(
            e.what() == what,
            TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception   ", e.what(), '\n'));
      },
      std::vprint_unicode(file, "hello", std::make_format_args()));
}
#endif // defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS)

static void test_read_only() {
  FILE* file = fopen(filename.c_str(), "r");
  assert(file);

  TEST_VALIDATE_EXCEPTION(
      std::system_error,
      [&]([[maybe_unused]] const std::system_error& e) {
        [[maybe_unused]] std::string_view what{
            "failed to write formatted output: " TEST_IF_AIX("Broken pipe", "Operation not permitted")};
        TEST_LIBCPP_REQUIRE(
            e.what() == what,
            TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception   ", e.what(), '\n'));
      },
      std::vprint_unicode(file, "hello", std::make_format_args()));
}

static void test_new_line() {
  // Text does newline translation.
  {
    FILE* file = fopen(filename.c_str(), "w");
    assert(file);

    std::vprint_unicode(file, "\n", std::make_format_args());
#ifndef _WIN32
    assert(std::ftell(file) == 1);
#else
    assert(std::ftell(file) == 2);
#endif
  }
  // Binary no newline translation.
  {
    FILE* file = fopen(filename.c_str(), "wb");
    assert(file);

    std::vprint_unicode(file, "\n", std::make_format_args());
    assert(std::ftell(file) == 1);
  }
}

int main(int, char**) {
  print_tests(test_file, test_exception);

#if defined(TEST_HAS_GLIBC) && !defined(TEST_HAS_NO_WIDE_CHARACTERS)
  test_wide_stream();
#endif
  test_read_only();
  test_new_line();

  return 0;
}
