//===-- runtime/io-error.cpp ----------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

#include "io-error.h"
#include "config.h"
#include "tools.h"
#include "flang/Runtime/magic-numbers.h"
#include <cerrno>
#include <cstdarg>
#include <cstdio>
#include <cstring>

namespace Fortran::runtime::io {

void IoErrorHandler::SignalError(int iostatOrErrno, const char *msg, ...) {
  if (iostatOrErrno == IostatEnd && (flags_ & hasEnd)) {
    if (ioStat_ == IostatOk || ioStat_ < IostatEnd) {
      ioStat_ = IostatEnd;
    }
  } else if (iostatOrErrno == IostatEor && (flags_ & hasEor)) {
    if (ioStat_ == IostatOk || ioStat_ < IostatEor) {
      ioStat_ = IostatEor; // least priority
    }
  } else if (iostatOrErrno != IostatOk) {
    if (flags_ & (hasIoStat | hasErr)) {
      if (ioStat_ <= 0) {
        ioStat_ = iostatOrErrno; // priority over END=/EOR=
        if (msg && (flags_ & hasIoMsg)) {
          char buffer[256];
          va_list ap;
          va_start(ap, msg);
          std::vsnprintf(buffer, sizeof buffer, msg, ap);
          ioMsg_ = SaveDefaultCharacter(buffer, std::strlen(buffer) + 1, *this);
          va_end(ap);
        }
      }
    } else if (msg) {
      va_list ap;
      va_start(ap, msg);
      CrashArgs(msg, ap);
      va_end(ap);
    } else if (const char *errstr{IostatErrorString(iostatOrErrno)}) {
      Crash(errstr);
    } else {
      Crash("I/O error (errno=%d): %s", iostatOrErrno,
          std::strerror(iostatOrErrno));
    }
  }
}

void IoErrorHandler::SignalError(int iostatOrErrno) {
  SignalError(iostatOrErrno, nullptr);
}

void IoErrorHandler::Forward(
    int ioStatOrErrno, const char *msg, std::size_t length) {
  if (ioStat_ != IostatOk && msg && (flags_ & hasIoMsg)) {
    ioMsg_ = SaveDefaultCharacter(msg, length, *this);
  }
  if (ioStatOrErrno != IostatOk && msg) {
    SignalError(ioStatOrErrno, "%.*s", static_cast<int>(length), msg);
  } else {
    SignalError(ioStatOrErrno);
  }
}

void IoErrorHandler::SignalErrno() { SignalError(errno); }

void IoErrorHandler::SignalEnd() { SignalError(IostatEnd); }

void IoErrorHandler::SignalEor() { SignalError(IostatEor); }

bool IoErrorHandler::GetIoMsg(char *buffer, std::size_t bufferLength) {
  const char *msg{ioMsg_.get()};
  if (!msg) {
    msg = IostatErrorString(ioStat_);
  }
  if (msg) {
    ToFortranDefaultCharacter(buffer, bufferLength, msg);
    return true;
  }

  char *newBuf;
  // Following code is taken from llvm/lib/Support/Errno.cpp
  // in LLVM v9.0.1
#if HAVE_STRERROR_R
  // strerror_r is thread-safe.
#if defined(__GLIBC__) && defined(_GNU_SOURCE)
  // glibc defines its own incompatible version of strerror_r
  // which may not use the buffer supplied.
  newBuf = ::strerror_r(ioStat_, buffer, bufferLength);
#else
  return ::strerror_r(ioStat_, buffer, bufferLength) == 0;
#endif
#elif HAVE_DECL_STRERROR_S // "Windows Secure API"
  return ::strerror_s(buffer, bufferLength, ioStat_) == 0;
#elif HAVE_STRERROR
  // Copy the thread un-safe result of strerror into
  // the buffer as fast as possible to minimize impact
  // of collision of strerror in multiple threads.
  newBuf = strerror(ioStat_);
#else
  // Strange that this system doesn't even have strerror
  return false;
#endif
  ::strncpy(buffer, newBuf, bufferLength - 1);
  buffer[bufferLength - 1] = '\n';
  return true;
}
} // namespace Fortran::runtime::io
