//===----------------------------------------------------------------------===////
//
// 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 FILESYSTEM_ERROR_H
#define FILESYSTEM_ERROR_H

#include <__assert>
#include <__chrono/time_point.h>
#include <__config>
#include <cerrno>
#include <cstdarg>
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <string>
#include <system_error>
#include <utility> // __libcpp_unreachable

#include "format_string.h"

#if defined(_LIBCPP_WIN32API)
#  define WIN32_LEAN_AND_MEAN
#  define NOMINMAX
#  include <windows.h> // ERROR_* macros
#endif

_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM

namespace detail {

// On windows, libc functions use errno, but system functions use GetLastError.
// So, callers need to be careful which of these next functions they call!

inline error_code capture_errno() {
  _LIBCPP_ASSERT_INTERNAL(errno != 0, "Expected errno to be non-zero");
  return error_code(errno, generic_category());
}

inline error_code get_last_error() {
#if defined(_LIBCPP_WIN32API)
  return std::error_code(GetLastError(), std::system_category());
#else
  return capture_errno();
#endif
}

template <class T>
T error_value();
template <>
inline constexpr void error_value<void>() {}
template <>
inline bool error_value<bool>() {
  return false;
}
#if __SIZEOF_SIZE_T__ != __SIZEOF_LONG_LONG__
template <>
inline size_t error_value<size_t>() {
  return size_t(-1);
}
#endif
template <>
inline uintmax_t error_value<uintmax_t>() {
  return uintmax_t(-1);
}
template <>
inline constexpr file_time_type error_value<file_time_type>() {
  return file_time_type::min();
}
template <>
inline path error_value<path>() {
  return {};
}

template <class T>
struct ErrorHandler {
  const char* func_name_;
  error_code* ec_ = nullptr;
  const path* p1_ = nullptr;
  const path* p2_ = nullptr;

  ErrorHandler(const char* fname, error_code* ec, const path* p1 = nullptr, const path* p2 = nullptr)
      : func_name_(fname), ec_(ec), p1_(p1), p2_(p2) {
    if (ec_)
      ec_->clear();
  }

  T report(const error_code& ec) const {
    if (ec_) {
      *ec_ = ec;
      return error_value<T>();
    }
    string what = string("in ") + func_name_;
    switch (bool(p1_) + bool(p2_)) {
    case 0:
      filesystem::__throw_filesystem_error(what, ec);
    case 1:
      filesystem::__throw_filesystem_error(what, *p1_, ec);
    case 2:
      filesystem::__throw_filesystem_error(what, *p1_, *p2_, ec);
    }
    __libcpp_unreachable();
  }

  _LIBCPP_ATTRIBUTE_FORMAT(__printf__, 3, 0)
  void report_impl(const error_code& ec, const char* msg, va_list ap) const {
    if (ec_) {
      *ec_ = ec;
      return;
    }
    string what = string("in ") + func_name_ + ": " + detail::vformat_string(msg, ap);
    switch (bool(p1_) + bool(p2_)) {
    case 0:
      filesystem::__throw_filesystem_error(what, ec);
    case 1:
      filesystem::__throw_filesystem_error(what, *p1_, ec);
    case 2:
      filesystem::__throw_filesystem_error(what, *p1_, *p2_, ec);
    }
    __libcpp_unreachable();
  }

  _LIBCPP_ATTRIBUTE_FORMAT(__printf__, 3, 4)
  T report(const error_code& ec, const char* msg, ...) const {
    va_list ap;
    va_start(ap, msg);
#if _LIBCPP_HAS_EXCEPTIONS
    try {
#endif // _LIBCPP_HAS_EXCEPTIONS
      report_impl(ec, msg, ap);
#if _LIBCPP_HAS_EXCEPTIONS
    } catch (...) {
      va_end(ap);
      throw;
    }
#endif // _LIBCPP_HAS_EXCEPTIONS
    va_end(ap);
    return error_value<T>();
  }

  T report(errc const& err) const { return report(make_error_code(err)); }

  _LIBCPP_ATTRIBUTE_FORMAT(__printf__, 3, 4)
  T report(errc const& err, const char* msg, ...) const {
    va_list ap;
    va_start(ap, msg);
#if _LIBCPP_HAS_EXCEPTIONS
    try {
#endif // _LIBCPP_HAS_EXCEPTIONS
      report_impl(make_error_code(err), msg, ap);
#if _LIBCPP_HAS_EXCEPTIONS
    } catch (...) {
      va_end(ap);
      throw;
    }
#endif // _LIBCPP_HAS_EXCEPTIONS
    va_end(ap);
    return error_value<T>();
  }

private:
  ErrorHandler(ErrorHandler const&)            = delete;
  ErrorHandler& operator=(ErrorHandler const&) = delete;
};

} // namespace detail

_LIBCPP_END_NAMESPACE_FILESYSTEM

#endif // FILESYSTEM_ERROR_H
